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