Setting UV coords on a procedural ring correctly at the seam?

Hello Unity forums!! Thank you for taking the time to read this.
I am making a dynamic procedural ring (Spaceship centrifuge, like Halo but small) to act as the play space for the game I am creating. Whilst the positions in world space and the normals work fine, I am having trouble setting the UV coords at the seam and cannot find a solution anywhere.

What I suspect is happening is that the coordinates are wrong at the seam. Each vertex is given a UV x based on how far round it is around the ring mesh. A vertex halfway around the loop from the first point will have a UV of 0.5, for example. A random vertex may have a UV x value of, say, 0.08 and the one to the right 0.09, and this difference is uniform all the way around until the end. However, when reaching the last vertex, the vertex to it’s right will always be 0 as it’s the start again, and so the difference is seen as close to -1, IE the entire UV backwards, and squished into that tiny seam, rather than continuing the texture as desired (see pictures below)


I know I can’t set the seam vertex to be simultaneously 0 and 1, so I was wondering what the best way around this is?

Here is the code that constructs the vertex, UV and triangle arrays.

Any help regarding this would be much appreciated!! Thank you <3

    public void AssembleMeshData(Vector3[,] _vertices, Mesh _outputMesh) // Turns the grid of vertices into a single list that the mesh supports, then creates triangular faces for each tile
    {
        Vector3[] vertices = new Vector3[tileCount.x * (tileCount.y + 1)];
        Vector2[] uvs = new Vector2[vertices.Length];
        int[] triangles = new int[((tileCount.x) * tileCount.y) * 6];

        int i = 0;
        for (int y = 0; y < tileCount.y + 1; y++)  // Cycles through the 2D grid of tiles and increments i, so it can store them in a 1D array
        {
            for (int x = 0; x < tileCount.x; x++)
            {
                vertices[i] = _vertices[x, y];
                i++;
            }
        }

        /* HELLO UNITY FORUMS, THE AREA OF INTEREST IS HERE */

        i = 0; // Itterator keeps track of linear position along the array
        for (int y = 0; y < tileCount.y + 1; y++) // Loop through the Y axis + 1, since each tile is made of 2 vertices, there's an extra vertex.
        {
            for (int x = 0; x < tileCount.x; x++) // Loop through the X axis, no spare vertex as the ring of vertices folds on itself
            {
                uvs[i] = new Vector2(x / (float)tileCount.x, y / (float)tileCount.y); // Each UV coord is set up by the angle around the ring. IE 90 degrees around the ring, the UV would be 0.25, 180 degrees around would be 0.5
                i++;
            }
        }

        /* --------------------------------------------------------------------------------------------------------------------------- */

        i = 0;
        for (int y = 0; y < tileCount.y; y++) // Does not do the top row as each tile takes up it's own vertex and the one above
        {
            for (int x = 0; x < tileCount.x - 1; x++) // Cycles through each vertex (Except the top row) and makes it into the bottom left vertex of each tile, and thus the ID for the tile.
            {
                triangles[i + 0] = x + tileCount.x * y;         // Triangle 1
                triangles[i + 1] = x + tileCount.x * (y + 1);
                triangles[i + 2] = x + tileCount.x * y + 1;

                triangles[i + 3] = x + tileCount.x * (y + 1);   // Triangle 2
                triangles[i + 4] = x + tileCount.x * (y + 1) + 1;
                triangles[i + 5] = x + tileCount.x * y + 1;
                i += 6; // Increment i by 6 to move onto the next tile
            }
            triangles[i + 0] = tileCount.x + tileCount.x * y - 1;           // Triangle 1 of seam tiles
            triangles[i + 1] = tileCount.x + tileCount.x * (y + 1) - 1;
            triangles[i + 2] = tileCount.x * y;

            triangles[i + 3] = tileCount.x + tileCount.x * (y + 1) - 1;     // Triangle 2 of seam tiles
            triangles[i + 4] = tileCount.x * (y + 1);
            triangles[i + 5] = tileCount.x * y;
            i += 6; // Increment i by 6 to move onto the next tile
        }

        _outputMesh.vertices = vertices;
        _outputMesh.uv = uvs;
        _outputMesh.triangles = triangles;
        _outputMesh.RecalculateNormals();
    }

You will have to have a zero-dimensional gap where it wraps, which means duplicate verts at the same point: one set mapped going clockwise, one mapped going counter-clockwise.

To visualize it, make a C with your thumb and forefinger. The tip of your thumb is UV = 0 and the tip of your forefinger is UV = 1. Close that C into an O by touching your thumb to your forefinger. Those identical endpoints need to both be present, at the same precise location in space, but one is used by the next segment on your thumb, one is used by the next segment on your forefinger.

The underlying topological problem is that you cannot bidirectionally map an infinite space (linearly going around a cylinder or circle forever) to a finite space (0 to 1).

Oh and also: props for doing procedural generation! Isn’t it awesome in Unity3D? So easy!

1 Like

This makes a lot of sense!! Thank you!! Your finger and thumb analogy visualisation helped a lot!

And yes, procedural generation is a lot of fun in Unity, and perfect when you need something dynamic like this centrifuge which can be various sizes!

2 Likes