From http://www.jwz.org/xscreensaver/xscreensaver-5.30.tar.gz
[xscreensaver] / hacks / glx / lavalite.c
1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
2  *
3  * This software Copyright (c) 2002-2014 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 (gltrackball_event_handler (event, bp->trackball,
1245                                  MI_WIDTH (mi), MI_HEIGHT (mi),
1246                                  &bp->button_down_p))
1247     return True;
1248
1249   return False;
1250 }
1251
1252
1253 static void
1254 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1255 {
1256   XColor c;
1257   a[3] = 1.0;  /* alpha */
1258
1259   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1260     {
1261       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1262       exit (1);
1263     }
1264   a[0] = c.red   / 65536.0;
1265   a[1] = c.green / 65536.0;
1266   a[2] = c.blue  / 65536.0;
1267 }
1268
1269
1270 ENTRYPOINT void 
1271 init_lavalite (ModeInfo *mi)
1272 {
1273   lavalite_configuration *bp;
1274   int wire = MI_IS_WIREFRAME(mi);
1275
1276   if (!bps) {
1277     bps = (lavalite_configuration *)
1278       calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1279     if (!bps) {
1280       fprintf(stderr, "%s: out of memory\n", progname);
1281       exit(1);
1282     }
1283   }
1284
1285   bp = &bps[MI_SCREEN(mi)];
1286
1287   bp->glx_context = init_GL(mi);
1288
1289   reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1290
1291   {
1292     char *s = do_style;
1293     if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1294     else if (!strcasecmp (s, "giant"))  bp->style = GIANT;
1295     else if (!strcasecmp (s, "cone"))   bp->style = CONE;
1296     else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1297     else if (!strcasecmp (s, "random"))
1298       {
1299         if (random() & 1) bp->style = CLASSIC;  /* half the time */
1300         else bp->style = (random() % ((int) ROCKET+1));
1301       }
1302     else
1303       {
1304         fprintf (stderr,
1305          "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1306                  progname, s);
1307         exit (1);
1308       }
1309   }
1310
1311   parse_color (mi, "lava",  lava_color_str,  lava_color);
1312   parse_color (mi, "fluid", fluid_color_str, fluid_color);
1313   parse_color (mi, "base",  base_color_str,  base_color);
1314   parse_color (mi, "table", table_color_str, table_color);
1315
1316   if (!wire)
1317     {
1318       GLfloat amb[4]  = {0.0, 0.0, 0.0, 1.0};
1319       GLfloat dif[4]  = {1.0, 1.0, 1.0, 1.0};
1320       GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1321       GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1322
1323       glEnable(GL_LIGHTING);
1324       glEnable(GL_LIGHT0);
1325       glEnable(GL_LIGHT1);
1326       glEnable(GL_DEPTH_TEST);
1327       glEnable(GL_CULL_FACE);
1328       glEnable(GL_NORMALIZE);
1329       glShadeModel(GL_SMOOTH);
1330
1331       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1332       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1333       glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1334
1335       glLightfv(GL_LIGHT1, GL_AMBIENT,  amb);
1336       glLightfv(GL_LIGHT1, GL_DIFFUSE,  dif);
1337       glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1338
1339       glLightfv(GL_LIGHT2, GL_AMBIENT,  amb);
1340       glLightfv(GL_LIGHT2, GL_DIFFUSE,  dif);
1341       glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1342     }
1343
1344   {
1345     Bool spinx=False, spiny=False, spinz=False;
1346     double spin_speed   = 0.4;
1347     double wander_speed = 0.03;
1348
1349     char *s = do_spin;
1350     while (*s)
1351       {
1352         if      (*s == 'x' || *s == 'X') spinx = True;
1353         else if (*s == 'y' || *s == 'Y') spiny = True;
1354         else if (*s == 'z' || *s == 'Z') spinz = True;
1355         else if (*s == '0') ;
1356         else
1357           {
1358             fprintf (stderr,
1359          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1360                      progname, do_spin);
1361             exit (1);
1362           }
1363         s++;
1364       }
1365
1366     bp->rot = make_rotator (spinx ? spin_speed : 0,
1367                             spiny ? spin_speed : 0,
1368                             spinz ? spin_speed : 0,
1369                             1.0,
1370                             do_wander ? wander_speed : 0,
1371                             False);
1372     bp->rot2 = make_rotator (spin_speed, 0, 0,
1373                              1, 0.1,
1374                              False);
1375     bp->trackball = gltrackball_init (False);
1376
1377     /* move initial camera position up by around 15 degrees:
1378        in other words, tilt the scene toward the viewer. */
1379     gltrackball_start (bp->trackball, 50, 50, 100, 100);
1380     gltrackball_track (bp->trackball, 50,  5, 100, 100);
1381
1382     /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1383        look like we're looking up at it instead of down at it! */
1384     if (bp->style == GIANT)
1385       gltrackball_track (bp->trackball, 50, -12, 100, 100);
1386     else if (bp->style == ROCKET)  /* same for rocket, but not as much */
1387       gltrackball_track (bp->trackball, 50, -4, 100, 100);
1388   }
1389
1390   switch (bp->style)
1391     {
1392     case CLASSIC: bp->model = classic_lamp; break;
1393     case GIANT:   bp->model = giant_lamp;   break;
1394     case CONE:    bp->model = cone_lamp;    break;
1395     case ROCKET:  bp->model = rocket_lamp;  break;
1396     default: abort(); break;
1397     }
1398
1399   bp->max_bottle_radius = max_bottle_radius (bp);
1400
1401   bp->launch_chance = speed;
1402   bp->blobs_per_group = BLOBS_PER_GROUP;
1403   bp->just_started_p = True;
1404
1405   bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1406                 + 2);
1407   bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1408
1409   bp->bottle_list = glGenLists (1);
1410   bp->ball_list = glGenLists (1);
1411
1412   generate_bottle (mi);
1413   generate_static_blobs (mi);
1414 }
1415
1416
1417 /* Render one frame
1418  */
1419
1420 ENTRYPOINT void
1421 draw_lavalite (ModeInfo *mi)
1422 {
1423   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1424   Display *dpy = MI_DISPLAY(mi);
1425   Window window = MI_WINDOW(mi);
1426
1427   if (!bp->glx_context)
1428     return;
1429
1430   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1431
1432   glMatrixMode (GL_MODELVIEW);
1433   glPushMatrix ();
1434
1435   {
1436     double cx, cy, cz; /* camera position, 0-1. */
1437     double px, py, pz; /* object position, 0-1. */
1438     double rx, ry, rz; /* object rotation, 0-1. */
1439
1440     get_position (bp->rot2, 0,   &cy, &cz, !bp->button_down_p);
1441     get_rotation (bp->rot2, &cx, 0,   0,   !bp->button_down_p);
1442
1443     get_position (bp->rot,  &px, &py, &pz, !bp->button_down_p);
1444     get_rotation (bp->rot,  &rx, &ry, &rz, !bp->button_down_p);
1445
1446 #if 1
1447     cx = 0.5;
1448     cy = 0.5;
1449     cz = 1.0;
1450
1451 #else  /* #### this crud doesn't really work yet */
1452
1453
1454     /* We have c[xyz] parameters describing a camera position, but we don't
1455        want to just map those to points in space: the lamp doesn't look very
1456        good from the inside, or from underneath...
1457
1458        Good observation points form a ring around the lamp: basically, a
1459        torus ringing the lamp, parallel to the lamp's floor.
1460
1461        We interpret cz as distance from the origin.
1462        cy as elevation.
1463        cx is then used as position in the torus (theta).
1464      */
1465
1466     {
1467       double cx2, cy2, cz2;
1468       double d;
1469
1470       cx2 = 0.5;
1471       cy2 = 0.5;
1472       cz2 = 1.0;
1473
1474       cy2 = (cy * 0.4);         /* cam elevation: 0.0 (table) - 0.4 up. */
1475       d = 0.9 + cz;             /* cam distance: 0.9 - 1.9. */
1476
1477       cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1478       cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1479
1480
1481       cx = cx2;
1482       cy = cy2;
1483       cz = cz2;
1484     }
1485 #endif  /* 0 */
1486
1487     glLoadIdentity();
1488     glRotatef(current_device_rotation(), 0, 0, 1);
1489
1490     gluLookAt ((cx - 0.5) * 8,          /* Position the camera */
1491                (cy - 0.5) * 8,
1492                (cz - 0.5) * 8,
1493                0, 0, 0,
1494                0, 1, 0);
1495
1496     gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1497
1498     /* Place the lights relative to the object, before the object has
1499        been rotated or wandered within the scene. */
1500     glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1501     glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1502     glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1503
1504
1505     /* Position the lamp in the scene according to the "wander" settings */
1506     glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1507
1508     /* Rotate the object according to the "spin" settings */
1509     glRotatef (rx * 360, 1.0, 0.0, 0.0);
1510     glRotatef (ry * 360, 0.0, 1.0, 0.0);
1511     glRotatef (rz * 360, 0.0, 0.0, 1.0);
1512
1513     /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1514     switch (bp->style)
1515       {
1516       case CLASSIC: glTranslatef (0, 0, 0.33); break;
1517       case GIANT:   glTranslatef (0, 0, 0.33); break;
1518       case CONE:    glTranslatef (0, 0, 0.16); break;
1519       case ROCKET:  glTranslatef (0, 0, 0.30);
1520                     glScalef (0.85,0.85,0.85); break;
1521       default: abort(); break;
1522       }
1523   }
1524
1525   animate_lava (mi);
1526
1527   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1528   glCallList (bp->bottle_list);
1529   glCallList (bp->ball_list);
1530   glPopMatrix ();
1531
1532   if (mi->fps_p) do_fps (mi);
1533   glFinish();
1534
1535   glXSwapBuffers(dpy, window);
1536 }
1537
1538 XSCREENSAVER_MODULE ("Lavalite", lavalite)
1539
1540 #endif /* USE_GL */