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