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 refresh_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;
304 glViewport (0, 0, (GLint) width, (GLint) height);
306 glMatrixMode(GL_PROJECTION);
308 gluPerspective (30.0, 1/h, 1.0, 100.0);
310 glMatrixMode(GL_MODELVIEW);
312 gluLookAt( 0.0, 0.0, 30.0,
316 glClear(GL_COLOR_BUFFER_BIT);
325 load_texture (ModeInfo *mi, const char *filename)
327 Display *dpy = mi->dpy;
328 Visual *visual = mi->xgwa.visual;
329 Colormap cmap = mi->xgwa.colormap;
335 !strcasecmp (filename, "(none)"))
337 glDisable (GL_TEXTURE_2D);
341 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
342 if (!image) return False;
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);
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);
360 glEnable (GL_TEXTURE_2D);
366 /* Generating the lamp's bottle, caps, and base.
370 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
374 GLfloat step = M_PI * 2 / faces;
378 glFrontFace (up_p ? GL_CW : GL_CCW);
379 glNormal3f (0, (up_p ? 1 : -1), 0);
380 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
385 for (j = 0, th = 0; j <= faces; j++)
387 glTexCoord2f (-j / (GLfloat) faces, 1);
388 glVertex3f (0, z, 0);
390 glTexCoord2f (-j / (GLfloat) faces, 0);
391 glVertex3f (x, z, y);
397 glTexCoord2f (-j / (GLfloat) faces, 0);
398 glVertex3f (x, z, y);
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)
416 GLfloat x, y, x0=0, y0=0;
417 GLfloat step = M_PI * 2 / faces;
421 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
422 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
434 if (smooth_p) faces++;
436 for (i = 0; i < faces; i++)
438 int nsign = (inside_out_p ? -1 : 1);
441 glNormal3f (x * nsign, z1, y * nsign);
443 glNormal3f (x0 * nsign, z1, y0 * nsign);
445 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
446 glVertex3f (x * r1, z1, y * r1);
448 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
449 glVertex3f (x * r0, z0, y * r0);
460 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
461 glVertex3f (x * r0, z0, y * r0);
463 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
464 glVertex3f (x * r1, z1, y * r1);
476 draw_table (GLfloat z, Bool wire)
479 GLfloat step = M_PI * 2 / faces;
485 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
489 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
493 glTexCoord2f (-0.5, 0.5);
497 for (j = 0, th = 0; j <= faces; j++)
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);
512 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
514 static const int coords[2][8][2] = {
535 int maxx = coords[0][countof(coords[0])-1][0];
536 int maxy = coords[0][countof(coords[0])-1][1];
539 for (x = 1; x < countof(coords[0]); x++)
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;
554 glNormal3f (0, 0, -1);
555 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
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);
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);
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);
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);
609 generate_bottle (ModeInfo *mi)
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;
616 const lamp_geometry *top_slice = bp->model;
617 const char *current_texture = 0;
618 lamp_part last_part = 0;
620 if (faces < 3) faces = 3;
621 else if (wire && faces > 20) faces = 20;
622 else if (faces > 60) faces = 60;
624 bp->bottle_poly_count = 0;
626 glNewList (bp->bottle_list, GL_COMPILE);
629 glRotatef (90, 1, 0, 0);
630 glTranslatef (0, -0.5, 0);
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);
638 const lamp_geometry *bot_slice = top_slice + 1;
640 const char *texture = 0;
644 glDisable (GL_LIGHT2);
646 switch (top_slice->part)
656 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
663 if (!wire && texture && texture != current_texture)
665 current_texture = texture;
666 load_texture (mi, current_texture);
669 /* Color the discs darker than the tube walls. */
670 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
672 /* Do a top disc if this is the first slice of the CAP or BASE.
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,
680 /* Do a bottom disc if this is the last slice of the CAP or BASE.
682 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
683 (top_slice->part == BASE && bot_slice->part == 0))
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);
691 if (bot_slice->part == 0) /* done! */
696 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
698 t0 = top_slice->texture_elevation;
699 t1 = bot_slice->texture_elevation;
701 /* Restart the texture coordinates for the glass.
703 if (top_slice->part == BOTTLE)
705 Bool first_p = (top_slice[-1].part != BOTTLE);
706 Bool last_p = (bot_slice->part != BOTTLE);
711 bp->bottle_poly_count +=
712 draw_tube (top_slice->radius, bot_slice->radius,
713 top_slice->elevation, bot_slice->elevation,
716 (top_slice->part == BOTTLE),
719 last_part = top_slice->part;
723 if (bp->style == ROCKET)
726 for (i = 0; i < 3; i++)
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);
734 glTranslatef (0, -0.1, 0); /* move floor down a little */
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);
744 glDisable (GL_TEXTURE_2D); /* done with textured objects */
751 /* Generating blobbies
755 bellrand (double extent) /* like frand(), but a bell curve. */
757 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
762 static void move_ball (ModeInfo *mi, metaball *b);
764 /* Bring a ball into play, and re-randomize its values.
767 reset_ball (ModeInfo *mi, metaball *b)
769 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
772 b->R = 0.12 + bellrand(0.10);
774 b->pos_r = bellrand (0.9);
775 b->pos_th = frand(M_PI*2);
778 b->dr = bellrand(TILT);
790 /* returns the first metaball that is not in use, or 0.
793 get_ball (ModeInfo *mi)
795 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
797 for (i = 0; i < bp->nballs; i++)
799 metaball *b = &bp->balls[i];
807 /* Generate the blobs that don't move: the ones at teh top and bottom
808 that are part of the scenery.
811 generate_static_blobs (ModeInfo *mi)
813 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
824 /* the giant blob at the bottom of the bottle.
834 /* the small blob at the top of the bottle.
844 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
846 for (i = 0; i < bp->blobs_per_group; i++)
860 max_bottle_radius (lavalite_configuration *bp)
863 const lamp_geometry *slice;
864 for (slice = bp->model; slice->part != 0; slice++)
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 */
876 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
878 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
879 const lamp_geometry *slice;
882 for (slice = bp->model; slice->part != 0; slice++)
883 if (z > slice->elevation)
886 topz = slice->elevation;
887 topr = slice->radius;
890 if (topz == -999) return 0;
892 for (; slice->part != 0; slice++)
893 if (z > slice->elevation)
895 botz = slice->elevation;
896 botr = slice->radius;
899 if (botz == -999) return 0;
901 ratio = (z - botz) / (topz - botz);
903 return (botr + ((topr - botr) * ratio));
908 move_ball (ModeInfo *mi, metaball *b)
910 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
911 double gravity = GRAVITY;
914 if (b->static_p) return;
926 else if (b->pos_r < 0)
928 b->pos_r = -b->pos_r;
932 real_r = b->pos_r * bottle_radius_at (bp, b->z);
934 b->x = cos (b->pos_th) * real_r;
935 b->y = sin (b->pos_th) * real_r;
937 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
942 /* This function makes sure that balls that are part of a group always stay
943 relatively close to each other.
946 clamp_balls (ModeInfo *mi)
948 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
950 for (i = 0; i < bp->nballs; i++)
952 metaball *b = &bp->balls[i];
953 if (b->alive_p && b->leader)
956 double minz = b->leader->z - zslack;
957 double maxz = b->leader->z + zslack;
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.)
964 We aren't currently clamping r, only z -- doesn't seem to
967 This is kind of flaky, I think. Sometimes you can see
968 the blobbies "twitch". That's no good.
973 if (b->dz < 0) b->dz = -b->dz;
979 if (b->dz > 0) b->dz = -b->dz;
988 move_balls (ModeInfo *mi) /* for great justice */
990 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
992 for (i = 0; i < bp->nballs; i++)
994 metaball *b = &bp->balls[i];
1004 /* Rendering blobbies using marching cubes.
1008 compute_metaball_influence (lavalite_configuration *bp,
1009 double x, double y, double z,
1010 int nballs, metaball *balls)
1015 for (i = 0; i < nballs; i++)
1017 metaball *b = &balls[i];
1019 double d2, r, R, r2, R2;
1021 if (!b->alive_p) continue;
1028 if (dx > R || dx < -R || /* quick check before multiplying */
1029 dy > R || dy < -R ||
1033 d2 = (dx*dx + dy*dy + dz*dz);
1039 if (d2 <= r2) /* (d <= r) inside the hard radius */
1041 else if (d2 > R2) /* (d > R) outside the radius of influence */
1043 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1045 /* was: vv += 1 - ((d-r) / (R-r)); */
1046 vv += 1 - ((d2-r2) / (R2-r2));
1054 /* callback for marching_cubes() */
1056 obj_init (double grid_size, void *closure)
1058 lavalite_configuration *bp = (lavalite_configuration *) closure;
1059 bp->grid_size = grid_size;
1065 /* Returns True if the given point is outside of the glass tube.
1068 clipped_by_glass_p (double x, double y, double z,
1069 lavalite_configuration *bp)
1071 double d2, or, or2, ir2;
1073 or = bp->max_bottle_radius;
1075 if (x > or || x < -or || /* quick check before multiplying */
1080 or = bottle_radius_at (bp, z);
1084 if (d2 > or2) /* (sqrt(d) > or) */
1089 if (d2 > ir2) /* (sqrt(d) > ir) */
1093 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1094 return (1 - (d2-dr2) / (dr1-dr2));
1102 /* callback for marching_cubes() */
1104 obj_compute (double x, double y, double z, void *closure)
1106 lavalite_configuration *bp = (lavalite_configuration *) closure;
1109 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1113 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1116 clip = clipped_by_glass_p (x, y, z, bp);
1117 if (clip == 0) return 0;
1120 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1124 /* callback for marching_cubes() */
1126 obj_free (void *closure)
1131 /* Send a new blob travelling upward.
1132 This blob will actually be composed of N metaballs that are near
1136 launch_balls (ModeInfo *mi)
1138 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1139 metaball *b0 = get_ball (mi);
1143 reset_ball (mi, b0);
1145 for (i = 0; i < bp->blobs_per_group; i++)
1147 metaball *b1 = get_ball (mi);
1151 reset_ball (mi, b1);
1154 # define FROB(FIELD,AMT) \
1155 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1157 /* FROB (pos_r, 0.7); */
1158 /* FROB (pos_th, 0.7); */
1168 animate_lava (ModeInfo *mi)
1170 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1171 int wire = MI_IS_WIREFRAME(mi);
1172 Bool just_started_p = bp->just_started_p;
1174 double isolevel = 0.3;
1176 /* Maybe bubble a new blobby to the surface.
1178 if (just_started_p ||
1179 frand(1.0) < bp->launch_chance)
1181 bp->just_started_p = False;
1184 if (do_impatient && just_started_p)
1189 for (i = 0; i < bp->nballs; i++)
1191 metaball *b = &bp->balls[i];
1192 if (b->alive_p && !b->static_p && !b->leader &&
1202 glNewList (bp->ball_list, GL_COMPILE);
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);
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.
1212 glTranslatef (0, 0, -0.5);
1214 mi->polygon_count = 0;
1217 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1218 s = 1.0/bp->grid_size;
1221 glTranslatef (-0.5, -0.5, 0);
1223 marching_cubes (resolution, isolevel, wire, do_smooth,
1224 obj_init, obj_compute, obj_free, bp,
1225 &mi->polygon_count);
1229 mi->polygon_count += bp->bottle_poly_count;
1237 /* Startup initialization
1241 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1243 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1245 if (gltrackball_event_handler (event, bp->trackball,
1246 MI_WIDTH (mi), MI_HEIGHT (mi),
1247 &bp->button_down_p))
1255 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1258 a[3] = 1.0; /* alpha */
1260 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1262 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1265 a[0] = c.red / 65536.0;
1266 a[1] = c.green / 65536.0;
1267 a[2] = c.blue / 65536.0;
1272 init_lavalite (ModeInfo *mi)
1274 lavalite_configuration *bp;
1275 int wire = MI_IS_WIREFRAME(mi);
1277 MI_INIT (mi, bps, NULL);
1279 bp = &bps[MI_SCREEN(mi)];
1281 bp->glx_context = init_GL(mi);
1283 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1287 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1288 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1289 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1290 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1291 else if (!strcasecmp (s, "random"))
1293 if (random() & 1) bp->style = CLASSIC; /* half the time */
1294 else bp->style = (random() % ((int) ROCKET+1));
1299 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1305 parse_color (mi, "lava", lava_color_str, lava_color);
1306 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1307 parse_color (mi, "base", base_color_str, base_color);
1308 parse_color (mi, "table", table_color_str, table_color);
1312 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1313 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1314 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1315 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1317 glEnable(GL_LIGHTING);
1318 glEnable(GL_LIGHT0);
1319 glEnable(GL_LIGHT1);
1320 glEnable(GL_DEPTH_TEST);
1321 glEnable(GL_CULL_FACE);
1322 glEnable(GL_NORMALIZE);
1323 glShadeModel(GL_SMOOTH);
1325 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1326 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1327 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1329 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1330 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1331 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1333 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1334 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1335 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1339 Bool spinx=False, spiny=False, spinz=False;
1340 double spin_speed = 0.4;
1341 double wander_speed = 0.03;
1346 if (*s == 'x' || *s == 'X') spinx = True;
1347 else if (*s == 'y' || *s == 'Y') spiny = True;
1348 else if (*s == 'z' || *s == 'Z') spinz = True;
1349 else if (*s == '0') ;
1353 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1360 bp->rot = make_rotator (spinx ? spin_speed : 0,
1361 spiny ? spin_speed : 0,
1362 spinz ? spin_speed : 0,
1364 do_wander ? wander_speed : 0,
1366 bp->rot2 = make_rotator (spin_speed, 0, 0,
1369 bp->trackball = gltrackball_init (False);
1371 /* Tilt the scene a bit: lean the normal lamps toward the viewer,
1372 and the huge lamps away. */
1373 gltrackball_reset (bp->trackball,
1375 (bp->style == ROCKET || bp->style == GIANT
1382 case CLASSIC: bp->model = classic_lamp; break;
1383 case GIANT: bp->model = giant_lamp; break;
1384 case CONE: bp->model = cone_lamp; break;
1385 case ROCKET: bp->model = rocket_lamp; break;
1386 default: abort(); break;
1389 bp->max_bottle_radius = max_bottle_radius (bp);
1391 bp->launch_chance = speed;
1392 bp->blobs_per_group = BLOBS_PER_GROUP;
1393 bp->just_started_p = True;
1395 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1397 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1399 bp->bottle_list = glGenLists (1);
1400 bp->ball_list = glGenLists (1);
1402 generate_bottle (mi);
1403 generate_static_blobs (mi);
1411 draw_lavalite (ModeInfo *mi)
1413 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1414 Display *dpy = MI_DISPLAY(mi);
1415 Window window = MI_WINDOW(mi);
1417 if (!bp->glx_context)
1420 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1422 glMatrixMode (GL_MODELVIEW);
1426 double cx, cy, cz; /* camera position, 0-1. */
1427 double px, py, pz; /* object position, 0-1. */
1428 double rx, ry, rz; /* object rotation, 0-1. */
1430 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1431 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1433 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1434 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1441 #else /* #### this crud doesn't really work yet */
1444 /* We have c[xyz] parameters describing a camera position, but we don't
1445 want to just map those to points in space: the lamp doesn't look very
1446 good from the inside, or from underneath...
1448 Good observation points form a ring around the lamp: basically, a
1449 torus ringing the lamp, parallel to the lamp's floor.
1451 We interpret cz as distance from the origin.
1453 cx is then used as position in the torus (theta).
1457 double cx2, cy2, cz2;
1464 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1465 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1467 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1468 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1478 glRotatef(current_device_rotation(), 0, 0, 1);
1480 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1486 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1488 glRotatef (-90, 1, 0, 0); /* Right side up */
1491 /* Place the lights relative to the object, before the object has
1492 been rotated or wandered within the scene. */
1493 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1494 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1495 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1498 /* Position the lamp in the scene according to the "wander" settings */
1499 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1501 /* Rotate the object according to the "spin" settings */
1502 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1503 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1504 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1506 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1509 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1510 case GIANT: glTranslatef (0, 0, 0.33); break;
1511 case CONE: glTranslatef (0, 0, 0.16); break;
1512 case ROCKET: glTranslatef (0, 0, 0.30);
1513 glScalef (0.85,0.85,0.85); break;
1514 default: abort(); break;
1520 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1521 glCallList (bp->bottle_list);
1522 glCallList (bp->ball_list);
1525 if (mi->fps_p) do_fps (mi);
1528 glXSwapBuffers(dpy, window);
1531 XSCREENSAVER_MODULE ("Lavalite", lavalite)