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 <stddef.h> /* for ptrdiff_t, referenced by GL.h when GL_GLEXT_LEGACY defined */ 42#ifdef _WIN32 43#include <windows.h> 44#endif 45#define GL_GLEXT_LEGACY 46#include <GL/glew.h> /* OpenGL Utility Toolkit header */ 47#include "glut_wrap.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. */ 55static int stencilReflection = 1, stencilShadow = 1, offsetShadow = 1; 56static int renderShadow = 1, renderDinosaur = 1, renderReflection = 1; 57static int linearFiltering = 0, useMipmaps = 0, useTexture = 1; 58static int reportSpeed = 0; 59static int animation = 1; 60static GLboolean lightSwitch = GL_TRUE; 61static int directionalLight = 1; 62static int forceExtension = 0; 63 64/* Time varying or user-controled variables. */ 65static float jump = 0.0; 66static float lightAngle = 0.0, lightHeight = 20; 67GLfloat angle = -150; /* in degrees */ 68GLfloat angle2 = 30; /* in degrees */ 69 70int moving, startx, starty; 71int lightMoving = 0, lightStartX, lightStartY; 72 73enum { 74 MISSING, EXTENSION, ONE_DOT_ONE 75}; 76int polygonOffsetVersion; 77 78static GLdouble bodyWidth = 3.0; 79/* *INDENT-OFF* */ 80static 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} }; 84static 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} }; 87static 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} }; 89static GLfloat eye[][2] = { {8.75, 15}, {9, 14.7}, {9.6, 14.7}, {10.1, 15}, 90 {9.6, 15.25}, {9, 15.25} }; 91static GLfloat lightPosition[4]; 92static GLfloat lightColor[] = {0.8, 1.0, 0.8, 1.0}; /* green-tinted */ 93static 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. */ 97static 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 116static void 117makeFloorTexture(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 160enum { 161 X, Y, Z, W 162}; 163enum { 164 A, B, C, D 165}; 166 167/* Create a matrix that will project the desired shadow. */ 168static void 169shadowMatrix(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. */ 204static void 205findPlane(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 227static void 228extrudeSolidFromPolygon(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++) { 260#if 1 /* weird, but seems to be legal */ 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); 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 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. */ 303typedef 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 308static void 309makeDinosaur(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 321static void 322drawDinosaur(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 344static 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). */ 352static void 353drawFloor(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 379static GLfloat floorPlane[4]; 380static GLfloat floorShadow[4][4]; 381 382static void 383redraw(void) 384{ 385 int start = 0, end = 0; 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); 627 fflush(stdout); 628 } 629 630 glutSwapBuffers(); 631} 632 633/* ARGSUSED2 */ 634static void 635mouse(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 */ 660static void 661motion(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. */ 680static void 681idle(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 694enum { 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 700static void 701controlLights(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. */ 757static void 758visible(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 */ 772static void 773key(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 */ 784static void 785special(int k, int x, int y) 786{ 787 glutPostRedisplay(); 788} 789 790int 791main(int argc, char **argv) 792{ 793 int i; 794 795 glutInit(&argc, argv); 796 797 for (i=1; i<argc; i++) { 798 if (!strcmp("-linear", argv[i])) { 799 linearFiltering = 1; 800 } else if (!strcmp("-mipmap", argv[i])) { 801 useMipmaps = 1; 802 } else if (!strcmp("-ext", argv[i])) { 803 forceExtension = 1; 804 } 805 } 806 807 glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL); 808 809#if 0 810 /* In GLUT 4.0, you'll be able to do this an be sure to 811 get 2 bits of stencil if the machine has it for you. */ 812 glutInitDisplayString("samples stencil>=2 rgb double depth"); 813#endif 814 815 glutCreateWindow("Shadowy Leapin' Lizards"); 816 glewInit(); 817 818 if (glutGet(GLUT_WINDOW_STENCIL_SIZE) <= 1) { 819 printf("dinoshade: Sorry, I need at least 2 bits of stencil.\n"); 820 exit(1); 821 } 822 823 /* Register GLUT callbacks. */ 824 glutDisplayFunc(redraw); 825 glutMouseFunc(mouse); 826 glutMotionFunc(motion); 827 glutVisibilityFunc(visible); 828 glutKeyboardFunc(key); 829 glutSpecialFunc(special); 830 831 glutCreateMenu(controlLights); 832 833 glutAddMenuEntry("Toggle motion", M_MOTION); 834 glutAddMenuEntry("-----------------------", M_NONE); 835 glutAddMenuEntry("Toggle light", M_LIGHT); 836 glutAddMenuEntry("Toggle texture", M_TEXTURE); 837 glutAddMenuEntry("Toggle shadows", M_SHADOWS); 838 glutAddMenuEntry("Toggle reflection", M_REFLECTION); 839 glutAddMenuEntry("Toggle dinosaur", M_DINOSAUR); 840 glutAddMenuEntry("-----------------------", M_NONE); 841 glutAddMenuEntry("Toggle reflection stenciling", M_STENCIL_REFLECTION); 842 glutAddMenuEntry("Toggle shadow stenciling", M_STENCIL_SHADOW); 843 glutAddMenuEntry("Toggle shadow offset", M_OFFSET_SHADOW); 844 glutAddMenuEntry("----------------------", M_NONE); 845 glutAddMenuEntry("Positional light", M_POSITIONAL); 846 glutAddMenuEntry("Directional light", M_DIRECTIONAL); 847 glutAddMenuEntry("-----------------------", M_NONE); 848 glutAddMenuEntry("Toggle performance", M_PERFORMANCE); 849 glutAttachMenu(GLUT_RIGHT_BUTTON); 850 makeDinosaur(); 851 852#ifdef GL_VERSION_1_1 853 if (GLEW_VERSION_1_1 && !forceExtension) { 854 polygonOffsetVersion = ONE_DOT_ONE; 855 glPolygonOffset(-2.0, -9.0); 856 } else 857#endif 858 { 859#ifdef GL_EXT_polygon_offset 860 /* check for the polygon offset extension */ 861 if (GLEW_EXT_polygon_offset) { 862 polygonOffsetVersion = EXTENSION; 863 glPolygonOffsetEXT(-2.0, -0.002); 864 } else 865#endif 866 { 867 polygonOffsetVersion = MISSING; 868 printf("\ndinoshine: Missing polygon offset.\n"); 869 printf(" Expect shadow depth aliasing artifacts.\n\n"); 870 fflush(stdout); 871 } 872 } 873 874 glEnable(GL_CULL_FACE); 875 glEnable(GL_DEPTH_TEST); 876 glEnable(GL_TEXTURE_2D); 877 glLineWidth(3.0); 878 879 glMatrixMode(GL_PROJECTION); 880 gluPerspective( /* field of view in degree */ 40.0, 881 /* aspect ratio */ 1.0, 882 /* Z near */ 20.0, /* Z far */ 100.0); 883 glMatrixMode(GL_MODELVIEW); 884 gluLookAt(0.0, 8.0, 60.0, /* eye is at (0,8,60) */ 885 0.0, 8.0, 0.0, /* center is at (0,8,0) */ 886 0.0, 1.0, 0.); /* up is in postivie Y direction */ 887 888 glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); 889 glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor); 890 glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.1); 891 glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.05); 892 glEnable(GL_LIGHT0); 893 glEnable(GL_LIGHTING); 894 895 makeFloorTexture(); 896 897 /* Setup floor plane for projected shadow calculations. */ 898 findPlane(floorPlane, floorVertices[1], floorVertices[2], floorVertices[3]); 899 900 glutMainLoop(); 901 return 0; /* ANSI C requires main to return int. */ 902} 903