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