1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002-2014 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);
1278 bps = (lavalite_configuration *)
1279 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1281 fprintf(stderr, "%s: out of memory\n", progname);
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 /* 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);
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);
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;
1400 bp->max_bottle_radius = max_bottle_radius (bp);
1402 bp->launch_chance = speed;
1403 bp->blobs_per_group = BLOBS_PER_GROUP;
1404 bp->just_started_p = True;
1406 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1408 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1410 bp->bottle_list = glGenLists (1);
1411 bp->ball_list = glGenLists (1);
1413 generate_bottle (mi);
1414 generate_static_blobs (mi);
1422 draw_lavalite (ModeInfo *mi)
1424 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1425 Display *dpy = MI_DISPLAY(mi);
1426 Window window = MI_WINDOW(mi);
1428 if (!bp->glx_context)
1431 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1433 glMatrixMode (GL_MODELVIEW);
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. */
1441 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1442 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1444 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1445 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1452 #else /* #### this crud doesn't really work yet */
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...
1459 Good observation points form a ring around the lamp: basically, a
1460 torus ringing the lamp, parallel to the lamp's floor.
1462 We interpret cz as distance from the origin.
1464 cx is then used as position in the torus (theta).
1468 double cx2, cy2, cz2;
1475 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1476 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1478 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1479 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1489 glRotatef(current_device_rotation(), 0, 0, 1);
1491 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1497 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
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);
1506 /* Position the lamp in the scene according to the "wander" settings */
1507 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
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);
1514 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
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;
1528 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1529 glCallList (bp->bottle_list);
1530 glCallList (bp->ball_list);
1533 if (mi->fps_p) do_fps (mi);
1536 glXSwapBuffers(dpy, window);
1539 XSCREENSAVER_MODULE ("Lavalite", lavalite)