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