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