[SOLVED] Custom Mesh is wrong with appropriate vertices and triangle order

I am trying to create a mesh for a render to just wrap around a cylinder (basically a tire without a rim) and I can’t seem to get the mesh right.

And here is a higher “Resolution” of the simple, triangular shape to help understand that it should be “hollow”

I have a list of vertices and using the lowest resolution of 9 vertices, I have an organized list: 8757007--1186816--upload_2023-1-25_17-7-43.png

The arrangement of these vertices, starting at element 0 goes from bright green to bright purple. Starting at the most bright green on the top left of this shape, then moving down-left, then right, back up to the top in the center of the shape and progressing through that way.

I’ve created my int array of triangles and they all seem to be correct. I’ve written a function for left-hand and right-hand winding, but neither of these work when viewing the mesh.
8757007--1186819--upload_2023-1-25_17-13-15.png

After I’ve calculated the vertices and the triangles, I apply them both to the mesh like so:

mesh.vertices = newVerts;
mesh.triangles = triangles;
mesh.RecalculateNormals();

I’m not entirely sure why this mesh isn’t lining up with the order here.

Instead of moving around the open end, do I need to go down the direction of the curve so it can close at the end?

What helped me a lot when doing procedural mesh work was this function in OnDrawGizmos to label the vertices in 3D space:

Once you have vertex numbers labeled and compare them to your triangles array your error should become obvious!

2 Likes

Might just be an indexing bug.

Remember when winding a cylinder, if you want to keep UVs intact, you WILL need to dupe one row of verts.

You’re welcome to look at my cylinder winder here:

and just modify it to accomodate your “inward tucked” ends.

2 Likes

Thank you! I will try to implement this, though I did it a cheap way by adding a sphere and looking at each vert one by one. I’m going to get this in there now anyways.

I definitely did not know this! I will get that put in there asap to see what I can come up with. I will take a look at your code as well, thank you for the example!

1 Like

You could also use my UVViewer I wrote about 10 years ago. It had now a triangle list that lets you visualize the triangles and vertices, both in the UV map (which was the main purpose) as well as inside the sceneview. The triangle list shows the indices of the vertices they are composed of. It should help to at least analyse the actual mesh. Though since it seems you produce the mesh procedurally, you probably have a logic issue in your code.

Can you actually show the code that generates the mesh? If it’s a parameterized mesh generator, what parameters do you actually have? I guess you have a sector count around the cylinder as well as some sort of “track” count, right? Maybe you calculate your “stride” wrong when creating the triangles. When you create a triangle / quad you need to use the matching vertices in neighboring rings.

2 Likes

Oh that sounds incredible! I will try to get this in my project as soon as I get back to editing tonight or tomorrow! Thank you!

When going through the 9 vert model, I was able to manually follow the triangle generation. I’m not sure what you mean about “stride”, but I assume it has to do with how it goes back and forth when trying to generate?

INCOMING WALL OF CODE
Disclaimer: I’m writing this quick and dirty until it works, then I plan on going back and making it actually efficient. I know I normally black out when I see posts like this, so I understand if I don’t get a response

As for the actual code, I’ll post a bunch here so future viewers can see the process I went through. So, I’m making a custom tire generator, starting with inputting the actual size of the tire (e.g. 235/35/19) as well as requesting a circumference resolution and a width resolution. This generates vertices basically from the center out, then re-arranges them from left to right. Also with this, I decided to use an AnimationCurve (probably something better out there) to practically draw the profile of the tire. The last line here is setting the value for the 0 time in the curve so it creates the appropriate rim size - This is called in the update and this is all done with ExecuteInEditMode on:

private void HandleTireSize()
    {
        widthInM = width * .001f;
        wheelDiameterInM = wheelDiameter * 0.0254f;
        sidewallHeightInM = widthInM * (sidewallHeight * .01f);
        overallHeight = (wheelDiameterInM + (sidewallHeightInM * 2));
        tireEdgeProfile.AddKey(0, wheelDiameterInM / overallHeight);
    }

Now, the first thing I wanted to do was draw gizmo lines to view my circles:

private void OnDrawGizmos()
    {
        Matrix4x4 rotationMatrix = Matrix4x4.TRS(root.position, root.localRotation, root.lossyScale);
        Gizmos.matrix = rotationMatrix;
        Gizmos.color = Color.cyan;

        int newWidthResolution = (widthResolution * 2) + 1;
        float step = widthInM / (newWidthResolution - 1);
        vertices = new Vector3[newWidthResolution * circumferenceResolution];

        for (int i = 0; i < newWidthResolution; i++)
        {
            int inverseI = i - widthResolution;
            float stepPercent = (step * inverseI);
            float falloffPercent = (float) (widthResolution - (Mathf.Abs(inverseI))) / widthResolution;
            float falloffRadius = tireEdgeProfile.Evaluate(falloffPercent) * overallHeight / 2;
            Vector3 stepLocation = new Vector3((transform.localPosition.x + stepPercent), transform.localPosition.y, transform.localPosition.z);
            Vector3[] points = GizmosExtensions.DrawWireCircleReturnPoints(falloffRadius, circumferenceResolution, stepLocation, transform.right);

            for (int p = 0; p < points.Length; p++)
            {
                int index = (i * circumferenceResolution) + p;
                vertices[index] = points[p];
            }
        }

GenerateMesh();

}

At the end of this, I added the vertices generated from my GizmosExtension.DrawWireCircleReturnPoints to a new array. That function also actually draws Gizmo lines from i -> i+1 so I can see what the tire actually looks like as a cylinder, though there aren’t any lines connecting the circles yet.

At the very end, I have the GenerateMesh() function which simply orders the vertices and then makes an array of indexes pointing to those vertices. I then generate a new mesh, clear it and give the mesh these verts and tris

private void GenerateMesh()
    {
        Vector3[] newVerts = Triangulator.OrderVertices(vertices);
        triangles = Triangulator.GetTriangles(newVerts, circumferenceResolution, winding);

        Mesh mesh = new Mesh();
        GetComponent<MeshFilter>().mesh = mesh;
        mesh.Clear();
      
        mesh.vertices = newVerts;
        mesh.triangles = triangles;
        mesh.RecalculateNormals();
        mesh.name = "CleanerWheelMesh";
    }

So, going back a bit, I have a class named Triangulator that does two things, one is to order the vertices, the second is to make the triangles.

This is probably where it goes wrong?

public static Vector3[] OrderVertices(Vector3[] vertices)
    {
        Vector3 centroid = new Vector3();
        for (int i = 0; i < vertices.Length; i++)
        {
            centroid += vertices[i];
        }
        centroid /= vertices.Length;

        // Sort the vertices by angle from the centroid
        vertices = vertices.OrderBy(v => Mathf.Atan2(v.y - centroid.y, v.x - centroid.x)).ToArray();
        return vertices;
    }

Just in case, here’s the InsideTriangle function

private static bool InsideTriangle(Vector3 A, Vector3 B, Vector3 C, Vector3 P)
    {
        float ax, ay, az, bx, by, bz, cx, cy, cz, apx, apy, apz, bpx, bpy, bpz, cpx, cpy, cpz;
        float cCROSSap, bCROSScp, aCROSSbp;

        ax = C.x - B.x; ay = C.y - B.y; az = C.z - B.z;
        bx = A.x - C.x; by = A.y - C.y; bz = A.z - C.z;
        cx = B.x - A.x; cy = B.y - A.y; cz = B.z - A.z;
        apx = P.x - A.x; apy = P.y - A.y; apz = P.z - A.z;
        bpx = P.x - B.x; bpy = P.y - B.y; bpz = P.z - B.z;
        cpx = P.x - C.x; cpy = P.y - C.y; cpz = P.z - C.z;

        aCROSSbp = ax * bpy - ay * bpx;
        cCROSSap = cx * apy - cy * apx;
        bCROSScp = bx * cpy - by * cpx;

        return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
    }

And here is the GetTriangles function:

public static int[] GetTriangles(Vector3[] Vertices, int resolution, string direction = "left")
    {
        int nbLevel = Vertices.Length / resolution;
        int e = resolution;

        List<int> triangles = new List<int>();
        for (int i = 0; i < (nbLevel - 1); i++)
        {
            for (int j = 0; j < (e - 1); j++)
            {
                // This makes a 4-sided polygon
                // triangle 1 :
                triangles.Add(i * e + j);
                if (direction != "right") triangles.Add(i * e + j + e);
                triangles.Add(i * e + j + 1);
                if (direction == "right") triangles.Add(i * e + j + e);

                // triangle 2:
                triangles.Add(i * e + j + 1);
                if (direction != "right") triangles.Add(i * e + j + e);
                triangles.Add(i * e + j + (e + 1));
                if (direction == "right") triangles.Add(i * e + j + e);
            }
            //create the last 2 triangles
            // triangle 1 :
            triangles.Add(i * e + (e - 1));
            if (direction != "right") triangles.Add(i * e + ((e * 2) -1));
            triangles.Add(i * e + 0);
            if (direction == "right") triangles.Add(i * e + ((e * 2) -1));

            // triangle 2:
            triangles.Add(i * e + 0);
            if (direction != "right") triangles.Add(i * e + ((e * 2) -1));
            triangles.Add(i * e + e);
            if (direction == "right") triangles.Add(i * e + ((e * 2) -1));
        }
        return triangles.ToArray();
    }

Anyways, congratulations if you made it this far. I was able to implement the code that shows vert numbers, but haven’t gotten a chance to actually look yet. After that I plan on checking the create cylinder mesh code above as well.

Thank you for this, I checked the labels and everything seems in order. I’ve even made a “rotate” script to change the order so they go across the cylinder, rather than following the edge first.

Well, your “OrderVertices” method orders the vertices only on their angle they describe on the x-y plane. Since your mesh has several layers your “order” would just be a mess as all those layers are essentially counted as one.

There should be absolutely no reason to re-order the vertices. We don’t know exactly what your “GizmosExtensions.DrawWireCircleReturnPoints” does, but we would assume that it returns the points of a circle in order. Since you do this several times for each “ring” with the same amount of points in each ring, the triangles simply need to be matched accordingly.

Just for example, imagine you generate a circle with 10 points (circumferenceResolution would be 10). That means the first 10 points (index 0 to 9) is the first “ring”. Your outer loop now appends the second ring simply after the first with the same order (so index 10 to 19 would be the second ring). When you generate the triangles / quads, all you have to do is matching up the corresponding vertices. So the vertices 0, 1, 10 and 11 would form a quad. You know that Unity does support a mesh topology quad which may simplify the generation a bit. Though if you want to explicitly generate triangles, that’s fine, just split the quad into to triangles.

We don’t know if your DrawWireCircleReturnPoints method actually duplicates the first / last element. If it does not you would get some weird texture mapping between the last point and the first one. So I guess that your first point is actually equal to the last one. So in your 3 points case it’s actually 4 points since the first and last are at the same position. If that’s not the case, be aware that you would run into issues with texture mapping.

If that last point is a duplicate of the first (except with different UVs) we don’t have to think about wrapping around. We simply build strips of triangles / quads and the end just happens to meet the start. The “stride” between two rings would be your actual offset between the two rings. So in the case of 4 points in a ring (to form a triangle) the stride is 4. So when building the triangles / quads, adding the stride just gives you the exact same vertex, just on the next ring of vertices. So following that logic you can directly build all triangles.

About the “facing” of your triangles, that depends on the order the rings are generated. That again depends on that DrawWireCircleReturnPoints method and how it generates the points. If the sin / cos is used mathematically correct, the points should be arranged counter clockwise.

ps: I just saw that your GetTriangles method actually has an explicit case for the “last triangles”. That would tell us that the points do not have a duplicated last point and you actually wrap around each “ring”. While that’s generally possible, if you want to have a texture mapped onto that mesh, you can not specify the UV coordinates for that last triangles because they share the same vertices on both sides.

Generating a cylinder like mesh is actually pretty trivial. Your usage of those utility methods seems to make the whole thing much more complicated.

Ah shoot, I thought I was missing something. I want to say, I’m very impressed you were able to digest all of that spaghetti so quickly! I’m still reading through your reply and taking that all in. I’m currently trying to start with a single triangle (the first) and slowly add more to see where it goes wrong.

You’ll see in this DrawWireCircleReturnPoints, it does exactly as you mentioned, where it delivers the points around the ring first, rather than across.

public static Vector3[] DrawWireCircleReturnPoints(float radius, int resolution, Vector3 center, Vector3 axis)
    {
        Quaternion qAxis = Quaternion.Euler(axis.x, axis.y, axis.z);
        Vector3[] points = GetPointsAroundAxis(radius, resolution, center, qAxis);
        Vector3 lastPoint = points[resolution - 1];
        for (int i = 0; i < resolution; i++)
        {
            Vector3 nextPoint = points[i];
            Gizmos.DrawLine(lastPoint, nextPoint);
            lastPoint = nextPoint;
        }
       

        return points;
    }

Here is the “GetPointsAroundAxis” function

private static Vector3[] GetPointsAroundAxis(float radius, int resolution, Vector3 center, Quaternion axis)
    {
        Vector3 scale = Vector3.one;
        Vector3[] points = new Vector3[resolution];
        for (int i = 0; i < resolution; i++) {
            float angle = 2 * Mathf.PI * i / resolution;
            float y = radius * Mathf.Cos(angle);
            float z = radius * Mathf.Sin(angle);
            points[i] = new Vector3(center.x + 0, center.y + y, center.z + z);
        }
        return points;
    }

It currently does not duplicate the first point. I am going to do some more testing with that as well, starting from @Kurt-Dekker 's comment about duplicating the first point.

I am going to read up the rest of your reply and come back with questions/answers for you. Thank you for taking your time to go over this!

This is exactly how it’s working now - When I generate the triangles, assuming 9 points total with the first ring having 3, the vertices are labeled 0, 1, 2. The second ring is 3, 4, 5, then of course the third is 6, 7, 8. When I create the triangles, as you can see in the last screenshot of my original post, the first triangle is made up of 0, 1, 3. The second triangle is 1, 4, 3.

Here is a screenshot of the vertices numbered
8757361--1186870--upload_2023-1-25_21-27-56.png

It absolutely does seem that way! I didn’t know where to start. I would still consider myself an amateur in Unity without the full knowledge of what is available to me.

I’m going to do this little test with creating a single triangle and adding on to that just to see where everything went wrong. Everything seems to be pointing at the fact that I didn’t duplicate the first point to be the last as well. I am going to try that option as well right after this.

Thank you again! I’ll write back with any findings.

So it seems the OrderVertices function is where things were messing up. I realized now that when I was looking at the list of vertices in the inspector, I had overridden them with a new list that wasn’t a public value, so the vertices array that came back was different than the list in the inspector.

Positive movement at least! Now, for some reason (Still looking into it) The mesh follows circumferenceResolution increase, but does not follow the widthResolution increase. I’m sure that’s simple, I just need to find it. Once I find that, I will just need to look into the UVs to make sure the texture is applied appropriately. More to learn!

Alright, I found a bit more erroneous code (The part where I rotate the vertices) that was messing everything up. It all seems to be working now. Thank you for talking me through that!

8757421--1186876--upload_2023-1-25_21-56-7.png

Once I figure out the UV mapping, I will be well on my way.