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