From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / lavalite.c
1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
2  *
3  * This software Copyright (c) 2002-2017 Jamie Zawinski <jwz@jwz.org>
4  *
5  * Permission to use, copy, modify, distribute, and sell this software and its
6  * documentation for any purpose is hereby granted without fee, provided that
7  * the above copyright notice appear in all copies and that both that
8  * copyright notice and this permission notice appear in supporting
9  * documentation.  No representations are made about the suitability of this
10  * software for any purpose.  It is provided "as is" without express or 
11  * implied warranty.
12  *
13  * LAVA®, LAVA LITE®, LAVA WORLD INTERNATIONAL® and the configuration of the
14  * LAVA® brand motion lamp are registered trademarks of Haggerty Enterprises,
15  * Inc.  The configuration of the globe and base of the motion lamp are
16  * registered trademarks of Haggerty Enterprises, Inc. in the U.S.A. and in
17  * other countries around the world.
18  *
19  * Official Lava Lite web site: http://www.lavaworld.com/
20  *
21  * Implementation details:
22  *
23  * The blobs are generated using metaballs.  For an explanation of what
24  * those are, see http://astronomy.swin.edu.au/~pbourke/modelling/implicitsurf/
25  * or http://www.fifi.org/doc/povray-manual/pov30005.htm
26  *
27  * Basically, each bubble of lava is a few (4) overlapping spherical metaballs
28  * of various sizes following similar but slightly different steep, slow
29  * parabolic arcs from the bottom to the top and back.
30  *
31  * We then polygonize the surface of the lava using the marching squares
32  * algorithm implemented in marching.c.
33  *
34  * Like real lavalites, this program is very slow.
35  *
36  * Surprisingly, it's loading the CPU and not the graphics engine: the speed
37  * bottleneck is in *computing* the scene rather than *rendering* it.  We
38  * actually don't use a huge number of polygons, but computing the mesh for
39  * the surface takes a lot of cycles.
40  *
41  * I eliminated all the square roots, but there is still a lot of
42  * floating-point multiplication in here.  I tried optimizing away the
43  * fp divisions, but that didn't seem to make a difference.
44  *
45  *     -style            lamp shape: classic, giant, cone, rocket, or random.
46  *     -speed            frequency at which new blobs launch.
47  *     -resolution       density of the polygon mesh.
48  *     -count            max number of blobs allowed at once.
49  *     -wander, -spin    whether to roll the scene around the screen.
50  *     -lava-color       color of the blobbies
51  *     -fluid-color      color of the stuff the blobbies float in
52  *     -base-color       color of the base of the lamp
53  *     -table-color      color of the table under the lamp
54  *     -impatient        at startup, skip forward in the animation so
55  *                       that at least one blob is fully exposed.
56  *
57  * TODO:
58  *
59  *    - make the table look better, somehow.
60  *    - should the lava be emissive?  should the one at the bottom be
61  *      more brightly colored than the ones at the top?  light distance?
62  *    - is there some way to put a specular reflection on the front glass
63  *      itself?  Maybe render it twice with FRONT/BACK tweaked, or alpha
64  *      with depth buffering turned off?
65  */
66
67 #define DEFAULTS        "*delay:        30000       \n" \
68                         "*showFPS:      False       \n" \
69                         "*wireframe:    False       \n" \
70                         "*geometry:     600x900\n"      \
71                         "*count:      " DEF_COUNT " \n" \
72
73 # define refresh_lavalite 0
74 # define release_lavalite 0
75
76
77 #define BLOBS_PER_GROUP 4
78
79 #define GRAVITY         0.000013    /* odwnward acceleration */
80 #define CONVECTION      0.005       /* initial upward velocity (bell curve) */
81 #define TILT            0.00166666  /* horizontal velocity (bell curve) */
82
83 #undef countof
84 #define countof(x) (sizeof((x))/sizeof((*x)))
85
86 #undef ABS
87 #define ABS(n) ((n)<0?-(n):(n))
88 #undef SIGNOF
89 #define SIGNOF(n) ((n)<0?-1:1)
90
91 #include "xlockmore.h"
92 #include "marching.h"
93 #include "rotator.h"
94 #include "gltrackball.h"
95 #include "xpm-ximage.h"
96 #include <ctype.h>
97
98 #ifdef USE_GL /* whole file */
99
100
101 #define DEF_SPIN        "Z"
102 #define DEF_WANDER      "False"
103 #define DEF_SPEED       "0.003"
104 #define DEF_RESOLUTION  "40"
105 #define DEF_SMOOTH      "True"
106 #define DEF_COUNT       "3"
107 #define DEF_STYLE       "random"
108 #define DEF_IMPATIENT   "False"
109 #define DEF_LCOLOR      "#FF0000" /* lava */
110 #define DEF_FCOLOR      "#00AAFF" /* fluid */
111 #define DEF_BCOLOR      "#666666" /* base */
112 #define DEF_TCOLOR      "#000000" /*"#00FF00"*/ /* table */
113
114 #define DEF_FTEX        "(none)"
115 #define DEF_BTEX        "(none)"
116 #define DEF_TTEX        "(none)"
117
118 typedef struct metaball metaball;
119
120 struct metaball {
121
122   Bool alive_p;
123   Bool static_p;
124
125   double r;             /* hard radius */
126   double R;             /* radius of field of influence */
127
128   double z;             /* vertical position */
129   double pos_r;         /* position on horizontal circle */
130   double pos_th;        /* position on horizontal circle */
131   double dr, dz;        /* current velocity */
132
133   double x, y;          /* h planar position - compused from the above */
134
135   metaball *leader;     /* stay close to this other ball */
136 };
137
138
139 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
140 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
141
142 typedef struct {
143   lamp_part part;
144   GLfloat elevation;
145   GLfloat radius;
146   GLfloat texture_elevation;
147 } lamp_geometry;
148
149 static const lamp_geometry classic_lamp[] = {
150   { CAP,    1.16, 0.089, 0.00 },
151   { BOTTLE, 0.97, 0.120, 0.40 },
152   { BOTTLE, 0.13, 0.300, 0.87 },
153   { BOTTLE, 0.07, 0.300, 0.93 },
154   { BASE,   0.00, 0.280, 0.00 },
155   { BASE,  -0.40, 0.120, 0.50 },
156   { BASE,  -0.80, 0.280, 1.00 },
157   { 0, 0, 0, 0 },
158 };
159
160 static const lamp_geometry giant_lamp[] = {
161   { CAP,    1.12, 0.105, 0.00 },
162   { BOTTLE, 0.97, 0.130, 0.30 },
163   { BOTTLE, 0.20, 0.300, 0.87 },
164   { BOTTLE, 0.15, 0.300, 0.93 },
165   { BASE,   0.00, 0.230, 0.00 },
166   { BASE,  -0.18, 0.140, 0.20 },
167   { BASE,  -0.80, 0.280, 1.00 },
168   { 0, 0, 0, 0 },
169 };
170
171 static const lamp_geometry cone_lamp[] = {
172   { CAP,    1.35, 0.001, 0.00 },
173   { CAP,    1.35, 0.020, 0.00 },
174   { CAP,    1.30, 0.055, 0.05 },
175   { BOTTLE, 0.97, 0.120, 0.40 },
176   { BOTTLE, 0.13, 0.300, 0.87 },
177   { BASE,   0.00, 0.300, 0.00 },
178   { BASE,  -0.04, 0.320, 0.04 },
179   { BASE,  -0.60, 0.420, 0.50 },
180   { 0, 0, 0, 0 },
181 };
182
183 static const lamp_geometry rocket_lamp[] = {
184   { CAP,    1.35, 0.001, 0.00 },
185   { CAP,    1.34, 0.020, 0.00 },
186   { CAP,    1.30, 0.055, 0.05 },
187   { BOTTLE, 0.97, 0.120, 0.40 },
188   { BOTTLE, 0.13, 0.300, 0.87 },
189   { BOTTLE, 0.07, 0.300, 0.93 },
190   { BASE,   0.00, 0.280, 0.00 },
191   { BASE,  -0.50, 0.180, 0.50 },
192   { BASE,  -0.75, 0.080, 0.75 },
193   { BASE,  -0.80, 0.035, 0.80 },
194   { BASE,  -0.90, 0.035, 1.00 },
195   { 0, 0, 0, 0 },
196 };
197
198
199
200 typedef struct {
201   GLXContext *glx_context;
202   lamp_style style;
203   const lamp_geometry *model;
204   rotator *rot;
205   rotator *rot2;
206   trackball_state *trackball;
207   Bool button_down_p;
208
209   GLfloat max_bottle_radius;       /* radius of widest part of the bottle */
210
211   GLfloat launch_chance;           /* how often to percolate */
212   int blobs_per_group;             /* how many metaballs we launch at once */
213   Bool just_started_p;             /* so we launch some goo right away */
214
215   int grid_size;                   /* resolution for marching-cubes */
216   int nballs;
217   metaball *balls;
218
219   GLuint bottle_list;
220   GLuint ball_list;
221
222   int bottle_poly_count;           /* polygons in the bottle only */
223
224 } lavalite_configuration;
225
226 static lavalite_configuration *bps = NULL;
227
228 static char *do_spin;
229 static char *do_style;
230 static GLfloat speed;
231 static Bool do_wander;
232 static int resolution;
233 static Bool do_smooth;
234 static Bool do_impatient;
235
236 static char *lava_color_str, *fluid_color_str, *base_color_str,
237   *table_color_str;
238 static char *fluid_tex, *base_tex, *table_tex;
239
240 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
241
242 static const GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
243 static const GLfloat lava_shininess = 128.0;
244 static const GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
245
246 static const GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
247 static const GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
248 static const GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
249
250
251
252 static XrmOptionDescRec opts[] = {
253   { "-style",  ".style",  XrmoptionSepArg, 0 },
254   { "-spin",   ".spin",   XrmoptionSepArg, 0 },
255   { "+spin",   ".spin",   XrmoptionNoArg, "" },
256   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
257   { "-wander", ".wander", XrmoptionNoArg, "True" },
258   { "+wander", ".wander", XrmoptionNoArg, "False" },
259   { "-resolution", ".resolution", XrmoptionSepArg, 0 },
260   { "-smooth", ".smooth", XrmoptionNoArg, "True" },
261   { "+smooth", ".smooth", XrmoptionNoArg, "False" },
262   { "-impatient", ".impatient", XrmoptionNoArg, "True" },
263   { "+impatient", ".impatient", XrmoptionNoArg, "False" },
264
265   { "-lava-color",   ".lavaColor",   XrmoptionSepArg, 0 },
266   { "-fluid-color",  ".fluidColor",  XrmoptionSepArg, 0 },
267   { "-base-color",   ".baseColor",   XrmoptionSepArg, 0 },
268   { "-table-color",  ".tableColor",  XrmoptionSepArg, 0 },
269
270   { "-fluid-texture",".fluidTexture",  XrmoptionSepArg, 0 },
271   { "-base-texture", ".baseTexture",   XrmoptionSepArg, 0 },
272   { "-table-texture",".tableTexture",  XrmoptionSepArg, 0 },
273 };
274
275 static argtype vars[] = {
276   {&do_style,     "style",      "Style",      DEF_STYLE,      t_String},
277   {&do_spin,      "spin",       "Spin",       DEF_SPIN,       t_String},
278   {&do_wander,    "wander",     "Wander",     DEF_WANDER,     t_Bool},
279   {&speed,        "speed",      "Speed",      DEF_SPEED,      t_Float},
280   {&resolution,   "resolution", "Resolution", DEF_RESOLUTION, t_Int},
281   {&do_smooth,    "smooth",     "Smooth",     DEF_SMOOTH,     t_Bool},
282   {&do_impatient, "impatient",  "Impatient",  DEF_IMPATIENT,  t_Bool},
283
284   {&lava_color_str,  "lavaColor",    "LavaColor",  DEF_LCOLOR, t_String},
285   {&fluid_color_str, "fluidColor",   "FluidColor", DEF_FCOLOR, t_String},
286   {&base_color_str,  "baseColor",    "BaseColor",  DEF_BCOLOR, t_String},
287   {&table_color_str, "tableColor",   "TableColor", DEF_TCOLOR, t_String},
288
289   {&fluid_tex,       "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
290   {&base_tex,        "baseTexture",  "BaseTexture",  DEF_BTEX, t_String},
291   {&table_tex,       "tableTexture", "BaseTexture",  DEF_TTEX, t_String},
292 };
293
294 ENTRYPOINT ModeSpecOpt lavalite_opts = {countof(opts), opts, countof(vars), vars, NULL};
295
296
297 /* Window management, etc
298  */
299 ENTRYPOINT void
300 reshape_lavalite (ModeInfo *mi, int width, int height)
301 {
302   GLfloat h = (GLfloat) height / (GLfloat) width;
303
304   glViewport (0, 0, (GLint) width, (GLint) height);
305
306   glMatrixMode(GL_PROJECTION);
307   glLoadIdentity();
308   gluPerspective (30.0, 1/h, 1.0, 100.0);
309
310   glMatrixMode(GL_MODELVIEW);
311   glLoadIdentity();
312   gluLookAt( 0.0, 0.0, 30.0,
313              0.0, 0.0, 0.0,
314              0.0, 1.0, 0.0);
315
316   glClear(GL_COLOR_BUFFER_BIT);
317 }
318
319
320 \f
321 /* Textures
322  */
323
324 static Bool
325 load_texture (ModeInfo *mi, const char *filename)
326 {
327   Display *dpy = mi->dpy;
328   Visual *visual = mi->xgwa.visual;
329   Colormap cmap = mi->xgwa.colormap;
330   char buf[1024];
331   XImage *image;
332
333   if (!filename ||
334       !*filename ||
335       !strcasecmp (filename, "(none)"))
336     {
337       glDisable (GL_TEXTURE_2D);
338       return False;
339     }
340
341   image = xpm_file_to_ximage (dpy, visual, cmap, filename);
342   if (!image) return False;
343
344   clear_gl_error();
345   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
346                image->width, image->height, 0,
347                GL_RGBA, GL_UNSIGNED_BYTE, image->data);
348   sprintf (buf, "texture: %.100s (%dx%d)",
349            filename, image->width, image->height);
350   check_gl_error(buf);
351
352   glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
353   glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
354   glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
355   glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
356   glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
357   glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
358   glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
359
360   glEnable (GL_TEXTURE_2D);
361   return True;
362 }
363
364
365 \f
366 /* Generating the lamp's bottle, caps, and base.
367  */
368
369 static int
370 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
371 {
372   int j;
373   GLfloat th;
374   GLfloat step = M_PI * 2 / faces;
375   int polys = 0;
376   GLfloat x, y;
377
378   glFrontFace (up_p ? GL_CW : GL_CCW);
379   glNormal3f (0, (up_p ? 1 : -1), 0);
380   glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
381
382   x = r;
383   y = 0;
384
385   for (j = 0, th = 0; j <= faces; j++)
386     {
387       glTexCoord2f (-j / (GLfloat) faces, 1);
388       glVertex3f (0, z, 0);
389
390       glTexCoord2f (-j / (GLfloat) faces, 0);
391       glVertex3f (x, z, y);
392
393       th += step;
394       x  = r * cos (th);
395       y  = r * sin (th);
396
397       glTexCoord2f (-j / (GLfloat) faces, 0);
398       glVertex3f (x, z, y);
399
400       polys++;
401     }
402   glEnd();
403
404   return polys;
405 }
406
407
408 static int
409 draw_tube (GLfloat r0, GLfloat r1,
410            GLfloat z0, GLfloat z1,
411            GLfloat t0, GLfloat t1,
412            int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
413 {
414   int polys = 0;
415   GLfloat th;
416   GLfloat x, y, x0=0, y0=0;
417   GLfloat step = M_PI * 2 / faces;
418   GLfloat s2 = step/2;
419   int i;
420
421   glFrontFace (inside_out_p ? GL_CW : GL_CCW);
422   glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
423
424   th = 0;
425   x = 1;
426   y = 0;
427
428   if (!smooth_p)
429     {
430       x0 = cos (s2);
431       y0 = sin (s2);
432     }
433
434   if (smooth_p) faces++;
435
436   for (i = 0; i < faces; i++)
437     {
438       int nsign = (inside_out_p ? -1 : 1);
439
440       if (smooth_p)
441         glNormal3f (x  * nsign, z1, y  * nsign);
442       else
443         glNormal3f (x0 * nsign, z1, y0 * nsign);
444
445       glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
446       glVertex3f (x * r1, z1, y * r1);
447
448       glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
449       glVertex3f (x * r0, z0, y * r0);
450
451       th += step;
452       x  = cos (th);
453       y  = sin (th);
454
455       if (!smooth_p)
456         {
457           x0 = cos (th + s2);
458           y0 = sin (th + s2);
459
460           glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
461           glVertex3f (x * r0, z0, y * r0);
462
463           glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
464           glVertex3f (x * r1, z1, y * r1);
465         }
466
467       polys++;
468     }
469   glEnd();
470
471   return polys;
472 }
473
474
475 static int
476 draw_table (GLfloat z, Bool wire)
477 {
478   GLfloat faces = 6;
479   GLfloat step = M_PI * 2 / faces;
480   GLfloat s = 8;
481   GLfloat th;
482   int j;
483   int polys = 0;
484
485   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
486
487   glFrontFace(GL_CW);
488   glNormal3f(0, 1, 0);
489   glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
490
491   if (! wire)
492     {
493       glTexCoord2f (-0.5, 0.5);
494       glVertex3f(0, z, 0);
495     }
496
497   for (j = 0, th = 0; j <= faces; j++)
498     {
499       GLfloat x = cos (th);
500       GLfloat y = sin (th);
501       glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
502       glVertex3f(x*s, z, y*s);
503       th += step;
504       polys++;
505     }
506   glEnd();
507   return polys;
508 }
509
510
511 static int
512 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
513 {
514   static const int coords[2][8][2] = {
515     { {  0,   0 },
516       { 10,  10 },
517       { 20,  23 },
518       { 30,  41 },
519       { 40,  64 },
520       { 45,  81 },
521       { 50, 103 },
522       { 53, 134 } },
523     { {  0,  54 },
524       { 10,  57 },
525       { 20,  64 },
526       { 30,  75 },
527       { 40,  92 },
528       { 45, 104 },
529       { 50, 127 },
530       { 51, 134 } 
531     }
532   };
533
534   int polys = 0;
535   int maxx = coords[0][countof(coords[0])-1][0];
536   int maxy = coords[0][countof(coords[0])-1][1];
537   unsigned int x;
538
539   for (x = 1; x < countof(coords[0]); x++)
540     {
541       GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
542       GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
543       GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
544       GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
545       GLfloat px2 = (GLfloat) coords[0][x  ][0] / maxx * w;
546       GLfloat py2 = (GLfloat) coords[0][x  ][1] / maxy * h;
547       GLfloat px3 = (GLfloat) coords[1][x  ][0] / maxx * w;
548       GLfloat py3 = (GLfloat) coords[1][x  ][1] / maxy * h;
549       GLfloat zz = d/2;
550
551       /* Left side
552        */
553       glFrontFace (GL_CW);
554       glNormal3f (0, 0, -1);
555       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
556
557       glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
558       glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
559       glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
560       glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
561       polys++;
562       glEnd();
563
564       /* Right side
565        */
566       glFrontFace (GL_CCW);
567       glNormal3f (0, 0, -1);
568       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
569       glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
570       glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
571       glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
572       glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
573       polys++;
574       glEnd();
575
576       /* Top edge
577        */
578       glFrontFace (GL_CCW);
579       glNormal3f (1, -1, 0); /* #### wrong */
580       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
581       glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
582       glTexCoord2f(px0, py0); glVertex3f (px0, -py0,  zz);
583       glTexCoord2f(px2, py2); glVertex3f (px2, -py2,  zz);
584       glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
585       polys++;
586       glEnd();
587
588       /* Bottom edge
589        */
590       glFrontFace (GL_CW);
591       glNormal3f (-1, 1, 0); /* #### wrong */
592       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
593       glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
594       glTexCoord2f(px1, py1); glVertex3f (px1, -py1,  zz);
595       glTexCoord2f(px3, py3); glVertex3f (px3, -py3,  zz);
596       glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
597       polys++;
598       glEnd();
599
600
601     }
602
603   return polys;
604
605 }
606
607
608 static void
609 generate_bottle (ModeInfo *mi)
610 {
611   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
612   int wire = MI_IS_WIREFRAME(mi);
613   int faces = resolution * 1.5;
614   Bool smooth = do_smooth;
615
616   const lamp_geometry *top_slice = bp->model;
617   const char *current_texture = 0;
618   lamp_part last_part = 0;
619
620   if (faces < 3)  faces = 3;
621   else if (wire && faces > 20) faces = 20;
622   else if (faces > 60) faces = 60;
623
624   bp->bottle_poly_count = 0;
625
626   glNewList (bp->bottle_list, GL_COMPILE);
627   glPushMatrix();
628
629   glRotatef (90, 1, 0, 0);
630   glTranslatef (0, -0.5, 0);
631
632   /* All parts of the lamp use the same specularity and shininess. */
633   glMaterialfv (GL_FRONT, GL_SPECULAR,  lava_spec);
634   glMateriali  (GL_FRONT, GL_SHININESS, lava_shininess);
635
636   while (1)
637     {
638       const lamp_geometry *bot_slice = top_slice + 1;
639
640       const char *texture = 0;
641       GLfloat *color = 0;
642       GLfloat t0, t1;
643
644       glDisable (GL_LIGHT2);
645
646       switch (top_slice->part)
647         {
648         case CAP:
649         case BASE:
650           texture = base_tex;
651           color   = base_color;
652           break;
653         case BOTTLE:
654           texture = fluid_tex;
655           color   = fluid_color;
656           if (!wire) glEnable (GL_LIGHT2);   /* light2 affects only fluid */
657           break;
658         default:
659           abort();
660           break;
661         }
662
663       if (!wire && texture && texture != current_texture)
664         {
665           current_texture = texture;
666           load_texture (mi, current_texture);
667         }
668         
669       /* Color the discs darker than the tube walls. */
670       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
671
672       /* Do a top disc if this is the first slice of the CAP or BASE.
673        */
674       if ((top_slice->part == CAP  && last_part == 0) ||
675           (top_slice->part == BASE && last_part == BOTTLE))
676         bp->bottle_poly_count +=
677           draw_disc (top_slice->radius, top_slice->elevation, faces,
678                      True, wire);
679
680       /* Do a bottom disc if this is the last slice of the CAP or BASE.
681        */
682       if ((top_slice->part == CAP  && bot_slice->part == BOTTLE) ||
683           (top_slice->part == BASE && bot_slice->part == 0))
684         {
685           const lamp_geometry *sl = (bot_slice->part == 0
686                                      ? top_slice : bot_slice);
687           bp->bottle_poly_count +=
688             draw_disc (sl->radius, sl->elevation, faces, False, wire);
689         }
690
691       if (bot_slice->part == 0)    /* done! */
692         break;
693
694       /* Do a tube or cone
695        */
696       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
697
698       t0 = top_slice->texture_elevation;
699       t1 = bot_slice->texture_elevation;
700
701       /* Restart the texture coordinates for the glass.
702        */
703       if (top_slice->part == BOTTLE)
704         {
705           Bool first_p = (top_slice[-1].part != BOTTLE);
706           Bool last_p  = (bot_slice->part    != BOTTLE);
707           if (first_p) t0 = 0;
708           if (last_p)  t1 = 1;
709         }
710
711       bp->bottle_poly_count +=
712         draw_tube (top_slice->radius, bot_slice->radius,
713                    top_slice->elevation, bot_slice->elevation,
714                    t0, t1,
715                    faces,
716                    (top_slice->part == BOTTLE),
717                    smooth, wire);
718
719       last_part = top_slice->part;
720       top_slice++;
721     }
722
723   if (bp->style == ROCKET)
724     {
725       int i;
726       for (i = 0; i < 3; i++)
727         {
728           glPushMatrix();
729           glRotatef (120 * i, 0, 1, 0);
730           glTranslatef (0.14, -0.05, 0);
731           bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
732           glPopMatrix();
733         }
734       glTranslatef (0, -0.1, 0);  /* move floor down a little */
735     }
736
737
738   if (!wire) load_texture (mi, table_tex);
739   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
740   bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
741
742
743   glPopMatrix ();
744   glDisable (GL_TEXTURE_2D);   /* done with textured objects */
745   glEndList ();
746 }
747
748
749
750 \f
751 /* Generating blobbies
752  */
753
754 static double
755 bellrand (double extent)    /* like frand(), but a bell curve. */
756 {
757   return (((frand(extent) + frand(extent) + frand(extent)) / 3)
758           - (extent/2));
759 }
760
761
762 static void move_ball (ModeInfo *mi, metaball *b);
763
764 /* Bring a ball into play, and re-randomize its values.
765  */
766 static void
767 reset_ball (ModeInfo *mi, metaball *b)
768 {
769 /*  lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
770
771   b->r = 0.00001;
772   b->R = 0.12 + bellrand(0.10);
773
774   b->pos_r = bellrand (0.9);
775   b->pos_th = frand(M_PI*2);
776   b->z = 0;
777
778   b->dr = bellrand(TILT);
779   b->dz = CONVECTION;
780
781   b->leader = 0;
782
783   if (!b->alive_p)
784     b->alive_p = True;
785
786   move_ball (mi, b);
787 }
788
789
790 /* returns the first metaball that is not in use, or 0.
791  */
792 static metaball *
793 get_ball (ModeInfo *mi)
794 {
795   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
796   int i;
797   for (i = 0; i < bp->nballs; i++)
798     {
799       metaball *b = &bp->balls[i];
800       if (!b->alive_p)
801         return b;
802     }
803   return 0;
804 }
805
806
807 /* Generate the blobs that don't move: the ones at teh top and bottom
808    that are part of the scenery.
809  */
810 static void
811 generate_static_blobs (ModeInfo *mi)
812 {
813   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
814   metaball *b0, *b1;
815   int i;
816
817   b0 = get_ball (mi);
818   if (!b0) abort();
819   b0->static_p = True;
820   b0->alive_p = True;
821   b0->R = 0.6;
822   b0->r = 0.3;
823
824   /* the giant blob at the bottom of the bottle.
825    */
826   b0->pos_r = 0;
827   b0->pos_th = 0;
828   b0->dr = 0;
829   b0->dz = 0;
830   b0->x = 0;
831   b0->y = 0;
832   b0->z = -0.43;
833
834   /* the small blob at the top of the bottle.
835    */
836   b1 = get_ball (mi);
837   if (!b1) abort();
838
839   *b1 = *b0;
840   b1->R = 0.16;
841   b1->r = 0.135;
842   b1->z = 1.078;
843
844   /* Some extra blobs at the bottom of the bottle, to jumble the surface.
845    */
846   for (i = 0; i < bp->blobs_per_group; i++)
847     {
848       b1 = get_ball (mi);
849       if (!b1) abort();
850       reset_ball (mi, b1);
851       b1->static_p = True;
852       b1->z = frand(0.04);
853       b1->dr = 0;
854       b1->dz = 0;
855     }
856 }
857
858
859 static GLfloat
860 max_bottle_radius (lavalite_configuration *bp)
861 {
862   GLfloat r = 0;
863   const lamp_geometry *slice;
864   for (slice = bp->model; slice->part != 0; slice++)
865     {
866       if (slice->part == BOTTLE && slice->radius > r)
867         r = slice->radius;      /* top */
868       if (slice[1].radius > r)
869         r = slice[1].radius;    /* bottom */
870     }
871   return r;
872 }
873
874
875 static GLfloat
876 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
877 {
878   GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
879   const lamp_geometry *slice;
880   GLfloat ratio;
881
882   for (slice = bp->model; slice->part != 0; slice++)
883     if (z > slice->elevation)
884       {
885         slice--;
886         topz = slice->elevation;
887         topr = slice->radius;
888         break;
889       }
890   if (topz == -999) return 0;
891
892   for (; slice->part != 0; slice++)
893     if (z > slice->elevation)
894       {
895         botz = slice->elevation;
896         botr = slice->radius;
897         break;
898       }
899   if (botz == -999) return 0;
900
901   ratio = (z - botz) / (topz - botz);
902
903   return (botr + ((topr - botr) * ratio));
904 }
905
906
907 static void
908 move_ball (ModeInfo *mi, metaball *b)
909 {
910   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
911   double gravity = GRAVITY;
912   double real_r;
913
914   if (b->static_p) return;
915
916   b->pos_r += b->dr;
917   b->z     += b->dz;
918
919   b->dz -= gravity;
920
921   if (b->pos_r > 0.9)
922     {
923       b->pos_r = 0.9;
924       b->dr = -b->dr;
925     }
926   else if (b->pos_r < 0)
927     {
928       b->pos_r = -b->pos_r;
929       b->dr = -b->dr;
930     }
931
932   real_r = b->pos_r * bottle_radius_at (bp, b->z);
933
934   b->x = cos (b->pos_th) * real_r;
935   b->y = sin (b->pos_th) * real_r;
936
937   if (b->z < -b->R)  /* dropped below bottom of glass - turn it off */
938     b->alive_p = False;
939 }
940
941
942 /* This function makes sure that balls that are part of a group always stay
943    relatively close to each other.
944  */
945 static void
946 clamp_balls (ModeInfo *mi)
947 {
948   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
949   int i;
950   for (i = 0; i < bp->nballs; i++)
951     {
952       metaball *b = &bp->balls[i];
953       if (b->alive_p && b->leader)
954         {
955           double zslack = 0.1;
956           double minz = b->leader->z - zslack;
957           double maxz = b->leader->z + zslack;
958
959           /* Try to keep the Z values near those of the leader.
960              Don't let it go out of range (above or below) and clamp it
961              if it does.  If we've clamped it, make sure dz will be
962              moving it in the right direction (back toward the leader.)
963
964              We aren't currently clamping r, only z -- doesn't seem to
965              be needed.
966
967              This is kind of flaky, I think.  Sometimes you can see
968              the blobbies "twitch".  That's no good.
969            */
970
971           if (b->z < minz)
972             {
973               if (b->dz < 0) b->dz = -b->dz;
974               b->z = minz - b->dz;
975             }
976
977           if (b->z > maxz)
978             {
979               if (b->dz > 0) b->dz = -b->dz;
980               b->z = maxz + b->dz;
981             }
982         }
983     }
984 }
985
986
987 static void
988 move_balls (ModeInfo *mi)   /* for great justice */
989 {
990   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
991   int i;
992   for (i = 0; i < bp->nballs; i++)
993     {
994       metaball *b = &bp->balls[i];
995       if (b->alive_p)
996         move_ball (mi, b);
997     }
998
999   clamp_balls (mi);
1000 }
1001
1002
1003 \f
1004 /* Rendering blobbies using marching cubes.
1005  */
1006
1007 static double
1008 compute_metaball_influence (lavalite_configuration *bp,
1009                             double x, double y, double z,
1010                             int nballs, metaball *balls)
1011 {
1012   double vv = 0;
1013   int i;
1014
1015   for (i = 0; i < nballs; i++)
1016     {
1017       metaball *b = &balls[i];
1018       double dx, dy, dz;
1019       double d2, r, R, r2, R2;
1020
1021       if (!b->alive_p) continue;
1022
1023       dx = x - b->x;
1024       dy = y - b->y;
1025       dz = z - b->z;
1026       R = b->R;
1027
1028       if (dx > R || dx < -R ||    /* quick check before multiplying */
1029           dy > R || dy < -R ||
1030           dz > R || dz < -R)
1031         continue;
1032
1033       d2 = (dx*dx + dy*dy + dz*dz);
1034       r = b->r;
1035
1036       r2 = r*r;
1037       R2 = R*R;
1038
1039       if (d2 <= r2)             /* (d <= r)   inside the hard radius */
1040         vv += 1;
1041       else if (d2 > R2)         /* (d > R)   outside the radius of influence */
1042         ;
1043       else          /* somewhere in between: linear drop-off from r=1 to R=0 */
1044         {
1045           /* was: vv += 1 - ((d-r) / (R-r)); */
1046           vv += 1 - ((d2-r2) / (R2-r2));
1047         }
1048     }
1049
1050   return vv;
1051 }
1052
1053
1054 /* callback for marching_cubes() */
1055 static void *
1056 obj_init (double grid_size, void *closure)
1057 {
1058   lavalite_configuration *bp = (lavalite_configuration *) closure;
1059   bp->grid_size = grid_size;
1060
1061   return closure;
1062 }
1063
1064
1065 /* Returns True if the given point is outside of the glass tube.
1066  */
1067 static double
1068 clipped_by_glass_p (double x, double y, double z,
1069                     lavalite_configuration *bp)
1070 {
1071   double d2, or, or2, ir2;
1072
1073   or = bp->max_bottle_radius;
1074
1075   if (x > or || x < -or ||    /* quick check before multiplying */
1076       y > or || y < -or)
1077     return 0;
1078
1079   d2 = (x*x + y*y);
1080   or = bottle_radius_at (bp, z);
1081
1082   or2 = or*or;
1083
1084   if (d2 > or2)   /* (sqrt(d) > or) */
1085     return 0;
1086
1087   ir2 = or2 * 0.7;
1088
1089   if (d2 > ir2)  /* (sqrt(d) > ir) */
1090     {
1091       double dr1 = or2;
1092       double dr2 = ir2;
1093       /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1094       return (1 - (d2-dr2) / (dr1-dr2));
1095     }
1096
1097   return 1;
1098 }
1099
1100
1101
1102 /* callback for marching_cubes() */
1103 static double
1104 obj_compute (double x, double y, double z, void *closure)
1105 {
1106   lavalite_configuration *bp = (lavalite_configuration *) closure;
1107   double clip;
1108
1109   x /= bp->grid_size;   /* convert from 0-N to 0-1. */
1110   y /= bp->grid_size;
1111   z /= bp->grid_size;
1112
1113   x -= 0.5;     /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1114   y -= 0.5;
1115
1116   clip = clipped_by_glass_p (x, y, z, bp);
1117   if (clip == 0) return 0;
1118
1119   return (clip *
1120           compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1121 }
1122
1123
1124 /* callback for marching_cubes() */
1125 static void
1126 obj_free (void *closure)
1127 {
1128 }
1129
1130
1131 /* Send a new blob travelling upward.
1132    This blob will actually be composed of N metaballs that are near
1133    each other.
1134  */
1135 static void
1136 launch_balls (ModeInfo *mi)
1137 {
1138   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1139   metaball *b0 = get_ball (mi);
1140   int i;
1141
1142   if (!b0) return;
1143   reset_ball (mi, b0);
1144
1145   for (i = 0; i < bp->blobs_per_group; i++)
1146     {
1147       metaball *b1 = get_ball (mi);
1148       if (!b1) break;
1149       *b1 = *b0;
1150
1151       reset_ball (mi, b1);
1152       b1->leader = b0;
1153
1154 # define FROB(FIELD,AMT) \
1155          b1->FIELD += (bellrand(AMT) * b0->FIELD)
1156
1157    /* FROB (pos_r,  0.7); */
1158    /* FROB (pos_th, 0.7); */
1159       FROB (dr, 0.8);
1160       FROB (dz, 0.6);
1161 # undef FROB
1162     }
1163
1164 }
1165
1166
1167 static void
1168 animate_lava (ModeInfo *mi)
1169 {
1170   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1171   int wire = MI_IS_WIREFRAME(mi);
1172   Bool just_started_p = bp->just_started_p;
1173
1174   double isolevel = 0.3;
1175
1176   /* Maybe bubble a new blobby to the surface.
1177    */
1178   if (just_started_p ||
1179       frand(1.0) < bp->launch_chance)
1180     {
1181       bp->just_started_p = False;
1182       launch_balls (mi);
1183
1184       if (do_impatient && just_started_p)
1185         while (1)
1186           {
1187             int i;
1188             move_balls (mi);
1189             for (i = 0; i < bp->nballs; i++)
1190               {
1191                 metaball *b = &bp->balls[i];
1192                 if (b->alive_p && !b->static_p && !b->leader &&
1193                     b->z > 0.5)
1194                   goto DONE;
1195               }
1196           }
1197       DONE: ;
1198     }
1199
1200   move_balls (mi);
1201
1202   glNewList (bp->ball_list, GL_COMPILE);
1203   glPushMatrix();
1204
1205   glMaterialfv (GL_FRONT, GL_SPECULAR,            lava_spec);
1206   glMateriali  (GL_FRONT, GL_SHININESS,           lava_shininess);
1207   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1208
1209   /* For the blobbies, the origin is on the axis at the bottom of the
1210      glass bottle; and the top of the bottle is +1 on Z.
1211    */
1212   glTranslatef (0, 0, -0.5);
1213
1214   mi->polygon_count = 0;
1215   {
1216     double s;
1217     if (bp->grid_size == 0) bp->grid_size = 1;  /* first time through */
1218     s = 1.0/bp->grid_size;
1219
1220     glPushMatrix();
1221     glTranslatef (-0.5, -0.5, 0);
1222     glScalef (s, s, s);
1223     marching_cubes (resolution, isolevel, wire, do_smooth,
1224                     obj_init, obj_compute, obj_free, bp,
1225                     &mi->polygon_count);
1226     glPopMatrix();
1227   }
1228
1229   mi->polygon_count += bp->bottle_poly_count;
1230
1231   glPopMatrix();
1232   glEndList ();
1233 }
1234
1235
1236 \f
1237 /* Startup initialization
1238  */
1239
1240 ENTRYPOINT Bool
1241 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1242 {
1243   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1244
1245   if (gltrackball_event_handler (event, bp->trackball,
1246                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1247                                  &bp->button_down_p))
1248     return True;
1249
1250   return False;
1251 }
1252
1253
1254 static void
1255 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1256 {
1257   XColor c;
1258   a[3] = 1.0;  /* alpha */
1259
1260   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1261     {
1262       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1263       exit (1);
1264     }
1265   a[0] = c.red   / 65536.0;
1266   a[1] = c.green / 65536.0;
1267   a[2] = c.blue  / 65536.0;
1268 }
1269
1270
1271 ENTRYPOINT void 
1272 init_lavalite (ModeInfo *mi)
1273 {
1274   lavalite_configuration *bp;
1275   int wire = MI_IS_WIREFRAME(mi);
1276
1277   MI_INIT (mi, bps, NULL);
1278
1279   bp = &bps[MI_SCREEN(mi)];
1280
1281   bp->glx_context = init_GL(mi);
1282
1283   reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1284
1285   {
1286     char *s = do_style;
1287     if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1288     else if (!strcasecmp (s, "giant"))  bp->style = GIANT;
1289     else if (!strcasecmp (s, "cone"))   bp->style = CONE;
1290     else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1291     else if (!strcasecmp (s, "random"))
1292       {
1293         if (random() & 1) bp->style = CLASSIC;  /* half the time */
1294         else bp->style = (random() % ((int) ROCKET+1));
1295       }
1296     else
1297       {
1298         fprintf (stderr,
1299          "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1300                  progname, s);
1301         exit (1);
1302       }
1303   }
1304
1305   parse_color (mi, "lava",  lava_color_str,  lava_color);
1306   parse_color (mi, "fluid", fluid_color_str, fluid_color);
1307   parse_color (mi, "base",  base_color_str,  base_color);
1308   parse_color (mi, "table", table_color_str, table_color);
1309
1310   if (!wire)
1311     {
1312       GLfloat amb[4]  = {0.0, 0.0, 0.0, 1.0};
1313       GLfloat dif[4]  = {1.0, 1.0, 1.0, 1.0};
1314       GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1315       GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1316
1317       glEnable(GL_LIGHTING);
1318       glEnable(GL_LIGHT0);
1319       glEnable(GL_LIGHT1);
1320       glEnable(GL_DEPTH_TEST);
1321       glEnable(GL_CULL_FACE);
1322       glEnable(GL_NORMALIZE);
1323       glShadeModel(GL_SMOOTH);
1324
1325       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1326       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1327       glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1328
1329       glLightfv(GL_LIGHT1, GL_AMBIENT,  amb);
1330       glLightfv(GL_LIGHT1, GL_DIFFUSE,  dif);
1331       glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1332
1333       glLightfv(GL_LIGHT2, GL_AMBIENT,  amb);
1334       glLightfv(GL_LIGHT2, GL_DIFFUSE,  dif);
1335       glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1336     }
1337
1338   {
1339     Bool spinx=False, spiny=False, spinz=False;
1340     double spin_speed   = 0.4;
1341     double wander_speed = 0.03;
1342
1343     char *s = do_spin;
1344     while (*s)
1345       {
1346         if      (*s == 'x' || *s == 'X') spinx = True;
1347         else if (*s == 'y' || *s == 'Y') spiny = True;
1348         else if (*s == 'z' || *s == 'Z') spinz = True;
1349         else if (*s == '0') ;
1350         else
1351           {
1352             fprintf (stderr,
1353          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1354                      progname, do_spin);
1355             exit (1);
1356           }
1357         s++;
1358       }
1359
1360     bp->rot = make_rotator (spinx ? spin_speed : 0,
1361                             spiny ? spin_speed : 0,
1362                             spinz ? spin_speed : 0,
1363                             1.0,
1364                             do_wander ? wander_speed : 0,
1365                             False);
1366     bp->rot2 = make_rotator (spin_speed, 0, 0,
1367                              1, 0.1,
1368                              False);
1369     bp->trackball = gltrackball_init (False);
1370
1371     /* Tilt the scene a bit: lean the normal lamps toward the viewer,
1372        and the huge lamps away. */
1373     gltrackball_reset (bp->trackball,
1374                        -0.3 + frand(0.6),
1375                        (bp->style == ROCKET || bp->style == GIANT
1376                         ?  frand (0.2)
1377                         : -frand (0.6)));
1378   }
1379
1380   switch (bp->style)
1381     {
1382     case CLASSIC: bp->model = classic_lamp; break;
1383     case GIANT:   bp->model = giant_lamp;   break;
1384     case CONE:    bp->model = cone_lamp;    break;
1385     case ROCKET:  bp->model = rocket_lamp;  break;
1386     default: abort(); break;
1387     }
1388
1389   bp->max_bottle_radius = max_bottle_radius (bp);
1390
1391   bp->launch_chance = speed;
1392   bp->blobs_per_group = BLOBS_PER_GROUP;
1393   bp->just_started_p = True;
1394
1395   bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1396                 + 2);
1397   bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1398
1399   bp->bottle_list = glGenLists (1);
1400   bp->ball_list = glGenLists (1);
1401
1402   generate_bottle (mi);
1403   generate_static_blobs (mi);
1404 }
1405
1406
1407 /* Render one frame
1408  */
1409
1410 ENTRYPOINT void
1411 draw_lavalite (ModeInfo *mi)
1412 {
1413   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1414   Display *dpy = MI_DISPLAY(mi);
1415   Window window = MI_WINDOW(mi);
1416
1417   if (!bp->glx_context)
1418     return;
1419
1420   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1421
1422   glMatrixMode (GL_MODELVIEW);
1423   glPushMatrix ();
1424
1425   {
1426     double cx, cy, cz; /* camera position, 0-1. */
1427     double px, py, pz; /* object position, 0-1. */
1428     double rx, ry, rz; /* object rotation, 0-1. */
1429
1430     get_position (bp->rot2, 0,   &cy, &cz, !bp->button_down_p);
1431     get_rotation (bp->rot2, &cx, 0,   0,   !bp->button_down_p);
1432
1433     get_position (bp->rot,  &px, &py, &pz, !bp->button_down_p);
1434     get_rotation (bp->rot,  &rx, &ry, &rz, !bp->button_down_p);
1435
1436 #if 1
1437     cx = 0.5;
1438     cy = 0.5;
1439     cz = 1.0;
1440
1441 #else  /* #### this crud doesn't really work yet */
1442
1443
1444     /* We have c[xyz] parameters describing a camera position, but we don't
1445        want to just map those to points in space: the lamp doesn't look very
1446        good from the inside, or from underneath...
1447
1448        Good observation points form a ring around the lamp: basically, a
1449        torus ringing the lamp, parallel to the lamp's floor.
1450
1451        We interpret cz as distance from the origin.
1452        cy as elevation.
1453        cx is then used as position in the torus (theta).
1454      */
1455
1456     {
1457       double cx2, cy2, cz2;
1458       double d;
1459
1460       cx2 = 0.5;
1461       cy2 = 0.5;
1462       cz2 = 1.0;
1463
1464       cy2 = (cy * 0.4);         /* cam elevation: 0.0 (table) - 0.4 up. */
1465       d = 0.9 + cz;             /* cam distance: 0.9 - 1.9. */
1466
1467       cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1468       cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1469
1470
1471       cx = cx2;
1472       cy = cy2;
1473       cz = cz2;
1474     }
1475 #endif  /* 0 */
1476
1477     glLoadIdentity();
1478     glRotatef(current_device_rotation(), 0, 0, 1);
1479
1480     gluLookAt ((cx - 0.5) * 8,          /* Position the camera */
1481                (cy - 0.5) * 8,
1482                (cz - 0.5) * 8,
1483                0, 0, 0,
1484                0, 1, 0);
1485
1486     gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1487
1488     glRotatef (-90, 1, 0, 0);  /* Right side up */
1489
1490
1491     /* Place the lights relative to the object, before the object has
1492        been rotated or wandered within the scene. */
1493     glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1494     glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1495     glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1496
1497
1498     /* Position the lamp in the scene according to the "wander" settings */
1499     glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1500
1501     /* Rotate the object according to the "spin" settings */
1502     glRotatef (rx * 360, 1.0, 0.0, 0.0);
1503     glRotatef (ry * 360, 0.0, 1.0, 0.0);
1504     glRotatef (rz * 360, 0.0, 0.0, 1.0);
1505
1506     /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1507     switch (bp->style)
1508       {
1509       case CLASSIC: glTranslatef (0, 0, 0.33); break;
1510       case GIANT:   glTranslatef (0, 0, 0.33); break;
1511       case CONE:    glTranslatef (0, 0, 0.16); break;
1512       case ROCKET:  glTranslatef (0, 0, 0.30);
1513                     glScalef (0.85,0.85,0.85); break;
1514       default: abort(); break;
1515       }
1516   }
1517
1518   animate_lava (mi);
1519
1520   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1521   glCallList (bp->bottle_list);
1522   glCallList (bp->ball_list);
1523   glPopMatrix ();
1524
1525   if (mi->fps_p) do_fps (mi);
1526   glFinish();
1527
1528   glXSwapBuffers(dpy, window);
1529 }
1530
1531 XSCREENSAVER_MODULE ("Lavalite", lavalite)
1532
1533 #endif /* USE_GL */