1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002-2017 Jamie Zawinski <jwz@jwz.org>
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that
8 * copyright notice and this permission notice appear in supporting
9 * documentation. No representations are made about the suitability of this
10 * software for any purpose. It is provided "as is" without express or
13 * LAVA®, LAVA LITE®, LAVA WORLD INTERNATIONAL® and the configuration of the
14 * LAVA® brand motion lamp are registered trademarks of Haggerty Enterprises,
15 * Inc. The configuration of the globe and base of the motion lamp are
16 * registered trademarks of Haggerty Enterprises, Inc. in the U.S.A. and in
17 * other countries around the world.
19 * Official Lava Lite web site: http://www.lavaworld.com/
21 * Implementation details:
23 * The blobs are generated using metaballs. For an explanation of what
24 * those are, see http://astronomy.swin.edu.au/~pbourke/modelling/implicitsurf/
25 * or http://www.fifi.org/doc/povray-manual/pov30005.htm
27 * Basically, each bubble of lava is a few (4) overlapping spherical metaballs
28 * of various sizes following similar but slightly different steep, slow
29 * parabolic arcs from the bottom to the top and back.
31 * We then polygonize the surface of the lava using the marching squares
32 * algorithm implemented in marching.c.
34 * Like real lavalites, this program is very slow.
36 * Surprisingly, it's loading the CPU and not the graphics engine: the speed
37 * bottleneck is in *computing* the scene rather than *rendering* it. We
38 * actually don't use a huge number of polygons, but computing the mesh for
39 * the surface takes a lot of cycles.
41 * I eliminated all the square roots, but there is still a lot of
42 * floating-point multiplication in here. I tried optimizing away the
43 * fp divisions, but that didn't seem to make a difference.
45 * -style lamp shape: classic, giant, cone, rocket, or random.
46 * -speed frequency at which new blobs launch.
47 * -resolution density of the polygon mesh.
48 * -count max number of blobs allowed at once.
49 * -wander, -spin whether to roll the scene around the screen.
50 * -lava-color color of the blobbies
51 * -fluid-color color of the stuff the blobbies float in
52 * -base-color color of the base of the lamp
53 * -table-color color of the table under the lamp
54 * -impatient at startup, skip forward in the animation so
55 * that at least one blob is fully exposed.
59 * - make the table look better, somehow.
60 * - should the lava be emissive? should the one at the bottom be
61 * more brightly colored than the ones at the top? light distance?
62 * - is there some way to put a specular reflection on the front glass
63 * itself? Maybe render it twice with FRONT/BACK tweaked, or alpha
64 * with depth buffering turned off?
67 #define DEFAULTS "*delay: 30000 \n" \
68 "*showFPS: False \n" \
69 "*wireframe: False \n" \
70 "*geometry: 600x900\n" \
71 "*count: " DEF_COUNT " \n" \
73 # define free_lavalite 0
74 # define release_lavalite 0
77 #define BLOBS_PER_GROUP 4
79 #define GRAVITY 0.000013 /* odwnward acceleration */
80 #define CONVECTION 0.005 /* initial upward velocity (bell curve) */
81 #define TILT 0.00166666 /* horizontal velocity (bell curve) */
84 #define countof(x) (sizeof((x))/sizeof((*x)))
87 #define ABS(n) ((n)<0?-(n):(n))
89 #define SIGNOF(n) ((n)<0?-1:1)
91 #include "xlockmore.h"
94 #include "gltrackball.h"
95 #include "ximage-loader.h"
98 #ifdef USE_GL /* whole file */
102 #define DEF_WANDER "False"
103 #define DEF_SPEED "0.003"
104 #define DEF_RESOLUTION "40"
105 #define DEF_SMOOTH "True"
106 #define DEF_COUNT "3"
107 #define DEF_STYLE "random"
108 #define DEF_IMPATIENT "False"
109 #define DEF_LCOLOR "#FF0000" /* lava */
110 #define DEF_FCOLOR "#00AAFF" /* fluid */
111 #define DEF_BCOLOR "#666666" /* base */
112 #define DEF_TCOLOR "#000000" /*"#00FF00"*/ /* table */
114 #define DEF_FTEX "(none)"
115 #define DEF_BTEX "(none)"
116 #define DEF_TTEX "(none)"
118 typedef struct metaball metaball;
125 double r; /* hard radius */
126 double R; /* radius of field of influence */
128 double z; /* vertical position */
129 double pos_r; /* position on horizontal circle */
130 double pos_th; /* position on horizontal circle */
131 double dr, dz; /* current velocity */
133 double x, y; /* h planar position - compused from the above */
135 metaball *leader; /* stay close to this other ball */
139 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
140 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
146 GLfloat texture_elevation;
149 static const lamp_geometry classic_lamp[] = {
150 { CAP, 1.16, 0.089, 0.00 },
151 { BOTTLE, 0.97, 0.120, 0.40 },
152 { BOTTLE, 0.13, 0.300, 0.87 },
153 { BOTTLE, 0.07, 0.300, 0.93 },
154 { BASE, 0.00, 0.280, 0.00 },
155 { BASE, -0.40, 0.120, 0.50 },
156 { BASE, -0.80, 0.280, 1.00 },
160 static const lamp_geometry giant_lamp[] = {
161 { CAP, 1.12, 0.105, 0.00 },
162 { BOTTLE, 0.97, 0.130, 0.30 },
163 { BOTTLE, 0.20, 0.300, 0.87 },
164 { BOTTLE, 0.15, 0.300, 0.93 },
165 { BASE, 0.00, 0.230, 0.00 },
166 { BASE, -0.18, 0.140, 0.20 },
167 { BASE, -0.80, 0.280, 1.00 },
171 static const lamp_geometry cone_lamp[] = {
172 { CAP, 1.35, 0.001, 0.00 },
173 { CAP, 1.35, 0.020, 0.00 },
174 { CAP, 1.30, 0.055, 0.05 },
175 { BOTTLE, 0.97, 0.120, 0.40 },
176 { BOTTLE, 0.13, 0.300, 0.87 },
177 { BASE, 0.00, 0.300, 0.00 },
178 { BASE, -0.04, 0.320, 0.04 },
179 { BASE, -0.60, 0.420, 0.50 },
183 static const lamp_geometry rocket_lamp[] = {
184 { CAP, 1.35, 0.001, 0.00 },
185 { CAP, 1.34, 0.020, 0.00 },
186 { CAP, 1.30, 0.055, 0.05 },
187 { BOTTLE, 0.97, 0.120, 0.40 },
188 { BOTTLE, 0.13, 0.300, 0.87 },
189 { BOTTLE, 0.07, 0.300, 0.93 },
190 { BASE, 0.00, 0.280, 0.00 },
191 { BASE, -0.50, 0.180, 0.50 },
192 { BASE, -0.75, 0.080, 0.75 },
193 { BASE, -0.80, 0.035, 0.80 },
194 { BASE, -0.90, 0.035, 1.00 },
201 GLXContext *glx_context;
203 const lamp_geometry *model;
206 trackball_state *trackball;
209 GLfloat max_bottle_radius; /* radius of widest part of the bottle */
211 GLfloat launch_chance; /* how often to percolate */
212 int blobs_per_group; /* how many metaballs we launch at once */
213 Bool just_started_p; /* so we launch some goo right away */
215 int grid_size; /* resolution for marching-cubes */
222 int bottle_poly_count; /* polygons in the bottle only */
224 } lavalite_configuration;
226 static lavalite_configuration *bps = NULL;
228 static char *do_spin;
229 static char *do_style;
230 static GLfloat speed;
231 static Bool do_wander;
232 static int resolution;
233 static Bool do_smooth;
234 static Bool do_impatient;
236 static char *lava_color_str, *fluid_color_str, *base_color_str,
238 static char *fluid_tex, *base_tex, *table_tex;
240 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
242 static const GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
243 static const GLfloat lava_shininess = 128.0;
244 static const GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
246 static const GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
247 static const GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
248 static const GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
252 static XrmOptionDescRec opts[] = {
253 { "-style", ".style", XrmoptionSepArg, 0 },
254 { "-spin", ".spin", XrmoptionSepArg, 0 },
255 { "+spin", ".spin", XrmoptionNoArg, "" },
256 { "-speed", ".speed", XrmoptionSepArg, 0 },
257 { "-wander", ".wander", XrmoptionNoArg, "True" },
258 { "+wander", ".wander", XrmoptionNoArg, "False" },
259 { "-resolution", ".resolution", XrmoptionSepArg, 0 },
260 { "-smooth", ".smooth", XrmoptionNoArg, "True" },
261 { "+smooth", ".smooth", XrmoptionNoArg, "False" },
262 { "-impatient", ".impatient", XrmoptionNoArg, "True" },
263 { "+impatient", ".impatient", XrmoptionNoArg, "False" },
265 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
266 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
267 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
268 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
270 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
271 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
272 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
275 static argtype vars[] = {
276 {&do_style, "style", "Style", DEF_STYLE, t_String},
277 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
278 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
279 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
280 {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
281 {&do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
282 {&do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
284 {&lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR, t_String},
285 {&fluid_color_str, "fluidColor", "FluidColor", DEF_FCOLOR, t_String},
286 {&base_color_str, "baseColor", "BaseColor", DEF_BCOLOR, t_String},
287 {&table_color_str, "tableColor", "TableColor", DEF_TCOLOR, t_String},
289 {&fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
290 {&base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
291 {&table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
294 ENTRYPOINT ModeSpecOpt lavalite_opts = {countof(opts), opts, countof(vars), vars, NULL};
297 /* Window management, etc
300 reshape_lavalite (ModeInfo *mi, int width, int height)
302 GLfloat h = (GLfloat) height / (GLfloat) width;
305 if (width > height * 5) { /* tiny window: show middle */
308 h = height / (GLfloat) width;
311 glViewport (0, y, (GLint) width, (GLint) height);
313 glMatrixMode(GL_PROJECTION);
315 gluPerspective (30.0, 1/h, 1.0, 100.0);
317 glMatrixMode(GL_MODELVIEW);
319 gluLookAt( 0.0, 0.0, 30.0,
323 glClear(GL_COLOR_BUFFER_BIT);
332 load_texture (ModeInfo *mi, const char *filename)
334 Display *dpy = mi->dpy;
335 Visual *visual = mi->xgwa.visual;
341 !strcasecmp (filename, "(none)"))
343 glDisable (GL_TEXTURE_2D);
347 image = file_to_ximage (dpy, visual, filename);
348 if (!image) return False;
351 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
352 image->width, image->height, 0,
353 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
354 sprintf (buf, "texture: %.100s (%dx%d)",
355 filename, image->width, image->height);
358 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
359 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
360 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
361 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
362 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
363 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
364 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
366 glEnable (GL_TEXTURE_2D);
372 /* Generating the lamp's bottle, caps, and base.
376 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
380 GLfloat step = M_PI * 2 / faces;
384 glFrontFace (up_p ? GL_CW : GL_CCW);
385 glNormal3f (0, (up_p ? 1 : -1), 0);
386 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
391 for (j = 0, th = 0; j <= faces; j++)
393 glTexCoord2f (-j / (GLfloat) faces, 1);
394 glVertex3f (0, z, 0);
396 glTexCoord2f (-j / (GLfloat) faces, 0);
397 glVertex3f (x, z, y);
403 glTexCoord2f (-j / (GLfloat) faces, 0);
404 glVertex3f (x, z, y);
415 draw_tube (GLfloat r0, GLfloat r1,
416 GLfloat z0, GLfloat z1,
417 GLfloat t0, GLfloat t1,
418 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
422 GLfloat x, y, x0=0, y0=0;
423 GLfloat step = M_PI * 2 / faces;
427 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
428 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
440 if (smooth_p) faces++;
442 for (i = 0; i < faces; i++)
444 int nsign = (inside_out_p ? -1 : 1);
447 glNormal3f (x * nsign, z1, y * nsign);
449 glNormal3f (x0 * nsign, z1, y0 * nsign);
451 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
452 glVertex3f (x * r1, z1, y * r1);
454 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
455 glVertex3f (x * r0, z0, y * r0);
466 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
467 glVertex3f (x * r0, z0, y * r0);
469 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
470 glVertex3f (x * r1, z1, y * r1);
482 draw_table (GLfloat z, Bool wire)
485 GLfloat step = M_PI * 2 / faces;
491 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
495 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
499 glTexCoord2f (-0.5, 0.5);
503 for (j = 0, th = 0; j <= faces; j++)
505 GLfloat x = cos (th);
506 GLfloat y = sin (th);
507 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
508 glVertex3f(x*s, z, y*s);
518 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
520 static const int coords[2][8][2] = {
541 int maxx = coords[0][countof(coords[0])-1][0];
542 int maxy = coords[0][countof(coords[0])-1][1];
545 for (x = 1; x < countof(coords[0]); x++)
547 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
548 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
549 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
550 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
551 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
552 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
553 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
554 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
560 glNormal3f (0, 0, -1);
561 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
563 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
564 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
565 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
566 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
572 glFrontFace (GL_CCW);
573 glNormal3f (0, 0, -1);
574 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
575 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
576 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
577 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
578 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
584 glFrontFace (GL_CCW);
585 glNormal3f (1, -1, 0); /* #### wrong */
586 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
587 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
588 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
589 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
590 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
597 glNormal3f (-1, 1, 0); /* #### wrong */
598 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
599 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
600 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
601 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
602 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
615 generate_bottle (ModeInfo *mi)
617 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
618 int wire = MI_IS_WIREFRAME(mi);
619 int faces = resolution * 1.5;
620 Bool smooth = do_smooth;
622 const lamp_geometry *top_slice = bp->model;
623 const char *current_texture = 0;
624 lamp_part last_part = 0;
626 if (faces < 3) faces = 3;
627 else if (wire && faces > 20) faces = 20;
628 else if (faces > 60) faces = 60;
630 bp->bottle_poly_count = 0;
632 glNewList (bp->bottle_list, GL_COMPILE);
635 glRotatef (90, 1, 0, 0);
636 glTranslatef (0, -0.5, 0);
638 /* All parts of the lamp use the same specularity and shininess. */
639 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
640 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
644 const lamp_geometry *bot_slice = top_slice + 1;
646 const char *texture = 0;
650 glDisable (GL_LIGHT2);
652 switch (top_slice->part)
662 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
669 if (!wire && texture && texture != current_texture)
671 current_texture = texture;
672 load_texture (mi, current_texture);
675 /* Color the discs darker than the tube walls. */
676 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
678 /* Do a top disc if this is the first slice of the CAP or BASE.
680 if ((top_slice->part == CAP && last_part == 0) ||
681 (top_slice->part == BASE && last_part == BOTTLE))
682 bp->bottle_poly_count +=
683 draw_disc (top_slice->radius, top_slice->elevation, faces,
686 /* Do a bottom disc if this is the last slice of the CAP or BASE.
688 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
689 (top_slice->part == BASE && bot_slice->part == 0))
691 const lamp_geometry *sl = (bot_slice->part == 0
692 ? top_slice : bot_slice);
693 bp->bottle_poly_count +=
694 draw_disc (sl->radius, sl->elevation, faces, False, wire);
697 if (bot_slice->part == 0) /* done! */
702 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
704 t0 = top_slice->texture_elevation;
705 t1 = bot_slice->texture_elevation;
707 /* Restart the texture coordinates for the glass.
709 if (top_slice->part == BOTTLE)
711 Bool first_p = (top_slice[-1].part != BOTTLE);
712 Bool last_p = (bot_slice->part != BOTTLE);
717 bp->bottle_poly_count +=
718 draw_tube (top_slice->radius, bot_slice->radius,
719 top_slice->elevation, bot_slice->elevation,
722 (top_slice->part == BOTTLE),
725 last_part = top_slice->part;
729 if (bp->style == ROCKET)
732 for (i = 0; i < 3; i++)
735 glRotatef (120 * i, 0, 1, 0);
736 glTranslatef (0.14, -0.05, 0);
737 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
740 glTranslatef (0, -0.1, 0); /* move floor down a little */
744 if (!wire) load_texture (mi, table_tex);
745 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
746 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
750 glDisable (GL_TEXTURE_2D); /* done with textured objects */
757 /* Generating blobbies
761 bellrand (double extent) /* like frand(), but a bell curve. */
763 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
768 static void move_ball (ModeInfo *mi, metaball *b);
770 /* Bring a ball into play, and re-randomize its values.
773 reset_ball (ModeInfo *mi, metaball *b)
775 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
778 b->R = 0.12 + bellrand(0.10);
780 b->pos_r = bellrand (0.9);
781 b->pos_th = frand(M_PI*2);
784 b->dr = bellrand(TILT);
796 /* returns the first metaball that is not in use, or 0.
799 get_ball (ModeInfo *mi)
801 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
803 for (i = 0; i < bp->nballs; i++)
805 metaball *b = &bp->balls[i];
813 /* Generate the blobs that don't move: the ones at teh top and bottom
814 that are part of the scenery.
817 generate_static_blobs (ModeInfo *mi)
819 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
830 /* the giant blob at the bottom of the bottle.
840 /* the small blob at the top of the bottle.
850 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
852 for (i = 0; i < bp->blobs_per_group; i++)
866 max_bottle_radius (lavalite_configuration *bp)
869 const lamp_geometry *slice;
870 for (slice = bp->model; slice->part != 0; slice++)
872 if (slice->part == BOTTLE && slice->radius > r)
873 r = slice->radius; /* top */
874 if (slice[1].radius > r)
875 r = slice[1].radius; /* bottom */
882 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
884 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
885 const lamp_geometry *slice;
888 for (slice = bp->model; slice->part != 0; slice++)
889 if (z > slice->elevation)
892 topz = slice->elevation;
893 topr = slice->radius;
896 if (topz == -999) return 0;
898 for (; slice->part != 0; slice++)
899 if (z > slice->elevation)
901 botz = slice->elevation;
902 botr = slice->radius;
905 if (botz == -999) return 0;
907 ratio = (z - botz) / (topz - botz);
909 return (botr + ((topr - botr) * ratio));
914 move_ball (ModeInfo *mi, metaball *b)
916 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
917 double gravity = GRAVITY;
920 if (b->static_p) return;
932 else if (b->pos_r < 0)
934 b->pos_r = -b->pos_r;
938 real_r = b->pos_r * bottle_radius_at (bp, b->z);
940 b->x = cos (b->pos_th) * real_r;
941 b->y = sin (b->pos_th) * real_r;
943 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
948 /* This function makes sure that balls that are part of a group always stay
949 relatively close to each other.
952 clamp_balls (ModeInfo *mi)
954 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
956 for (i = 0; i < bp->nballs; i++)
958 metaball *b = &bp->balls[i];
959 if (b->alive_p && b->leader)
962 double minz = b->leader->z - zslack;
963 double maxz = b->leader->z + zslack;
965 /* Try to keep the Z values near those of the leader.
966 Don't let it go out of range (above or below) and clamp it
967 if it does. If we've clamped it, make sure dz will be
968 moving it in the right direction (back toward the leader.)
970 We aren't currently clamping r, only z -- doesn't seem to
973 This is kind of flaky, I think. Sometimes you can see
974 the blobbies "twitch". That's no good.
979 if (b->dz < 0) b->dz = -b->dz;
985 if (b->dz > 0) b->dz = -b->dz;
994 move_balls (ModeInfo *mi) /* for great justice */
996 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
998 for (i = 0; i < bp->nballs; i++)
1000 metaball *b = &bp->balls[i];
1010 /* Rendering blobbies using marching cubes.
1014 compute_metaball_influence (lavalite_configuration *bp,
1015 double x, double y, double z,
1016 int nballs, metaball *balls)
1021 for (i = 0; i < nballs; i++)
1023 metaball *b = &balls[i];
1025 double d2, r, R, r2, R2;
1027 if (!b->alive_p) continue;
1034 if (dx > R || dx < -R || /* quick check before multiplying */
1035 dy > R || dy < -R ||
1039 d2 = (dx*dx + dy*dy + dz*dz);
1045 if (d2 <= r2) /* (d <= r) inside the hard radius */
1047 else if (d2 > R2) /* (d > R) outside the radius of influence */
1049 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1051 /* was: vv += 1 - ((d-r) / (R-r)); */
1052 vv += 1 - ((d2-r2) / (R2-r2));
1060 /* callback for marching_cubes() */
1062 obj_init (double grid_size, void *closure)
1064 lavalite_configuration *bp = (lavalite_configuration *) closure;
1065 bp->grid_size = grid_size;
1071 /* Returns True if the given point is outside of the glass tube.
1074 clipped_by_glass_p (double x, double y, double z,
1075 lavalite_configuration *bp)
1077 double d2, or, or2, ir2;
1079 or = bp->max_bottle_radius;
1081 if (x > or || x < -or || /* quick check before multiplying */
1086 or = bottle_radius_at (bp, z);
1090 if (d2 > or2) /* (sqrt(d) > or) */
1095 if (d2 > ir2) /* (sqrt(d) > ir) */
1099 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1100 return (1 - (d2-dr2) / (dr1-dr2));
1108 /* callback for marching_cubes() */
1110 obj_compute (double x, double y, double z, void *closure)
1112 lavalite_configuration *bp = (lavalite_configuration *) closure;
1115 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1119 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1122 clip = clipped_by_glass_p (x, y, z, bp);
1123 if (clip == 0) return 0;
1126 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1130 /* callback for marching_cubes() */
1132 obj_free (void *closure)
1137 /* Send a new blob travelling upward.
1138 This blob will actually be composed of N metaballs that are near
1142 launch_balls (ModeInfo *mi)
1144 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1145 metaball *b0 = get_ball (mi);
1149 reset_ball (mi, b0);
1151 for (i = 0; i < bp->blobs_per_group; i++)
1153 metaball *b1 = get_ball (mi);
1157 reset_ball (mi, b1);
1160 # define FROB(FIELD,AMT) \
1161 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1163 /* FROB (pos_r, 0.7); */
1164 /* FROB (pos_th, 0.7); */
1174 animate_lava (ModeInfo *mi)
1176 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1177 int wire = MI_IS_WIREFRAME(mi);
1178 Bool just_started_p = bp->just_started_p;
1180 double isolevel = 0.3;
1182 /* Maybe bubble a new blobby to the surface.
1184 if (just_started_p ||
1185 frand(1.0) < bp->launch_chance)
1187 bp->just_started_p = False;
1190 if (do_impatient && just_started_p)
1195 for (i = 0; i < bp->nballs; i++)
1197 metaball *b = &bp->balls[i];
1198 if (b->alive_p && !b->static_p && !b->leader &&
1208 glNewList (bp->ball_list, GL_COMPILE);
1211 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1212 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1213 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1215 /* For the blobbies, the origin is on the axis at the bottom of the
1216 glass bottle; and the top of the bottle is +1 on Z.
1218 glTranslatef (0, 0, -0.5);
1220 mi->polygon_count = 0;
1223 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1224 s = 1.0/bp->grid_size;
1227 glTranslatef (-0.5, -0.5, 0);
1229 marching_cubes (resolution, isolevel, wire, do_smooth,
1230 obj_init, obj_compute, obj_free, bp,
1231 &mi->polygon_count);
1235 mi->polygon_count += bp->bottle_poly_count;
1243 /* Startup initialization
1247 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1249 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1251 if (gltrackball_event_handler (event, bp->trackball,
1252 MI_WIDTH (mi), MI_HEIGHT (mi),
1253 &bp->button_down_p))
1261 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1264 a[3] = 1.0; /* alpha */
1266 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1268 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1271 a[0] = c.red / 65536.0;
1272 a[1] = c.green / 65536.0;
1273 a[2] = c.blue / 65536.0;
1278 init_lavalite (ModeInfo *mi)
1280 lavalite_configuration *bp;
1281 int wire = MI_IS_WIREFRAME(mi);
1285 bp = &bps[MI_SCREEN(mi)];
1287 bp->glx_context = init_GL(mi);
1289 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1293 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1294 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1295 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1296 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1297 else if (!strcasecmp (s, "random"))
1299 if (random() & 1) bp->style = CLASSIC; /* half the time */
1300 else bp->style = (random() % ((int) ROCKET+1));
1305 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1311 parse_color (mi, "lava", lava_color_str, lava_color);
1312 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1313 parse_color (mi, "base", base_color_str, base_color);
1314 parse_color (mi, "table", table_color_str, table_color);
1318 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1319 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1320 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1321 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1323 glEnable(GL_LIGHTING);
1324 glEnable(GL_LIGHT0);
1325 glEnable(GL_LIGHT1);
1326 glEnable(GL_DEPTH_TEST);
1327 glEnable(GL_CULL_FACE);
1328 glEnable(GL_NORMALIZE);
1329 glShadeModel(GL_SMOOTH);
1331 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1332 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1333 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1335 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1336 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1337 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1339 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1340 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1341 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1345 Bool spinx=False, spiny=False, spinz=False;
1346 double spin_speed = 0.4;
1347 double wander_speed = 0.03;
1352 if (*s == 'x' || *s == 'X') spinx = True;
1353 else if (*s == 'y' || *s == 'Y') spiny = True;
1354 else if (*s == 'z' || *s == 'Z') spinz = True;
1355 else if (*s == '0') ;
1359 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1366 bp->rot = make_rotator (spinx ? spin_speed : 0,
1367 spiny ? spin_speed : 0,
1368 spinz ? spin_speed : 0,
1370 do_wander ? wander_speed : 0,
1372 bp->rot2 = make_rotator (spin_speed, 0, 0,
1375 bp->trackball = gltrackball_init (False);
1377 /* Tilt the scene a bit: lean the normal lamps toward the viewer,
1378 and the huge lamps away. */
1379 gltrackball_reset (bp->trackball,
1381 (bp->style == ROCKET || bp->style == GIANT
1388 case CLASSIC: bp->model = classic_lamp; break;
1389 case GIANT: bp->model = giant_lamp; break;
1390 case CONE: bp->model = cone_lamp; break;
1391 case ROCKET: bp->model = rocket_lamp; break;
1392 default: abort(); break;
1395 bp->max_bottle_radius = max_bottle_radius (bp);
1397 bp->launch_chance = speed;
1398 bp->blobs_per_group = BLOBS_PER_GROUP;
1399 bp->just_started_p = True;
1401 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1403 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1405 bp->bottle_list = glGenLists (1);
1406 bp->ball_list = glGenLists (1);
1408 generate_bottle (mi);
1409 generate_static_blobs (mi);
1417 draw_lavalite (ModeInfo *mi)
1419 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1420 Display *dpy = MI_DISPLAY(mi);
1421 Window window = MI_WINDOW(mi);
1423 if (!bp->glx_context)
1426 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
1428 glMatrixMode (GL_MODELVIEW);
1432 double cx, cy, cz; /* camera position, 0-1. */
1433 double px, py, pz; /* object position, 0-1. */
1434 double rx, ry, rz; /* object rotation, 0-1. */
1436 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1437 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1439 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1440 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1447 #else /* #### this crud doesn't really work yet */
1450 /* We have c[xyz] parameters describing a camera position, but we don't
1451 want to just map those to points in space: the lamp doesn't look very
1452 good from the inside, or from underneath...
1454 Good observation points form a ring around the lamp: basically, a
1455 torus ringing the lamp, parallel to the lamp's floor.
1457 We interpret cz as distance from the origin.
1459 cx is then used as position in the torus (theta).
1463 double cx2, cy2, cz2;
1470 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1471 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1473 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1474 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1484 glRotatef(current_device_rotation(), 0, 0, 1);
1486 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1492 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1494 glRotatef (-90, 1, 0, 0); /* Right side up */
1497 /* Place the lights relative to the object, before the object has
1498 been rotated or wandered within the scene. */
1499 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1500 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1501 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1504 /* Position the lamp in the scene according to the "wander" settings */
1505 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1507 /* Rotate the object according to the "spin" settings */
1508 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1509 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1510 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1512 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1515 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1516 case GIANT: glTranslatef (0, 0, 0.33); break;
1517 case CONE: glTranslatef (0, 0, 0.16); break;
1518 case ROCKET: glTranslatef (0, 0, 0.30);
1519 glScalef (0.85,0.85,0.85); break;
1520 default: abort(); break;
1526 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1527 glCallList (bp->bottle_list);
1528 glCallList (bp->ball_list);
1531 if (mi->fps_p) do_fps (mi);
1534 glXSwapBuffers(dpy, window);
1537 XSCREENSAVER_MODULE ("Lavalite", lavalite)