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