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