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