6
\$\begingroup\$

I'm trying to programmatically generate vertices and indices for a torus. I found this piece of code somewhere, and it looks like it works, but I'm not certain it is correct.

With my little knowledge of trigonometry, I figured out how the vertex generation part works, but I'm stuck at the index buffer part (though I know what the modulus operator is used for).

Does the code work correctly? Can anyone explain what it does to generate the correct indices?

I tried to output the index buffer and noticed there's a triangle with indices (10,10,10), which is weird and makes me think this may be wrong. Plus the UVs look weird for the last 'slice' of the torus, as in this pic.

int sides = 10, cs_sides = 40;
float radius = 3.5 * 10.0;
float cs_radius = 0.75 * 10.0; 

numVertices = sides * cs_sides;
Vertices = malloc(sizeof(Vertex) * numVertices);

numIndices = (2 * ((sides+1) * cs_sides) + cs_sides);
Indices = malloc(sizeof(GLushort) * numIndices);

int angleincs = 360/sides;
int cs_angleincs = 360/cs_sides;
float currentradius, zval;

//calculating the vertex array
for (int j=0, m=0; j<360; j+=cs_angleincs, m++)
{
    currentradius = radius + (cs_radius * cosf(j * D_TO_R ));
    zval = cs_radius * sinf(j * D_TO_R );

    int index = (m*sides);
    for (int i=0, n=0; i<360; i+=angleincs, n++)
    {
        Vertices[index + n].Position[0] = currentradius * cosf(i * D_TO_R ); // x 
        Vertices[index + n].Position[1] = currentradius * sinf(i * D_TO_R ); // y
        Vertices[index + n].Position[2] = zval;                              // z

        Vertices[index + n].Color[0] = 1.0;
        Vertices[index + n].Color[1] = 0.0;
        Vertices[index + n].Color[2] = 0.0;
        Vertices[index + n].Color[3] = 1.0;

        float u = (float)i/sides;
        float v = ((float)j + u)/cs_sides;

        Vertices[index + n].TexCoord[0] = u;
        Vertices[index + n].TexCoord[1] = v;
    }
}

// cs_sides = 40
// sides = 10


int i=0, n=0;
//calculating the index array
for (;i<cs_sides; i++) {
    for (int j=0; j<sides; j++) {
        Indices[n++] = i * sides + j;
        Indices[n++] = ((i+1) % cs_sides) * sides + j;
    }

    Indices[n++] = i * sides;
    Indices[n++] = ((i+1)%cs_sides) * sides;
    Indices[n++] = ((i+1)%cs_sides) * sides;
}
\$\endgroup\$

2 Answers 2

24
\$\begingroup\$

In order to build a torus you'll need to understand some basic vector algebra:

  1. vectors sums: X:=(x1,x2,x3), Y:=(y1,y2,y3); X + Y=(x1+y1, x2+y2, x3+y3)
  2. scalar multiplication: X:=(x1,X2,x3); a·X = (a·x1, a·x2, a·x3)
  3. norm: |X| = √(x1·x1 + x2·x2 + x3·x3)
  4. [optional] scalar/dot product: X·Y= (x1·y1 + x2·y2 + x3·y3) so |X|² = X·X

Our Torus will be centered on C, its radius1 (d) will be parallel to the XY plane and the radius will be d'. We will use u to run on the first circle and v to run to the second circle around the ring. Both u and v will go from 0 to 2π in radians.

first radius

We use u to compute the point P on the circle centered on C. To construct the second circle, we similarly construct a second circle that is centered on P and parallel to the direction C->P. I hope this image explains what I mean:

what I mean

The reddish circle is constructed same as the first, with a difference: the circle does not lies on a standard plane but on the plane parallel both to the Z axis and the C-->P direction (the segment labeled d, the radius).

Lets go back to the first circle: when we say

P=C + d *(cos(u), sin(u), 0)

we are saying that

component adding

where X = (1,0,0), Y = (0,1,0), Z = (0,0,1): the "component adding".

We can do the same for the second circle using P as center, discarding the third component (as it is zero), using the Z axis as for the sin component and the W axis for the cos component.

The W axis is simply an arbitrary axis that we construct so it is directed from C to P and has unitary length (as X, Y and Z were).

The norm of a vector is its length so we can define W as follows:

W=(P-C)/|P-C|

we take W' = P - C because it the vector that "moves" C to P ( C + W' = C + P - C = P); then we set W to have the direction of W' and the length of 1.

This is the result:

result

Now you have a way to compute the point Q in terms of u and v.

Noticing W is always (cos(u),sin(u),0), we can put it all together and write the function Q(u, v):

Q(u,v)

You can now construct your quads by connecting: (u, v) -> (u, v+1) -> (u-1, v+1) -> (u-1, v)

For triangles, only connect:

(u, v) -> (u, v+1) -> (u-1, v) and

(u-1, v) -> (u, v+1) -> (u-1, v+1)

(P.S. Doesn't your framework have some sort of torus primitive?)

1(the author messed up and called radius diameter originally, hence the d)

\$\endgroup\$
3
  • \$\begingroup\$ Ciao! Thanks a lot for your explanation. It would be awesome if you could expand this and explain how to build the vertex and index buffers, because actually that's where I'm stuck (as I said in the question). Also note that I can't use quads as I'm using OpenGL ES. \$\endgroup\$
    – ySgPjx
    Sep 4, 2011 at 12:38
  • \$\begingroup\$ I don't understand what is missing. You can compute the value for n x m points dividing the u range by n samples and the v range by m. Put every computed point into your vertex buffer and use a plain mapping to generate the index buffer as I explain. Remember that both u and v vary from 0 to 2 PI \$\endgroup\$
    – FxIII
    Sep 4, 2011 at 14:44
  • 1
    \$\begingroup\$ when i say (u,v) i mean the index of the vertex whit a given u and v so (u-1, v) means the previous vertex in u direction and (u, v+1) means the next vertex in v. \$\endgroup\$
    – FxIII
    Sep 4, 2011 at 14:47
4
\$\begingroup\$

I have been looking for a small algorithm to generate a torus mesh along its UV texture coordinates (I simply use cross product for normals). To my surprise, the only useful result that came through different search engines were this answer (and other answers telling you to use a god damn API).

The thing is there is a few issues with this algorithm and to spare you the hassle of a few hours of debugging, here are my corrections:

First issue: the UV coordinates seems completely wrong :

float u = (float)i/sides;
float v = ((float)j + u)/cs_sides;

The values assigned to u and v should be between 0 and 1. Here the u coordinates contain the slice number. I don't know how the values are used on the opengl side, but I fail to see a practical use for these. Instead just use:

float u = i / 360.;
float v = 2. * j / 360 - 1;
if (v < 0) v = -v;

Here I mirror the texture, otherwise it is way too squished (or at least use a very wide texture). That way you can give those values directly to glTexCoord().

Next issue: although the indices generates a mesh of a torus, it will look weird if you try to texture it, even with the correction I gave for UV values:

Torus with bad junction

Here I use a simple white and black checkboard texture. You can see that one section of the torus looks wrong. What happened here is that the last ring has its U coordinate very close to 1. The index array will then generate a mesh that link the last ring with the first ring (which has a U coordinate of 0) : that's why the last slice of the torus appears with almost an entire copy of the texture.

Easy fix: iterate both loops from 0 to 360 including (instead excluding).

Edit: added and tested normals with Gouraud shading (no texture, 20 sides):

torus with Gouraud shading

Corrected version:

typedef struct Model_t *       Model;

struct Model_t
{
    int        vertex, index;
    float *    vertices;
    float *    tex;
    float *    normals;
    uint16_t * indices;
};
void vecNormalize(float A[3])
{
    float len = 1 / sqrt(A[0]*A[0] + A[1]*A[1] + A[2]*A[2]);
    A[0] *= len;
    A[1] *= len;
    A[2] *= len;
}
#define D_TO_R     (M_PI/180)
Model modelGenTorus(int sides, int cs_sides, float radius, float cs_radius)
{
    int numVertices = (sides+1) * (cs_sides+1);
    int numIndices = (2*sides+4) * cs_sides;

    Model torus = malloc(sizeof *torus + numVertices * 8 * sizeof (float) + numIndices * 2);
    float * Vertices;
    float * TexCoord;
    uint16_t * Indices;

    torus->vertices = Vertices = (float *) (torus + 1);
    torus->normals  = Normals  = Vertices + numVertices * 3;
    torus->tex      = TexCoord = Normals + numVertices * 3;
    torus->indices  = Indices  = (uint16_t *) (torus->tex + numVertices * 2);
    torus->index    = numIndices;
    torus->vertex   = numVertices;

    int angleincs = 360/sides;
    int cs_angleincs = 360/cs_sides;
    float currentradius, zval;
    int i, j, nextrow;

    /* iterate cs_sides: inner ring */
    for (j = 0; j <= 360; j += cs_angleincs)
    {
        currentradius = radius + (cs_radius * cosf(j * D_TO_R));
        zval = cs_radius * sinf(j * D_TO_R);

        /* iterate sides: outer ring */
        for (i = 0; i <= 360; i += angleincs, Vertices += 3, TexCoord += 2)
        {
            Vertices[0] = currentradius * cosf(i * D_TO_R);
            Vertices[1] = currentradius * sinf(i * D_TO_R);
            Vertices[2] = zval;

            float u = i / 360.;
            float v = 2. * j / 360 - 1;
            if (v < 0) v = -v;

            TexCoord[0] = u;
            TexCoord[1] = v;
        }
    }

    /* compute normals: loops are swapped */
    for (i = 0, nextrow = (sides+1) * 3, Vertices = torus->vertices; i <= 360; i += angleincs, Normals += 3, Vertices += 3)
    {
        float * vert;
        float * norm;
        float xc = radius * cos(i * D_TO_R);
        float yc = radius * sin(i * D_TO_R);
        for (j = 0, vert = Vertices, norm = Normals; j <= 360; j += cs_angleincs, norm += nextrow, vert += nextrow)
        {
            norm[0] = vert[0] - xc;
            norm[1] = vert[1] - yc;
            norm[2] = vert[2];
            vecNormalize(norm);
        }
    }

    /* indices grouped by GL_TRIANGLE_STRIP, face oriented clock-wise */

    /* inner ring */
    for (i = 0, nextrow = sides + 1; i < cs_sides; i ++)
    {
        /* outer ring */
        for (j = 0; j < sides; j ++)
        {
            *Indices++ = i * nextrow + j;
            *Indices++ = (i + 1) * nextrow + j;
        }

        /* generate dummy triangle to avoid messing next ring */
        Indices[0] = i * nextrow + j;
        Indices[1] = Indices[2] = Indices[3] = Indices[0] + nextrow;
        Indices += 4;
    }
    return torus;
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .