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