http://www.jwz.org/xscreensaver/xscreensaver-5.11.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   Bool have_texture = False;
615
616   const lamp_geometry *top_slice = bp->model;
617   const char *current_texture = 0;
618   lamp_part last_part = 0;
619
620   if (faces < 3)  faces = 3;
621   else if (wire && faces > 20) faces = 20;
622   else if (faces > 60) faces = 60;
623
624   bp->bottle_poly_count = 0;
625
626   glNewList (bp->bottle_list, GL_COMPILE);
627   glPushMatrix();
628
629   glRotatef (90, 1, 0, 0);
630   glTranslatef (0, -0.5, 0);
631
632   /* All parts of the lamp use the same specularity and shininess. */
633   glMaterialfv (GL_FRONT, GL_SPECULAR,  lava_spec);
634   glMateriali  (GL_FRONT, GL_SHININESS, lava_shininess);
635
636   while (1)
637     {
638       const lamp_geometry *bot_slice = top_slice + 1;
639
640       const char *texture = 0;
641       GLfloat *color = 0;
642       GLfloat t0, t1;
643
644       glDisable (GL_LIGHT2);
645
646       switch (top_slice->part)
647         {
648         case CAP:
649         case BASE:
650           texture = base_tex;
651           color   = base_color;
652           break;
653         case BOTTLE:
654           texture = fluid_tex;
655           color   = fluid_color;
656           if (!wire) glEnable (GL_LIGHT2);   /* light2 affects only fluid */
657           break;
658         default:
659           abort();
660           break;
661         }
662
663       have_texture = False;
664       if (!wire && texture && texture != current_texture)
665         {
666           current_texture = texture;
667           have_texture = load_texture (mi, current_texture);
668         }
669         
670       /* Color the discs darker than the tube walls. */
671       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
672
673       /* Do a top disc if this is the first slice of the CAP or BASE.
674        */
675       if ((top_slice->part == CAP  && last_part == 0) ||
676           (top_slice->part == BASE && last_part == BOTTLE))
677         bp->bottle_poly_count +=
678           draw_disc (top_slice->radius, top_slice->elevation, faces,
679                      True, wire);
680
681       /* Do a bottom disc if this is the last slice of the CAP or BASE.
682        */
683       if ((top_slice->part == CAP  && bot_slice->part == BOTTLE) ||
684           (top_slice->part == BASE && bot_slice->part == 0))
685         {
686           const lamp_geometry *sl = (bot_slice->part == 0
687                                      ? top_slice : bot_slice);
688           bp->bottle_poly_count +=
689             draw_disc (sl->radius, sl->elevation, faces, False, wire);
690         }
691
692       if (bot_slice->part == 0)    /* done! */
693         break;
694
695       /* Do a tube or cone
696        */
697       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
698
699       t0 = top_slice->texture_elevation;
700       t1 = bot_slice->texture_elevation;
701
702       /* Restart the texture coordinates for the glass.
703        */
704       if (top_slice->part == BOTTLE)
705         {
706           Bool first_p = (top_slice[-1].part != BOTTLE);
707           Bool last_p  = (bot_slice->part    != BOTTLE);
708           if (first_p) t0 = 0;
709           if (last_p)  t1 = 1;
710         }
711
712       bp->bottle_poly_count +=
713         draw_tube (top_slice->radius, bot_slice->radius,
714                    top_slice->elevation, bot_slice->elevation,
715                    t0, t1,
716                    faces,
717                    (top_slice->part == BOTTLE),
718                    smooth, wire);
719
720       last_part = top_slice->part;
721       top_slice++;
722     }
723
724   if (bp->style == ROCKET)
725     {
726       int i;
727       for (i = 0; i < 3; i++)
728         {
729           glPushMatrix();
730           glRotatef (120 * i, 0, 1, 0);
731           glTranslatef (0.14, -0.05, 0);
732           bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
733           glPopMatrix();
734         }
735       glTranslatef (0, -0.1, 0);  /* move floor down a little */
736     }
737
738
739   have_texture = !wire && load_texture (mi, table_tex);
740   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
741   bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
742
743
744   glPopMatrix ();
745   glDisable (GL_TEXTURE_2D);   /* done with textured objects */
746   glEndList ();
747 }
748
749
750
751 \f
752 /* Generating blobbies
753  */
754
755 static double
756 bellrand (double extent)    /* like frand(), but a bell curve. */
757 {
758   return (((frand(extent) + frand(extent) + frand(extent)) / 3)
759           - (extent/2));
760 }
761
762
763 static void move_ball (ModeInfo *mi, metaball *b);
764
765 /* Bring a ball into play, and re-randomize its values.
766  */
767 static void
768 reset_ball (ModeInfo *mi, metaball *b)
769 {
770 /*  lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
771
772   b->r = 0.00001;
773   b->R = 0.12 + bellrand(0.10);
774
775   b->pos_r = bellrand (0.9);
776   b->pos_th = frand(M_PI*2);
777   b->z = 0;
778
779   b->dr = bellrand(TILT);
780   b->dz = CONVECTION;
781
782   b->leader = 0;
783
784   if (!b->alive_p)
785     b->alive_p = True;
786
787   move_ball (mi, b);
788 }
789
790
791 /* returns the first metaball that is not in use, or 0.
792  */
793 static metaball *
794 get_ball (ModeInfo *mi)
795 {
796   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
797   int i;
798   for (i = 0; i < bp->nballs; i++)
799     {
800       metaball *b = &bp->balls[i];
801       if (!b->alive_p)
802         return b;
803     }
804   return 0;
805 }
806
807
808 /* Generate the blobs that don't move: the ones at teh top and bottom
809    that are part of the scenery.
810  */
811 static void
812 generate_static_blobs (ModeInfo *mi)
813 {
814   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
815   metaball *b0, *b1;
816   int i;
817
818   b0 = get_ball (mi);
819   if (!b0) abort();
820   b0->static_p = True;
821   b0->alive_p = True;
822   b0->R = 0.6;
823   b0->r = 0.3;
824
825   /* the giant blob at the bottom of the bottle.
826    */
827   b0->pos_r = 0;
828   b0->pos_th = 0;
829   b0->dr = 0;
830   b0->dz = 0;
831   b0->x = 0;
832   b0->y = 0;
833   b0->z = -0.43;
834
835   /* the small blob at the top of the bottle.
836    */
837   b1 = get_ball (mi);
838   if (!b1) abort();
839
840   *b1 = *b0;
841   b1->R = 0.16;
842   b1->r = 0.135;
843   b1->z = 1.078;
844
845   /* Some extra blobs at the bottom of the bottle, to jumble the surface.
846    */
847   for (i = 0; i < bp->blobs_per_group; i++)
848     {
849       b1 = get_ball (mi);
850       if (!b1) abort();
851       reset_ball (mi, b1);
852       b1->static_p = True;
853       b1->z = frand(0.04);
854       b1->dr = 0;
855       b1->dz = 0;
856     }
857 }
858
859
860 static GLfloat
861 max_bottle_radius (lavalite_configuration *bp)
862 {
863   GLfloat r = 0;
864   const lamp_geometry *slice;
865   for (slice = bp->model; slice->part != 0; slice++)
866     {
867       if (slice->part == BOTTLE && slice->radius > r)
868         r = slice->radius;      /* top */
869       if (slice[1].radius > r)
870         r = slice[1].radius;    /* bottom */
871     }
872   return r;
873 }
874
875
876 static GLfloat
877 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
878 {
879   GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
880   const lamp_geometry *slice;
881   GLfloat ratio;
882
883   for (slice = bp->model; slice->part != 0; slice++)
884     if (z > slice->elevation)
885       {
886         slice--;
887         topz = slice->elevation;
888         topr = slice->radius;
889         break;
890       }
891   if (topz == -999) return 0;
892
893   for (; slice->part != 0; slice++)
894     if (z > slice->elevation)
895       {
896         botz = slice->elevation;
897         botr = slice->radius;
898         break;
899       }
900   if (botz == -999) return 0;
901
902   ratio = (z - botz) / (topz - botz);
903
904   return (botr + ((topr - botr) * ratio));
905 }
906
907
908 static void
909 move_ball (ModeInfo *mi, metaball *b)
910 {
911   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
912   double gravity = GRAVITY;
913   double real_r;
914
915   if (b->static_p) return;
916
917   b->pos_r += b->dr;
918   b->z     += b->dz;
919
920   b->dz -= gravity;
921
922   if (b->pos_r > 0.9)
923     {
924       b->pos_r = 0.9;
925       b->dr = -b->dr;
926     }
927   else if (b->pos_r < 0)
928     {
929       b->pos_r = -b->pos_r;
930       b->dr = -b->dr;
931     }
932
933   real_r = b->pos_r * bottle_radius_at (bp, b->z);
934
935   b->x = cos (b->pos_th) * real_r;
936   b->y = sin (b->pos_th) * real_r;
937
938   if (b->z < -b->R)  /* dropped below bottom of glass - turn it off */
939     b->alive_p = False;
940 }
941
942
943 /* This function makes sure that balls that are part of a group always stay
944    relatively close to each other.
945  */
946 static void
947 clamp_balls (ModeInfo *mi)
948 {
949   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
950   int i;
951   for (i = 0; i < bp->nballs; i++)
952     {
953       metaball *b = &bp->balls[i];
954       if (b->alive_p && b->leader)
955         {
956           double zslack = 0.1;
957           double minz = b->leader->z - zslack;
958           double maxz = b->leader->z + zslack;
959
960           /* Try to keep the Z values near those of the leader.
961              Don't let it go out of range (above or below) and clamp it
962              if it does.  If we've clamped it, make sure dz will be
963              moving it in the right direction (back toward the leader.)
964
965              We aren't currently clamping r, only z -- doesn't seem to
966              be needed.
967
968              This is kind of flaky, I think.  Sometimes you can see
969              the blobbies "twitch".  That's no good.
970            */
971
972           if (b->z < minz)
973             {
974               if (b->dz < 0) b->dz = -b->dz;
975               b->z = minz - b->dz;
976             }
977
978           if (b->z > maxz)
979             {
980               if (b->dz > 0) b->dz = -b->dz;
981               b->z = maxz + b->dz;
982             }
983         }
984     }
985 }
986
987
988 static void
989 move_balls (ModeInfo *mi)   /* for great justice */
990 {
991   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
992   int i;
993   for (i = 0; i < bp->nballs; i++)
994     {
995       metaball *b = &bp->balls[i];
996       if (b->alive_p)
997         move_ball (mi, b);
998     }
999
1000   clamp_balls (mi);
1001 }
1002
1003
1004 \f
1005 /* Rendering blobbies using marching cubes.
1006  */
1007
1008 static double
1009 compute_metaball_influence (lavalite_configuration *bp,
1010                             double x, double y, double z,
1011                             int nballs, metaball *balls)
1012 {
1013   double vv = 0;
1014   int i;
1015
1016   for (i = 0; i < nballs; i++)
1017     {
1018       metaball *b = &balls[i];
1019       double dx, dy, dz;
1020       double d2, r, R, r2, R2;
1021
1022       if (!b->alive_p) continue;
1023
1024       dx = x - b->x;
1025       dy = y - b->y;
1026       dz = z - b->z;
1027       R = b->R;
1028
1029       if (dx > R || dx < -R ||    /* quick check before multiplying */
1030           dy > R || dy < -R ||
1031           dz > R || dz < -R)
1032         continue;
1033
1034       d2 = (dx*dx + dy*dy + dz*dz);
1035       r = b->r;
1036
1037       r2 = r*r;
1038       R2 = R*R;
1039
1040       if (d2 <= r2)             /* (d <= r)   inside the hard radius */
1041         vv += 1;
1042       else if (d2 > R2)         /* (d > R)   outside the radius of influence */
1043         ;
1044       else          /* somewhere in between: linear drop-off from r=1 to R=0 */
1045         {
1046           /* was: vv += 1 - ((d-r) / (R-r)); */
1047           vv += 1 - ((d2-r2) / (R2-r2));
1048         }
1049     }
1050
1051   return vv;
1052 }
1053
1054
1055 /* callback for marching_cubes() */
1056 static void *
1057 obj_init (double grid_size, void *closure)
1058 {
1059   lavalite_configuration *bp = (lavalite_configuration *) closure;
1060   bp->grid_size = grid_size;
1061
1062   return closure;
1063 }
1064
1065
1066 /* Returns True if the given point is outside of the glass tube.
1067  */
1068 static double
1069 clipped_by_glass_p (double x, double y, double z,
1070                     lavalite_configuration *bp)
1071 {
1072   double d2, or, or2, ir2;
1073
1074   or = bp->max_bottle_radius;
1075
1076   if (x > or || x < -or ||    /* quick check before multiplying */
1077       y > or || y < -or)
1078     return 0;
1079
1080   d2 = (x*x + y*y);
1081   or = bottle_radius_at (bp, z);
1082
1083   or2 = or*or;
1084
1085   if (d2 > or2)   /* (sqrt(d) > or) */
1086     return 0;
1087
1088   ir2 = or2 * 0.7;
1089
1090   if (d2 > ir2)  /* (sqrt(d) > ir) */
1091     {
1092       double dr1 = or2;
1093       double dr2 = ir2;
1094       /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1095       return (1 - (d2-dr2) / (dr1-dr2));
1096     }
1097
1098   return 1;
1099 }
1100
1101
1102
1103 /* callback for marching_cubes() */
1104 static double
1105 obj_compute (double x, double y, double z, void *closure)
1106 {
1107   lavalite_configuration *bp = (lavalite_configuration *) closure;
1108   double clip;
1109
1110   x /= bp->grid_size;   /* convert from 0-N to 0-1. */
1111   y /= bp->grid_size;
1112   z /= bp->grid_size;
1113
1114   x -= 0.5;     /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1115   y -= 0.5;
1116
1117   clip = clipped_by_glass_p (x, y, z, bp);
1118   if (clip == 0) return 0;
1119
1120   return (clip *
1121           compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1122 }
1123
1124
1125 /* callback for marching_cubes() */
1126 static void
1127 obj_free (void *closure)
1128 {
1129 }
1130
1131
1132 /* Send a new blob travelling upward.
1133    This blob will actually be composed of N metaballs that are near
1134    each other.
1135  */
1136 static void
1137 launch_balls (ModeInfo *mi)
1138 {
1139   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1140   metaball *b0 = get_ball (mi);
1141   int i;
1142
1143   if (!b0) return;
1144   reset_ball (mi, b0);
1145
1146   for (i = 0; i < bp->blobs_per_group; i++)
1147     {
1148       metaball *b1 = get_ball (mi);
1149       if (!b1) break;
1150       *b1 = *b0;
1151
1152       reset_ball (mi, b1);
1153       b1->leader = b0;
1154
1155 # define FROB(FIELD,AMT) \
1156          b1->FIELD += (bellrand(AMT) * b0->FIELD)
1157
1158    /* FROB (pos_r,  0.7); */
1159    /* FROB (pos_th, 0.7); */
1160       FROB (dr, 0.8);
1161       FROB (dz, 0.6);
1162 # undef FROB
1163     }
1164
1165 }
1166
1167
1168 static void
1169 animate_lava (ModeInfo *mi)
1170 {
1171   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1172   int wire = MI_IS_WIREFRAME(mi);
1173   Bool just_started_p = bp->just_started_p;
1174
1175   double isolevel = 0.3;
1176
1177   /* Maybe bubble a new blobby to the surface.
1178    */
1179   if (just_started_p ||
1180       frand(1.0) < bp->launch_chance)
1181     {
1182       bp->just_started_p = False;
1183       launch_balls (mi);
1184
1185       if (do_impatient && just_started_p)
1186         while (1)
1187           {
1188             int i;
1189             move_balls (mi);
1190             for (i = 0; i < bp->nballs; i++)
1191               {
1192                 metaball *b = &bp->balls[i];
1193                 if (b->alive_p && !b->static_p && !b->leader &&
1194                     b->z > 0.5)
1195                   goto DONE;
1196               }
1197           }
1198       DONE: ;
1199     }
1200
1201   move_balls (mi);
1202
1203   glNewList (bp->ball_list, GL_COMPILE);
1204   glPushMatrix();
1205
1206   glMaterialfv (GL_FRONT, GL_SPECULAR,            lava_spec);
1207   glMateriali  (GL_FRONT, GL_SHININESS,           lava_shininess);
1208   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1209
1210   /* For the blobbies, the origin is on the axis at the bottom of the
1211      glass bottle; and the top of the bottle is +1 on Z.
1212    */
1213   glTranslatef (0, 0, -0.5);
1214
1215   mi->polygon_count = 0;
1216   {
1217     double s;
1218     if (bp->grid_size == 0) bp->grid_size = 1;  /* first time through */
1219     s = 1.0/bp->grid_size;
1220
1221     glPushMatrix();
1222     glTranslatef (-0.5, -0.5, 0);
1223     glScalef (s, s, s);
1224     marching_cubes (resolution, isolevel, wire, do_smooth,
1225                     obj_init, obj_compute, obj_free, bp,
1226                     &mi->polygon_count);
1227     glPopMatrix();
1228   }
1229
1230   mi->polygon_count += bp->bottle_poly_count;
1231
1232   glPopMatrix();
1233   glEndList ();
1234 }
1235
1236
1237 \f
1238 /* Startup initialization
1239  */
1240
1241 ENTRYPOINT Bool
1242 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1243 {
1244   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1245
1246   if (event->xany.type == ButtonPress &&
1247       event->xbutton.button == Button1)
1248     {
1249       bp->button_down_p = True;
1250       gltrackball_start (bp->trackball,
1251                          event->xbutton.x, event->xbutton.y,
1252                          MI_WIDTH (mi), MI_HEIGHT (mi));
1253       return True;
1254     }
1255   else if (event->xany.type == ButtonRelease &&
1256            event->xbutton.button == Button1)
1257     {
1258       bp->button_down_p = False;
1259       return True;
1260     }
1261   else if (event->xany.type == ButtonPress &&
1262            (event->xbutton.button == Button4 ||
1263             event->xbutton.button == Button5 ||
1264             event->xbutton.button == Button6 ||
1265             event->xbutton.button == Button7))
1266     {
1267       gltrackball_mousewheel (bp->trackball, event->xbutton.button, 5,
1268                               !!event->xbutton.state);
1269       return True;
1270     }
1271   else if (event->xany.type == MotionNotify &&
1272            bp->button_down_p)
1273     {
1274       gltrackball_track (bp->trackball,
1275                          event->xmotion.x, event->xmotion.y,
1276                          MI_WIDTH (mi), MI_HEIGHT (mi));
1277       return True;
1278     }
1279
1280   return False;
1281 }
1282
1283
1284 static void
1285 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1286 {
1287   XColor c;
1288   a[3] = 1.0;  /* alpha */
1289
1290   if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1291     {
1292       fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1293       exit (1);
1294     }
1295   a[0] = c.red   / 65536.0;
1296   a[1] = c.green / 65536.0;
1297   a[2] = c.blue  / 65536.0;
1298 }
1299
1300
1301 ENTRYPOINT void 
1302 init_lavalite (ModeInfo *mi)
1303 {
1304   lavalite_configuration *bp;
1305   int wire = MI_IS_WIREFRAME(mi);
1306
1307   if (!bps) {
1308     bps = (lavalite_configuration *)
1309       calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1310     if (!bps) {
1311       fprintf(stderr, "%s: out of memory\n", progname);
1312       exit(1);
1313     }
1314
1315     bp = &bps[MI_SCREEN(mi)];
1316   }
1317
1318   bp = &bps[MI_SCREEN(mi)];
1319
1320   bp->glx_context = init_GL(mi);
1321
1322   reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1323
1324   {
1325     char *s = do_style;
1326     if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1327     else if (!strcasecmp (s, "giant"))  bp->style = GIANT;
1328     else if (!strcasecmp (s, "cone"))   bp->style = CONE;
1329     else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1330     else if (!strcasecmp (s, "random"))
1331       {
1332         if (random() & 1) bp->style = CLASSIC;  /* half the time */
1333         else bp->style = (random() % ((int) ROCKET+1));
1334       }
1335     else
1336       {
1337         fprintf (stderr,
1338          "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1339                  progname, s);
1340         exit (1);
1341       }
1342   }
1343
1344   parse_color (mi, "lava",  lava_color_str,  lava_color);
1345   parse_color (mi, "fluid", fluid_color_str, fluid_color);
1346   parse_color (mi, "base",  base_color_str,  base_color);
1347   parse_color (mi, "table", table_color_str, table_color);
1348
1349   if (!wire)
1350     {
1351       GLfloat amb[4]  = {0.0, 0.0, 0.0, 1.0};
1352       GLfloat dif[4]  = {1.0, 1.0, 1.0, 1.0};
1353       GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1354       GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1355
1356       glEnable(GL_LIGHTING);
1357       glEnable(GL_LIGHT0);
1358       glEnable(GL_LIGHT1);
1359       glEnable(GL_DEPTH_TEST);
1360       glEnable(GL_CULL_FACE);
1361       glEnable(GL_NORMALIZE);
1362       glShadeModel(GL_SMOOTH);
1363
1364       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
1365       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
1366       glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1367
1368       glLightfv(GL_LIGHT1, GL_AMBIENT,  amb);
1369       glLightfv(GL_LIGHT1, GL_DIFFUSE,  dif);
1370       glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1371
1372       glLightfv(GL_LIGHT2, GL_AMBIENT,  amb);
1373       glLightfv(GL_LIGHT2, GL_DIFFUSE,  dif);
1374       glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1375     }
1376
1377   {
1378     Bool spinx=False, spiny=False, spinz=False;
1379     double spin_speed   = 0.4;
1380     double wander_speed = 0.03;
1381
1382     char *s = do_spin;
1383     while (*s)
1384       {
1385         if      (*s == 'x' || *s == 'X') spinx = True;
1386         else if (*s == 'y' || *s == 'Y') spiny = True;
1387         else if (*s == 'z' || *s == 'Z') spinz = True;
1388         else if (*s == '0') ;
1389         else
1390           {
1391             fprintf (stderr,
1392          "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1393                      progname, do_spin);
1394             exit (1);
1395           }
1396         s++;
1397       }
1398
1399     bp->rot = make_rotator (spinx ? spin_speed : 0,
1400                             spiny ? spin_speed : 0,
1401                             spinz ? spin_speed : 0,
1402                             1.0,
1403                             do_wander ? wander_speed : 0,
1404                             False);
1405     bp->rot2 = make_rotator (spin_speed, 0, 0,
1406                              1, 0.1,
1407                              False);
1408     bp->trackball = gltrackball_init ();
1409
1410     /* move initial camera position up by around 15 degrees:
1411        in other words, tilt the scene toward the viewer. */
1412     gltrackball_start (bp->trackball, 50, 50, 100, 100);
1413     gltrackball_track (bp->trackball, 50,  5, 100, 100);
1414
1415     /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1416        look like we're looking up at it instead of down at it! */
1417     if (bp->style == GIANT)
1418       gltrackball_track (bp->trackball, 50, -12, 100, 100);
1419     else if (bp->style == ROCKET)  /* same for rocket, but not as much */
1420       gltrackball_track (bp->trackball, 50, -4, 100, 100);
1421   }
1422
1423   switch (bp->style)
1424     {
1425     case CLASSIC: bp->model = classic_lamp; break;
1426     case GIANT:   bp->model = giant_lamp;   break;
1427     case CONE:    bp->model = cone_lamp;    break;
1428     case ROCKET:  bp->model = rocket_lamp;  break;
1429     default: abort(); break;
1430     }
1431
1432   bp->max_bottle_radius = max_bottle_radius (bp);
1433
1434   bp->launch_chance = speed;
1435   bp->blobs_per_group = BLOBS_PER_GROUP;
1436   bp->just_started_p = True;
1437
1438   bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1439                 + 2);
1440   bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1441
1442   bp->bottle_list = glGenLists (1);
1443   bp->ball_list = glGenLists (1);
1444
1445   generate_bottle (mi);
1446   generate_static_blobs (mi);
1447 }
1448
1449
1450 /* Render one frame
1451  */
1452
1453 ENTRYPOINT void
1454 draw_lavalite (ModeInfo *mi)
1455 {
1456   lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1457   Display *dpy = MI_DISPLAY(mi);
1458   Window window = MI_WINDOW(mi);
1459
1460   if (!bp->glx_context)
1461     return;
1462
1463   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1464
1465   glMatrixMode (GL_MODELVIEW);
1466   glPushMatrix ();
1467
1468   {
1469     double cx, cy, cz; /* camera position, 0-1. */
1470     double px, py, pz; /* object position, 0-1. */
1471     double rx, ry, rz; /* object rotation, 0-1. */
1472
1473     get_position (bp->rot2, 0,   &cy, &cz, !bp->button_down_p);
1474     get_rotation (bp->rot2, &cx, 0,   0,   !bp->button_down_p);
1475
1476     get_position (bp->rot,  &px, &py, &pz, !bp->button_down_p);
1477     get_rotation (bp->rot,  &rx, &ry, &rz, !bp->button_down_p);
1478
1479 #if 1
1480     cx = 0.5;
1481     cy = 0.5;
1482     cz = 1.0;
1483
1484 #else  /* #### this crud doesn't really work yet */
1485
1486
1487     /* We have c[xyz] parameters describing a camera position, but we don't
1488        want to just map those to points in space: the lamp doesn't look very
1489        good from the inside, or from underneath...
1490
1491        Good observation points form a ring around the lamp: basically, a
1492        torus ringing the lamp, parallel to the lamp's floor.
1493
1494        We interpret cz as distance from the origin.
1495        cy as elevation.
1496        cx is then used as position in the torus (theta).
1497      */
1498
1499     {
1500       double cx2, cy2, cz2;
1501       double d;
1502
1503       cx2 = 0.5;
1504       cy2 = 0.5;
1505       cz2 = 1.0;
1506
1507       cy2 = (cy * 0.4);         /* cam elevation: 0.0 (table) - 0.4 up. */
1508       d = 0.9 + cz;             /* cam distance: 0.9 - 1.9. */
1509
1510       cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1511       cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1512
1513
1514       cx = cx2;
1515       cy = cy2;
1516       cz = cz2;
1517     }
1518 #endif  /* 0 */
1519
1520     glLoadIdentity();
1521
1522     gluLookAt ((cx - 0.5) * 8,          /* Position the camera */
1523                (cy - 0.5) * 8,
1524                (cz - 0.5) * 8,
1525                0, 0, 0,
1526                0, 1, 0);
1527
1528     gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1529
1530
1531     /* Place the lights relative to the object, before the object has
1532        been rotated or wandered within the scene. */
1533     glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1534     glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1535     glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1536
1537
1538     /* Position the lamp in the scene according to the "wander" settings */
1539     glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1540
1541     /* Rotate the object according to the "spin" settings */
1542     glRotatef (rx * 360, 1.0, 0.0, 0.0);
1543     glRotatef (ry * 360, 0.0, 1.0, 0.0);
1544     glRotatef (rz * 360, 0.0, 0.0, 1.0);
1545
1546     /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1547     switch (bp->style)
1548       {
1549       case CLASSIC: glTranslatef (0, 0, 0.33); break;
1550       case GIANT:   glTranslatef (0, 0, 0.33); break;
1551       case CONE:    glTranslatef (0, 0, 0.16); break;
1552       case ROCKET:  glTranslatef (0, 0, 0.30);
1553                     glScalef (0.85,0.85,0.85); break;
1554       default: abort(); break;
1555       }
1556   }
1557
1558   animate_lava (mi);
1559
1560   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1561   glCallList (bp->bottle_list);
1562   glCallList (bp->ball_list);
1563   glPopMatrix ();
1564
1565   if (mi->fps_p) do_fps (mi);
1566   glFinish();
1567
1568   glXSwapBuffers(dpy, window);
1569 }
1570
1571 XSCREENSAVER_MODULE ("Lavalite", lavalite)
1572
1573 #endif /* USE_GL */