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