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