inline float DotProduct (VECTOR &V1, VECTOR &V2) // Calculate The Angle Between The 2 Vectors
{
return V1.X * V2.X + V1.Y * V2.Y + V1.Z * V2.Z; // Return The Angle
}
inline float Magnitude (VECTOR &V) // Calculate The Length Of The Vector
{
return sqrtf (V.X * V.X + V.Y * V.Y + V.Z * V.Z); // Return The Length Of The Vector
}
void Normalize (VECTOR &V) // Creates A Vector With A Unit Length Of 1
{
float M = Magnitude (V); // Calculate The Length Of The Vector
if (M != 0.0f) // Make Sure We Don't Divide By 0
{
V.X /= M; // Normalize The 3 Components
V.Y /= M;
V.Z /= M;
}
}
This function rotates a vector using the matrix provided. Please note that it ONLY rotates the vector — it has nothing to do with the position of the vector. This is used when rotating normals to make sure that they stay pointing in the right direction when we calculate the lighting.
void RotateVector (MATRIX &M, VECTOR &V, VECTOR &D) // Rotate A Vector Using The Supplied Matrix
{
D.X = (M.Data[0] * V.X) + (M.Data[4] * V.Y) + (M.Data[8] * V.Z); // Rotate Around The X Axis
D.Y = (M.Data[1] * V.X) + (M.Data[5] * V.Y) + (M.Data[9] * V.Z); // Rotate Around The Y Axis
D.Z = (M.Data[2] * V.X) + (M.Data[6] * V.Y) + (M.Data[10] * V.Z); // Rotate Around The Z Axis
}
The first major function of the engine, Initialize does exactly what is says. I've cut out a few lines of code as they are not needed in the explaination.
// Any GL Init Code & User Initialiazation Goes Here
BOOL Initialize (GL_Window* window, Keys* keys) {
These 3 variables are used to load the shader file. Line contains space for a single line in the text file, while shaderData stores the actual shader values. You may be wondering why we have 96 values instead of 32. Well, we need to convert the greyscale values to RGB so that OpenGL can use them. We can still store the values as greyscale, but we will simply use the same value for the R, G and B components when uploading the texture.
char Line[255]; // Storage For 255 Characters
float shaderData[32][3]; // Storate For The 96 Shader Values
FILE *In = NULL; // File Pointer
When drawing the lines, we want to make sure that they are nice and smooth. Initially this value is turned off, but by pressing the "2" key, it can be toggled on/off.
glShadeModel (GL_SMOOTH); // Enables Smooth Color Shading
glDisable (GL_LINE_SMOOTH); // Initially Disable Line Smoothing
glEnable (GL_CULL_FACE); // Enable OpenGL Face Culling
We disable OpenGL lighting because we do all of the lighting calculations ourself.
glDisable (GL_LIGHTING); // Disable OpenGL Lighting
Here is where we load the shader file. It is simply 32 floating point values stored as ASCII (for easy modification), each one on a seperate line.
In = fopen ("Data\\shader.txt", "r"); // Open The Shader File
if (In) // Check To See If The File Opened
{
for (i = 0; i < 32; i++) // Loop Though The 32 Greyscale Values
{
if (feof (In)) // Check For The End Of The File
break;
fgets (Line, 255, In); // Get The Current Line
Here we convert the greyscale value into RGB, as described above.
// Copy Over The Value
shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = atof (Line);
}
fclose (In); // Close The File
} else return FALSE; // It Went Horribly Horribly Wrong
Now we upload the texture. At it clearly states, do not use any kind of filtering on the texture or else it will look odd, to say the least. GL_TEXTURE_1D is used because it is a 1D array of values.
glGenTextures (1, &shaderTexture[0]); // Get A Free Texture ID
glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind This Texture. From Now On It Will Be 1D
// For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering!
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// Upload
glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData);
Now set the lighting direction. I've got it pointing down positive Z, which means it's going to hit the model face-on.
lightAngle.X = 0.0f; // Set The X Direction
lightAngle.Y = 0.0f; // Set The Y Direction
lightAngle.Z = 1.0f; // Set The Z Direction
Normalize (lightAngle); // Normalize The Light Direction
Load in the mesh from file (described above).
return ReadMesh (); // Return The Value Of ReadMesh
}
The opposite of the above function, Deinitalize deletes the texture and polygon data created by Initalize and ReadMesh.
void Deinitialize (void) // Any User DeInitialization Goes Here
{
glDeleteTextures (1, &shaderTexture[0]); // Delete The Shader Texture
delete [] polyData; // Delete The Polygon Data
}
The main demo loop. All this does is process the input and update the angle. Controls are as follows:
<SPACE> = Toggle rotation
1 = Toggle outline drawing
2 = Toggle outline anti-alaising
<UP> = Increase line width
<DOWM> = Decrease line width
void Update (DWORD milliseconds) // Perform Motion Updates Here
{
if (g_keys->keyDown [' '] == TRUE) // Is the Space Bar Being Pressed?
{
modelRotate = !modelRotate; // Toggle Model Rotation On/Off
g_keys->keyDown [' '] = FALSE;
}
if (g_keys->keyDown ['1'] == TRUE) // Is The Number 1 Being Pressed?
{
outlineDraw = !outlineDraw; // Toggle Outline Drawing On/Off
g_keys->keyDown ['1'] = FALSE;
}
if (g_keys->keyDown ['2'] == TRUE) // Is The Number 2 Being Pressed?
{
outlineSmooth = !outlineSmooth; // Toggle Anti-Aliasing On/Off
g_keys->keyDown ['2'] = FALSE;
}
if (g_keys->keyDown [VK_UP] == TRUE) // Is The Up Arrow Being Pressed?
{
outlineWidth++; // Increase Line Width
g_keys->keyDown [VK_UP] = FALSE;
}
if (g_keys->keyDown [VK_DOWN] == TRUE) // Is The Down Arrow Being Pressed?
{
outlineWidth--; // Decrease Line Width
g_keys->keyDown [VK_DOWN] = FALSE;
}
if (modelRotate) // Check To See If Rotation Is Enabled