Outline mesh edges have inconsistent thickness when generated from polygon collider 2D

Hi everyone,

I’m working on generating an outline mesh in Unity based on a PolygonCollider2D, but I’m stepping into a strange issue. I have a script that takes a polygon collider and generates a mesh outline with a specified thickness. The issue I’m seeing is that some of the edges (marked with arrows in the attached image) look thinner than expected, even though I’m applying the same thickness throughout. Most of the edges have the correct thickness, but a few don’t match up.

I suspect that the issue may have something to do with how the normals are being calculated or the way triangles are generated, but I’m not sure what’s causing this inconsistency.

Has anyone encountered something similar or have any suggestions on what might be going wrong?

Here’s the portion of the script that handles the mesh generation:

    private Mesh GenerateOutlineMeshFromCollider(PolygonCollider2D polygonCollider,
                                                 float thickness) {
        if (polygonCollider == null) {
            Debug.LogError("Selected GameObject does not have a PolygonCollider2D component!");
            return null;
        }

        Mesh mesh = new Mesh();
        Polygon2D poly = new Polygon2D(polygonCollider.points);
        Vector2[] points = poly.GetTranslatedToOrigin();
        int pointCount = points.Length;
        Vector2[] normals = poly.CalculateOutwardNormals();
        Vector3[] vertices = new Vector3[pointCount * 2];
        int[] triangles = new int[(pointCount * 2) * 3];

        // Calculate vertices
        for (int i = 0; i < pointCount; i++) {
            Vector2 p = points[i];
            Vector2 outer = p;
            Vector2 inner = p + normals[i] * thickness;

            vertices[i * 2] = new Vector3(inner.x, inner.y, 0);
            vertices[i * 2 + 1] = new Vector3(outer.x, outer.y, 0);

            // Debug.Log(
            //     $"{i}: P{prevPoint} {currentPoint} {nextPoint}  N{inwardNormal}  Vi{vertices[i *
            //     2 + 1]}  Vo{vertices[i * 2]}");
        }

        // Calculate triangles for each edge
        for (int i = 0; i < pointCount - 1; i++) {
            int index = i * 6; // 6 indices (2 triangles) per edge

            // Inner triangle
            triangles[index] = i * 2;
            triangles[index + 1] = i * 2 + 1;
            triangles[index + 2] = i * 2 + 2;

            // Outer triangle
            triangles[index + 3] = i * 2 + 1;
            triangles[index + 4] = i * 2 + 3;
            triangles[index + 5] = i * 2 + 2;
        }

        // Close the loop for the last edge
        int lastIndex = (pointCount - 1) * 6;
        triangles[lastIndex] = (pointCount - 1) * 2;
        triangles[lastIndex + 1] = (pointCount - 1) * 2 + 1;
        triangles[lastIndex + 2] = 0;

        triangles[lastIndex + 3] = (pointCount - 1) * 2 + 1;
        triangles[lastIndex + 4] = 1;
        triangles[lastIndex + 5] = 0;

        mesh.vertices = vertices;
        mesh.triangles = triangles;

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        return mesh;
    }

(please ignore the fact that the meaning of outer and inner above is reversed as I had been messing with the normals)

And this is how I calculate outward normals and vertices:

    public Vector2[] CalculateOutwardNormals() {
        Vector2[] normals = new Vector2[length];

        for (int i = 0; i < length; i++) {
            Vector2 currentPoint = points[i];
            Vector2 prevPoint = points[(i - 1 + length) % length];
            Vector2 nextPoint = points[(i + 1) % length];

            Vector2 edgeToPrev = prevPoint - currentPoint;
            Vector2 edgeToNext = nextPoint - currentPoint;

            // Check for zero-length or very small edges
            if (edgeToPrev.magnitude < Mathf.Epsilon || edgeToNext.magnitude < Mathf.Epsilon) {
                normals[i] = Vector2.zero;
                continue;
            }

            edgeToPrev.Normalize();
            edgeToNext.Normalize();

            Vector2 normalToPrev = new Vector2(-edgeToPrev.y, edgeToPrev.x);
            Vector2 normalToNext = new Vector2(-edgeToNext.y, edgeToNext.x);
            Vector2 normal = normalToPrev - normalToNext;
            normals[i] = normal.magnitude < Mathf.Epsilon ? normal : normal.normalized;
        }

        return normals;
    }

Screenshot below illustrates the issue. Lines in red facing out are a representation of the normals. Shapes in white are the generated outlines; the shapes above the generated outlines are the polygon colliders. Thickness should be the exact same for all edges in both generated outlines.

I’ve resolved the issue! It turns out I was approaching the problem of generating the 2D polygon outline incorrectly. The main problem stemmed from how I was handling the normals and constructing the mesh triangles.

The solution was to change the way I calculate the outline vertices and handle convex/concave corners properly by calculating miter points. Great learning experience.