// The Cel-Shading code from NeHe's lesson 37: http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=37 // // This code was created by Jeff Molofee '99 (ported to Linux/GLUT by Kah-Wah Tang 2002 with help from the lesson 1 basecode for Linux by Richard Campbell) // Finally, it was ported to Amiga OS (and converted from C++ to C) by Hans de Ruiter // // If you've found this code useful, please let me know. // // Visit me at www.xs4all.nl/~chtang // (email Kah-Wah Tang at tombraider28@hotmail.com) // #include // Header File For The GLUT Library #include // Header File For The OpenGL32 Library #include // Header File For The GLu32 Library #include // Header file for sleeping. #include #include #include /* Minimum stack space */ static USED const char *stack = "$STACK:65535"; /* ascii code for the escape key */ #define ESCAPE 27 #define FALSE 0 #define TRUE 1 /* The number of our GLUT window */ int window; #ifndef CDS_FULLSCREEN // CDS_FULLSCREEN Is Not Defined By Some #define CDS_FULLSCREEN 4 // Compilers. By Defining It This Way, #endif // We Can Avoid Errors #define CORRECT_ENDIAN_32(x) (((0xFF000000 & (x)) >> 24) | ((0x00FF0000 & (x)) >> 8) | (((uint32)(0x0000FF00 & (x))) << 8) | (((uint32)(0x000000FF & (x))) << 24)) // User Defined Structures typedef struct tagMATRIX // A Structure To Hold An OpenGL Matrix ( NEW ) { float Data[16]; // We Use [16] Due To OpenGL's Matrix Format ( NEW ) } MATRIX; typedef struct tagVECTOR // A Structure To Hold A Single Vector ( NEW ) { float X, Y, Z; // The Components Of The Vector ( NEW ) } VECTOR; typedef struct tagVERTEX // A Structure To Hold A Single Vertex ( NEW ) { VECTOR Nor; // Vertex Normal ( NEW ) VECTOR Pos; // Vertex Position ( NEW ) } VERTEX; typedef struct tagPOLYGON // A Structure To Hold A Single Polygon ( NEW ) { VERTEX Verts[3]; // Array Of 3 VERTEX Structures ( NEW ) } POLYGON; // User Defined Variables BOOL outlineDraw = TRUE; // Flag To Draw The Outline ( NEW ) BOOL outlineSmooth = FALSE; // Flag To Anti-Alias The Lines ( NEW ) float outlineColor[3] = { 0.0f, 0.0f, 0.0f }; // Color Of The Lines ( NEW ) float outlineWidth = 3.0f; // Width Of The Lines ( NEW ) VECTOR lightAngle; // The Direction Of The Light ( NEW ) BOOL lightRotate = FALSE; // Flag To See If We Rotate The Light ( NEW ) float modelAngle = 0.0f; // Y-Axis Angle Of The Model ( NEW ) BOOL modelRotate = TRUE; // Flag To Rotate The Model ( NEW ) POLYGON *polyData = NULL; // Polygon Data ( NEW ) int polyNum = 0; // Number Of Polygons ( NEW ) GLuint shaderTexture[1]; // Storage For One Texture ( NEW ) // File Functions BOOL ReadMesh(void) // Reads The Contents Of The "model.txt" File ( NEW ) { FILE *In = fopen ("Data/Model.txt", "rb"); // Open The File ( NEW ) if (!In) return FALSE; // Return FALSE If File Not Opened ( NEW ) fread (&polyNum, sizeof (int), 1, In); // Read The Header (i.e. Number Of Polygons) ( NEW ) polyNum = CORRECT_ENDIAN_32(polyNum); printf("polyNum: %d\n", polyNum); polyData = (POLYGON*)malloc(sizeof(POLYGON) * polyNum); // Allocate The Memory ( NEW ) fread (&polyData[0], sizeof (POLYGON) * polyNum, 1, In); // Read In All Polygon Data ( NEW ) // Perform endian swapping int numElements = polyNum * sizeof(POLYGON) / sizeof(float); GLuint *data = (GLuint*)polyData; for(int i = 0; i < numElements; ++i) { data[i] = CORRECT_ENDIAN_32(data[i]); } fclose (In); // Close The File ( NEW ) return TRUE; // It Worked ( NEW ) } // Math Functions inline float DotProduct (VECTOR *V1, VECTOR *V2) // Calculate The Angle Between The 2 Vectors ( NEW ) { return V1->X * V2->X + V1->Y * V2->Y + V1->Z * V2->Z; // Return The Angle ( NEW ) } inline float Magnitude (VECTOR *V) // Calculate The Length Of The Vector ( NEW ) { return sqrtf (V->X * V->X + V->Y * V->Y + V->Z * V->Z); // Return The Length Of The Vector ( NEW ) } void Normalize (VECTOR *V) // Creates A Vector With A Unit Length Of 1 ( NEW ) { float M = Magnitude (V); // Calculate The Length Of The Vector ( NEW ) if (M != 0.0f) // Make Sure We Don't Divide By 0 ( NEW ) { V->X /= M; // Normalize The 3 Components ( NEW ) V->Y /= M; V->Z /= M; } } void RotateVector (MATRIX *M, VECTOR *V, VECTOR *D) // Rotate A Vector Using The Supplied Matrix ( NEW ) { D->X = (M->Data[0] * V->X) + (M->Data[4] * V->Y) + (M->Data[8] * V->Z); // Rotate Around The X Axis ( NEW ) D->Y = (M->Data[1] * V->X) + (M->Data[5] * V->Y) + (M->Data[9] * V->Z); // Rotate Around The Y Axis ( NEW ) D->Z = (M->Data[2] * V->X) + (M->Data[6] * V->Y) + (M->Data[10] * V->Z); // Rotate Around The Z Axis ( NEW ) } /* A general OpenGL initialization function. Sets all of the initial parameters. */ BOOL InitGL(int Width, int Height) // We call this right after our OpenGL window is created. { int i; // Looping Variable ( NEW ) char Line[255]; // Storage For 255 Characters ( NEW ) float shaderData[32][3]; // Storate For The 96 Shader Values ( NEW ) FILE *In = NULL; // File Pointer ( NEW ) //g_window = window; //g_keys = keys; // Start Of User Initialization glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Realy Nice perspective calculations glClearColor (0.7f, 0.7f, 0.7f, 0.0f); // Light Grey Background glClearDepth (1.0f); // Depth Buffer Setup glEnable (GL_DEPTH_TEST); // Enable Depth Testing glDepthFunc (GL_LESS); // The Type Of Depth Test To Do glShadeModel (GL_SMOOTH); // Enables Smooth Color Shading ( NEW ) glDisable (GL_LINE_SMOOTH); // Initially Disable Line Smoothing ( NEW ) glEnable (GL_CULL_FACE); // Enable OpenGL Face Culling ( NEW ) glDisable (GL_LIGHTING); // Disable OpenGL Lighting ( NEW ) In = fopen ("Data/Shader.txt", "r"); // Open The Shader File ( NEW ) if (In) // Check To See If The File Opened ( NEW ) { for (i = 0; i < 32; i++) // Loop Though The 32 Greyscale Values ( NEW ) { if (feof (In)) // Check For The End Of The File ( NEW ) break; fgets (Line, 255, In); // Get The Current Line ( NEW ) shaderData[i][0] = shaderData[i][1] = shaderData[i][2] = (float)atof(Line); // Copy Over The Value ( NEW ) } fclose (In); // Close The File ( NEW ) } else return FALSE; // It Went Horribly Horribly Wrong ( NEW ) glGenTextures (1, &shaderTexture[0]); // Get A Free Texture ID ( NEW ) glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind This Texture. From Now On It Will Be 1D ( NEW ) // For Crying Out Loud Don't Let OpenGL Use Bi/Trilinear Filtering! ( NEW ) glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri (GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexImage1D (GL_TEXTURE_1D, 0, GL_RGB, 32, 0, GL_RGB , GL_FLOAT, shaderData); // Upload ( NEW ) lightAngle.X = 0.0f; // Set The X Direction ( NEW ) lightAngle.Y = 0.0f; // Set The Y Direction ( NEW ) lightAngle.Z = 1.0f; // Set The Z Direction ( NEW ) Normalize (&lightAngle); // Normalize The Light Direction ( NEW ) return ReadMesh (); } /* The function called when our window is resized (which shouldn't happen, because we're fullscreen) */ void ReSizeGLScene(int Width, int Height) { if (Height==0) // Prevent A Divide By Zero If The Window Is Too Small Height=1; glViewport(0, 0, Width, Height); // Reset The Current Viewport And Perspective Transformation glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0f,(GLfloat)Width/(GLfloat)Height,0.1f,100.0f); glMatrixMode(GL_MODELVIEW); } /* The main drawing function. */ void DrawGLScene() { int i, j; // Looping Variables ( NEW ) float TmpShade; // Temporary Shader Value ( NEW ) MATRIX TmpMatrix; // Temporary MATRIX Structure ( NEW ) VECTOR TmpVector, TmpNormal; // Temporary VECTOR Structures ( NEW ) glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear The Buffers glLoadIdentity (); // Reset The Matrix if (outlineSmooth) // Check To See If We Want Anti-Aliased Lines ( NEW ) { glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); // Use The Good Calculations ( NEW ) glEnable (GL_LINE_SMOOTH); // Enable Anti-Aliasing ( NEW ) } else // We Don't Want Smooth Lines ( NEW ) glDisable (GL_LINE_SMOOTH); // Disable Anti-Aliasing ( NEW ) glTranslatef (0.0f, 0.0f, -2.0f); // Move 2 Units Away From The Screen ( NEW ) glRotatef (modelAngle, 0.0f, 1.0f, 0.0f); // Rotate The Model On It's Y-Axis ( NEW ) glGetFloatv (GL_MODELVIEW_MATRIX, TmpMatrix.Data); // Get The Generated Matrix ( NEW ) // Cel-Shading Code // glEnable (GL_TEXTURE_1D); // Enable 1D Texturing ( NEW ) glBindTexture (GL_TEXTURE_1D, shaderTexture[0]); // Bind Our Texture ( NEW ) glColor3f (1.0f, 1.0f, 1.0f); // Set The Color Of The Model ( NEW ) glBegin (GL_TRIANGLES); // Tell OpenGL That We're Drawing Triangles for (i = 0; i < polyNum; i++) // Loop Through Each Polygon ( NEW ) { for (j = 0; j < 3; j++) // Loop Through Each Vertex ( NEW ) { TmpNormal.X = polyData[i].Verts[j].Nor.X; // Fill Up The TmpNormal Structure With TmpNormal.Y = polyData[i].Verts[j].Nor.Y; // The Current Vertices' Normal Values ( NEW ) TmpNormal.Z = polyData[i].Verts[j].Nor.Z; RotateVector (&TmpMatrix, &TmpNormal, &TmpVector); // Rotate This By The Matrix ( NEW ) Normalize (&TmpVector); // Normalize The New Normal ( NEW ) TmpShade = DotProduct (&TmpVector, &lightAngle); // Calculate The Shade Value ( NEW ) if (TmpShade < 0.0f) TmpShade = 0.0f; // Clamp The Value to 0 If Negative ( NEW ) glTexCoord1f (TmpShade); // Set The Texture Co-ordinate As The Shade Value ( NEW ) glVertex3fv (&polyData[i].Verts[j].Pos.X); // Send The Vertex Position ( NEW ) } } glEnd (); // Tell OpenGL To Finish Drawing glDisable (GL_TEXTURE_1D); // Disable 1D Textures ( NEW ) // Outline Code // if (outlineDraw) // Check To See If We Want To Draw The Outline ( NEW ) { glEnable (GL_BLEND); // Enable Blending ( NEW ) glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); // Set The Blend Mode ( NEW ) glPolygonMode (GL_BACK, GL_LINE); // Draw Backfacing Polygons As Wireframes ( NEW ) glLineWidth (outlineWidth); // Set The Line Width ( NEW ) glCullFace (GL_FRONT); // Don't Draw Any Front-Facing Polygons ( NEW ) glDepthFunc (GL_LEQUAL); // Change The Depth Mode ( NEW ) glColor3fv (&outlineColor[0]); // Set The Outline Color ( NEW ) glBegin (GL_TRIANGLES); // Tell OpenGL What We Want To Draw for (i = 0; i < polyNum; i++) // Loop Through Each Polygon ( NEW ) { for (j = 0; j < 3; j++) // Loop Through Each Vertex ( NEW ) { glVertex3fv (&polyData[i].Verts[j].Pos.X); // Send The Vertex Position ( NEW ) } } glEnd (); // Tell OpenGL We've Finished glDepthFunc (GL_LESS); // Reset The Depth-Testing Mode ( NEW ) glCullFace (GL_BACK); // Reset The Face To Be Culled ( NEW ) glPolygonMode (GL_BACK, GL_FILL); // Reset Back-Facing Polygon Drawing Mode ( NEW ) glDisable (GL_BLEND); // Disable Blending ( NEW ) } // since this is double buffered, swap the buffers to display what just got drawn. glutSwapBuffers(); if (modelRotate) // Check To See If Rotation Is Enabled ( NEW ) modelAngle += 2.0f; } void Deinitialize (void) // Any User DeInitialization Goes Here { glDeleteTextures (1, &shaderTexture[0]); // Delete The Shader Texture ( NEW ) free(polyData); // Delete The Polygon Data ( NEW ) } /* The function called whenever a key is pressed. */ void keyPressed(unsigned char key, int x, int y) { /* avoid thrashing this procedure */ usleep(100); /* If escape is pressed, kill everything. */ switch (key) { case ESCAPE: // kill everything. /* shut down our window */ Deinitialize(); glutDestroyWindow(window); /* exit the program...normal termination. */ exit(1); break; // redundant. case 32: modelRotate = !modelRotate; break; case 49: outlineDraw = !outlineDraw; break; case 50: outlineSmooth = !outlineSmooth; break; default: printf ("Key %d pressed. No action there yet.\n", key); break; } // Update Angle Based On The Clock } void specialKeyPressed(int key, int x, int y) { /* avoid thrashing this procedure */ usleep(100); switch (key) { case GLUT_KEY_UP: // decrease x rotation speed; outlineWidth++; break; case GLUT_KEY_DOWN: // increase x rotation speed; outlineWidth--; break; default: break; } } int main(int argc, char **argv) { /* Initialize GLUT state - glut will take any command line arguments that pertain to it or X Windows - look at its documentation at http://reality.sgi.com/mjk/spec3/spec3.html */ glutInit(&argc, argv); /* Select type of Display mode: Double buffer RGBA color Alpha components supported Depth buffer */ glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); /* get a 640 x 480 window */ glutInitWindowSize(640, 480); /* the window starts at the upper left corner of the screen */ glutInitWindowPosition(0, 0); /* Open a window */ window = glutCreateWindow("Jeff Molofee's GL Code Tutorial ... NeHe '99"); /* Register the function to do all our OpenGL drawing. */ glutDisplayFunc(&DrawGLScene); /* Go fullscreen. This is as soon as possible. */ //glutFullScreen(); /* Even if there are no events, redraw our gl scene. */ glutIdleFunc(&DrawGLScene); /* Register the function called when our window is resized. */ glutReshapeFunc(&ReSizeGLScene); /* Register the function called when the keyboard is pressed. */ glutKeyboardFunc(&keyPressed); glutSpecialFunc(&specialKeyPressed); /* Initialize our window. */ InitGL(640, 480); /* Start Event Processing Engine */ glutMainLoop(); return 1; }