1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002-2006 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);
344 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
345 image->width, image->height, 0,
346 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
347 sprintf (buf, "texture: %.100s (%dx%d)",
348 filename, image->width, image->height);
351 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
352 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
353 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
354 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
355 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
356 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
357 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
359 glEnable (GL_TEXTURE_2D);
365 /* Generating the lamp's bottle, caps, and base.
369 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
373 GLfloat step = M_PI * 2 / faces;
377 glFrontFace (up_p ? GL_CW : GL_CCW);
378 glNormal3f (0, (up_p ? 1 : -1), 0);
379 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
384 for (j = 0, th = 0; j <= faces; j++)
386 glTexCoord2f (-j / (GLfloat) faces, 1);
387 glVertex3f (0, z, 0);
389 glTexCoord2f (-j / (GLfloat) faces, 0);
390 glVertex3f (x, z, y);
396 glTexCoord2f (-j / (GLfloat) faces, 0);
397 glVertex3f (x, z, y);
408 draw_tube (GLfloat r0, GLfloat r1,
409 GLfloat z0, GLfloat z1,
410 GLfloat t0, GLfloat t1,
411 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
415 GLfloat x, y, x0=0, y0=0;
416 GLfloat step = M_PI * 2 / faces;
420 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
421 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
433 if (smooth_p) faces++;
435 for (i = 0; i < faces; i++)
437 int nsign = (inside_out_p ? -1 : 1);
440 glNormal3f (x * nsign, z1, y * nsign);
442 glNormal3f (x0 * nsign, z1, y0 * nsign);
444 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
445 glVertex3f (x * r1, z1, y * r1);
447 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
448 glVertex3f (x * r0, z0, y * r0);
459 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
460 glVertex3f (x * r0, z0, y * r0);
462 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
463 glVertex3f (x * r1, z1, y * r1);
475 draw_table (GLfloat z, Bool wire)
478 GLfloat step = M_PI * 2 / faces;
484 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
488 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
492 glTexCoord2f (-0.5, 0.5);
496 for (j = 0, th = 0; j <= faces; j++)
498 GLfloat x = cos (th);
499 GLfloat y = sin (th);
500 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
501 glVertex3f(x*s, z, y*s);
511 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
513 static const int coords[2][8][2] = {
534 int maxx = coords[0][countof(coords[0])-1][0];
535 int maxy = coords[0][countof(coords[0])-1][1];
538 for (x = 1; x < countof(coords[0]); x++)
540 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
541 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
542 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
543 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
544 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
545 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
546 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
547 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
553 glNormal3f (0, 0, -1);
554 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
556 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
557 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
558 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
559 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
565 glFrontFace (GL_CCW);
566 glNormal3f (0, 0, -1);
567 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
568 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
569 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
570 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
571 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
577 glFrontFace (GL_CCW);
578 glNormal3f (1, -1, 0); /* #### wrong */
579 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
580 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
581 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
582 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
583 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
590 glNormal3f (-1, 1, 0); /* #### wrong */
591 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
592 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
593 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
594 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
595 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
608 generate_bottle (ModeInfo *mi)
610 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
611 int wire = MI_IS_WIREFRAME(mi);
612 int faces = resolution * 1.5;
613 Bool smooth = do_smooth;
614 Bool have_texture = False;
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 have_texture = False;
664 if (!wire && texture && texture != current_texture)
666 current_texture = texture;
667 have_texture = load_texture (mi, current_texture);
670 /* Color the discs darker than the tube walls. */
671 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
673 /* Do a top disc if this is the first slice of the CAP or BASE.
675 if ((top_slice->part == CAP && last_part == 0) ||
676 (top_slice->part == BASE && last_part == BOTTLE))
677 bp->bottle_poly_count +=
678 draw_disc (top_slice->radius, top_slice->elevation, faces,
681 /* Do a bottom disc if this is the last slice of the CAP or BASE.
683 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
684 (top_slice->part == BASE && bot_slice->part == 0))
686 const lamp_geometry *sl = (bot_slice->part == 0
687 ? top_slice : bot_slice);
688 bp->bottle_poly_count +=
689 draw_disc (sl->radius, sl->elevation, faces, False, wire);
692 if (bot_slice->part == 0) /* done! */
697 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
699 t0 = top_slice->texture_elevation;
700 t1 = bot_slice->texture_elevation;
702 /* Restart the texture coordinates for the glass.
704 if (top_slice->part == BOTTLE)
706 Bool first_p = (top_slice[-1].part != BOTTLE);
707 Bool last_p = (bot_slice->part != BOTTLE);
712 bp->bottle_poly_count +=
713 draw_tube (top_slice->radius, bot_slice->radius,
714 top_slice->elevation, bot_slice->elevation,
717 (top_slice->part == BOTTLE),
720 last_part = top_slice->part;
724 if (bp->style == ROCKET)
727 for (i = 0; i < 3; i++)
730 glRotatef (120 * i, 0, 1, 0);
731 glTranslatef (0.14, -0.05, 0);
732 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
735 glTranslatef (0, -0.1, 0); /* move floor down a little */
739 have_texture = !wire && load_texture (mi, table_tex);
740 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
741 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
745 glDisable (GL_TEXTURE_2D); /* done with textured objects */
752 /* Generating blobbies
756 bellrand (double extent) /* like frand(), but a bell curve. */
758 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
763 static void move_ball (ModeInfo *mi, metaball *b);
765 /* Bring a ball into play, and re-randomize its values.
768 reset_ball (ModeInfo *mi, metaball *b)
770 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
773 b->R = 0.12 + bellrand(0.10);
775 b->pos_r = bellrand (0.9);
776 b->pos_th = frand(M_PI*2);
779 b->dr = bellrand(TILT);
791 /* returns the first metaball that is not in use, or 0.
794 get_ball (ModeInfo *mi)
796 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
798 for (i = 0; i < bp->nballs; i++)
800 metaball *b = &bp->balls[i];
808 /* Generate the blobs that don't move: the ones at teh top and bottom
809 that are part of the scenery.
812 generate_static_blobs (ModeInfo *mi)
814 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
825 /* the giant blob at the bottom of the bottle.
835 /* the small blob at the top of the bottle.
845 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
847 for (i = 0; i < bp->blobs_per_group; i++)
861 max_bottle_radius (lavalite_configuration *bp)
864 const lamp_geometry *slice;
865 for (slice = bp->model; slice->part != 0; slice++)
867 if (slice->part == BOTTLE && slice->radius > r)
868 r = slice->radius; /* top */
869 if (slice[1].radius > r)
870 r = slice[1].radius; /* bottom */
877 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
879 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
880 const lamp_geometry *slice;
883 for (slice = bp->model; slice->part != 0; slice++)
884 if (z > slice->elevation)
887 topz = slice->elevation;
888 topr = slice->radius;
891 if (topz == -999) return 0;
893 for (; slice->part != 0; slice++)
894 if (z > slice->elevation)
896 botz = slice->elevation;
897 botr = slice->radius;
900 if (botz == -999) return 0;
902 ratio = (z - botz) / (topz - botz);
904 return (botr + ((topr - botr) * ratio));
909 move_ball (ModeInfo *mi, metaball *b)
911 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
912 double gravity = GRAVITY;
915 if (b->static_p) return;
927 else if (b->pos_r < 0)
929 b->pos_r = -b->pos_r;
933 real_r = b->pos_r * bottle_radius_at (bp, b->z);
935 b->x = cos (b->pos_th) * real_r;
936 b->y = sin (b->pos_th) * real_r;
938 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
943 /* This function makes sure that balls that are part of a group always stay
944 relatively close to each other.
947 clamp_balls (ModeInfo *mi)
949 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
951 for (i = 0; i < bp->nballs; i++)
953 metaball *b = &bp->balls[i];
954 if (b->alive_p && b->leader)
957 double minz = b->leader->z - zslack;
958 double maxz = b->leader->z + zslack;
960 /* Try to keep the Z values near those of the leader.
961 Don't let it go out of range (above or below) and clamp it
962 if it does. If we've clamped it, make sure dz will be
963 moving it in the right direction (back toward the leader.)
965 We aren't currently clamping r, only z -- doesn't seem to
968 This is kind of flaky, I think. Sometimes you can see
969 the blobbies "twitch". That's no good.
974 if (b->dz < 0) b->dz = -b->dz;
980 if (b->dz > 0) b->dz = -b->dz;
989 move_balls (ModeInfo *mi) /* for great justice */
991 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
993 for (i = 0; i < bp->nballs; i++)
995 metaball *b = &bp->balls[i];
1005 /* Rendering blobbies using marching cubes.
1009 compute_metaball_influence (lavalite_configuration *bp,
1010 double x, double y, double z,
1011 int nballs, metaball *balls)
1016 for (i = 0; i < nballs; i++)
1018 metaball *b = &balls[i];
1020 double d2, r, R, r2, R2;
1022 if (!b->alive_p) continue;
1029 if (dx > R || dx < -R || /* quick check before multiplying */
1030 dy > R || dy < -R ||
1034 d2 = (dx*dx + dy*dy + dz*dz);
1040 if (d2 <= r2) /* (d <= r) inside the hard radius */
1042 else if (d2 > R2) /* (d > R) outside the radius of influence */
1044 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1046 /* was: vv += 1 - ((d-r) / (R-r)); */
1047 vv += 1 - ((d2-r2) / (R2-r2));
1055 /* callback for marching_cubes() */
1057 obj_init (double grid_size, void *closure)
1059 lavalite_configuration *bp = (lavalite_configuration *) closure;
1060 bp->grid_size = grid_size;
1066 /* Returns True if the given point is outside of the glass tube.
1069 clipped_by_glass_p (double x, double y, double z,
1070 lavalite_configuration *bp)
1072 double d2, or, or2, ir2;
1074 or = bp->max_bottle_radius;
1076 if (x > or || x < -or || /* quick check before multiplying */
1081 or = bottle_radius_at (bp, z);
1085 if (d2 > or2) /* (sqrt(d) > or) */
1090 if (d2 > ir2) /* (sqrt(d) > ir) */
1094 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1095 return (1 - (d2-dr2) / (dr1-dr2));
1103 /* callback for marching_cubes() */
1105 obj_compute (double x, double y, double z, void *closure)
1107 lavalite_configuration *bp = (lavalite_configuration *) closure;
1110 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1114 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1117 clip = clipped_by_glass_p (x, y, z, bp);
1118 if (clip == 0) return 0;
1121 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1125 /* callback for marching_cubes() */
1127 obj_free (void *closure)
1132 /* Send a new blob travelling upward.
1133 This blob will actually be composed of N metaballs that are near
1137 launch_balls (ModeInfo *mi)
1139 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1140 metaball *b0 = get_ball (mi);
1144 reset_ball (mi, b0);
1146 for (i = 0; i < bp->blobs_per_group; i++)
1148 metaball *b1 = get_ball (mi);
1152 reset_ball (mi, b1);
1155 # define FROB(FIELD,AMT) \
1156 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1158 /* FROB (pos_r, 0.7); */
1159 /* FROB (pos_th, 0.7); */
1169 animate_lava (ModeInfo *mi)
1171 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1172 int wire = MI_IS_WIREFRAME(mi);
1173 Bool just_started_p = bp->just_started_p;
1175 double isolevel = 0.3;
1177 /* Maybe bubble a new blobby to the surface.
1179 if (just_started_p ||
1180 frand(1.0) < bp->launch_chance)
1182 bp->just_started_p = False;
1185 if (do_impatient && just_started_p)
1190 for (i = 0; i < bp->nballs; i++)
1192 metaball *b = &bp->balls[i];
1193 if (b->alive_p && !b->static_p && !b->leader &&
1203 glNewList (bp->ball_list, GL_COMPILE);
1206 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1207 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1208 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1210 /* For the blobbies, the origin is on the axis at the bottom of the
1211 glass bottle; and the top of the bottle is +1 on Z.
1213 glTranslatef (0, 0, -0.5);
1215 mi->polygon_count = 0;
1218 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1219 s = 1.0/bp->grid_size;
1222 glTranslatef (-0.5, -0.5, 0);
1224 marching_cubes (resolution, isolevel, wire, do_smooth,
1225 obj_init, obj_compute, obj_free, bp,
1226 &mi->polygon_count);
1230 mi->polygon_count += bp->bottle_poly_count;
1238 /* Startup initialization
1242 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1244 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1246 if (event->xany.type == ButtonPress &&
1247 event->xbutton.button == Button1)
1249 bp->button_down_p = True;
1250 gltrackball_start (bp->trackball,
1251 event->xbutton.x, event->xbutton.y,
1252 MI_WIDTH (mi), MI_HEIGHT (mi));
1255 else if (event->xany.type == ButtonRelease &&
1256 event->xbutton.button == Button1)
1258 bp->button_down_p = False;
1261 else if (event->xany.type == ButtonPress &&
1262 (event->xbutton.button == Button4 ||
1263 event->xbutton.button == Button5 ||
1264 event->xbutton.button == Button6 ||
1265 event->xbutton.button == Button7))
1267 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 5,
1268 !!event->xbutton.state);
1271 else if (event->xany.type == MotionNotify &&
1274 gltrackball_track (bp->trackball,
1275 event->xmotion.x, event->xmotion.y,
1276 MI_WIDTH (mi), MI_HEIGHT (mi));
1285 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1288 a[3] = 1.0; /* alpha */
1290 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1292 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1295 a[0] = c.red / 65536.0;
1296 a[1] = c.green / 65536.0;
1297 a[2] = c.blue / 65536.0;
1302 init_lavalite (ModeInfo *mi)
1304 lavalite_configuration *bp;
1305 int wire = MI_IS_WIREFRAME(mi);
1308 bps = (lavalite_configuration *)
1309 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1311 fprintf(stderr, "%s: out of memory\n", progname);
1315 bp = &bps[MI_SCREEN(mi)];
1318 bp = &bps[MI_SCREEN(mi)];
1320 bp->glx_context = init_GL(mi);
1322 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1326 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1327 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1328 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1329 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1330 else if (!strcasecmp (s, "random"))
1332 if (random() & 1) bp->style = CLASSIC; /* half the time */
1333 else bp->style = (random() % ((int) ROCKET+1));
1338 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1344 parse_color (mi, "lava", lava_color_str, lava_color);
1345 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1346 parse_color (mi, "base", base_color_str, base_color);
1347 parse_color (mi, "table", table_color_str, table_color);
1351 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1352 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1353 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1354 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1356 glEnable(GL_LIGHTING);
1357 glEnable(GL_LIGHT0);
1358 glEnable(GL_LIGHT1);
1359 glEnable(GL_DEPTH_TEST);
1360 glEnable(GL_CULL_FACE);
1361 glEnable(GL_NORMALIZE);
1362 glShadeModel(GL_SMOOTH);
1364 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1365 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1366 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1368 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1369 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1370 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1372 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1373 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1374 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1378 Bool spinx=False, spiny=False, spinz=False;
1379 double spin_speed = 0.4;
1380 double wander_speed = 0.03;
1385 if (*s == 'x' || *s == 'X') spinx = True;
1386 else if (*s == 'y' || *s == 'Y') spiny = True;
1387 else if (*s == 'z' || *s == 'Z') spinz = True;
1388 else if (*s == '0') ;
1392 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1399 bp->rot = make_rotator (spinx ? spin_speed : 0,
1400 spiny ? spin_speed : 0,
1401 spinz ? spin_speed : 0,
1403 do_wander ? wander_speed : 0,
1405 bp->rot2 = make_rotator (spin_speed, 0, 0,
1408 bp->trackball = gltrackball_init ();
1410 /* move initial camera position up by around 15 degrees:
1411 in other words, tilt the scene toward the viewer. */
1412 gltrackball_start (bp->trackball, 50, 50, 100, 100);
1413 gltrackball_track (bp->trackball, 50, 5, 100, 100);
1415 /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1416 look like we're looking up at it instead of down at it! */
1417 if (bp->style == GIANT)
1418 gltrackball_track (bp->trackball, 50, -12, 100, 100);
1419 else if (bp->style == ROCKET) /* same for rocket, but not as much */
1420 gltrackball_track (bp->trackball, 50, -4, 100, 100);
1425 case CLASSIC: bp->model = classic_lamp; break;
1426 case GIANT: bp->model = giant_lamp; break;
1427 case CONE: bp->model = cone_lamp; break;
1428 case ROCKET: bp->model = rocket_lamp; break;
1429 default: abort(); break;
1432 bp->max_bottle_radius = max_bottle_radius (bp);
1434 bp->launch_chance = speed;
1435 bp->blobs_per_group = BLOBS_PER_GROUP;
1436 bp->just_started_p = True;
1438 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1440 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1442 bp->bottle_list = glGenLists (1);
1443 bp->ball_list = glGenLists (1);
1445 generate_bottle (mi);
1446 generate_static_blobs (mi);
1454 draw_lavalite (ModeInfo *mi)
1456 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1457 Display *dpy = MI_DISPLAY(mi);
1458 Window window = MI_WINDOW(mi);
1460 if (!bp->glx_context)
1463 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1465 glMatrixMode (GL_MODELVIEW);
1469 double cx, cy, cz; /* camera position, 0-1. */
1470 double px, py, pz; /* object position, 0-1. */
1471 double rx, ry, rz; /* object rotation, 0-1. */
1473 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1474 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1476 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1477 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1484 #else /* #### this crud doesn't really work yet */
1487 /* We have c[xyz] parameters describing a camera position, but we don't
1488 want to just map those to points in space: the lamp doesn't look very
1489 good from the inside, or from underneath...
1491 Good observation points form a ring around the lamp: basically, a
1492 torus ringing the lamp, parallel to the lamp's floor.
1494 We interpret cz as distance from the origin.
1496 cx is then used as position in the torus (theta).
1500 double cx2, cy2, cz2;
1507 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1508 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1510 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1511 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1522 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1528 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1531 /* Place the lights relative to the object, before the object has
1532 been rotated or wandered within the scene. */
1533 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1534 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1535 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1538 /* Position the lamp in the scene according to the "wander" settings */
1539 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1541 /* Rotate the object according to the "spin" settings */
1542 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1543 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1544 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1546 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1549 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1550 case GIANT: glTranslatef (0, 0, 0.33); break;
1551 case CONE: glTranslatef (0, 0, 0.16); break;
1552 case ROCKET: glTranslatef (0, 0, 0.30);
1553 glScalef (0.85,0.85,0.85); break;
1554 default: abort(); break;
1560 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1561 glCallList (bp->bottle_list);
1562 glCallList (bp->ball_list);
1565 if (mi->fps_p) do_fps (mi);
1568 glXSwapBuffers(dpy, window);
1571 XSCREENSAVER_MODULE ("Lavalite", lavalite)