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