| @@ -0,0 +1,891 @@ | |||
| /* Copyright (c) Mark J. Kilgard, 1994, 1997. */ | |||
| /* This program is freely distributable without licensing fees | |||
| and is provided without guarantee or warrantee expressed or | |||
| implied. This program is -not- in the public domain. */ | |||
| /* Example for PC game developers to show how to *combine* texturing, | |||
| reflections, and projected shadows all in real-time with OpenGL. | |||
| Robust reflections use stenciling. Robust projected shadows | |||
| use both stenciling and polygon offset. PC game programmers | |||
| should realize that neither stenciling nor polygon offset are | |||
| supported by Direct3D, so these real-time rendering algorithms | |||
| are only really viable with OpenGL. | |||
| The program has modes for disabling the stenciling and polygon | |||
| offset uses. It is worth running this example with these features | |||
| toggled off so you can see the sort of artifacts that result. | |||
| Notice that the floor texturing, reflections, and shadowing | |||
| all co-exist properly. */ | |||
| /* When you run this program: Left mouse button controls the | |||
| view. Middle mouse button controls light position (left & | |||
| right rotates light around dino; up & down moves light | |||
| position up and down). Right mouse button pops up menu. */ | |||
| /* Check out the comments in the "redraw" routine to see how the | |||
| reflection blending and surface stenciling is done. You can | |||
| also see in "redraw" how the projected shadows are rendered, | |||
| including the use of stenciling and polygon offset. */ | |||
| /* This program is derived from glutdino.c */ | |||
| /* Compile: cc -o dinoshade dinoshade.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */ | |||
| #include <stdio.h> | |||
| #include <stdlib.h> | |||
| #include <string.h> | |||
| #include <math.h> /* for cos(), sin(), and sqrt() */ | |||
| #include <GL/glut.h> /* OpenGL Utility Toolkit header */ | |||
| /* Some <math.h> files do not define M_PI... */ | |||
| #ifndef M_PI | |||
| #define M_PI 3.14159265358979323846 | |||
| #endif | |||
| /* Variable controlling various rendering modes. */ | |||
| static int stencilReflection = 1, stencilShadow = 1, offsetShadow = 1; | |||
| static int renderShadow = 1, renderDinosaur = 1, renderReflection = 1; | |||
| static int linearFiltering = 0, useMipmaps = 0, useTexture = 1; | |||
| static int reportSpeed = 0; | |||
| static int animation = 1; | |||
| static GLboolean lightSwitch = GL_TRUE; | |||
| static int directionalLight = 1; | |||
| static int forceExtension = 0; | |||
| /* Time varying or user-controled variables. */ | |||
| static float jump = 0.0; | |||
| static float lightAngle = 0.0, lightHeight = 20; | |||
| GLfloat angle = -150; /* in degrees */ | |||
| GLfloat angle2 = 30; /* in degrees */ | |||
| int moving, startx, starty; | |||
| int lightMoving = 0, lightStartX, lightStartY; | |||
| enum { | |||
| MISSING, EXTENSION, ONE_DOT_ONE | |||
| }; | |||
| int polygonOffsetVersion; | |||
| static GLdouble bodyWidth = 3.0; | |||
| /* *INDENT-OFF* */ | |||
| static GLfloat body[][2] = { {0, 3}, {1, 1}, {5, 1}, {8, 4}, {10, 4}, {11, 5}, | |||
| {11, 11.5}, {13, 12}, {13, 13}, {10, 13.5}, {13, 14}, {13, 15}, {11, 16}, | |||
| {8, 16}, {7, 15}, {7, 13}, {8, 12}, {7, 11}, {6, 6}, {4, 3}, {3, 2}, | |||
| {1, 2} }; | |||
| static GLfloat arm[][2] = { {8, 10}, {9, 9}, {10, 9}, {13, 8}, {14, 9}, {16, 9}, | |||
| {15, 9.5}, {16, 10}, {15, 10}, {15.5, 11}, {14.5, 10}, {14, 11}, {14, 10}, | |||
| {13, 9}, {11, 11}, {9, 11} }; | |||
| static GLfloat leg[][2] = { {8, 6}, {8, 4}, {9, 3}, {9, 2}, {8, 1}, {8, 0.5}, {9, 0}, | |||
| {12, 0}, {10, 1}, {10, 2}, {12, 4}, {11, 6}, {10, 7}, {9, 7} }; | |||
| static GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15}, | |||
| {9.6, 15.25}, {9, 15.25} }; | |||
| static GLfloat lightPosition[4]; | |||
| static GLfloat lightColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */ | |||
| static GLfloat skinColor[] = {0.1, 1.0, 0.1, 1.0}, eyeColor[] = {1.0, 0.2, 0.2, 1.0}; | |||
| /* *INDENT-ON* */ | |||
| /* Nice floor texture tiling pattern. */ | |||
| static char *circles[] = { | |||
| "....xxxx........", | |||
| "..xxxxxxxx......", | |||
| ".xxxxxxxxxx.....", | |||
| ".xxx....xxx.....", | |||
| "xxx......xxx....", | |||
| "xxx......xxx....", | |||
| "xxx......xxx....", | |||
| "xxx......xxx....", | |||
| ".xxx....xxx.....", | |||
| ".xxxxxxxxxx.....", | |||
| "..xxxxxxxx......", | |||
| "....xxxx........", | |||
| "................", | |||
| "................", | |||
| "................", | |||
| "................", | |||
| }; | |||
| static void | |||
| makeFloorTexture(void) | |||
| { | |||
| GLubyte floorTexture[16][16][3]; | |||
| GLubyte *loc; | |||
| int s, t; | |||
| /* Setup RGB image for the texture. */ | |||
| loc = (GLubyte*) floorTexture; | |||
| for (t = 0; t < 16; t++) { | |||
| for (s = 0; s < 16; s++) { | |||
| if (circles[t][s] == 'x') { | |||
| /* Nice green. */ | |||
| loc[0] = 0x1f; | |||
| loc[1] = 0x8f; | |||
| loc[2] = 0x1f; | |||
| } else { | |||
| /* Light gray. */ | |||
| loc[0] = 0xaa; | |||
| loc[1] = 0xaa; | |||
| loc[2] = 0xaa; | |||
| } | |||
| loc += 3; | |||
| } | |||
| } | |||
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |||
| if (useMipmaps) { | |||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, | |||
| GL_LINEAR_MIPMAP_LINEAR); | |||
| gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 16, 16, | |||
| GL_RGB, GL_UNSIGNED_BYTE, floorTexture); | |||
| } else { | |||
| if (linearFiltering) { | |||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |||
| } else { | |||
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |||
| } | |||
| glTexImage2D(GL_TEXTURE_2D, 0, 3, 16, 16, 0, | |||
| GL_RGB, GL_UNSIGNED_BYTE, floorTexture); | |||
| } | |||
| } | |||
| enum { | |||
| X, Y, Z, W | |||
| }; | |||
| enum { | |||
| A, B, C, D | |||
| }; | |||
| /* Create a matrix that will project the desired shadow. */ | |||
| void | |||
| shadowMatrix(GLfloat shadowMat[4][4], | |||
| GLfloat groundplane[4], | |||
| GLfloat lightpos[4]) | |||
| { | |||
| GLfloat dot; | |||
| /* Find dot product between light position vector and ground plane normal. */ | |||
| dot = groundplane[X] * lightpos[X] + | |||
| groundplane[Y] * lightpos[Y] + | |||
| groundplane[Z] * lightpos[Z] + | |||
| groundplane[W] * lightpos[W]; | |||
| shadowMat[0][0] = dot - lightpos[X] * groundplane[X]; | |||
| shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y]; | |||
| shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z]; | |||
| shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W]; | |||
| shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X]; | |||
| shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y]; | |||
| shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z]; | |||
| shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W]; | |||
| shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X]; | |||
| shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y]; | |||
| shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z]; | |||
| shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W]; | |||
| shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X]; | |||
| shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y]; | |||
| shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z]; | |||
| shadowMat[3][3] = dot - lightpos[W] * groundplane[W]; | |||
| } | |||
| /* Find the plane equation given 3 points. */ | |||
| void | |||
| findPlane(GLfloat plane[4], | |||
| GLfloat v0[3], GLfloat v1[3], GLfloat v2[3]) | |||
| { | |||
| GLfloat vec0[3], vec1[3]; | |||
| /* Need 2 vectors to find cross product. */ | |||
| vec0[X] = v1[X] - v0[X]; | |||
| vec0[Y] = v1[Y] - v0[Y]; | |||
| vec0[Z] = v1[Z] - v0[Z]; | |||
| vec1[X] = v2[X] - v0[X]; | |||
| vec1[Y] = v2[Y] - v0[Y]; | |||
| vec1[Z] = v2[Z] - v0[Z]; | |||
| /* find cross product to get A, B, and C of plane equation */ | |||
| plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y]; | |||
| plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]); | |||
| plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X]; | |||
| plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]); | |||
| } | |||
| void | |||
| extrudeSolidFromPolygon(GLfloat data[][2], unsigned int dataSize, | |||
| GLdouble thickness, GLuint side, GLuint edge, GLuint whole) | |||
| { | |||
| static GLUtriangulatorObj *tobj = NULL; | |||
| GLdouble vertex[3], dx, dy, len; | |||
| int i; | |||
| int count = (int) (dataSize / (2 * sizeof(GLfloat))); | |||
| if (tobj == NULL) { | |||
| tobj = gluNewTess(); /* create and initialize a GLU | |||
| polygon tesselation object */ | |||
| gluTessCallback(tobj, GLU_BEGIN, glBegin); | |||
| gluTessCallback(tobj, GLU_VERTEX, glVertex2fv); /* semi-tricky */ | |||
| gluTessCallback(tobj, GLU_END, glEnd); | |||
| } | |||
| glNewList(side, GL_COMPILE); | |||
| glShadeModel(GL_SMOOTH); /* smooth minimizes seeing | |||
| tessellation */ | |||
| gluBeginPolygon(tobj); | |||
| for (i = 0; i < count; i++) { | |||
| vertex[0] = data[i][0]; | |||
| vertex[1] = data[i][1]; | |||
| vertex[2] = 0; | |||
| gluTessVertex(tobj, vertex, data[i]); | |||
| } | |||
| gluEndPolygon(tobj); | |||
| glEndList(); | |||
| glNewList(edge, GL_COMPILE); | |||
| glShadeModel(GL_FLAT); /* flat shade keeps angular hands | |||
| from being "smoothed" */ | |||
| glBegin(GL_QUAD_STRIP); | |||
| for (i = 0; i <= count; i++) { | |||
| /* mod function handles closing the edge */ | |||
| glVertex3f(data[i % count][0], data[i % count][1], 0.0); | |||
| glVertex3f(data[i % count][0], data[i % count][1], thickness); | |||
| /* Calculate a unit normal by dividing by Euclidean | |||
| distance. We * could be lazy and use | |||
| glEnable(GL_NORMALIZE) so we could pass in * arbitrary | |||
| normals for a very slight performance hit. */ | |||
| dx = data[(i + 1) % count][1] - data[i % count][1]; | |||
| dy = data[i % count][0] - data[(i + 1) % count][0]; | |||
| len = sqrt(dx * dx + dy * dy); | |||
| glNormal3f(dx / len, dy / len, 0.0); | |||
| } | |||
| glEnd(); | |||
| glEndList(); | |||
| glNewList(whole, GL_COMPILE); | |||
| glFrontFace(GL_CW); | |||
| glCallList(edge); | |||
| glNormal3f(0.0, 0.0, -1.0); /* constant normal for side */ | |||
| glCallList(side); | |||
| glPushMatrix(); | |||
| glTranslatef(0.0, 0.0, thickness); | |||
| glFrontFace(GL_CCW); | |||
| glNormal3f(0.0, 0.0, 1.0); /* opposite normal for other side */ | |||
| glCallList(side); | |||
| glPopMatrix(); | |||
| glEndList(); | |||
| } | |||
| /* Enumerants for refering to display lists. */ | |||
| typedef enum { | |||
| RESERVED, BODY_SIDE, BODY_EDGE, BODY_WHOLE, ARM_SIDE, ARM_EDGE, ARM_WHOLE, | |||
| LEG_SIDE, LEG_EDGE, LEG_WHOLE, EYE_SIDE, EYE_EDGE, EYE_WHOLE | |||
| } displayLists; | |||
| static void | |||
| makeDinosaur(void) | |||
| { | |||
| extrudeSolidFromPolygon(body, sizeof(body), bodyWidth, | |||
| BODY_SIDE, BODY_EDGE, BODY_WHOLE); | |||
| extrudeSolidFromPolygon(arm, sizeof(arm), bodyWidth / 4, | |||
| ARM_SIDE, ARM_EDGE, ARM_WHOLE); | |||
| extrudeSolidFromPolygon(leg, sizeof(leg), bodyWidth / 2, | |||
| LEG_SIDE, LEG_EDGE, LEG_WHOLE); | |||
| extrudeSolidFromPolygon(eye, sizeof(eye), bodyWidth + 0.2, | |||
| EYE_SIDE, EYE_EDGE, EYE_WHOLE); | |||
| } | |||
| static void | |||
| drawDinosaur(void) | |||
| { | |||
| glPushMatrix(); | |||
| /* Translate the dinosaur to be at (0,8,0). */ | |||
| glTranslatef(-8, 0, -bodyWidth / 2); | |||
| glTranslatef(0.0, jump, 0.0); | |||
| glMaterialfv(GL_FRONT, GL_DIFFUSE, skinColor); | |||
| glCallList(BODY_WHOLE); | |||
| glTranslatef(0.0, 0.0, bodyWidth); | |||
| glCallList(ARM_WHOLE); | |||
| glCallList(LEG_WHOLE); | |||
| glTranslatef(0.0, 0.0, -bodyWidth - bodyWidth / 4); | |||
| glCallList(ARM_WHOLE); | |||
| glTranslatef(0.0, 0.0, -bodyWidth / 4); | |||
| glCallList(LEG_WHOLE); | |||
| glTranslatef(0.0, 0.0, bodyWidth / 2 - 0.1); | |||
| glMaterialfv(GL_FRONT, GL_DIFFUSE, eyeColor); | |||
| glCallList(EYE_WHOLE); | |||
| glPopMatrix(); | |||
| } | |||
| static GLfloat floorVertices[4][3] = { | |||
| { -20.0, 0.0, 20.0 }, | |||
| { 20.0, 0.0, 20.0 }, | |||
| { 20.0, 0.0, -20.0 }, | |||
| { -20.0, 0.0, -20.0 }, | |||
| }; | |||
| /* Draw a floor (possibly textured). */ | |||
| static void | |||
| drawFloor(void) | |||
| { | |||
| glDisable(GL_LIGHTING); | |||
| if (useTexture) { | |||
| glEnable(GL_TEXTURE_2D); | |||
| } | |||
| glBegin(GL_QUADS); | |||
| glTexCoord2f(0.0, 0.0); | |||
| glVertex3fv(floorVertices[0]); | |||
| glTexCoord2f(0.0, 16.0); | |||
| glVertex3fv(floorVertices[1]); | |||
| glTexCoord2f(16.0, 16.0); | |||
| glVertex3fv(floorVertices[2]); | |||
| glTexCoord2f(16.0, 0.0); | |||
| glVertex3fv(floorVertices[3]); | |||
| glEnd(); | |||
| if (useTexture) { | |||
| glDisable(GL_TEXTURE_2D); | |||
| } | |||
| glEnable(GL_LIGHTING); | |||
| } | |||
| static GLfloat floorPlane[4]; | |||
| static GLfloat floorShadow[4][4]; | |||
| static void | |||
| redraw(void) | |||
| { | |||
| int start, end; | |||
| if (reportSpeed) { | |||
| start = glutGet(GLUT_ELAPSED_TIME); | |||
| } | |||
| /* Clear; default stencil clears to zero. */ | |||
| if ((stencilReflection && renderReflection) || (stencilShadow && renderShadow)) { | |||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | |||
| } else { | |||
| /* Avoid clearing stencil when not using it. */ | |||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); | |||
| } | |||
| /* Reposition the light source. */ | |||
| lightPosition[0] = 12*cos(lightAngle); | |||
| lightPosition[1] = lightHeight; | |||
| lightPosition[2] = 12*sin(lightAngle); | |||
| if (directionalLight) { | |||
| lightPosition[3] = 0.0; | |||
| } else { | |||
| lightPosition[3] = 1.0; | |||
| } | |||
| shadowMatrix(floorShadow, floorPlane, lightPosition); | |||
| glPushMatrix(); | |||
| /* Perform scene rotations based on user mouse input. */ | |||
| glRotatef(angle2, 1.0, 0.0, 0.0); | |||
| glRotatef(angle, 0.0, 1.0, 0.0); | |||
| /* Tell GL new light source position. */ | |||
| glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); | |||
| if (renderReflection) { | |||
| if (stencilReflection) { | |||
| /* We can eliminate the visual "artifact" of seeing the "flipped" | |||
| dinosaur underneath the floor by using stencil. The idea is | |||
| draw the floor without color or depth update but so that | |||
| a stencil value of one is where the floor will be. Later when | |||
| rendering the dinosaur reflection, we will only update pixels | |||
| with a stencil value of 1 to make sure the reflection only | |||
| lives on the floor, not below the floor. */ | |||
| /* Don't update color or depth. */ | |||
| glDisable(GL_DEPTH_TEST); | |||
| glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); | |||
| /* Draw 1 into the stencil buffer. */ | |||
| glEnable(GL_STENCIL_TEST); | |||
| glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); | |||
| glStencilFunc(GL_ALWAYS, 1, 0xffffffff); | |||
| /* Now render floor; floor pixels just get their stencil set to 1. */ | |||
| drawFloor(); | |||
| /* Re-enable update of color and depth. */ | |||
| glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); | |||
| glEnable(GL_DEPTH_TEST); | |||
| /* Now, only render where stencil is set to 1. */ | |||
| glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */ | |||
| glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); | |||
| } | |||
| glPushMatrix(); | |||
| /* The critical reflection step: Reflect dinosaur through the floor | |||
| (the Y=0 plane) to make a relection. */ | |||
| glScalef(1.0, -1.0, 1.0); | |||
| /* Reflect the light position. */ | |||
| glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); | |||
| /* To avoid our normals getting reversed and hence botched lighting | |||
| on the reflection, turn on normalize. */ | |||
| glEnable(GL_NORMALIZE); | |||
| glCullFace(GL_FRONT); | |||
| /* Draw the reflected dinosaur. */ | |||
| drawDinosaur(); | |||
| /* Disable noramlize again and re-enable back face culling. */ | |||
| glDisable(GL_NORMALIZE); | |||
| glCullFace(GL_BACK); | |||
| glPopMatrix(); | |||
| /* Switch back to the unreflected light position. */ | |||
| glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); | |||
| if (stencilReflection) { | |||
| glDisable(GL_STENCIL_TEST); | |||
| } | |||
| } | |||
| /* Back face culling will get used to only draw either the top or the | |||
| bottom floor. This let's us get a floor with two distinct | |||
| appearances. The top floor surface is reflective and kind of red. | |||
| The bottom floor surface is not reflective and blue. */ | |||
| /* Draw "bottom" of floor in blue. */ | |||
| glFrontFace(GL_CW); /* Switch face orientation. */ | |||
| glColor4f(0.1, 0.1, 0.7, 1.0); | |||
| drawFloor(); | |||
| glFrontFace(GL_CCW); | |||
| if (renderShadow) { | |||
| if (stencilShadow) { | |||
| /* Draw the floor with stencil value 3. This helps us only | |||
| draw the shadow once per floor pixel (and only on the | |||
| floor pixels). */ | |||
| glEnable(GL_STENCIL_TEST); | |||
| glStencilFunc(GL_ALWAYS, 3, 0xffffffff); | |||
| glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); | |||
| } | |||
| } | |||
| /* Draw "top" of floor. Use blending to blend in reflection. */ | |||
| glEnable(GL_BLEND); | |||
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
| glColor4f(0.7, 0.0, 0.0, 0.3); | |||
| glColor4f(1.0, 1.0, 1.0, 0.3); | |||
| drawFloor(); | |||
| glDisable(GL_BLEND); | |||
| if (renderDinosaur) { | |||
| /* Draw "actual" dinosaur, not its reflection. */ | |||
| drawDinosaur(); | |||
| } | |||
| if (renderShadow) { | |||
| /* Render the projected shadow. */ | |||
| if (stencilShadow) { | |||
| /* Now, only render where stencil is set above 2 (ie, 3 where | |||
| the top floor is). Update stencil with 2 where the shadow | |||
| gets drawn so we don't redraw (and accidently reblend) the | |||
| shadow). */ | |||
| glStencilFunc(GL_LESS, 2, 0xffffffff); /* draw if ==1 */ | |||
| glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); | |||
| } | |||
| /* To eliminate depth buffer artifacts, we use polygon offset | |||
| to raise the depth of the projected shadow slightly so | |||
| that it does not depth buffer alias with the floor. */ | |||
| if (offsetShadow) { | |||
| switch (polygonOffsetVersion) { | |||
| case EXTENSION: | |||
| #ifdef GL_EXT_polygon_offset | |||
| glEnable(GL_POLYGON_OFFSET_EXT); | |||
| break; | |||
| #endif | |||
| #ifdef GL_VERSION_1_1 | |||
| case ONE_DOT_ONE: | |||
| glEnable(GL_POLYGON_OFFSET_FILL); | |||
| break; | |||
| #endif | |||
| case MISSING: | |||
| /* Oh well. */ | |||
| break; | |||
| } | |||
| } | |||
| /* Render 50% black shadow color on top of whatever the | |||
| floor appareance is. */ | |||
| glEnable(GL_BLEND); | |||
| glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); | |||
| glDisable(GL_LIGHTING); /* Force the 50% black. */ | |||
| glColor4f(0.0, 0.0, 0.0, 0.5); | |||
| glPushMatrix(); | |||
| /* Project the shadow. */ | |||
| glMultMatrixf((GLfloat *) floorShadow); | |||
| drawDinosaur(); | |||
| glPopMatrix(); | |||
| glDisable(GL_BLEND); | |||
| glEnable(GL_LIGHTING); | |||
| if (offsetShadow) { | |||
| switch (polygonOffsetVersion) { | |||
| #ifdef GL_EXT_polygon_offset | |||
| case EXTENSION: | |||
| glDisable(GL_POLYGON_OFFSET_EXT); | |||
| break; | |||
| #endif | |||
| #ifdef GL_VERSION_1_1 | |||
| case ONE_DOT_ONE: | |||
| glDisable(GL_POLYGON_OFFSET_FILL); | |||
| break; | |||
| #endif | |||
| case MISSING: | |||
| /* Oh well. */ | |||
| break; | |||
| } | |||
| } | |||
| if (stencilShadow) { | |||
| glDisable(GL_STENCIL_TEST); | |||
| } | |||
| } | |||
| glPushMatrix(); | |||
| glDisable(GL_LIGHTING); | |||
| glColor3f(1.0, 1.0, 0.0); | |||
| if (directionalLight) { | |||
| /* Draw an arrowhead. */ | |||
| glDisable(GL_CULL_FACE); | |||
| glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]); | |||
| glRotatef(lightAngle * -180.0 / M_PI, 0, 1, 0); | |||
| glRotatef(atan(lightHeight/12) * 180.0 / M_PI, 0, 0, 1); | |||
| glBegin(GL_TRIANGLE_FAN); | |||
| glVertex3f(0, 0, 0); | |||
| glVertex3f(2, 1, 1); | |||
| glVertex3f(2, -1, 1); | |||
| glVertex3f(2, -1, -1); | |||
| glVertex3f(2, 1, -1); | |||
| glVertex3f(2, 1, 1); | |||
| glEnd(); | |||
| /* Draw a white line from light direction. */ | |||
| glColor3f(1.0, 1.0, 1.0); | |||
| glBegin(GL_LINES); | |||
| glVertex3f(0, 0, 0); | |||
| glVertex3f(5, 0, 0); | |||
| glEnd(); | |||
| glEnable(GL_CULL_FACE); | |||
| } else { | |||
| /* Draw a yellow ball at the light source. */ | |||
| glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]); | |||
| glutSolidSphere(1.0, 5, 5); | |||
| } | |||
| glEnable(GL_LIGHTING); | |||
| glPopMatrix(); | |||
| glPopMatrix(); | |||
| if (reportSpeed) { | |||
| glFinish(); | |||
| end = glutGet(GLUT_ELAPSED_TIME); | |||
| printf("Speed %.3g frames/sec (%d ms)\n", 1000.0/(end-start), end-start); | |||
| } | |||
| glutSwapBuffers(); | |||
| } | |||
| /* ARGSUSED2 */ | |||
| static void | |||
| mouse(int button, int state, int x, int y) | |||
| { | |||
| if (button == GLUT_LEFT_BUTTON) { | |||
| if (state == GLUT_DOWN) { | |||
| moving = 1; | |||
| startx = x; | |||
| starty = y; | |||
| } | |||
| if (state == GLUT_UP) { | |||
| moving = 0; | |||
| } | |||
| } | |||
| if (button == GLUT_MIDDLE_BUTTON) { | |||
| if (state == GLUT_DOWN) { | |||
| lightMoving = 1; | |||
| lightStartX = x; | |||
| lightStartY = y; | |||
| } | |||
| if (state == GLUT_UP) { | |||
| lightMoving = 0; | |||
| } | |||
| } | |||
| } | |||
| /* ARGSUSED1 */ | |||
| static void | |||
| motion(int x, int y) | |||
| { | |||
| if (moving) { | |||
| angle = angle + (x - startx); | |||
| angle2 = angle2 + (y - starty); | |||
| startx = x; | |||
| starty = y; | |||
| glutPostRedisplay(); | |||
| } | |||
| if (lightMoving) { | |||
| lightAngle += (x - lightStartX)/40.0; | |||
| lightHeight += (lightStartY - y)/20.0; | |||
| lightStartX = x; | |||
| lightStartY = y; | |||
| glutPostRedisplay(); | |||
| } | |||
| } | |||
| /* Advance time varying state when idle callback registered. */ | |||
| static void | |||
| idle(void) | |||
| { | |||
| static float time = 0.0; | |||
| time = glutGet(GLUT_ELAPSED_TIME) / 500.0; | |||
| jump = 4.0 * fabs(sin(time)*0.5); | |||
| if (!lightMoving) { | |||
| lightAngle += 0.03; | |||
| } | |||
| glutPostRedisplay(); | |||
| } | |||
| enum { | |||
| M_NONE, M_MOTION, M_LIGHT, M_TEXTURE, M_SHADOWS, M_REFLECTION, M_DINOSAUR, | |||
| M_STENCIL_REFLECTION, M_STENCIL_SHADOW, M_OFFSET_SHADOW, | |||
| M_POSITIONAL, M_DIRECTIONAL, M_PERFORMANCE | |||
| }; | |||
| static void | |||
| controlLights(int value) | |||
| { | |||
| switch (value) { | |||
| case M_NONE: | |||
| return; | |||
| case M_MOTION: | |||
| animation = 1 - animation; | |||
| if (animation) { | |||
| glutIdleFunc(idle); | |||
| } else { | |||
| glutIdleFunc(NULL); | |||
| } | |||
| break; | |||
| case M_LIGHT: | |||
| lightSwitch = !lightSwitch; | |||
| if (lightSwitch) { | |||
| glEnable(GL_LIGHT0); | |||
| } else { | |||
| glDisable(GL_LIGHT0); | |||
| } | |||
| break; | |||
| case M_TEXTURE: | |||
| useTexture = !useTexture; | |||
| break; | |||
| case M_SHADOWS: | |||
| renderShadow = 1 - renderShadow; | |||
| break; | |||
| case M_REFLECTION: | |||
| renderReflection = 1 - renderReflection; | |||
| break; | |||
| case M_DINOSAUR: | |||
| renderDinosaur = 1 - renderDinosaur; | |||
| break; | |||
| case M_STENCIL_REFLECTION: | |||
| stencilReflection = 1 - stencilReflection; | |||
| break; | |||
| case M_STENCIL_SHADOW: | |||
| stencilShadow = 1 - stencilShadow; | |||
| break; | |||
| case M_OFFSET_SHADOW: | |||
| offsetShadow = 1 - offsetShadow; | |||
| break; | |||
| case M_POSITIONAL: | |||
| directionalLight = 0; | |||
| break; | |||
| case M_DIRECTIONAL: | |||
| directionalLight = 1; | |||
| break; | |||
| case M_PERFORMANCE: | |||
| reportSpeed = 1 - reportSpeed; | |||
| break; | |||
| } | |||
| glutPostRedisplay(); | |||
| } | |||
| /* When not visible, stop animating. Restart when visible again. */ | |||
| static void | |||
| visible(int vis) | |||
| { | |||
| if (vis == GLUT_VISIBLE) { | |||
| if (animation) | |||
| glutIdleFunc(idle); | |||
| } else { | |||
| if (!animation) | |||
| glutIdleFunc(NULL); | |||
| } | |||
| } | |||
| /* Press any key to redraw; good when motion stopped and | |||
| performance reporting on. */ | |||
| /* ARGSUSED */ | |||
| static void | |||
| key(unsigned char c, int x, int y) | |||
| { | |||
| if (c == 27) { | |||
| exit(0); /* IRIS GLism, Escape quits. */ | |||
| } | |||
| glutPostRedisplay(); | |||
| } | |||
| /* Press any key to redraw; good when motion stopped and | |||
| performance reporting on. */ | |||
| /* ARGSUSED */ | |||
| static void | |||
| special(int k, int x, int y) | |||
| { | |||
| glutPostRedisplay(); | |||
| } | |||
| static int | |||
| supportsOneDotOne(void) | |||
| { | |||
| const char *version; | |||
| int major, minor; | |||
| version = (char *) glGetString(GL_VERSION); | |||
| if (sscanf(version, "%d.%d", &major, &minor) == 2) | |||
| return major >= 1 && minor >= 1; | |||
| return 0; /* OpenGL version string malformed! */ | |||
| } | |||
| int | |||
| main(int argc, char **argv) | |||
| { | |||
| int i; | |||
| glutInit(&argc, argv); | |||
| for (i=1; i<argc; i++) { | |||
| if (!strcmp("-linear", argv[i])) { | |||
| linearFiltering = 1; | |||
| } else if (!strcmp("-mipmap", argv[i])) { | |||
| useMipmaps = 1; | |||
| } else if (!strcmp("-ext", argv[i])) { | |||
| forceExtension = 1; | |||
| } | |||
| } | |||
| glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL); | |||
| #if 0 | |||
| /* In GLUT 4.0, you'll be able to do this an be sure to | |||
| get 2 bits of stencil if the machine has it for you. */ | |||
| glutInitDisplayString("samples stencil>=2 rgb double depth"); | |||
| #endif | |||
| glutCreateWindow("Shadowy Leapin' Lizards"); | |||
| if (glutGet(GLUT_WINDOW_STENCIL_SIZE) <= 1) { | |||
| printf("dinoshade: Sorry, I need at least 2 bits of stencil.\n"); | |||
| exit(1); | |||
| } | |||
| /* Register GLUT callbacks. */ | |||
| glutDisplayFunc(redraw); | |||
| glutMouseFunc(mouse); | |||
| glutMotionFunc(motion); | |||
| glutVisibilityFunc(visible); | |||
| glutKeyboardFunc(key); | |||
| glutSpecialFunc(special); | |||
| glutCreateMenu(controlLights); | |||
| glutAddMenuEntry("Toggle motion", M_MOTION); | |||
| glutAddMenuEntry("-----------------------", M_NONE); | |||
| glutAddMenuEntry("Toggle light", M_LIGHT); | |||
| glutAddMenuEntry("Toggle texture", M_TEXTURE); | |||
| glutAddMenuEntry("Toggle shadows", M_SHADOWS); | |||
| glutAddMenuEntry("Toggle reflection", M_REFLECTION); | |||
| glutAddMenuEntry("Toggle dinosaur", M_DINOSAUR); | |||
| glutAddMenuEntry("-----------------------", M_NONE); | |||
| glutAddMenuEntry("Toggle reflection stenciling", M_STENCIL_REFLECTION); | |||
| glutAddMenuEntry("Toggle shadow stenciling", M_STENCIL_SHADOW); | |||
| glutAddMenuEntry("Toggle shadow offset", M_OFFSET_SHADOW); | |||
| glutAddMenuEntry("----------------------", M_NONE); | |||
| glutAddMenuEntry("Positional light", M_POSITIONAL); | |||
| glutAddMenuEntry("Directional light", M_DIRECTIONAL); | |||
| glutAddMenuEntry("-----------------------", M_NONE); | |||
| glutAddMenuEntry("Toggle performance", M_PERFORMANCE); | |||
| glutAttachMenu(GLUT_RIGHT_BUTTON); | |||
| makeDinosaur(); | |||
| #ifdef GL_VERSION_1_1 | |||
| if (supportsOneDotOne() && !forceExtension) { | |||
| polygonOffsetVersion = ONE_DOT_ONE; | |||
| glPolygonOffset(-2.0, -9.0); | |||
| } else | |||
| #endif | |||
| { | |||
| #ifdef GL_EXT_polygon_offset | |||
| /* check for the polygon offset extension */ | |||
| if (glutExtensionSupported("GL_EXT_polygon_offset")) { | |||
| polygonOffsetVersion = EXTENSION; | |||
| glPolygonOffsetEXT(-2.0, -0.002); | |||
| } else | |||
| #endif | |||
| { | |||
| polygonOffsetVersion = MISSING; | |||
| printf("\ndinoshine: Missing polygon offset.\n"); | |||
| printf(" Expect shadow depth aliasing artifacts.\n\n"); | |||
| } | |||
| } | |||
| glEnable(GL_CULL_FACE); | |||
| glEnable(GL_DEPTH_TEST); | |||
| glEnable(GL_TEXTURE_2D); | |||
| glLineWidth(3.0); | |||
| glMatrixMode(GL_PROJECTION); | |||
| gluPerspective( /* field of view in degree */ 40.0, | |||
| /* aspect ratio */ 1.0, | |||
| /* Z near */ 20.0, /* Z far */ 100.0); | |||
| glMatrixMode(GL_MODELVIEW); | |||
| gluLookAt(0.0, 8.0, 60.0, /* eye is at (0,8,60) */ | |||
| 0.0, 8.0, 0.0, /* center is at (0,8,0) */ | |||
| 0.0, 1.0, 0.); /* up is in postivie Y direction */ | |||
| glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); | |||
| glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor); | |||
| glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1); | |||
| glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05); | |||
| glEnable(GL_LIGHT0); | |||
| glEnable(GL_LIGHTING); | |||
| makeFloorTexture(); | |||
| /* Setup floor plane for projected shadow calculations. */ | |||
| findPlane(floorPlane, floorVertices[1], floorVertices[2], floorVertices[3]); | |||
| glutMainLoop(); | |||
| return 0; /* ANSI C requires main to return int. */ | |||
| } | |||