1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002-2004 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 #include <X11/Intrinsic.h>
69 extern XtAppContext app;
71 #define PROGCLASS "LavaLite"
72 #define HACK_INIT init_lavalite
73 #define HACK_DRAW draw_lavalite
74 #define HACK_RESHAPE reshape_lavalite
75 #define HACK_HANDLE_EVENT lavalite_handle_event
76 #define EVENT_MASK PointerMotionMask
77 #define sws_opts xlockmore_opts
80 #define DEF_WANDER "False"
81 #define DEF_SPEED "0.003"
82 #define DEF_RESOLUTION "40"
83 #define DEF_SMOOTH "True"
85 #define DEF_STYLE "random"
86 #define DEF_IMPATIENT "False"
87 #define DEF_LCOLOR "#FF0000" /* lava */
88 #define DEF_FCOLOR "#00AAFF" /* fluid */
89 #define DEF_BCOLOR "#666666" /* base */
90 #define DEF_TCOLOR "#000000" /*"#00FF00"*/ /* table */
92 #define DEF_FTEX "(none)"
93 #define DEF_BTEX "(none)"
94 #define DEF_TTEX "(none)"
96 #define DEFAULTS "*delay: 30000 \n" \
97 "*showFPS: False \n" \
98 "*wireframe: False \n" \
99 "*geometry: 600x900\n" \
100 "*count: " DEF_COUNT " \n" \
102 #define BLOBS_PER_GROUP 4
104 #define GRAVITY 0.000013 /* odwnward acceleration */
105 #define CONVECTION 0.005 /* initial upward velocity (bell curve) */
106 #define TILT 0.00166666 /* horizontal velocity (bell curve) */
109 #define countof(x) (sizeof((x))/sizeof((*x)))
112 #define ABS(n) ((n)<0?-(n):(n))
114 #define SIGNOF(n) ((n)<0?-1:1)
116 #include "xlockmore.h"
117 #include "marching.h"
119 #include "gltrackball.h"
120 #include "xpm-ximage.h"
123 #ifdef USE_GL /* whole file */
128 typedef struct metaball metaball;
135 double r; /* hard radius */
136 double R; /* radius of field of influence */
138 double z; /* vertical position */
139 double pos_r; /* position on horizontal circle */
140 double pos_th; /* position on horizontal circle */
141 double dr, dz; /* current velocity */
143 double x, y; /* h planar position - compused from the above */
145 metaball *leader; /* stay close to this other ball */
149 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
150 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
156 GLfloat texture_elevation;
159 static lamp_geometry classic_lamp[] = {
160 { CAP, 1.16, 0.089, 0.00 },
161 { BOTTLE, 0.97, 0.120, 0.40 },
162 { BOTTLE, 0.13, 0.300, 0.87 },
163 { BOTTLE, 0.07, 0.300, 0.93 },
164 { BASE, 0.00, 0.280, 0.00 },
165 { BASE, -0.40, 0.120, 0.50 },
166 { BASE, -0.80, 0.280, 1.00 },
170 static lamp_geometry giant_lamp[] = {
171 { CAP, 1.12, 0.105, 0.00 },
172 { BOTTLE, 0.97, 0.130, 0.30 },
173 { BOTTLE, 0.20, 0.300, 0.87 },
174 { BOTTLE, 0.15, 0.300, 0.93 },
175 { BASE, 0.00, 0.230, 0.00 },
176 { BASE, -0.18, 0.140, 0.20 },
177 { BASE, -0.80, 0.280, 1.00 },
181 static lamp_geometry cone_lamp[] = {
182 { CAP, 1.35, 0.001, 0.00 },
183 { CAP, 1.35, 0.020, 0.00 },
184 { CAP, 1.30, 0.055, 0.05 },
185 { BOTTLE, 0.97, 0.120, 0.40 },
186 { BOTTLE, 0.13, 0.300, 0.87 },
187 { BASE, 0.00, 0.300, 0.00 },
188 { BASE, -0.04, 0.320, 0.04 },
189 { BASE, -0.60, 0.420, 0.50 },
193 static lamp_geometry rocket_lamp[] = {
194 { CAP, 1.35, 0.001, 0.00 },
195 { CAP, 1.34, 0.020, 0.00 },
196 { CAP, 1.30, 0.055, 0.05 },
197 { BOTTLE, 0.97, 0.120, 0.40 },
198 { BOTTLE, 0.13, 0.300, 0.87 },
199 { BOTTLE, 0.07, 0.300, 0.93 },
200 { BASE, 0.00, 0.280, 0.00 },
201 { BASE, -0.50, 0.180, 0.50 },
202 { BASE, -0.75, 0.080, 0.75 },
203 { BASE, -0.80, 0.035, 0.80 },
204 { BASE, -0.90, 0.035, 1.00 },
211 GLXContext *glx_context;
213 lamp_geometry *model;
216 trackball_state *trackball;
219 GLfloat max_bottle_radius; /* radius of widest part of the bottle */
221 GLfloat launch_chance; /* how often to percolate */
222 int blobs_per_group; /* how many metaballs we launch at once */
223 Bool just_started_p; /* so we launch some goo right away */
225 int grid_size; /* resolution for marching-cubes */
232 int bottle_poly_count; /* polygons in the bottle only */
234 } lavalite_configuration;
236 static lavalite_configuration *bps = NULL;
238 static char *do_spin;
239 static char *do_style;
240 static GLfloat speed;
241 static Bool do_wander;
242 static int resolution;
243 static Bool do_smooth;
244 static Bool do_impatient;
246 static char *lava_color_str, *fluid_color_str, *base_color_str,
248 static char *fluid_tex, *base_tex, *table_tex;
250 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
251 static GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
252 static GLfloat lava_shininess = 128.0;
253 static GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
255 static GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
256 static GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
257 static GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
261 static XrmOptionDescRec opts[] = {
262 { "-style", ".style", XrmoptionSepArg, 0 },
263 { "-spin", ".spin", XrmoptionSepArg, 0 },
264 { "+spin", ".spin", XrmoptionNoArg, "" },
265 { "-speed", ".speed", XrmoptionSepArg, 0 },
266 { "-wander", ".wander", XrmoptionNoArg, "True" },
267 { "+wander", ".wander", XrmoptionNoArg, "False" },
268 { "-resolution", ".resolution", XrmoptionSepArg, 0 },
269 { "-smooth", ".smooth", XrmoptionNoArg, "True" },
270 { "+smooth", ".smooth", XrmoptionNoArg, "False" },
271 { "-impatient", ".impatient", XrmoptionNoArg, "True" },
272 { "+impatient", ".impatient", XrmoptionNoArg, "False" },
274 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
275 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
276 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
277 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
279 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
280 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
281 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
284 static argtype vars[] = {
285 {&do_style, "style", "Style", DEF_STYLE, t_String},
286 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
287 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
288 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
289 {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
290 {&do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
291 {&do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
293 {&lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR, t_String},
294 {&fluid_color_str, "fluidColor", "FluidColor", DEF_FCOLOR, t_String},
295 {&base_color_str, "baseColor", "BaseColor", DEF_BCOLOR, t_String},
296 {&table_color_str, "tableColor", "TableColor", DEF_TCOLOR, t_String},
298 {&fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
299 {&base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
300 {&table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
303 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
306 /* Window management, etc
309 reshape_lavalite (ModeInfo *mi, int width, int height)
311 GLfloat h = (GLfloat) height / (GLfloat) width;
313 glViewport (0, 0, (GLint) width, (GLint) height);
315 glMatrixMode(GL_PROJECTION);
317 gluPerspective (30.0, 1/h, 1.0, 100.0);
319 glMatrixMode(GL_MODELVIEW);
321 gluLookAt( 0.0, 0.0, 30.0,
325 glClear(GL_COLOR_BUFFER_BIT);
334 load_texture (ModeInfo *mi, const char *filename)
336 Display *dpy = mi->dpy;
337 Visual *visual = mi->xgwa.visual;
338 Colormap cmap = mi->xgwa.colormap;
344 !strcasecmp (filename, "(none)"))
346 glDisable (GL_TEXTURE_2D);
350 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
353 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
354 image->width, image->height, 0,
355 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
356 sprintf (buf, "texture: %.100s (%dx%d)",
357 filename, image->width, image->height);
360 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
361 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
362 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
363 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
364 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
365 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
366 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
368 glEnable (GL_TEXTURE_2D);
374 /* Generating the lamp's bottle, caps, and base.
378 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
382 GLfloat step = M_PI * 2 / faces;
386 glFrontFace (up_p ? GL_CW : GL_CCW);
387 glNormal3f (0, (up_p ? 1 : -1), 0);
388 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
393 for (j = 0, th = 0; j <= faces; j++)
395 glTexCoord2f (-j / (GLfloat) faces, 1);
396 glVertex3f (0, z, 0);
398 glTexCoord2f (-j / (GLfloat) faces, 0);
399 glVertex3f (x, z, y);
405 glTexCoord2f (-j / (GLfloat) faces, 0);
406 glVertex3f (x, z, y);
417 draw_tube (GLfloat r0, GLfloat r1,
418 GLfloat z0, GLfloat z1,
419 GLfloat t0, GLfloat t1,
420 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
424 GLfloat x, y, x0=0, y0=0;
425 GLfloat step = M_PI * 2 / faces;
429 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
430 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
442 if (smooth_p) faces++;
444 for (i = 0; i < faces; i++)
446 int nsign = (inside_out_p ? -1 : 1);
449 glNormal3f (x * nsign, z1, y * nsign);
451 glNormal3f (x0 * nsign, z1, y0 * nsign);
453 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
454 glVertex3f (x * r1, z1, y * r1);
456 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
457 glVertex3f (x * r0, z0, y * r0);
468 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
469 glVertex3f (x * r0, z0, y * r0);
471 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
472 glVertex3f (x * r1, z1, y * r1);
484 draw_table (GLfloat z, Bool wire)
487 GLfloat step = M_PI * 2 / faces;
493 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
497 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
501 glTexCoord2f (-0.5, 0.5);
505 for (j = 0, th = 0; j <= faces; j++)
507 GLfloat x = cos (th);
508 GLfloat y = sin (th);
509 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
510 glVertex3f(x*s, z, y*s);
520 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
522 static int coords[2][8][2] = {
543 int maxx = coords[0][countof(coords[0])-1][0];
544 int maxy = coords[0][countof(coords[0])-1][1];
547 for (x = 1; x < countof(coords[0]); x++)
549 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
550 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
551 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
552 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
553 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
554 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
555 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
556 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
562 glNormal3f (0, 0, -1);
563 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
565 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
566 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
567 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
568 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
574 glFrontFace (GL_CCW);
575 glNormal3f (0, 0, -1);
576 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
577 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
578 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
579 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
580 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
586 glFrontFace (GL_CCW);
587 glNormal3f (1, -1, 0); /* #### wrong */
588 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
589 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
590 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
591 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
592 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
599 glNormal3f (-1, 1, 0); /* #### wrong */
600 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
601 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
602 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
603 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
604 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
617 generate_bottle (ModeInfo *mi)
619 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
620 int wire = MI_IS_WIREFRAME(mi);
621 int faces = resolution * 1.5;
622 Bool smooth = do_smooth;
623 Bool have_texture = False;
625 lamp_geometry *top_slice = bp->model;
626 const char *current_texture = 0;
627 lamp_part last_part = 0;
629 if (faces < 3) faces = 3;
630 else if (wire && faces > 20) faces = 20;
631 else if (faces > 60) faces = 60;
633 bp->bottle_poly_count = 0;
635 glNewList (bp->bottle_list, GL_COMPILE);
638 glRotatef (90, 1, 0, 0);
639 glTranslatef (0, -0.5, 0);
641 /* All parts of the lamp use the same specularity and shininess. */
642 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
643 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
647 lamp_geometry *bot_slice = top_slice + 1;
649 const char *texture = 0;
653 glDisable (GL_LIGHT2);
655 switch (top_slice->part)
665 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
672 have_texture = False;
673 if (!wire && texture && texture != current_texture)
675 current_texture = texture;
676 have_texture = load_texture (mi, current_texture);
679 /* Color the discs darker than the tube walls. */
680 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
682 /* Do a top disc if this is the first slice of the CAP or BASE.
684 if ((top_slice->part == CAP && last_part == 0) ||
685 (top_slice->part == BASE && last_part == BOTTLE))
686 bp->bottle_poly_count +=
687 draw_disc (top_slice->radius, top_slice->elevation, faces,
690 /* Do a bottom disc if this is the last slice of the CAP or BASE.
692 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
693 (top_slice->part == BASE && bot_slice->part == 0))
695 lamp_geometry *sl = (bot_slice->part == 0 ? top_slice : bot_slice);
696 bp->bottle_poly_count +=
697 draw_disc (sl->radius, sl->elevation, faces, False, wire);
700 if (bot_slice->part == 0) /* done! */
705 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
707 t0 = top_slice->texture_elevation;
708 t1 = bot_slice->texture_elevation;
710 /* Restart the texture coordinates for the glass.
712 if (top_slice->part == BOTTLE)
714 Bool first_p = (top_slice[-1].part != BOTTLE);
715 Bool last_p = (bot_slice->part != BOTTLE);
720 bp->bottle_poly_count +=
721 draw_tube (top_slice->radius, bot_slice->radius,
722 top_slice->elevation, bot_slice->elevation,
725 (top_slice->part == BOTTLE),
728 last_part = top_slice->part;
732 if (bp->style == ROCKET)
735 for (i = 0; i < 3; i++)
738 glRotatef (120 * i, 0, 1, 0);
739 glTranslatef (0.14, -0.05, 0);
740 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
743 glTranslatef (0, -0.1, 0); /* move floor down a little */
747 have_texture = !wire && load_texture (mi, table_tex);
748 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
749 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
753 glDisable (GL_TEXTURE_2D); /* done with textured objects */
760 /* Generating blobbies
764 bellrand (double extent) /* like frand(), but a bell curve. */
766 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
771 static void move_ball (ModeInfo *mi, metaball *b);
773 /* Bring a ball into play, and re-randomize its values.
776 reset_ball (ModeInfo *mi, metaball *b)
778 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
781 b->R = 0.12 + bellrand(0.10);
783 b->pos_r = bellrand (0.9);
784 b->pos_th = frand(M_PI*2);
787 b->dr = bellrand(TILT);
799 /* returns the first metaball that is not in use, or 0.
802 get_ball (ModeInfo *mi)
804 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
806 for (i = 0; i < bp->nballs; i++)
808 metaball *b = &bp->balls[i];
816 /* Generate the blobs that don't move: the ones at teh top and bottom
817 that are part of the scenery.
820 generate_static_blobs (ModeInfo *mi)
822 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
833 /* the giant blob at the bottom of the bottle.
843 /* the small blob at the top of the bottle.
853 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
855 for (i = 0; i < bp->blobs_per_group; i++)
869 max_bottle_radius (lavalite_configuration *bp)
872 lamp_geometry *slice;
873 for (slice = bp->model; slice->part != 0; slice++)
875 if (slice->part == BOTTLE && slice->radius > r)
876 r = slice->radius; /* top */
877 if (slice[1].radius > r)
878 r = slice[1].radius; /* bottom */
885 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
887 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
888 lamp_geometry *slice;
891 for (slice = bp->model; slice->part != 0; slice++)
892 if (z > slice->elevation)
895 topz = slice->elevation;
896 topr = slice->radius;
899 if (topz == -999) return 0;
901 for (; slice->part != 0; slice++)
902 if (z > slice->elevation)
904 botz = slice->elevation;
905 botr = slice->radius;
908 if (botz == -999) return 0;
910 ratio = (z - botz) / (topz - botz);
912 return (botr + ((topr - botr) * ratio));
917 move_ball (ModeInfo *mi, metaball *b)
919 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
920 double gravity = GRAVITY;
923 if (b->static_p) return;
935 else if (b->pos_r < 0)
937 b->pos_r = -b->pos_r;
941 real_r = b->pos_r * bottle_radius_at (bp, b->z);
943 b->x = cos (b->pos_th) * real_r;
944 b->y = sin (b->pos_th) * real_r;
946 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
951 /* This function makes sure that balls that are part of a group always stay
952 relatively close to each other.
955 clamp_balls (ModeInfo *mi)
957 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
959 for (i = 0; i < bp->nballs; i++)
961 metaball *b = &bp->balls[i];
962 if (b->alive_p && b->leader)
965 double minz = b->leader->z - zslack;
966 double maxz = b->leader->z + zslack;
968 /* Try to keep the Z values near those of the leader.
969 Don't let it go out of range (above or below) and clamp it
970 if it does. If we've clamped it, make sure dz will be
971 moving it in the right direction (back toward the leader.)
973 We aren't currently clamping r, only z -- doesn't seem to
976 This is kind of flaky, I think. Sometimes you can see
977 the blobbies "twitch". That's no good.
982 if (b->dz < 0) b->dz = -b->dz;
988 if (b->dz > 0) b->dz = -b->dz;
997 move_balls (ModeInfo *mi) /* for great justice */
999 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1001 for (i = 0; i < bp->nballs; i++)
1003 metaball *b = &bp->balls[i];
1013 /* Rendering blobbies using marching cubes.
1017 compute_metaball_influence (lavalite_configuration *bp,
1018 double x, double y, double z,
1019 int nballs, metaball *balls)
1024 for (i = 0; i < nballs; i++)
1026 metaball *b = &balls[i];
1028 double d2, r, R, r2, R2;
1030 if (!b->alive_p) continue;
1037 if (dx > R || dx < -R || /* quick check before multiplying */
1038 dy > R || dy < -R ||
1042 d2 = (dx*dx + dy*dy + dz*dz);
1048 if (d2 <= r2) /* (d <= r) inside the hard radius */
1050 else if (d2 > R2) /* (d > R) outside the radius of influence */
1052 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1054 /* was: vv += 1 - ((d-r) / (R-r)); */
1055 vv += 1 - ((d2-r2) / (R2-r2));
1063 /* callback for marching_cubes() */
1065 obj_init (double grid_size, void *closure)
1067 lavalite_configuration *bp = (lavalite_configuration *) closure;
1068 bp->grid_size = grid_size;
1074 /* Returns True if the given point is outside of the glass tube.
1077 clipped_by_glass_p (double x, double y, double z,
1078 lavalite_configuration *bp)
1080 double d2, or, or2, ir2;
1082 or = bp->max_bottle_radius;
1084 if (x > or || x < -or || /* quick check before multiplying */
1089 or = bottle_radius_at (bp, z);
1093 if (d2 > or2) /* (sqrt(d) > or) */
1098 if (d2 > ir2) /* (sqrt(d) > ir) */
1102 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1103 return (1 - (d2-dr2) / (dr1-dr2));
1111 /* callback for marching_cubes() */
1113 obj_compute (double x, double y, double z, void *closure)
1115 lavalite_configuration *bp = (lavalite_configuration *) closure;
1118 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1122 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1125 clip = clipped_by_glass_p (x, y, z, bp);
1126 if (clip == 0) return 0;
1129 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1133 /* callback for marching_cubes() */
1135 obj_free (void *closure)
1140 /* Send a new blob travelling upward.
1141 This blob will actually be composed of N metaballs that are near
1145 launch_balls (ModeInfo *mi)
1147 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1148 metaball *b0 = get_ball (mi);
1152 reset_ball (mi, b0);
1154 for (i = 0; i < bp->blobs_per_group; i++)
1156 metaball *b1 = get_ball (mi);
1160 reset_ball (mi, b1);
1163 # define FROB(FIELD,AMT) \
1164 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1166 /* FROB (pos_r, 0.7); */
1167 /* FROB (pos_th, 0.7); */
1177 animate_lava (ModeInfo *mi)
1179 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1180 int wire = MI_IS_WIREFRAME(mi);
1181 Bool just_started_p = bp->just_started_p;
1183 double isolevel = 0.3;
1185 /* Maybe bubble a new blobby to the surface.
1187 if (just_started_p ||
1188 frand(1.0) < bp->launch_chance)
1190 bp->just_started_p = False;
1193 if (do_impatient && just_started_p)
1198 for (i = 0; i < bp->nballs; i++)
1200 metaball *b = &bp->balls[i];
1201 if (b->alive_p && !b->static_p && !b->leader &&
1211 glNewList (bp->ball_list, GL_COMPILE);
1214 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1215 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1216 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1218 /* For the blobbies, the origin is on the axis at the bottom of the
1219 glass bottle; and the top of the bottle is +1 on Z.
1221 glTranslatef (0, 0, -0.5);
1223 mi->polygon_count = 0;
1226 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1227 s = 1.0/bp->grid_size;
1230 glTranslatef (-0.5, -0.5, 0);
1232 marching_cubes (resolution, isolevel, wire, do_smooth,
1233 obj_init, obj_compute, obj_free, bp,
1234 &mi->polygon_count);
1238 mi->polygon_count += bp->bottle_poly_count;
1246 /* Startup initialization
1250 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1252 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1254 if (event->xany.type == ButtonPress &&
1255 event->xbutton.button == Button1)
1257 bp->button_down_p = True;
1258 gltrackball_start (bp->trackball,
1259 event->xbutton.x, event->xbutton.y,
1260 MI_WIDTH (mi), MI_HEIGHT (mi));
1263 else if (event->xany.type == ButtonRelease &&
1264 event->xbutton.button == Button1)
1266 bp->button_down_p = False;
1269 else if (event->xany.type == ButtonPress &&
1270 (event->xbutton.button == Button4 ||
1271 event->xbutton.button == Button5))
1273 gltrackball_mousewheel (bp->trackball, event->xbutton.button, 5,
1274 !!event->xbutton.state);
1277 else if (event->xany.type == MotionNotify &&
1280 gltrackball_track (bp->trackball,
1281 event->xmotion.x, event->xmotion.y,
1282 MI_WIDTH (mi), MI_HEIGHT (mi));
1291 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1294 a[3] = 1.0; /* alpha */
1296 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1298 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1301 a[0] = c.red / 65536.0;
1302 a[1] = c.green / 65536.0;
1303 a[2] = c.blue / 65536.0;
1308 init_lavalite (ModeInfo *mi)
1310 lavalite_configuration *bp;
1311 int wire = MI_IS_WIREFRAME(mi);
1314 bps = (lavalite_configuration *)
1315 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1317 fprintf(stderr, "%s: out of memory\n", progname);
1321 bp = &bps[MI_SCREEN(mi)];
1324 bp = &bps[MI_SCREEN(mi)];
1326 bp->glx_context = init_GL(mi);
1328 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1332 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1333 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1334 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1335 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1336 else if (!strcasecmp (s, "random"))
1338 if (random() & 1) bp->style = CLASSIC; /* half the time */
1339 else bp->style = (random() % ((int) ROCKET+1));
1344 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1350 parse_color (mi, "lava", lava_color_str, lava_color);
1351 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1352 parse_color (mi, "base", base_color_str, base_color);
1353 parse_color (mi, "table", table_color_str, table_color);
1357 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1358 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1359 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1360 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1362 glEnable(GL_LIGHTING);
1363 glEnable(GL_LIGHT0);
1364 glEnable(GL_LIGHT1);
1365 glEnable(GL_DEPTH_TEST);
1366 glEnable(GL_CULL_FACE);
1367 glEnable(GL_NORMALIZE);
1368 glShadeModel(GL_SMOOTH);
1370 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1371 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1372 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1374 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1375 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1376 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1378 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1379 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1380 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1384 Bool spinx=False, spiny=False, spinz=False;
1385 double spin_speed = 0.4;
1386 double wander_speed = 0.03;
1391 if (*s == 'x' || *s == 'X') spinx = True;
1392 else if (*s == 'y' || *s == 'Y') spiny = True;
1393 else if (*s == 'z' || *s == 'Z') spinz = True;
1397 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1404 bp->rot = make_rotator (spinx ? spin_speed : 0,
1405 spiny ? spin_speed : 0,
1406 spinz ? spin_speed : 0,
1408 do_wander ? wander_speed : 0,
1410 bp->rot2 = make_rotator (spin_speed, 0, 0,
1413 bp->trackball = gltrackball_init ();
1415 /* move initial camera position up by around 15 degrees:
1416 in other words, tilt the scene toward the viewer. */
1417 gltrackball_start (bp->trackball, 50, 50, 100, 100);
1418 gltrackball_track (bp->trackball, 50, 5, 100, 100);
1420 /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1421 look like we're looking up at it instead of odwn at it! */
1422 if (bp->style == GIANT)
1423 gltrackball_track (bp->trackball, 50, -12, 100, 100);
1424 else if (bp->style == ROCKET) /* same for rocket, but not as much */
1425 gltrackball_track (bp->trackball, 50, -4, 100, 100);
1430 case CLASSIC: bp->model = classic_lamp; break;
1431 case GIANT: bp->model = giant_lamp; break;
1432 case CONE: bp->model = cone_lamp; break;
1433 case ROCKET: bp->model = rocket_lamp; break;
1434 default: abort(); break;
1437 bp->max_bottle_radius = max_bottle_radius (bp);
1439 bp->launch_chance = speed;
1440 bp->blobs_per_group = BLOBS_PER_GROUP;
1441 bp->just_started_p = True;
1443 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1445 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1447 bp->bottle_list = glGenLists (1);
1448 bp->ball_list = glGenLists (1);
1450 generate_bottle (mi);
1451 generate_static_blobs (mi);
1459 draw_lavalite (ModeInfo *mi)
1461 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1462 Display *dpy = MI_DISPLAY(mi);
1463 Window window = MI_WINDOW(mi);
1465 if (!bp->glx_context)
1468 glMatrixMode (GL_MODELVIEW);
1472 double cx, cy, cz; /* camera position, 0-1. */
1473 double px, py, pz; /* object position, 0-1. */
1474 double rx, ry, rz; /* object rotation, 0-1. */
1476 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1477 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1479 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1480 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1487 #else /* #### this crud doesn't really work yet */
1490 /* We have c[xyz] parameters describing a camera position, but we don't
1491 want to just map those to points in space: the lamp doesn't look very
1492 good from the inside, or from underneath...
1494 Good observation points form a ring around the lamp: basically, a
1495 torus ringing the lamp, parallel to the lamp's floor.
1497 We interpret cz as distance from the origin.
1499 cx is then used as position in the torus (theta).
1503 double cx2, cy2, cz2;
1510 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1511 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1513 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1514 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1525 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1531 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1534 /* Place the lights relative to the object, before the object has
1535 been rotated or wandered within the scene. */
1536 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1537 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1538 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1541 /* Position the lamp in the scene according to the "wander" settings */
1542 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1544 /* Rotate the object according to the "spin" settings */
1545 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1546 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1547 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1549 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1552 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1553 case GIANT: glTranslatef (0, 0, 0.33); break;
1554 case CONE: glTranslatef (0, 0, 0.16); break;
1555 case ROCKET: glTranslatef (0, 0, 0.30);
1556 glScalef (0.85,0.85,0.85); break;
1557 default: abort(); break;
1563 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1564 glCallList (bp->bottle_list);
1565 glCallList (bp->ball_list);
1568 if (mi->fps_p) do_fps (mi);
1571 glXSwapBuffers(dpy, window);