Brian Paul | 9b5bb59 | 2000-11-18 17:10:07 +0000 | [diff] [blame] | 1 | |
| 2 | /* Copyright (c) Mark J. Kilgard, 1994, 1997. */ |
| 3 | |
| 4 | /* This program is freely distributable without licensing fees |
| 5 | and is provided without guarantee or warrantee expressed or |
| 6 | implied. This program is -not- in the public domain. */ |
| 7 | |
| 8 | /* Example for PC game developers to show how to *combine* texturing, |
| 9 | reflections, and projected shadows all in real-time with OpenGL. |
| 10 | Robust reflections use stenciling. Robust projected shadows |
| 11 | use both stenciling and polygon offset. PC game programmers |
| 12 | should realize that neither stenciling nor polygon offset are |
| 13 | supported by Direct3D, so these real-time rendering algorithms |
| 14 | are only really viable with OpenGL. |
| 15 | |
| 16 | The program has modes for disabling the stenciling and polygon |
| 17 | offset uses. It is worth running this example with these features |
| 18 | toggled off so you can see the sort of artifacts that result. |
| 19 | |
| 20 | Notice that the floor texturing, reflections, and shadowing |
| 21 | all co-exist properly. */ |
| 22 | |
| 23 | /* When you run this program: Left mouse button controls the |
| 24 | view. Middle mouse button controls light position (left & |
| 25 | right rotates light around dino; up & down moves light |
| 26 | position up and down). Right mouse button pops up menu. */ |
| 27 | |
| 28 | /* Check out the comments in the "redraw" routine to see how the |
| 29 | reflection blending and surface stenciling is done. You can |
| 30 | also see in "redraw" how the projected shadows are rendered, |
| 31 | including the use of stenciling and polygon offset. */ |
| 32 | |
| 33 | /* This program is derived from glutdino.c */ |
| 34 | |
| 35 | /* Compile: cc -o dinoshade dinoshade.c -lglut -lGLU -lGL -lXmu -lXext -lX11 -lm */ |
| 36 | |
| 37 | #include <stdio.h> |
| 38 | #include <stdlib.h> |
| 39 | #include <string.h> |
| 40 | #include <math.h> /* for cos(), sin(), and sqrt() */ |
Eric Anholt | 9bb0d62 | 2007-09-24 10:22:31 -0700 | [diff] [blame] | 41 | #include <stddef.h> /* for ptrdiff_t, referenced by GL.h when GL_GLEXT_LEGACY defined */ |
Karl Schultz | 40fac75 | 2002-01-16 01:03:25 +0000 | [diff] [blame] | 42 | #ifdef _WIN32 |
| 43 | #include <windows.h> |
| 44 | #endif |
| 45 | #define GL_GLEXT_LEGACY |
Brian Paul | 9b5bb59 | 2000-11-18 17:10:07 +0000 | [diff] [blame] | 46 | #include <GL/glut.h> /* OpenGL Utility Toolkit header */ |
| 47 | |
| 48 | /* Some <math.h> files do not define M_PI... */ |
| 49 | #ifndef M_PI |
| 50 | #define M_PI 3.14159265358979323846 |
| 51 | #endif |
| 52 | |
| 53 | /* Variable controlling various rendering modes. */ |
| 54 | static int stencilReflection = 1, stencilShadow = 1, offsetShadow = 1; |
| 55 | static int renderShadow = 1, renderDinosaur = 1, renderReflection = 1; |
| 56 | static int linearFiltering = 0, useMipmaps = 0, useTexture = 1; |
| 57 | static int reportSpeed = 0; |
| 58 | static int animation = 1; |
| 59 | static GLboolean lightSwitch = GL_TRUE; |
| 60 | static int directionalLight = 1; |
| 61 | static int forceExtension = 0; |
| 62 | |
| 63 | /* Time varying or user-controled variables. */ |
| 64 | static float jump = 0.0; |
| 65 | static float lightAngle = 0.0, lightHeight = 20; |
| 66 | GLfloat angle = -150; /* in degrees */ |
| 67 | GLfloat angle2 = 30; /* in degrees */ |
| 68 | |
| 69 | int moving, startx, starty; |
| 70 | int lightMoving = 0, lightStartX, lightStartY; |
| 71 | |
| 72 | enum { |
| 73 | MISSING, EXTENSION, ONE_DOT_ONE |
| 74 | }; |
| 75 | int polygonOffsetVersion; |
| 76 | |
| 77 | static GLdouble bodyWidth = 3.0; |
| 78 | /* *INDENT-OFF* */ |
| 79 | static GLfloat body[][2] = { {0, 3}, {1, 1}, {5, 1}, {8, 4}, {10, 4}, {11, 5}, |
| 80 | {11, 11.5}, {13, 12}, {13, 13}, {10, 13.5}, {13, 14}, {13, 15}, {11, 16}, |
| 81 | {8, 16}, {7, 15}, {7, 13}, {8, 12}, {7, 11}, {6, 6}, {4, 3}, {3, 2}, |
| 82 | {1, 2} }; |
| 83 | static GLfloat arm[][2] = { {8, 10}, {9, 9}, {10, 9}, {13, 8}, {14, 9}, {16, 9}, |
| 84 | {15, 9.5}, {16, 10}, {15, 10}, {15.5, 11}, {14.5, 10}, {14, 11}, {14, 10}, |
| 85 | {13, 9}, {11, 11}, {9, 11} }; |
| 86 | static GLfloat leg[][2] = { {8, 6}, {8, 4}, {9, 3}, {9, 2}, {8, 1}, {8, 0.5}, {9, 0}, |
| 87 | {12, 0}, {10, 1}, {10, 2}, {12, 4}, {11, 6}, {10, 7}, {9, 7} }; |
| 88 | static GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15}, |
| 89 | {9.6, 15.25}, {9, 15.25} }; |
| 90 | static GLfloat lightPosition[4]; |
| 91 | static GLfloat lightColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */ |
| 92 | static GLfloat skinColor[] = {0.1, 1.0, 0.1, 1.0}, eyeColor[] = {1.0, 0.2, 0.2, 1.0}; |
| 93 | /* *INDENT-ON* */ |
| 94 | |
| 95 | /* Nice floor texture tiling pattern. */ |
| 96 | static char *circles[] = { |
| 97 | "....xxxx........", |
| 98 | "..xxxxxxxx......", |
| 99 | ".xxxxxxxxxx.....", |
| 100 | ".xxx....xxx.....", |
| 101 | "xxx......xxx....", |
| 102 | "xxx......xxx....", |
| 103 | "xxx......xxx....", |
| 104 | "xxx......xxx....", |
| 105 | ".xxx....xxx.....", |
| 106 | ".xxxxxxxxxx.....", |
| 107 | "..xxxxxxxx......", |
| 108 | "....xxxx........", |
| 109 | "................", |
| 110 | "................", |
| 111 | "................", |
| 112 | "................", |
| 113 | }; |
| 114 | |
| 115 | static void |
| 116 | makeFloorTexture(void) |
| 117 | { |
| 118 | GLubyte floorTexture[16][16][3]; |
| 119 | GLubyte *loc; |
| 120 | int s, t; |
| 121 | |
| 122 | /* Setup RGB image for the texture. */ |
| 123 | loc = (GLubyte*) floorTexture; |
| 124 | for (t = 0; t < 16; t++) { |
| 125 | for (s = 0; s < 16; s++) { |
| 126 | if (circles[t][s] == 'x') { |
| 127 | /* Nice green. */ |
| 128 | loc[0] = 0x1f; |
| 129 | loc[1] = 0x8f; |
| 130 | loc[2] = 0x1f; |
| 131 | } else { |
| 132 | /* Light gray. */ |
| 133 | loc[0] = 0xaa; |
| 134 | loc[1] = 0xaa; |
| 135 | loc[2] = 0xaa; |
| 136 | } |
| 137 | loc += 3; |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| 142 | |
| 143 | if (useMipmaps) { |
| 144 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, |
| 145 | GL_LINEAR_MIPMAP_LINEAR); |
| 146 | gluBuild2DMipmaps(GL_TEXTURE_2D, 3, 16, 16, |
| 147 | GL_RGB, GL_UNSIGNED_BYTE, floorTexture); |
| 148 | } else { |
| 149 | if (linearFiltering) { |
| 150 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 151 | } else { |
| 152 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 153 | } |
| 154 | glTexImage2D(GL_TEXTURE_2D, 0, 3, 16, 16, 0, |
| 155 | GL_RGB, GL_UNSIGNED_BYTE, floorTexture); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | enum { |
| 160 | X, Y, Z, W |
| 161 | }; |
| 162 | enum { |
| 163 | A, B, C, D |
| 164 | }; |
| 165 | |
| 166 | /* Create a matrix that will project the desired shadow. */ |
| 167 | void |
| 168 | shadowMatrix(GLfloat shadowMat[4][4], |
| 169 | GLfloat groundplane[4], |
| 170 | GLfloat lightpos[4]) |
| 171 | { |
| 172 | GLfloat dot; |
| 173 | |
| 174 | /* Find dot product between light position vector and ground plane normal. */ |
| 175 | dot = groundplane[X] * lightpos[X] + |
| 176 | groundplane[Y] * lightpos[Y] + |
| 177 | groundplane[Z] * lightpos[Z] + |
| 178 | groundplane[W] * lightpos[W]; |
| 179 | |
| 180 | shadowMat[0][0] = dot - lightpos[X] * groundplane[X]; |
| 181 | shadowMat[1][0] = 0.f - lightpos[X] * groundplane[Y]; |
| 182 | shadowMat[2][0] = 0.f - lightpos[X] * groundplane[Z]; |
| 183 | shadowMat[3][0] = 0.f - lightpos[X] * groundplane[W]; |
| 184 | |
| 185 | shadowMat[X][1] = 0.f - lightpos[Y] * groundplane[X]; |
| 186 | shadowMat[1][1] = dot - lightpos[Y] * groundplane[Y]; |
| 187 | shadowMat[2][1] = 0.f - lightpos[Y] * groundplane[Z]; |
| 188 | shadowMat[3][1] = 0.f - lightpos[Y] * groundplane[W]; |
| 189 | |
| 190 | shadowMat[X][2] = 0.f - lightpos[Z] * groundplane[X]; |
| 191 | shadowMat[1][2] = 0.f - lightpos[Z] * groundplane[Y]; |
| 192 | shadowMat[2][2] = dot - lightpos[Z] * groundplane[Z]; |
| 193 | shadowMat[3][2] = 0.f - lightpos[Z] * groundplane[W]; |
| 194 | |
| 195 | shadowMat[X][3] = 0.f - lightpos[W] * groundplane[X]; |
| 196 | shadowMat[1][3] = 0.f - lightpos[W] * groundplane[Y]; |
| 197 | shadowMat[2][3] = 0.f - lightpos[W] * groundplane[Z]; |
| 198 | shadowMat[3][3] = dot - lightpos[W] * groundplane[W]; |
| 199 | |
| 200 | } |
| 201 | |
| 202 | /* Find the plane equation given 3 points. */ |
| 203 | void |
| 204 | findPlane(GLfloat plane[4], |
| 205 | GLfloat v0[3], GLfloat v1[3], GLfloat v2[3]) |
| 206 | { |
| 207 | GLfloat vec0[3], vec1[3]; |
| 208 | |
| 209 | /* Need 2 vectors to find cross product. */ |
| 210 | vec0[X] = v1[X] - v0[X]; |
| 211 | vec0[Y] = v1[Y] - v0[Y]; |
| 212 | vec0[Z] = v1[Z] - v0[Z]; |
| 213 | |
| 214 | vec1[X] = v2[X] - v0[X]; |
| 215 | vec1[Y] = v2[Y] - v0[Y]; |
| 216 | vec1[Z] = v2[Z] - v0[Z]; |
| 217 | |
| 218 | /* find cross product to get A, B, and C of plane equation */ |
| 219 | plane[A] = vec0[Y] * vec1[Z] - vec0[Z] * vec1[Y]; |
| 220 | plane[B] = -(vec0[X] * vec1[Z] - vec0[Z] * vec1[X]); |
| 221 | plane[C] = vec0[X] * vec1[Y] - vec0[Y] * vec1[X]; |
| 222 | |
| 223 | plane[D] = -(plane[A] * v0[X] + plane[B] * v0[Y] + plane[C] * v0[Z]); |
| 224 | } |
| 225 | |
| 226 | void |
| 227 | extrudeSolidFromPolygon(GLfloat data[][2], unsigned int dataSize, |
| 228 | GLdouble thickness, GLuint side, GLuint edge, GLuint whole) |
| 229 | { |
| 230 | static GLUtriangulatorObj *tobj = NULL; |
| 231 | GLdouble vertex[3], dx, dy, len; |
| 232 | int i; |
| 233 | int count = (int) (dataSize / (2 * sizeof(GLfloat))); |
| 234 | |
| 235 | if (tobj == NULL) { |
| 236 | tobj = gluNewTess(); /* create and initialize a GLU |
| 237 | polygon tesselation object */ |
| 238 | gluTessCallback(tobj, GLU_BEGIN, glBegin); |
| 239 | gluTessCallback(tobj, GLU_VERTEX, glVertex2fv); /* semi-tricky */ |
| 240 | gluTessCallback(tobj, GLU_END, glEnd); |
| 241 | } |
| 242 | glNewList(side, GL_COMPILE); |
| 243 | glShadeModel(GL_SMOOTH); /* smooth minimizes seeing |
| 244 | tessellation */ |
| 245 | gluBeginPolygon(tobj); |
| 246 | for (i = 0; i < count; i++) { |
| 247 | vertex[0] = data[i][0]; |
| 248 | vertex[1] = data[i][1]; |
| 249 | vertex[2] = 0; |
| 250 | gluTessVertex(tobj, vertex, data[i]); |
| 251 | } |
| 252 | gluEndPolygon(tobj); |
| 253 | glEndList(); |
| 254 | glNewList(edge, GL_COMPILE); |
| 255 | glShadeModel(GL_FLAT); /* flat shade keeps angular hands |
| 256 | from being "smoothed" */ |
| 257 | glBegin(GL_QUAD_STRIP); |
| 258 | for (i = 0; i <= count; i++) { |
Daniel Borca | 9ebce91 | 2005-01-19 07:52:49 +0000 | [diff] [blame] | 259 | #if 1 /* weird, but seems to be legal */ |
Brian Paul | 9b5bb59 | 2000-11-18 17:10:07 +0000 | [diff] [blame] | 260 | /* mod function handles closing the edge */ |
| 261 | glVertex3f(data[i % count][0], data[i % count][1], 0.0); |
| 262 | glVertex3f(data[i % count][0], data[i % count][1], thickness); |
| 263 | /* Calculate a unit normal by dividing by Euclidean |
| 264 | distance. We * could be lazy and use |
| 265 | glEnable(GL_NORMALIZE) so we could pass in * arbitrary |
| 266 | normals for a very slight performance hit. */ |
| 267 | dx = data[(i + 1) % count][1] - data[i % count][1]; |
| 268 | dy = data[i % count][0] - data[(i + 1) % count][0]; |
| 269 | len = sqrt(dx * dx + dy * dy); |
| 270 | glNormal3f(dx / len, dy / len, 0.0); |
Daniel Borca | 9ebce91 | 2005-01-19 07:52:49 +0000 | [diff] [blame] | 271 | #else /* the nice way of doing it */ |
| 272 | /* Calculate a unit normal by dividing by Euclidean |
| 273 | distance. We * could be lazy and use |
| 274 | glEnable(GL_NORMALIZE) so we could pass in * arbitrary |
| 275 | normals for a very slight performance hit. */ |
| 276 | dx = data[i % count][1] - data[(i - 1 + count) % count][1]; |
| 277 | dy = data[(i - 1 + count) % count][0] - data[i % count][0]; |
| 278 | len = sqrt(dx * dx + dy * dy); |
| 279 | glNormal3f(dx / len, dy / len, 0.0); |
| 280 | /* mod function handles closing the edge */ |
| 281 | glVertex3f(data[i % count][0], data[i % count][1], 0.0); |
| 282 | glVertex3f(data[i % count][0], data[i % count][1], thickness); |
| 283 | #endif |
Brian Paul | 9b5bb59 | 2000-11-18 17:10:07 +0000 | [diff] [blame] | 284 | } |
| 285 | glEnd(); |
| 286 | glEndList(); |
| 287 | glNewList(whole, GL_COMPILE); |
| 288 | glFrontFace(GL_CW); |
| 289 | glCallList(edge); |
| 290 | glNormal3f(0.0, 0.0, -1.0); /* constant normal for side */ |
| 291 | glCallList(side); |
| 292 | glPushMatrix(); |
| 293 | glTranslatef(0.0, 0.0, thickness); |
| 294 | glFrontFace(GL_CCW); |
| 295 | glNormal3f(0.0, 0.0, 1.0); /* opposite normal for other side */ |
| 296 | glCallList(side); |
| 297 | glPopMatrix(); |
| 298 | glEndList(); |
| 299 | } |
| 300 | |
| 301 | /* Enumerants for refering to display lists. */ |
| 302 | typedef enum { |
| 303 | RESERVED, BODY_SIDE, BODY_EDGE, BODY_WHOLE, ARM_SIDE, ARM_EDGE, ARM_WHOLE, |
| 304 | LEG_SIDE, LEG_EDGE, LEG_WHOLE, EYE_SIDE, EYE_EDGE, EYE_WHOLE |
| 305 | } displayLists; |
| 306 | |
| 307 | static void |
| 308 | makeDinosaur(void) |
| 309 | { |
| 310 | extrudeSolidFromPolygon(body, sizeof(body), bodyWidth, |
| 311 | BODY_SIDE, BODY_EDGE, BODY_WHOLE); |
| 312 | extrudeSolidFromPolygon(arm, sizeof(arm), bodyWidth / 4, |
| 313 | ARM_SIDE, ARM_EDGE, ARM_WHOLE); |
| 314 | extrudeSolidFromPolygon(leg, sizeof(leg), bodyWidth / 2, |
| 315 | LEG_SIDE, LEG_EDGE, LEG_WHOLE); |
| 316 | extrudeSolidFromPolygon(eye, sizeof(eye), bodyWidth + 0.2, |
| 317 | EYE_SIDE, EYE_EDGE, EYE_WHOLE); |
| 318 | } |
| 319 | |
| 320 | static void |
| 321 | drawDinosaur(void) |
| 322 | |
| 323 | { |
| 324 | glPushMatrix(); |
| 325 | /* Translate the dinosaur to be at (0,8,0). */ |
| 326 | glTranslatef(-8, 0, -bodyWidth / 2); |
| 327 | glTranslatef(0.0, jump, 0.0); |
| 328 | glMaterialfv(GL_FRONT, GL_DIFFUSE, skinColor); |
| 329 | glCallList(BODY_WHOLE); |
| 330 | glTranslatef(0.0, 0.0, bodyWidth); |
| 331 | glCallList(ARM_WHOLE); |
| 332 | glCallList(LEG_WHOLE); |
| 333 | glTranslatef(0.0, 0.0, -bodyWidth - bodyWidth / 4); |
| 334 | glCallList(ARM_WHOLE); |
| 335 | glTranslatef(0.0, 0.0, -bodyWidth / 4); |
| 336 | glCallList(LEG_WHOLE); |
| 337 | glTranslatef(0.0, 0.0, bodyWidth / 2 - 0.1); |
| 338 | glMaterialfv(GL_FRONT, GL_DIFFUSE, eyeColor); |
| 339 | glCallList(EYE_WHOLE); |
| 340 | glPopMatrix(); |
| 341 | } |
| 342 | |
| 343 | static GLfloat floorVertices[4][3] = { |
| 344 | { -20.0, 0.0, 20.0 }, |
| 345 | { 20.0, 0.0, 20.0 }, |
| 346 | { 20.0, 0.0, -20.0 }, |
| 347 | { -20.0, 0.0, -20.0 }, |
| 348 | }; |
| 349 | |
| 350 | /* Draw a floor (possibly textured). */ |
| 351 | static void |
| 352 | drawFloor(void) |
| 353 | { |
| 354 | glDisable(GL_LIGHTING); |
| 355 | |
| 356 | if (useTexture) { |
| 357 | glEnable(GL_TEXTURE_2D); |
| 358 | } |
| 359 | |
| 360 | glBegin(GL_QUADS); |
| 361 | glTexCoord2f(0.0, 0.0); |
| 362 | glVertex3fv(floorVertices[0]); |
| 363 | glTexCoord2f(0.0, 16.0); |
| 364 | glVertex3fv(floorVertices[1]); |
| 365 | glTexCoord2f(16.0, 16.0); |
| 366 | glVertex3fv(floorVertices[2]); |
| 367 | glTexCoord2f(16.0, 0.0); |
| 368 | glVertex3fv(floorVertices[3]); |
| 369 | glEnd(); |
| 370 | |
| 371 | if (useTexture) { |
| 372 | glDisable(GL_TEXTURE_2D); |
| 373 | } |
| 374 | |
| 375 | glEnable(GL_LIGHTING); |
| 376 | } |
| 377 | |
| 378 | static GLfloat floorPlane[4]; |
| 379 | static GLfloat floorShadow[4][4]; |
| 380 | |
| 381 | static void |
| 382 | redraw(void) |
| 383 | { |
| 384 | int start, end; |
| 385 | |
| 386 | if (reportSpeed) { |
| 387 | start = glutGet(GLUT_ELAPSED_TIME); |
| 388 | } |
| 389 | |
| 390 | /* Clear; default stencil clears to zero. */ |
| 391 | if ((stencilReflection && renderReflection) || (stencilShadow && renderShadow)) { |
| 392 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); |
| 393 | } else { |
| 394 | /* Avoid clearing stencil when not using it. */ |
| 395 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| 396 | } |
| 397 | |
| 398 | /* Reposition the light source. */ |
| 399 | lightPosition[0] = 12*cos(lightAngle); |
| 400 | lightPosition[1] = lightHeight; |
| 401 | lightPosition[2] = 12*sin(lightAngle); |
| 402 | if (directionalLight) { |
| 403 | lightPosition[3] = 0.0; |
| 404 | } else { |
| 405 | lightPosition[3] = 1.0; |
| 406 | } |
| 407 | |
| 408 | shadowMatrix(floorShadow, floorPlane, lightPosition); |
| 409 | |
| 410 | glPushMatrix(); |
| 411 | /* Perform scene rotations based on user mouse input. */ |
| 412 | glRotatef(angle2, 1.0, 0.0, 0.0); |
| 413 | glRotatef(angle, 0.0, 1.0, 0.0); |
| 414 | |
| 415 | /* Tell GL new light source position. */ |
| 416 | glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); |
| 417 | |
| 418 | if (renderReflection) { |
| 419 | if (stencilReflection) { |
| 420 | /* We can eliminate the visual "artifact" of seeing the "flipped" |
| 421 | dinosaur underneath the floor by using stencil. The idea is |
| 422 | draw the floor without color or depth update but so that |
| 423 | a stencil value of one is where the floor will be. Later when |
| 424 | rendering the dinosaur reflection, we will only update pixels |
| 425 | with a stencil value of 1 to make sure the reflection only |
| 426 | lives on the floor, not below the floor. */ |
| 427 | |
| 428 | /* Don't update color or depth. */ |
| 429 | glDisable(GL_DEPTH_TEST); |
| 430 | glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); |
| 431 | |
| 432 | /* Draw 1 into the stencil buffer. */ |
| 433 | glEnable(GL_STENCIL_TEST); |
| 434 | glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); |
| 435 | glStencilFunc(GL_ALWAYS, 1, 0xffffffff); |
| 436 | |
| 437 | /* Now render floor; floor pixels just get their stencil set to 1. */ |
| 438 | drawFloor(); |
| 439 | |
| 440 | /* Re-enable update of color and depth. */ |
| 441 | glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); |
| 442 | glEnable(GL_DEPTH_TEST); |
| 443 | |
| 444 | /* Now, only render where stencil is set to 1. */ |
| 445 | glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */ |
| 446 | glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); |
| 447 | } |
| 448 | |
| 449 | glPushMatrix(); |
| 450 | |
| 451 | /* The critical reflection step: Reflect dinosaur through the floor |
| 452 | (the Y=0 plane) to make a relection. */ |
| 453 | glScalef(1.0, -1.0, 1.0); |
| 454 | |
| 455 | /* Reflect the light position. */ |
| 456 | glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); |
| 457 | |
| 458 | /* To avoid our normals getting reversed and hence botched lighting |
| 459 | on the reflection, turn on normalize. */ |
| 460 | glEnable(GL_NORMALIZE); |
| 461 | glCullFace(GL_FRONT); |
| 462 | |
| 463 | /* Draw the reflected dinosaur. */ |
| 464 | drawDinosaur(); |
| 465 | |
| 466 | /* Disable noramlize again and re-enable back face culling. */ |
| 467 | glDisable(GL_NORMALIZE); |
| 468 | glCullFace(GL_BACK); |
| 469 | |
| 470 | glPopMatrix(); |
| 471 | |
| 472 | /* Switch back to the unreflected light position. */ |
| 473 | glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); |
| 474 | |
| 475 | if (stencilReflection) { |
| 476 | glDisable(GL_STENCIL_TEST); |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | /* Back face culling will get used to only draw either the top or the |
| 481 | bottom floor. This let's us get a floor with two distinct |
| 482 | appearances. The top floor surface is reflective and kind of red. |
| 483 | The bottom floor surface is not reflective and blue. */ |
| 484 | |
| 485 | /* Draw "bottom" of floor in blue. */ |
| 486 | glFrontFace(GL_CW); /* Switch face orientation. */ |
| 487 | glColor4f(0.1, 0.1, 0.7, 1.0); |
| 488 | drawFloor(); |
| 489 | glFrontFace(GL_CCW); |
| 490 | |
| 491 | if (renderShadow) { |
| 492 | if (stencilShadow) { |
| 493 | /* Draw the floor with stencil value 3. This helps us only |
| 494 | draw the shadow once per floor pixel (and only on the |
| 495 | floor pixels). */ |
| 496 | glEnable(GL_STENCIL_TEST); |
| 497 | glStencilFunc(GL_ALWAYS, 3, 0xffffffff); |
| 498 | glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); |
| 499 | } |
| 500 | } |
| 501 | |
| 502 | /* Draw "top" of floor. Use blending to blend in reflection. */ |
| 503 | glEnable(GL_BLEND); |
| 504 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| 505 | glColor4f(0.7, 0.0, 0.0, 0.3); |
| 506 | glColor4f(1.0, 1.0, 1.0, 0.3); |
| 507 | drawFloor(); |
| 508 | glDisable(GL_BLEND); |
| 509 | |
| 510 | if (renderDinosaur) { |
| 511 | /* Draw "actual" dinosaur, not its reflection. */ |
| 512 | drawDinosaur(); |
| 513 | } |
| 514 | |
| 515 | if (renderShadow) { |
| 516 | |
| 517 | /* Render the projected shadow. */ |
| 518 | |
| 519 | if (stencilShadow) { |
| 520 | |
| 521 | /* Now, only render where stencil is set above 2 (ie, 3 where |
| 522 | the top floor is). Update stencil with 2 where the shadow |
| 523 | gets drawn so we don't redraw (and accidently reblend) the |
| 524 | shadow). */ |
| 525 | glStencilFunc(GL_LESS, 2, 0xffffffff); /* draw if ==1 */ |
| 526 | glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE); |
| 527 | } |
| 528 | |
| 529 | /* To eliminate depth buffer artifacts, we use polygon offset |
| 530 | to raise the depth of the projected shadow slightly so |
| 531 | that it does not depth buffer alias with the floor. */ |
| 532 | if (offsetShadow) { |
| 533 | switch (polygonOffsetVersion) { |
| 534 | case EXTENSION: |
| 535 | #ifdef GL_EXT_polygon_offset |
| 536 | glEnable(GL_POLYGON_OFFSET_EXT); |
| 537 | break; |
| 538 | #endif |
| 539 | #ifdef GL_VERSION_1_1 |
| 540 | case ONE_DOT_ONE: |
| 541 | glEnable(GL_POLYGON_OFFSET_FILL); |
| 542 | break; |
| 543 | #endif |
| 544 | case MISSING: |
| 545 | /* Oh well. */ |
| 546 | break; |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | /* Render 50% black shadow color on top of whatever the |
| 551 | floor appareance is. */ |
| 552 | glEnable(GL_BLEND); |
| 553 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| 554 | glDisable(GL_LIGHTING); /* Force the 50% black. */ |
| 555 | glColor4f(0.0, 0.0, 0.0, 0.5); |
| 556 | |
| 557 | glPushMatrix(); |
| 558 | /* Project the shadow. */ |
| 559 | glMultMatrixf((GLfloat *) floorShadow); |
| 560 | drawDinosaur(); |
| 561 | glPopMatrix(); |
| 562 | |
| 563 | glDisable(GL_BLEND); |
| 564 | glEnable(GL_LIGHTING); |
| 565 | |
| 566 | if (offsetShadow) { |
| 567 | switch (polygonOffsetVersion) { |
| 568 | #ifdef GL_EXT_polygon_offset |
| 569 | case EXTENSION: |
| 570 | glDisable(GL_POLYGON_OFFSET_EXT); |
| 571 | break; |
| 572 | #endif |
| 573 | #ifdef GL_VERSION_1_1 |
| 574 | case ONE_DOT_ONE: |
| 575 | glDisable(GL_POLYGON_OFFSET_FILL); |
| 576 | break; |
| 577 | #endif |
| 578 | case MISSING: |
| 579 | /* Oh well. */ |
| 580 | break; |
| 581 | } |
| 582 | } |
| 583 | if (stencilShadow) { |
| 584 | glDisable(GL_STENCIL_TEST); |
| 585 | } |
| 586 | } |
| 587 | |
| 588 | glPushMatrix(); |
| 589 | glDisable(GL_LIGHTING); |
| 590 | glColor3f(1.0, 1.0, 0.0); |
| 591 | if (directionalLight) { |
| 592 | /* Draw an arrowhead. */ |
| 593 | glDisable(GL_CULL_FACE); |
| 594 | glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]); |
| 595 | glRotatef(lightAngle * -180.0 / M_PI, 0, 1, 0); |
| 596 | glRotatef(atan(lightHeight/12) * 180.0 / M_PI, 0, 0, 1); |
| 597 | glBegin(GL_TRIANGLE_FAN); |
| 598 | glVertex3f(0, 0, 0); |
| 599 | glVertex3f(2, 1, 1); |
| 600 | glVertex3f(2, -1, 1); |
| 601 | glVertex3f(2, -1, -1); |
| 602 | glVertex3f(2, 1, -1); |
| 603 | glVertex3f(2, 1, 1); |
| 604 | glEnd(); |
| 605 | /* Draw a white line from light direction. */ |
| 606 | glColor3f(1.0, 1.0, 1.0); |
| 607 | glBegin(GL_LINES); |
| 608 | glVertex3f(0, 0, 0); |
| 609 | glVertex3f(5, 0, 0); |
| 610 | glEnd(); |
| 611 | glEnable(GL_CULL_FACE); |
| 612 | } else { |
| 613 | /* Draw a yellow ball at the light source. */ |
| 614 | glTranslatef(lightPosition[0], lightPosition[1], lightPosition[2]); |
| 615 | glutSolidSphere(1.0, 5, 5); |
| 616 | } |
| 617 | glEnable(GL_LIGHTING); |
| 618 | glPopMatrix(); |
| 619 | |
| 620 | glPopMatrix(); |
| 621 | |
| 622 | if (reportSpeed) { |
| 623 | glFinish(); |
| 624 | end = glutGet(GLUT_ELAPSED_TIME); |
| 625 | printf("Speed %.3g frames/sec (%d ms)\n", 1000.0/(end-start), end-start); |
| 626 | } |
| 627 | |
| 628 | glutSwapBuffers(); |
| 629 | } |
| 630 | |
| 631 | /* ARGSUSED2 */ |
| 632 | static void |
| 633 | mouse(int button, int state, int x, int y) |
| 634 | { |
| 635 | if (button == GLUT_LEFT_BUTTON) { |
| 636 | if (state == GLUT_DOWN) { |
| 637 | moving = 1; |
| 638 | startx = x; |
| 639 | starty = y; |
| 640 | } |
| 641 | if (state == GLUT_UP) { |
| 642 | moving = 0; |
| 643 | } |
| 644 | } |
| 645 | if (button == GLUT_MIDDLE_BUTTON) { |
| 646 | if (state == GLUT_DOWN) { |
| 647 | lightMoving = 1; |
| 648 | lightStartX = x; |
| 649 | lightStartY = y; |
| 650 | } |
| 651 | if (state == GLUT_UP) { |
| 652 | lightMoving = 0; |
| 653 | } |
| 654 | } |
| 655 | } |
| 656 | |
| 657 | /* ARGSUSED1 */ |
| 658 | static void |
| 659 | motion(int x, int y) |
| 660 | { |
| 661 | if (moving) { |
| 662 | angle = angle + (x - startx); |
| 663 | angle2 = angle2 + (y - starty); |
| 664 | startx = x; |
| 665 | starty = y; |
| 666 | glutPostRedisplay(); |
| 667 | } |
| 668 | if (lightMoving) { |
| 669 | lightAngle += (x - lightStartX)/40.0; |
| 670 | lightHeight += (lightStartY - y)/20.0; |
| 671 | lightStartX = x; |
| 672 | lightStartY = y; |
| 673 | glutPostRedisplay(); |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | /* Advance time varying state when idle callback registered. */ |
| 678 | static void |
| 679 | idle(void) |
| 680 | { |
| 681 | static float time = 0.0; |
| 682 | |
| 683 | time = glutGet(GLUT_ELAPSED_TIME) / 500.0; |
| 684 | |
| 685 | jump = 4.0 * fabs(sin(time)*0.5); |
| 686 | if (!lightMoving) { |
| 687 | lightAngle += 0.03; |
| 688 | } |
| 689 | glutPostRedisplay(); |
| 690 | } |
| 691 | |
| 692 | enum { |
| 693 | M_NONE, M_MOTION, M_LIGHT, M_TEXTURE, M_SHADOWS, M_REFLECTION, M_DINOSAUR, |
| 694 | M_STENCIL_REFLECTION, M_STENCIL_SHADOW, M_OFFSET_SHADOW, |
| 695 | M_POSITIONAL, M_DIRECTIONAL, M_PERFORMANCE |
| 696 | }; |
| 697 | |
| 698 | static void |
| 699 | controlLights(int value) |
| 700 | { |
| 701 | switch (value) { |
| 702 | case M_NONE: |
| 703 | return; |
| 704 | case M_MOTION: |
| 705 | animation = 1 - animation; |
| 706 | if (animation) { |
| 707 | glutIdleFunc(idle); |
| 708 | } else { |
| 709 | glutIdleFunc(NULL); |
| 710 | } |
| 711 | break; |
| 712 | case M_LIGHT: |
| 713 | lightSwitch = !lightSwitch; |
| 714 | if (lightSwitch) { |
| 715 | glEnable(GL_LIGHT0); |
| 716 | } else { |
| 717 | glDisable(GL_LIGHT0); |
| 718 | } |
| 719 | break; |
| 720 | case M_TEXTURE: |
| 721 | useTexture = !useTexture; |
| 722 | break; |
| 723 | case M_SHADOWS: |
| 724 | renderShadow = 1 - renderShadow; |
| 725 | break; |
| 726 | case M_REFLECTION: |
| 727 | renderReflection = 1 - renderReflection; |
| 728 | break; |
| 729 | case M_DINOSAUR: |
| 730 | renderDinosaur = 1 - renderDinosaur; |
| 731 | break; |
| 732 | case M_STENCIL_REFLECTION: |
| 733 | stencilReflection = 1 - stencilReflection; |
| 734 | break; |
| 735 | case M_STENCIL_SHADOW: |
| 736 | stencilShadow = 1 - stencilShadow; |
| 737 | break; |
| 738 | case M_OFFSET_SHADOW: |
| 739 | offsetShadow = 1 - offsetShadow; |
| 740 | break; |
| 741 | case M_POSITIONAL: |
| 742 | directionalLight = 0; |
| 743 | break; |
| 744 | case M_DIRECTIONAL: |
| 745 | directionalLight = 1; |
| 746 | break; |
| 747 | case M_PERFORMANCE: |
| 748 | reportSpeed = 1 - reportSpeed; |
| 749 | break; |
| 750 | } |
| 751 | glutPostRedisplay(); |
| 752 | } |
| 753 | |
| 754 | /* When not visible, stop animating. Restart when visible again. */ |
| 755 | static void |
| 756 | visible(int vis) |
| 757 | { |
| 758 | if (vis == GLUT_VISIBLE) { |
| 759 | if (animation) |
| 760 | glutIdleFunc(idle); |
| 761 | } else { |
| 762 | if (!animation) |
| 763 | glutIdleFunc(NULL); |
| 764 | } |
| 765 | } |
| 766 | |
| 767 | /* Press any key to redraw; good when motion stopped and |
| 768 | performance reporting on. */ |
| 769 | /* ARGSUSED */ |
| 770 | static void |
| 771 | key(unsigned char c, int x, int y) |
| 772 | { |
| 773 | if (c == 27) { |
| 774 | exit(0); /* IRIS GLism, Escape quits. */ |
| 775 | } |
| 776 | glutPostRedisplay(); |
| 777 | } |
| 778 | |
| 779 | /* Press any key to redraw; good when motion stopped and |
| 780 | performance reporting on. */ |
| 781 | /* ARGSUSED */ |
| 782 | static void |
| 783 | special(int k, int x, int y) |
| 784 | { |
| 785 | glutPostRedisplay(); |
| 786 | } |
| 787 | |
| 788 | static int |
| 789 | supportsOneDotOne(void) |
| 790 | { |
| 791 | const char *version; |
| 792 | int major, minor; |
| 793 | |
| 794 | version = (char *) glGetString(GL_VERSION); |
| 795 | if (sscanf(version, "%d.%d", &major, &minor) == 2) |
Brian Paul | 90246d3 | 2008-11-11 14:33:11 -0700 | [diff] [blame^] | 796 | return major * 10 + minor >= 11; |
Brian Paul | 9b5bb59 | 2000-11-18 17:10:07 +0000 | [diff] [blame] | 797 | return 0; /* OpenGL version string malformed! */ |
| 798 | } |
| 799 | |
| 800 | int |
| 801 | main(int argc, char **argv) |
| 802 | { |
| 803 | int i; |
| 804 | |
| 805 | glutInit(&argc, argv); |
| 806 | |
| 807 | for (i=1; i<argc; i++) { |
| 808 | if (!strcmp("-linear", argv[i])) { |
| 809 | linearFiltering = 1; |
| 810 | } else if (!strcmp("-mipmap", argv[i])) { |
| 811 | useMipmaps = 1; |
| 812 | } else if (!strcmp("-ext", argv[i])) { |
| 813 | forceExtension = 1; |
| 814 | } |
| 815 | } |
| 816 | |
| 817 | glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL); |
| 818 | |
| 819 | #if 0 |
| 820 | /* In GLUT 4.0, you'll be able to do this an be sure to |
| 821 | get 2 bits of stencil if the machine has it for you. */ |
| 822 | glutInitDisplayString("samples stencil>=2 rgb double depth"); |
| 823 | #endif |
| 824 | |
| 825 | glutCreateWindow("Shadowy Leapin' Lizards"); |
| 826 | |
| 827 | if (glutGet(GLUT_WINDOW_STENCIL_SIZE) <= 1) { |
| 828 | printf("dinoshade: Sorry, I need at least 2 bits of stencil.\n"); |
| 829 | exit(1); |
| 830 | } |
| 831 | |
| 832 | /* Register GLUT callbacks. */ |
| 833 | glutDisplayFunc(redraw); |
| 834 | glutMouseFunc(mouse); |
| 835 | glutMotionFunc(motion); |
| 836 | glutVisibilityFunc(visible); |
| 837 | glutKeyboardFunc(key); |
| 838 | glutSpecialFunc(special); |
| 839 | |
| 840 | glutCreateMenu(controlLights); |
| 841 | |
| 842 | glutAddMenuEntry("Toggle motion", M_MOTION); |
| 843 | glutAddMenuEntry("-----------------------", M_NONE); |
| 844 | glutAddMenuEntry("Toggle light", M_LIGHT); |
| 845 | glutAddMenuEntry("Toggle texture", M_TEXTURE); |
| 846 | glutAddMenuEntry("Toggle shadows", M_SHADOWS); |
| 847 | glutAddMenuEntry("Toggle reflection", M_REFLECTION); |
| 848 | glutAddMenuEntry("Toggle dinosaur", M_DINOSAUR); |
| 849 | glutAddMenuEntry("-----------------------", M_NONE); |
| 850 | glutAddMenuEntry("Toggle reflection stenciling", M_STENCIL_REFLECTION); |
| 851 | glutAddMenuEntry("Toggle shadow stenciling", M_STENCIL_SHADOW); |
| 852 | glutAddMenuEntry("Toggle shadow offset", M_OFFSET_SHADOW); |
| 853 | glutAddMenuEntry("----------------------", M_NONE); |
| 854 | glutAddMenuEntry("Positional light", M_POSITIONAL); |
| 855 | glutAddMenuEntry("Directional light", M_DIRECTIONAL); |
| 856 | glutAddMenuEntry("-----------------------", M_NONE); |
| 857 | glutAddMenuEntry("Toggle performance", M_PERFORMANCE); |
| 858 | glutAttachMenu(GLUT_RIGHT_BUTTON); |
| 859 | makeDinosaur(); |
| 860 | |
| 861 | #ifdef GL_VERSION_1_1 |
| 862 | if (supportsOneDotOne() && !forceExtension) { |
| 863 | polygonOffsetVersion = ONE_DOT_ONE; |
| 864 | glPolygonOffset(-2.0, -9.0); |
| 865 | } else |
| 866 | #endif |
| 867 | { |
| 868 | #ifdef GL_EXT_polygon_offset |
| 869 | /* check for the polygon offset extension */ |
| 870 | if (glutExtensionSupported("GL_EXT_polygon_offset")) { |
| 871 | polygonOffsetVersion = EXTENSION; |
| 872 | glPolygonOffsetEXT(-2.0, -0.002); |
| 873 | } else |
| 874 | #endif |
| 875 | { |
| 876 | polygonOffsetVersion = MISSING; |
| 877 | printf("\ndinoshine: Missing polygon offset.\n"); |
| 878 | printf(" Expect shadow depth aliasing artifacts.\n\n"); |
| 879 | } |
| 880 | } |
| 881 | |
| 882 | glEnable(GL_CULL_FACE); |
| 883 | glEnable(GL_DEPTH_TEST); |
| 884 | glEnable(GL_TEXTURE_2D); |
| 885 | glLineWidth(3.0); |
| 886 | |
| 887 | glMatrixMode(GL_PROJECTION); |
| 888 | gluPerspective( /* field of view in degree */ 40.0, |
| 889 | /* aspect ratio */ 1.0, |
| 890 | /* Z near */ 20.0, /* Z far */ 100.0); |
| 891 | glMatrixMode(GL_MODELVIEW); |
| 892 | gluLookAt(0.0, 8.0, 60.0, /* eye is at (0,8,60) */ |
| 893 | 0.0, 8.0, 0.0, /* center is at (0,8,0) */ |
| 894 | 0.0, 1.0, 0.); /* up is in postivie Y direction */ |
| 895 | |
| 896 | glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); |
| 897 | glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor); |
| 898 | glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1); |
| 899 | glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05); |
| 900 | glEnable(GL_LIGHT0); |
| 901 | glEnable(GL_LIGHTING); |
| 902 | |
| 903 | makeFloorTexture(); |
| 904 | |
| 905 | /* Setup floor plane for projected shadow calculations. */ |
| 906 | findPlane(floorPlane, floorVertices[1], floorVertices[2], floorVertices[3]); |
| 907 | |
| 908 | glutMainLoop(); |
| 909 | return 0; /* ANSI C requires main to return int. */ |
| 910 | } |