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