1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002-2017 Jamie Zawinski <jwz@jwz.org>
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
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.
19 * Official Lava Lite web site: http://www.lavaworld.com/
21 * Implementation details:
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
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.
31 * We then polygonize the surface of the lava using the marching squares
32 * algorithm implemented in marching.c.
34 * Like real lavalites, this program is very slow.
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.
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.
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.
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?
67 #define DEFAULTS "*delay: 30000 \n" \
68 "*showFPS: False \n" \
69 "*wireframe: False \n" \
70 "*geometry: 600x900\n" \
71 "*count: " DEF_COUNT " \n" \
73 # define free_lavalite 0
74 # define release_lavalite 0
77 #define BLOBS_PER_GROUP 4
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) */
84 #define countof(x) (sizeof((x))/sizeof((*x)))
87 #define ABS(n) ((n)<0?-(n):(n))
89 #define SIGNOF(n) ((n)<0?-1:1)
91 #include "xlockmore.h"
94 #include "gltrackball.h"
95 #include "xpm-ximage.h"
98 #ifdef USE_GL /* whole file */
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 */
114 #define DEF_FTEX "(none)"
115 #define DEF_BTEX "(none)"
116 #define DEF_TTEX "(none)"
118 typedef struct metaball metaball;
125 double r; /* hard radius */
126 double R; /* radius of field of influence */
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 */
133 double x, y; /* h planar position - compused from the above */
135 metaball *leader; /* stay close to this other ball */
139 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
140 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
146 GLfloat texture_elevation;
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 },
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 },
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 },
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 },
201 GLXContext *glx_context;
203 const lamp_geometry *model;
206 trackball_state *trackball;
209 GLfloat max_bottle_radius; /* radius of widest part of the bottle */
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 */
215 int grid_size; /* resolution for marching-cubes */
222 int bottle_poly_count; /* polygons in the bottle only */
224 } lavalite_configuration;
226 static lavalite_configuration *bps = NULL;
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;
236 static char *lava_color_str, *fluid_color_str, *base_color_str,
238 static char *fluid_tex, *base_tex, *table_tex;
240 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
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};
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};
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" },
265 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
266 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
267 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
268 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
270 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
271 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
272 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
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},
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},
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},
294 ENTRYPOINT ModeSpecOpt lavalite_opts = {countof(opts), opts, countof(vars), vars, NULL};
297 /* Window management, etc
300 reshape_lavalite (ModeInfo *mi, int width, int height)
302 GLfloat h = (GLfloat) height / (GLfloat) width;
305 if (width > height * 5) { /* tiny window: show middle */
308 h = height / (GLfloat) width;
311 glViewport (0, y, (GLint) width, (GLint) height);
313 glMatrixMode(GL_PROJECTION);
315 gluPerspective (30.0, 1/h, 1.0, 100.0);
317 glMatrixMode(GL_MODELVIEW);
319 gluLookAt( 0.0, 0.0, 30.0,
323 glClear(GL_COLOR_BUFFER_BIT);
332 load_texture (ModeInfo *mi, const char *filename)
334 Display *dpy = mi->dpy;
335 Visual *visual = mi->xgwa.visual;
336 Colormap cmap = mi->xgwa.colormap;
342 !strcasecmp (filename, "(none)"))
344 glDisable (GL_TEXTURE_2D);
348 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
349 if (!image) return False;
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);
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);
367 glEnable (GL_TEXTURE_2D);
373 /* Generating the lamp's bottle, caps, and base.
377 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
381 GLfloat step = M_PI * 2 / faces;
385 glFrontFace (up_p ? GL_CW : GL_CCW);
386 glNormal3f (0, (up_p ? 1 : -1), 0);
387 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
392 for (j = 0, th = 0; j <= faces; j++)
394 glTexCoord2f (-j / (GLfloat) faces, 1);
395 glVertex3f (0, z, 0);
397 glTexCoord2f (-j / (GLfloat) faces, 0);
398 glVertex3f (x, z, y);
404 glTexCoord2f (-j / (GLfloat) faces, 0);
405 glVertex3f (x, z, y);
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)
423 GLfloat x, y, x0=0, y0=0;
424 GLfloat step = M_PI * 2 / faces;
428 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
429 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
441 if (smooth_p) faces++;
443 for (i = 0; i < faces; i++)
445 int nsign = (inside_out_p ? -1 : 1);
448 glNormal3f (x * nsign, z1, y * nsign);
450 glNormal3f (x0 * nsign, z1, y0 * nsign);
452 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
453 glVertex3f (x * r1, z1, y * r1);
455 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
456 glVertex3f (x * r0, z0, y * r0);
467 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
468 glVertex3f (x * r0, z0, y * r0);
470 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
471 glVertex3f (x * r1, z1, y * r1);
483 draw_table (GLfloat z, Bool wire)
486 GLfloat step = M_PI * 2 / faces;
492 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
496 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
500 glTexCoord2f (-0.5, 0.5);
504 for (j = 0, th = 0; j <= faces; j++)
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);
519 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
521 static const int coords[2][8][2] = {
542 int maxx = coords[0][countof(coords[0])-1][0];
543 int maxy = coords[0][countof(coords[0])-1][1];
546 for (x = 1; x < countof(coords[0]); x++)
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;
561 glNormal3f (0, 0, -1);
562 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
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);
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);
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);
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);
616 generate_bottle (ModeInfo *mi)
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;
623 const lamp_geometry *top_slice = bp->model;
624 const char *current_texture = 0;
625 lamp_part last_part = 0;
627 if (faces < 3) faces = 3;
628 else if (wire && faces > 20) faces = 20;
629 else if (faces > 60) faces = 60;
631 bp->bottle_poly_count = 0;
633 glNewList (bp->bottle_list, GL_COMPILE);
636 glRotatef (90, 1, 0, 0);
637 glTranslatef (0, -0.5, 0);
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);
645 const lamp_geometry *bot_slice = top_slice + 1;
647 const char *texture = 0;
651 glDisable (GL_LIGHT2);
653 switch (top_slice->part)
663 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
670 if (!wire && texture && texture != current_texture)
672 current_texture = texture;
673 load_texture (mi, current_texture);
676 /* Color the discs darker than the tube walls. */
677 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
679 /* Do a top disc if this is the first slice of the CAP or BASE.
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,
687 /* Do a bottom disc if this is the last slice of the CAP or BASE.
689 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
690 (top_slice->part == BASE && bot_slice->part == 0))
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);
698 if (bot_slice->part == 0) /* done! */
703 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
705 t0 = top_slice->texture_elevation;
706 t1 = bot_slice->texture_elevation;
708 /* Restart the texture coordinates for the glass.
710 if (top_slice->part == BOTTLE)
712 Bool first_p = (top_slice[-1].part != BOTTLE);
713 Bool last_p = (bot_slice->part != BOTTLE);
718 bp->bottle_poly_count +=
719 draw_tube (top_slice->radius, bot_slice->radius,
720 top_slice->elevation, bot_slice->elevation,
723 (top_slice->part == BOTTLE),
726 last_part = top_slice->part;
730 if (bp->style == ROCKET)
733 for (i = 0; i < 3; i++)
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);
741 glTranslatef (0, -0.1, 0); /* move floor down a little */
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);
751 glDisable (GL_TEXTURE_2D); /* done with textured objects */
758 /* Generating blobbies
762 bellrand (double extent) /* like frand(), but a bell curve. */
764 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
769 static void move_ball (ModeInfo *mi, metaball *b);
771 /* Bring a ball into play, and re-randomize its values.
774 reset_ball (ModeInfo *mi, metaball *b)
776 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
779 b->R = 0.12 + bellrand(0.10);
781 b->pos_r = bellrand (0.9);
782 b->pos_th = frand(M_PI*2);
785 b->dr = bellrand(TILT);
797 /* returns the first metaball that is not in use, or 0.
800 get_ball (ModeInfo *mi)
802 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
804 for (i = 0; i < bp->nballs; i++)
806 metaball *b = &bp->balls[i];
814 /* Generate the blobs that don't move: the ones at teh top and bottom
815 that are part of the scenery.
818 generate_static_blobs (ModeInfo *mi)
820 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
831 /* the giant blob at the bottom of the bottle.
841 /* the small blob at the top of the bottle.
851 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
853 for (i = 0; i < bp->blobs_per_group; i++)
867 max_bottle_radius (lavalite_configuration *bp)
870 const lamp_geometry *slice;
871 for (slice = bp->model; slice->part != 0; slice++)
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 */
883 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
885 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
886 const lamp_geometry *slice;
889 for (slice = bp->model; slice->part != 0; slice++)
890 if (z > slice->elevation)
893 topz = slice->elevation;
894 topr = slice->radius;
897 if (topz == -999) return 0;
899 for (; slice->part != 0; slice++)
900 if (z > slice->elevation)
902 botz = slice->elevation;
903 botr = slice->radius;
906 if (botz == -999) return 0;
908 ratio = (z - botz) / (topz - botz);
910 return (botr + ((topr - botr) * ratio));
915 move_ball (ModeInfo *mi, metaball *b)
917 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
918 double gravity = GRAVITY;
921 if (b->static_p) return;
933 else if (b->pos_r < 0)
935 b->pos_r = -b->pos_r;
939 real_r = b->pos_r * bottle_radius_at (bp, b->z);
941 b->x = cos (b->pos_th) * real_r;
942 b->y = sin (b->pos_th) * real_r;
944 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
949 /* This function makes sure that balls that are part of a group always stay
950 relatively close to each other.
953 clamp_balls (ModeInfo *mi)
955 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
957 for (i = 0; i < bp->nballs; i++)
959 metaball *b = &bp->balls[i];
960 if (b->alive_p && b->leader)
963 double minz = b->leader->z - zslack;
964 double maxz = b->leader->z + zslack;
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.)
971 We aren't currently clamping r, only z -- doesn't seem to
974 This is kind of flaky, I think. Sometimes you can see
975 the blobbies "twitch". That's no good.
980 if (b->dz < 0) b->dz = -b->dz;
986 if (b->dz > 0) b->dz = -b->dz;
995 move_balls (ModeInfo *mi) /* for great justice */
997 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
999 for (i = 0; i < bp->nballs; i++)
1001 metaball *b = &bp->balls[i];
1011 /* Rendering blobbies using marching cubes.
1015 compute_metaball_influence (lavalite_configuration *bp,
1016 double x, double y, double z,
1017 int nballs, metaball *balls)
1022 for (i = 0; i < nballs; i++)
1024 metaball *b = &balls[i];
1026 double d2, r, R, r2, R2;
1028 if (!b->alive_p) continue;
1035 if (dx > R || dx < -R || /* quick check before multiplying */
1036 dy > R || dy < -R ||
1040 d2 = (dx*dx + dy*dy + dz*dz);
1046 if (d2 <= r2) /* (d <= r) inside the hard radius */
1048 else if (d2 > R2) /* (d > R) outside the radius of influence */
1050 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1052 /* was: vv += 1 - ((d-r) / (R-r)); */
1053 vv += 1 - ((d2-r2) / (R2-r2));
1061 /* callback for marching_cubes() */
1063 obj_init (double grid_size, void *closure)
1065 lavalite_configuration *bp = (lavalite_configuration *) closure;
1066 bp->grid_size = grid_size;
1072 /* Returns True if the given point is outside of the glass tube.
1075 clipped_by_glass_p (double x, double y, double z,
1076 lavalite_configuration *bp)
1078 double d2, or, or2, ir2;
1080 or = bp->max_bottle_radius;
1082 if (x > or || x < -or || /* quick check before multiplying */
1087 or = bottle_radius_at (bp, z);
1091 if (d2 > or2) /* (sqrt(d) > or) */
1096 if (d2 > ir2) /* (sqrt(d) > ir) */
1100 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1101 return (1 - (d2-dr2) / (dr1-dr2));
1109 /* callback for marching_cubes() */
1111 obj_compute (double x, double y, double z, void *closure)
1113 lavalite_configuration *bp = (lavalite_configuration *) closure;
1116 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1120 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1123 clip = clipped_by_glass_p (x, y, z, bp);
1124 if (clip == 0) return 0;
1127 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1131 /* callback for marching_cubes() */
1133 obj_free (void *closure)
1138 /* Send a new blob travelling upward.
1139 This blob will actually be composed of N metaballs that are near
1143 launch_balls (ModeInfo *mi)
1145 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1146 metaball *b0 = get_ball (mi);
1150 reset_ball (mi, b0);
1152 for (i = 0; i < bp->blobs_per_group; i++)
1154 metaball *b1 = get_ball (mi);
1158 reset_ball (mi, b1);
1161 # define FROB(FIELD,AMT) \
1162 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1164 /* FROB (pos_r, 0.7); */
1165 /* FROB (pos_th, 0.7); */
1175 animate_lava (ModeInfo *mi)
1177 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1178 int wire = MI_IS_WIREFRAME(mi);
1179 Bool just_started_p = bp->just_started_p;
1181 double isolevel = 0.3;
1183 /* Maybe bubble a new blobby to the surface.
1185 if (just_started_p ||
1186 frand(1.0) < bp->launch_chance)
1188 bp->just_started_p = False;
1191 if (do_impatient && just_started_p)
1196 for (i = 0; i < bp->nballs; i++)
1198 metaball *b = &bp->balls[i];
1199 if (b->alive_p && !b->static_p && !b->leader &&
1209 glNewList (bp->ball_list, GL_COMPILE);
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);
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.
1219 glTranslatef (0, 0, -0.5);
1221 mi->polygon_count = 0;
1224 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1225 s = 1.0/bp->grid_size;
1228 glTranslatef (-0.5, -0.5, 0);
1230 marching_cubes (resolution, isolevel, wire, do_smooth,
1231 obj_init, obj_compute, obj_free, bp,
1232 &mi->polygon_count);
1236 mi->polygon_count += bp->bottle_poly_count;
1244 /* Startup initialization
1248 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1250 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1252 if (gltrackball_event_handler (event, bp->trackball,
1253 MI_WIDTH (mi), MI_HEIGHT (mi),
1254 &bp->button_down_p))
1262 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1265 a[3] = 1.0; /* alpha */
1267 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1269 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1272 a[0] = c.red / 65536.0;
1273 a[1] = c.green / 65536.0;
1274 a[2] = c.blue / 65536.0;
1279 init_lavalite (ModeInfo *mi)
1281 lavalite_configuration *bp;
1282 int wire = MI_IS_WIREFRAME(mi);
1286 bp = &bps[MI_SCREEN(mi)];
1288 bp->glx_context = init_GL(mi);
1290 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
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"))
1300 if (random() & 1) bp->style = CLASSIC; /* half the time */
1301 else bp->style = (random() % ((int) ROCKET+1));
1306 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
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);
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};
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);
1332 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1333 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1334 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1336 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1337 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1338 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1340 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1341 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1342 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1346 Bool spinx=False, spiny=False, spinz=False;
1347 double spin_speed = 0.4;
1348 double wander_speed = 0.03;
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') ;
1360 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1367 bp->rot = make_rotator (spinx ? spin_speed : 0,
1368 spiny ? spin_speed : 0,
1369 spinz ? spin_speed : 0,
1371 do_wander ? wander_speed : 0,
1373 bp->rot2 = make_rotator (spin_speed, 0, 0,
1376 bp->trackball = gltrackball_init (False);
1378 /* Tilt the scene a bit: lean the normal lamps toward the viewer,
1379 and the huge lamps away. */
1380 gltrackball_reset (bp->trackball,
1382 (bp->style == ROCKET || bp->style == GIANT
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;
1396 bp->max_bottle_radius = max_bottle_radius (bp);
1398 bp->launch_chance = speed;
1399 bp->blobs_per_group = BLOBS_PER_GROUP;
1400 bp->just_started_p = True;
1402 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1404 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1406 bp->bottle_list = glGenLists (1);
1407 bp->ball_list = glGenLists (1);
1409 generate_bottle (mi);
1410 generate_static_blobs (mi);
1418 draw_lavalite (ModeInfo *mi)
1420 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1421 Display *dpy = MI_DISPLAY(mi);
1422 Window window = MI_WINDOW(mi);
1424 if (!bp->glx_context)
1427 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1429 glMatrixMode (GL_MODELVIEW);
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. */
1437 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1438 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1440 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1441 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1448 #else /* #### this crud doesn't really work yet */
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...
1455 Good observation points form a ring around the lamp: basically, a
1456 torus ringing the lamp, parallel to the lamp's floor.
1458 We interpret cz as distance from the origin.
1460 cx is then used as position in the torus (theta).
1464 double cx2, cy2, cz2;
1471 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1472 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1474 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1475 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1485 glRotatef(current_device_rotation(), 0, 0, 1);
1487 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1493 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1495 glRotatef (-90, 1, 0, 0); /* Right side up */
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);
1505 /* Position the lamp in the scene according to the "wander" settings */
1506 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
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);
1513 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
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;
1527 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1528 glCallList (bp->bottle_list);
1529 glCallList (bp->ball_list);
1532 if (mi->fps_p) do_fps (mi);
1535 glXSwapBuffers(dpy, window);
1538 XSCREENSAVER_MODULE ("Lavalite", lavalite)