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