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 a[sub]1[/sub], a[sub]2[/sub], and a[sub]3[/sub] 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.
[size="3"]SuperQuadric Ellipsoid:
Position Coordinates:
Normal Vector:
Inside-Outside Function:
[size="3"]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);
}
/* 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;
}
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 ( );
}
}
/* 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;
}
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 );
[size="5"]Improved C++ Light Class
Next, let's develop a C++ class to encapsulate OpenGL's lighting features. In our implementation, we will encapsulate some of OpenGL's lighting functionality. For now we will only talk about the individual lights.
Before we get to any coding, I want to explain some of OpenGL's lighting algorithms so that you know how to use the functions. When I first learned about OpenGL's lighting functions, I had a hard time learning how to use them.
First, OpenGL requires that the light have either a position or direction vector. If you want the light to be located at a certain position, be sure that the w coordinate is set to one. If w is zero then the light will be located at infinity pointing in the direction you gave. Most people want the light located at infinity if they want a directional light. If you want a point light then specify 1 for w.
Next, OpenGL has a few color parameters that can be set to adjust how the light affects primitives. A primitive can be a triangle, quad, or other basic shape that is defined for the creation of objects. There are three colors that can be set. The first of those is the ambient color which is used to define the lowest possible color that will be visible even if there is no light. For example, if you have no lights on whatsoever, you'll notice it's pitch dark and you can't see anything. When there is ambient light, everything will be lit at constant intensity. That is what this is used for.
The next color is diffuse color which is used for determining what color will light the primitive. If you've ever had a colored light, like a flashlight with a tinted cover, say red, you'll notice that when you shine it one something it will look red. This is the same in OpenGL.
The final color is specular color. Specular color is the "shiny" color that highlights will have. Take metal, for example, when you shine light at it, it will have a very bright highlight. OpenGL supports this and can even specify a different color for specular highlights.
As for the rest of OpenGL's lighting features, I know little about how to use them. I am investigating them however. For now, knowing about colors and directional lights will get you going in the right direction.
Now, we'll implement the individual light class.
class CGL_Light3D {
public:
CGL_Light3D ( );
~CGL_Light3D ( );
void Init ( int light ); // Resets all light information and sets current
// light to a OpenGL light GL_LIGHT0, GL_LIGHT1, etc.
// specified by the user
void SetLight ( int light ); // Similar to Init (light) except that current
// light settings in the class are set to defaults.
void SetValues ( ); // Completely Sets Light Up. Must be called if any changes
// are made to the light.
void TurnOn ( ); // Turns on light
void TurnOff ( ); // Turns off light
void Toggle ( ); // Toggles light on or off
void GetOnOffState ( ); // Returns current on/off state
void SetAmbientColor ( float r, float g, float b, float a );
void GetAmbientColor ( float *r, float *g, float *b, float *a );
void SetAmbientColor ( float c[4] );
void GetAmbientColor ( float *c[4] );
void SetDiffuseColor ( float r, float g, float b, float a );
void GetDiffuseColor ( float *r, float *g, float *b, float *a );
void SetDiffuseColor ( float c[4] );
void GetDiffuseColor ( float *c[4] );
void SetPosition ( float x, float y, float z, float w );
void GetPosition ( float *x, float *y, float *z, float *w );
void SetPosition ( float p[4] );
void GetPosition ( float *p[4] );
void SetSpecular ( float r, float g, float b, float a );
void GetSpecular ( float *r, float *g, float *b, float *a );
void SetSpecular ( float s[4] );
void GetSpecular ( float *s[4] );
/* Spot Light Functions */
void SetSpotDirection ( float x, float y, float z );
void GetSpotDirection ( float *x, float *y, float *z );
void SetSpotDirection ( float s[3] );
void GetSpotDirection ( float *s[3] );
void SetSpotExponent ( float exponent );
float GetSpotExponent ( );
void SetSpotCutoff ( float cutoff );
float GetSpotCutoff ( );
/* Attenuation factors */
void SetConstantAtt ( float constant );
float GetConstantAtt ( );
void SetLinearAtt ( float linear );
float GetLinearAtt ( );
void SetQuadraticAtt ( float quadratic );
float GetQuadraticAtt ( );
private:
int LIGHT;
float ambient[4];
float diffuse[4];
float specular[4];
float position[4];
float spot_direction[3];
float spot_exponent;
float spot_cutoff;
float constant_attenuation;
float linear_attenuation;
float quadratic_attenuation;
int on_off_state;
};
void CGL_Light3D::Init ( int light ) {
LIGHT = light;
// set all members to OpenGL defaults
// all values comply with the OpenGL 1.2.1 Specification
ambient[0] = 0.0f;
ambient[1] = 0.0f;
ambient[2] = 0.0f;
ambient[3] = 1.0f;
if ( light == GL_LIGHT0 ) {
diffuse[0] = 1.0f;
diffuse[1] = 1.0f;
diffuse[2] = 1.0f;
diffuse[3] = 1.0f;
} else {
diffuse[0] = 0.0f;
diffuse[1] = 0.0f;
diffuse[2] = 0.0f;
diffuse[3] = 1.0f;
}
if ( light == GL_LIGHT0 ) {
specular[0] = 1.0f;
specular[1] = 1.0f;
specular[2] = 1.0f;
specular[3] = 1.0f;
} else {
specular[0] = 0.0f;
specular[1] = 0.0f;
specular[2] = 0.0f;
specular[3] = 1.0f;
}
position[0] = 0.0f;
position[1] = 1.0f;
position[2] = 0.0f;
position[3] = 0.0f;
spot_direction[0] = 0.0f;
spot_direction[1] = 0.0f;
spot_direction[2] = -1.0f;
spot_exponent = 0.0f;
spot_cutoff = 180.0f;
constant_attenuation = 1.0f;
linear_attenuation = 0.0f;
quadratic_attenuation = 0.0f;
}
void CGL_Light3D::SetValues ( ) {
glLightfv ( LIGHT, GL_AMBIENT, ambient );
glLightfv ( LIGHT, GL_DIFFUSE, diffuse );
glLightfv ( LIGHT, GL_SPECULAR, specular );
glLightfv ( LIGHT, GL_POSITION, position );
glLightfv ( LIGHT, GL_SPOT_DIRECTION, spot_direction );
glLightfv ( LIGHT, GL_SPOT_EXPONENT, &spot_exponent );
glLightfv ( LIGHT, GL_CONSTANT_ATTENUATION, &constant_attenuation );
glLightfv ( LIGHT, GL_LINEAR_ATTENUATION, &linear_attenuation );
glLightfv ( LIGHT, GL_QUADRATIC_ATTENUATION, &quadratic_attenuation );
}
[size="5"]Timing
Timing is a critical part of developing simulations. Many simulations suffer from this because they don't have support for controlling the speed of their program. Here is a sample implementation of a timing loop in Win32.
In this tutorial's program, we have defined a few variables, [font="Courier New"][color="#000080"]DTick[/color][/font], [font="Courier New"][color="#000080"]Tick1[/color][/font], and [font="Courier New"][color="#000080"]Tick2[/color][/font]. These are used to store the delta time, initial time, and final time, respectively. These are defined globally so they can be used by the entire program.
int Tick1, Tick2; // used to help keep speed constant
float DTick;
Tick1 = GetTickCount ( );
Tick2 = GetTickCount ( );
DTick = DTick = (float)((Tick2 - Tick1) / 30.0);
// update time
Tick1 = Tick2;
Tick2 = GetTickCount ( );
DTick = (float)((Tick2 - Tick1) / 30.0);
If you have any questions, please free feel to e-mail me at [email="microwerx@yahoo.com"]microwerx@yahoo.com[/email]. You can also visit my website at http://www.geocities.com/microwerx/ for more information, games, articles, and more!