Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
88 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

SuperQuadric Ellipsoids and Toroids,
OpenGL Lighting, and Timing

by Jonathan Metzgar


  Contents

 Super Quadrics
 A Lighting
 Class

 Timing

 Source & Demo
 Printable version

 


SuperQuadric Ellipsoids and Toroids

There has been some coverage of OpenGL's support for Quadrics via the GLU library. In this article, a superior model using Super Quadric shapes will be discussed. To start with, Super Quadrics are very similar to Quadrics except that we can control the shape with only two variables. Two, we can find if a given point is located inside, on the surface, or outside of the Super Quadric. Another useful operation that can be derived is the Inertia Tensor for rigid body equations. However, that will not be discussed in this tutorial. This code will use the code from NeHe's Lesson 1 as its basis.

The two Super Quadrics we will discuss will be the Ellipsoid and the Toroid. The Hyperboloid is another shape but I don't have enough information yet about it to include it here. An ellipsoid is based on a sphere and can be manipulated to generate a variety of shapes including pillows, rounded boxes, "stars," spheres, and others. Toroids are a torus shapes and can be manipulated in the same manor to pinch or squarify its shape.

As you may have already suspected, we will need a whole lot of math to generate such equations. There will be no derivations of equations, only the equations that produce these shapes. I couldn't find (or spend much time finding) the derivations for these equations at the time this tutorial was written. Anyway, I think I should explain some of the parameters first.

We only need to know about 5 variables for ellipsoids and 6 variables for toroids. The first three are a1, a2, and a3 and are used to define the radius for the X, Y and Z axes. The next two are the most important, n and e. These two are used to define the North-South roundness/squareness/pinched and East-West roundness/squareness/pinched shapes, respectively. Plugging a value of 1 for n and e will yield a sphere. Increasing these values will make the shape more pinched, while decreasing them makes the shape more square. Finally, alpha, which is specific to the toroid only, is used for the torus's inner radius.

Anyway, let's get going. First, we will define the mathematical functions for all these shapes.

SuperQuadric Ellipsoid:

Position Coordinates:

Normal Vector:

Inside-Outside Function:

SuperQuadric Toroid:

Position Coordinates:

Normal Vector:

Inside-Outside Function:


where

If you carefully examine the above equations, you'll notice that the trig functions are raised to a power. Since the domain of the functions include possibly negative cosine and sine results we need to define a proper behavior since raising a negative number to a power is undefined. The correct way to do such an operation is to multiply the sign of that value with the absolute value raised to the desired value. Defined below are functions which do exactly that.

Now that we have those defined, we can start coding! The first thing we have to do is write some utility functions based on the equations above.

/* Returns the sign of x */ float sgnf ( float x ) { if ( x < 0 ) return -1; if ( x > 0 ) return 1; return 0; } /* Returns the absolute value of x */ float absf ( float x ) { if ( x < 0 ) return -x; return x; } /* sqC (v, n) * This function implements the c(v,n) utility function * * c(v,n) = sgnf(cos(v)) * |cos(v)|^n */ float sqC ( float v, float n ) { return sgnf((float)cos(v)) * (float)powf(absf((float)cos(v)),n); } /* sqCT (v, n, alpha) * This function implements the CT(v,n,alpha) utility function * * CT(v,n,alpha) = alpha + c(v,n) */ float sqCT ( float v, float n, float alpha ) { return alpha + sqC(v,n); } /* sqS (v, n) * This function implements the s(v,n) utility function * * s(v,n) = sgnf(sin(v)) * |sin(v)|^n */ float sqS ( float v, float n ) { return sgnf((float)sin(v)) * (float)powf(absf((float)sin(v)),n); }

Now that those have been defined, we can implement the actual formula that will determine the surface coordinate and normal. To simplify matters, we will use pointers to the data we want to return.

/* sqEllipsoid(a1, a2, a3, u, v, n, e, *x, *y, *z, *nx, *ny, *nz) * * a1, a2, and a3 are the x, y, and z scaling factors, respecfully. * For proper generation of the solid, u should be >= -PI / 2 and <= PI / 2. * Similarly, v should be >= -PI and <= PI. */ void sqEllipsoid ( float a1, float a2, float a3, float u, float v, float n, float e, float *x, float *y, float *z, float *nx, float *ny, float *nz ) { *x = a1 * sqC (u, n) * sqC (v, e); *y = a2 * sqC (u, n) * sqS (v, e); *z = a3 * sqS (u, n); *nx= sqC (u, 2 - n) * sqC (v, 2 - e) / a1; *ny= sqC (u, 2 - n) * sqS (v, 2 - e) / a2; *nz= sqS (u, 2 - n) / a3; } /* sqToroid(a1, a2, a3, u, v, n, e, alpha, *x, *y, *z, *nx, *ny, *nz) * * a1, a2, and a3 are the x, y, and z scaling factors, respecfully. * For proper generation of the solid, u should be >= -PI and <= PI. * Similarly, v should be >= -PI and <= PI. * Also, alpha should be > 1. */ void sqToroid ( float a1, float a2, float a3, float u, float v, float n, float e, float alpha, float *x, float *y, float *z, float *nx, float *ny, float *nz ) { float A1, A2, A3; A1 = 1 / (a1 + alpha); A2 = 1 / (a2 + alpha); A3 = 1 / (a3 + alpha); *x = A1 * sqCT (u, e, alpha) * sqC (v, n); *y = A2 * sqCT (u, e, alpha) * sqS (v, n); *z = A3 * sqS (u, e); *nx= sqC (u, 2 - e) * sqC (v, 2 - n) / A1; *ny= sqC (u, 2 - e) * sqS (v, 2 - n) / A2; *nz= sqS (u, 2 - e) / A3; }

Finally, we can now write the generation functions that will actually draw those shapes using OpenGL. To further optimize this example, we'll implement the ability to generate a OpenGL display list for these objects. To send all our information about the superquadric to the function we'll also define a structure to hold more advanced information.

struct SuperQuadric { float a1, a2, a3; /* Scaling factors for x, y, and z */ float alpha; /* For generating toroids. This is the inner radius */ float n, e; /* North-South/East-West Roundness/Squareness Factors */ float u1, u2; /* Initial and Final U values */ float v1, v2; /* Initial and Final V values */ int u_segs; /* Number of segments for U */ int v_segs; /* Number of segments for V */ float s1, t1; /* Initial s and t texture coordinates */ float s2, t2; /* Final S and T texture coordinates */ int texture_flag; /* Flag determining texture coordinate specification */ int gl_list_id; /* OpenGL Display List ID */ }; /* sqSolidEllipsoid ( sq, make_display_list, gen_texture_coordinates ) * * Generates a solid ellipsoid using the parameters from sq and optionally * generates texture coordinates and a display list using the ID from sq. */ void sqSolidEllipsoid ( SuperQuadric *sq, int make_display_list, int gen_texture_coordinates ) { float U, dU, V, dV; float S, dS, T, dT; int X, Y; /* for looping */ float x, y, z; float nx, ny, nz; /* Calculate delta variables */ dU = (float)(sq->u2 - sq->u1) / (float)sq->u_segs; dV = (float)(sq->v2 - sq->v1) / (float)sq->v_segs; dS = (float)(sq->s2 - sq->s1) / (float)sq->u_segs; dT = (float)(sq->t2 - sq->t1) / (float)sq->v_segs; /* If we're going to make a display list then start it */ if ( make_display_list ) { glNewList ( sq->gl_list_id, GL_COMPILE ); } /* Initialize variables for loop */ U = sq->u1; S = sq->s1; glBegin ( GL_QUADS ); for ( Y = 0; Y < sq->u_segs; Y++ ) { /* Initialize variables for loop */ V = sq->v1; T = sq->t1; for ( X = 0; X < sq->v_segs; X++ ) { /* VERTEX #1 */ sqEllipsoid ( 1, 1, 1, U, V, sq->n, sq->e, &x, &y, &z, &nx, &ny, &nz ); glNormal3f ( nx, ny, nz ); glTexCoord2f ( S, T ); glVertex3f ( x, y, z ); /* VERTEX #2 */ sqEllipsoid ( 1, 1, 1, U + dU, V, sq->n, sq->e, &x, &y, &z, &nx, &ny, &nz ); glNormal3f ( nx, ny, nz ); glTexCoord2f ( S + dS, T ); glVertex3f ( x, y, z ); /* VERTEX #3 */ sqEllipsoid ( 1, 1, 1, U + dU, V + dV, sq->n, sq->e, &x, &y, &z, &nx, &ny, &nz ); glNormal3f ( nx, ny, nz ); glTexCoord2f ( S + dS, T + dT ); glVertex3f ( x, y, z ); /* VERTEX #4 */ sqEllipsoid ( 1, 1, 1, U, V + dV, sq->n, sq->e, &x, &y, &z, &nx, &ny, &nz ); glNormal3f ( nx, ny, nz ); glTexCoord2f ( S, T + dT ); glVertex3f ( x, y, z ); /* Update variables for next loop */ V += dV; T += dT; } /* Update variables for next loop */ S += dS; U += dU; } glEnd ( ); /* If we're making a display list then stop */ if ( make_display_list ) { glEndList ( ); } } /* sqSolidToroid ( sq, make_display_list, gen_texture_coordinates ) * * Generates a solid toroid using the parameters from sq and optionally * generates texture coordinates and a display list using the ID from sq. */ void sqSolidToroid ( SuperQuadric *sq, int make_display_list, int gen_texture_coordinates ) { float U, dU, V, dV; float S, dS, T, dT; int X, Y; /* for looping */ float x, y, z; float nx, ny, nz; /* Calculate delta variables */ dU = (float)(sq->u2 - sq->u1) / sq->u_segs; dV = (float)(sq->v2 - sq->v1) / sq->v_segs; dS = (float)(sq->s2 - sq->s1) / sq->u_segs; dT = (float)(sq->t2 - sq->t1) / sq->v_segs; /* If we're going to make a display list then start it */ if ( make_display_list ) { glNewList ( sq->gl_list_id, GL_COMPILE ); } /* Initialize variables for loop */ V = sq->v1; S = sq->s1; glBegin ( GL_QUADS ); for ( Y = 0; Y < sq->u_segs; Y++ ) { /* Initialize variables for loop */ U = sq->u1; T = sq->t1; for ( X = 0; X < sq->v_segs; X++ ) { /* VERTEX #1 */ sqToroid ( sq->a1, sq->a2, sq->a3, U, V, sq->n, sq->e, sq->alpha, &x, &y, &z, &nx, &ny, &nz ); if ( gen_texture_coordinates ) glTexCoord2f ( S, T ); glNormal3f ( nx, ny, nz ); glVertex3f ( x, y, z ); /* VERTEX #2 */ sqToroid ( sq->a1, sq->a2, sq->a3, U + dU, V, sq->n, sq->e, sq->alpha, &x, &y, &z, &nx, &ny, &nz ); if ( gen_texture_coordinates ) glTexCoord2f ( S + dS, T ); glNormal3f ( nx, ny, nz ); glVertex3f ( x, y, z ); /* VERTEX #3 */ sqToroid ( sq->a1, sq->a2, sq->a3, U + dU, V + dV, sq->n, sq->e, sq->alpha, &x, &y, &z, &nx, &ny, &nz ); if ( gen_texture_coordinates ) glTexCoord2f ( S + dS, T + dT ); glNormal3f ( nx, ny, nz ); glVertex3f ( x, y, z ); /* VERTEX #4 */ sqToroid ( sq->a1, sq->a2, sq->a3, U, V + dV, sq->n, sq->e, sq->alpha, &x, &y, &z, &nx, &ny, &nz ); if ( gen_texture_coordinates ) glTexCoord2f ( S, T + dT); glNormal3f ( nx, ny, nz ); glVertex3f ( x, y, z ); /* Update variables for next loop */ U += dU; T += dT; } /* Update variables for next loop */ S += dS; V += dV; } glEnd ( ); /* If we're making a display list then stop */ if ( make_display_list ) { glEndList ( ); } }

That's pretty much all you need to generate them. Now for the other functions that can help with colision detection.

/* sqEllipsoidInsideOut ( sq, x, y, z ) * * Tests to see if point P is inside the SuperQuadric sq. * Returns 1 if on the surface, > 1 if outside the surface, or * < 1 if inside the surface */ float sqEllipsoidInsideOut ( SuperQuadric *sq , float x, float y, float z ) { float result; result = powf ( powf ( x / sq->a1, 2 / sq->e ) + powf ( y / sq->a2, 2 / sq->e ), sq->e / sq->n ) + powf ( z / sq->a3, 2 / sq->n ); return result; } /* sqToroidInsideOut ( sq, x, y, z ) * * Tests to see if point P is inside the SuperQuadric sq. * Returns 1 if on the surface, > 1 if outside the surface, or * < 1 if inside the surface */ float sqToroidInsideOut ( SuperQuadric *sq , float x, float y, float z ) { float result; result = powf ( powf ( powf ( x / sq->a1, 2 / sq->e ) + powf ( y / sq->a2, 2 / sq->e ), sq->e / 2 ) - sq->alpha, 2 / sq->n ) + powf ( z / sq->a3, 2 / sq->n ); return result; }

Finally, we can add some functions that generate common shapes. You can find the source code in squadric.cpp.

void sqSolidSphere ( float radius, int slices, int segments ); void sqSolidCylinder ( float radius, int slices, int segments ); void sqSolidStar ( float radius, int slices, int segments ); void sqSolidDoublePyramid ( float radius, int slices, int segments ); void sqSolidTorus ( float radius1, float radius2, int slices, int segments ); void sqSolidPineappleSlice ( float radius1, float radius2, int slices, int segments ); void sqSolidPillow ( float radius, int slices, int segments ); void sqSolidSquareTorus ( float radius1, float radius2, int slices, int segments ); void sqSolidPinchedTorus ( float radius1, float radius2, int slices, int segments ); void sqSolidRoundCube ( float radius, int slices, int segments );

That's all the functions we need.


Next : OpenGL Lighting