|
Ok, here we go for my first tutorial. I'm going to start with something that gave me a lot of trouble. At the time that I figured this out, I hadn't studied vectors and their addition, subtraction, and multiplication yet, so it was hard for me to grasp. Now though, having taken physics, I understand it a lot better. So if you haven't done vectors yet, just read along. It's okay if you don't understand everything, but try.
In this tutorial, we will use data arrays to draw one of the figures(an icosahedron). Both vectors and data arrays are a too complex to cover in one tutorial. Consequently this tutorial will cover vectors and the next one will cover data arrays.
First of all, let's review vectors. If you don't already know, vectors are quantities that have both direction and magnitude(length). They are used a lot in physics to represent things such as velocity, which has both speed (the magnitude of the vector), and direction (the direction of the motion).
Vectors can be divided into x, y, and z components using a series of equations. In this program, we will never actually calculate the vector itself; we will only calculate the x, y, z components of the vector.
Figure 1-1
The length of a vector can be found by taking the square root of the sum of the components squared (wow). In simpler mathematic forms, this is:
Length=squareroot(v.x^2+v.y^2+v.z^2) Equation 1-1
So what are normal vectors? Normal vectors are vectors that are perpendicular to the plane which they are on. For example in this diagram:
Figure 1-2: The two vectors and normal vector of a plane.
C is the normal vector. To calculate C, you take the cross product of A and B (AxB=C).
Keep in mind that taking the cross product of A and B is not the same as taking the cross product of B and A. BxA would result in -C; in other words, the vector C would be pointing in the opposite direction. We will learn the equations for taking the cross product of two vectors later in the tutorial. Normal vectors are what the computer uses to calculate the lighting on surfaces, which is why we need to calculate them. For example, the difference between using normals and not is this:(lighting is enabled in both screenshots)
Image 1-1: Without Normals
to this:
Image 1-2: Using Normals
As you can see, the use of normals is what enables lighting to give objects a 3 dimensional appearance.
So let's get started.
|
The first thing we must do is include our math library as we will need it to perform the square root function.
|
GLfloat yspeed, xspeed,xrot,yrot;
GLfloat LA[]={1.0f,1.0f,1.0f,1.0f};
GLfloat LD[]={1.0f,1.0f,1.0f,1.0f};
GLfloat LightPos[]={0.0f,0.5f,1.0f,0.0f};
GLfloat x=(float)0.525731112119133606; //Position for points
GLfloat z=(float)0.850650808352039932; //Position for points
GLfloat vertices[12][3]={ //Vertices for our icosahedron
{-x,0.0,z},{x,0.0,z},{-x,0.0,-z},{x,0.0,-z},
{0.0,z,x},{0.0,z,-x},{0.0,-z,x},{0.0,-z,-x},
{z,x,0.0},{-z,x,0.0},{z,-x,0.0},{-z,-x,0.0}
};
GLint planes[20][3]={ //Which sets of three points create planes(20 planes,three vertices for each plane)
{1,4,0},{4,9,0},{4,5,9},{8,5,4},{1,8,4},
{1,10,8},{10,3,8},{8,3,5},{3,2,5},{3,7,2},
{3,10,7},{10,6,7},{6,11,7},{6,0,11},{6,1,0},
{10,1,6},{11,0,9},{2,11,9},{5,2,9},{11,2,7}
};
|
The code above initializes our global variables. The yspeed, xspeed, yrot, and xrot, are variables we use to rotate our figures.
The next three, LA, LD, and LightPos, are our lights Ambient and Diffuse properties, and the light's position. (if you don't know about lighting, you shouldn't be here, you should be looking at Nehe's lesson 7, and probably the rest of his tutorials too) Next are the variables x and z. I won't go into a lot of detail, but these are some numbers that we will use to define the 12 points that make up the icosahedron. They are given their values so that each vertex will be 1 unit from the center of the figure. Finally, we have the two arrays vertices, and planes.The vertices array stores the 12 points that will make up our icohedron, and the planes array, stores the information as to which points make up planes. In other words, if we have 4 points as shown below.
Figure 2-1
The planes array tells us which sets of three points make planes.
Figure 2-2
|
|
glEnableClientState(GL_VERTEX_ARRAY); // Enable Vertex Arrays
|
This piece of code goes into the Init function. It activates the vertex arrays, and allows us to use data arrays to draw our figure.
Finally, we get to our main Draw function:
|
Draw()
{
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); //Clear the buffers
int i,j; //Initiate our variables
glLoadIdentity(); //Clear the matrix
glTranslatef(-2.5f,0,-10 ); //Translate to our position
glRotatef(xrot,1.0f,0.0f,0.0f); //Rotate objects
glRotatef(yrot,0.0f,1.0f,0.0f); //Rotate objects
|
The first bit of code is pretty simple: clear the buffers, initiate variables, clear the matrix, translate to our position, and rotate the objects.
|
|
glVertexPointer(3,GL_FLOAT,0, vertices); //Tells the computer which array holds the vertices of our icosahedron
|
This next line tells the computer where to look for the information on the vertices of our figure. Remember above that we enabled Vertex Arrays, this line points us to which array contains the vertex coordinates.
|
|
glBegin(GL_TRIANGLES); //Begin drawing triangles
|
We begin drawing triangles.
|
for(i=0;i<20;i++) //Loop through however many planes there are
{
|
Next, we loop through each of the planes defined in planes and calculate the normal vector of each one. Note that if our figure had 44 planes, we would have to loop through the next section of code 44 times.
|
|
GLfloat v1[3],v2[3],norm[3]; //Create our variables
|
Next we initialize some variables. v1 and v2 will represent the two vectors A and B of a plane as shown in figure 1-2, and norm will represent the normal vector C as shown in figure 1-2. They are arrays of size three to hold each x, y, and z component of the vectors.
|
for(j=0; j<3;j++)
{
v1[j]=vertices[planes[i][0]][j]-vertices[planes[i][1]][j]; //Create a vector on the plane
v2[j]=vertices[planes[i][1]][j]-vertices[planes[i][2]][j]; //Create a second vector on the plane
}
|
Figure 1-2: The two vectors and normal vector of a plane.
I'm going to bring Figure 1-2 down here to help us out with this bit of code. This code calculates the two vectors of the plane (in the figure, the vectors A and B). Again we calculate the components of the vectors instead of the vectors themselves. Hence, we loop through this section of code three times, one for each component. We use following equations to get the two vectors:
A.j(v1.j)=vertex1.j-vertex2.j
And
B.j(v2.j)=vertex2.j-vertex3.j
Where j is either the x, y, or z component.
|
|
NormalizedCross(v1,v2,norm);; //Make a call to the cross function
|
Now that we have two vectors of the plane, we can find the vector perpendicular to the plane, the normal vector, by taking the cross product of them. So we make a call to the function NormalizedCross which cross multiplies the vectors, normalizes them(explained below) and then returns a Unit(normalized) Vector:
|
void NormalizedCross(GLfloat v1[3],GLfloat v2[3], GLfloat norm[3])
{
norm[0]=v1[1]*v2[2]-v1[2]*v2[1]; //Calculate the x component of the normal
norm[1]=v1[2]*v2[0]-v1[0]*v2[2]; //Calculate the y component of the normal
norm[2]=v1[0]*v2[1]-v1[1]*v2[0]; //Calculate the z component of the normal
|
In the NormalizedCross function, the three lines shown above calculate the x, y, and z components of the normal vector and store them in the norm array. The equations to cross multiply two vectors are:
The x component: norm.x=(v1.y*v2.z)-(v1.z*v2.y);
The y component: norm.y=(v1.z*v2.x)-(v1.x*v2.z);
The z component: norm.z=(v1.x*v2.y)-(v1.y*v2.x);
This is what the code above implements.
|
normalize(norm); //Normalize the vector using the function normalize
}
|
Next, we must call a function to normalize the vector. Normalizing a vector is scaling the length of the vector to 1. This does not affect the direction at all. The direction of the vector is the only thing the computer needs to calculate lighting, so you should always normalize vectors that will be used in light calculation. For example, normalizing the vector shown
Figure 3-1
would create a vector as shown below.
Figure 3-2
Where the direction is exactly the same but the length is 1.
Again, we will scalee each component of our vector, to create a normalized vector.
So we use the equation:
Length=squareroot(v.x^2+v.y^2+v.z^2) to find the length, and then divide the vector by the number generated. The code looks like this.
|
void normalize(GLfloat norm[3])
{
GLfloat length=(float)sqrt(norm[0]*norm[0]+norm[1]*norm[1]+norm[2]*norm[2]);//Find the lenght of the vector
//with squareroot(v.x^2+v.y^2+v.z^2)
if(length==0.0) return; // If the length of the vector is zero return
//If not, divide each component of the vector by the length
norm[0]/=length;
norm[1]/=length;
norm[2]/=length;
}
|
Now, the array norm should contain the x, y, and z components of our normal vector, our normalize function returns, as does our cross function, so we are back to the line of code right after NormalizedCross(v1,v2,norm); in the Draw function.
|
glNormal3fv(norm); //Set the normal to be the normal computed for the plane
//Draw the plane
glVertex3fv(&vertices[planes[i][0]][0]);
glVertex3fv(&vertices[planes[i][1]][0]);
glVertex3fv(&vertices[planes[i][2]][0]);
}
glEnd();
|
We set the normal by using glNormal3fv, passing the array norm which contains the information on our normal vector as the argument.
The next three lines of code draw the three points of our plane to the screen. And glEnd() ends our drawing of triangles.
The next part of the program draws a cube. Remember that normals are perpendicular to the plane.
Figure 4-1
As shown here, the normal for a plane such as the one above is aligned to one of the axis, so all but one of the components disappear. Since this is true, we can just use the glNormal3f function with (1.0, 0.0, 0.0) as the arguments. We would get the same result if we used the method above; however it takes less time to do it the latter way. Notice that glNormal3f takes three arguments, the x, y, and z components of the vector, while glNormal3fv takes an array which stores the components.
|
glLoadIdentity();
glTranslatef(2.5f,0.0f,-10);
glRotatef(xrot,1.0f,0.0f,0.0f);
glRotatef(yrot,0.0f,1.0f,0.0f);
glBegin(GL_QUADS);
// Front Face
glNormal3f(0.0f, 0.0f, 1.0f); // Normal Pointing Towards Viewer
glVertex3f( 1.0f, 1.0f, 1.0f); // Top Right Of The Quad (Front)
glVertex3f(-1.0f, 1.0f, 1.0f); // Top Left Of The Quad (Front)
glVertex3f(-1.0f,-1.0f, 1.0f); // Bottom Left Of The Quad (Front)
glVertex3f( 1.0f,-1.0f, 1.0f); // Bottom Right Of The Quad (Front)
// Back Face
glNormal3f(0.0f, 0.0f,-1.0f); // Normal Pointing Away From Viewer
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Back)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Back)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Back)
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Back)
// Top Face
glNormal3f( 0.0f, 1.0f, 0.0f); // Normal Pointing Up
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Top)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Top)
glVertex3f(-1.0f, 1.0f, 1.0f); // Bottom Left Of The Quad (Top)
glVertex3f( 1.0f, 1.0f, 1.0f); // Bottom Right Of The Quad (Top)
// Bottom Face
glNormal3f( 0.0f,-1.0f, 0.0f); // Normal Pointing Down
glVertex3f( 1.0f,-1.0f, 1.0f); // Top Right Of The Quad (Bottom)
glVertex3f(-1.0f,-1.0f, 1.0f); // Top Left Of The Quad (Bottom)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Bottom)
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Bottom)
// Right face
glNormal3f( 1.0f, 0.0f, 0.0f); // Normal Pointing Right
glVertex3f( 1.0f, 1.0f,-1.0f); // Top Right Of The Quad (Right)
glVertex3f( 1.0f, 1.0f, 1.0f); // Top Left Of The Quad (Right)
glVertex3f( 1.0f,-1.0f, 1.0f); // Bottom Left Of The Quad (Right)
glVertex3f( 1.0f,-1.0f,-1.0f); // Bottom Right Of The Quad (Right)
// Left Face
glNormal3f(-1.0f, 0.0f, 0.0f); // Normal Pointing Left
glVertex3f(-1.0f, 1.0f, 1.0f); // Top Right Of The Quad (Left)
glVertex3f(-1.0f, 1.0f,-1.0f); // Top Left Of The Quad (Left)
glVertex3f(-1.0f,-1.0f,-1.0f); // Bottom Left Of The Quad (Left)
glVertex3f(-1.0f,-1.0f, 1.0f); // Bottom Right Of The Quad (Left)
glEnd();
|
This code draws a cube with the correct normals.
|
if (g_keys->keyDown[VK_UP])
{
xspeed-=(float)(milliseconds)/5.0f;
}
if (g_keys->keyDown[VK_DOWN])
{
xspeed+=(float)(milliseconds)/5.0f;
}
if (g_keys->keyDown[VK_RIGHT])
{
yspeed+=(float)(milliseconds)/5.0f;
}
if (g_keys->keyDown[VK_LEFT])
{
yspeed-=(float)(milliseconds)/5.0f;
}
xrot+=xspeed*((float)(milliseconds)/50.0f);
yrot+=yspeed*((float)(milliseconds)/50.0f);
|
The last bit of code we need allows us to rotate our objects. This code goes in the Update function
So that's it for the first tutorial. If you have questions, trouble, or feedback, email me.
|