1 /* lavalite --- 3D Simulation a Lava Lite, written by jwz.
3 * This software Copyright (c) 2002 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: 10000 \n" \
97 "*showFPS: False \n" \
98 "*wireframe: False \n" \
99 "*count: " DEF_COUNT " \n" \
100 "*style: " DEF_STYLE " \n" \
101 "*speed: " DEF_SPEED " \n" \
102 "*spin: " DEF_SPIN "\n" \
103 "*wander: " DEF_WANDER "\n" \
104 "*resolution: " DEF_RESOLUTION "\n" \
105 "*smooth: " DEF_SMOOTH "\n" \
106 "*impatient: " DEF_IMPATIENT " \n" \
107 "*geometry: 600x900\n" \
108 "*lavaColor: " DEF_LCOLOR "\n" \
109 "*fluidColor: " DEF_FCOLOR "\n" \
110 "*baseColor: " DEF_BCOLOR "\n" \
111 "*tableColor: " DEF_TCOLOR "\n" \
112 "*fluidTexture: " DEF_FTEX "\n" \
113 "*baseTexture: " DEF_BTEX "\n" \
114 "*tableTexture: " DEF_TTEX "\n" \
117 #define BLOBS_PER_GROUP 4
119 #define GRAVITY 0.000013 /* odwnward acceleration */
120 #define CONVECTION 0.005 /* initial upward velocity (bell curve) */
121 #define TILT 0.00166666 /* horizontal velocity (bell curve) */
124 #define countof(x) (sizeof((x))/sizeof((*x)))
127 #define ABS(n) ((n)<0?-(n):(n))
129 #define SIGNOF(n) ((n)<0?-1:1)
131 #include "xlockmore.h"
132 #include "marching.h"
134 #include "gltrackball.h"
135 #include "xpm-ximage.h"
138 #ifdef USE_GL /* whole file */
143 typedef struct metaball metaball;
150 double r; /* hard radius */
151 double R; /* radius of field of influence */
153 double z; /* vertical position */
154 double pos_r; /* position on horizontal circle */
155 double pos_th; /* position on horizontal circle */
156 double dr, dz; /* current velocity */
158 double x, y; /* h planar position - compused from the above */
160 metaball *leader; /* stay close to this other ball */
164 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
165 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
171 GLfloat texture_elevation;
174 static lamp_geometry classic_lamp[] = {
175 { CAP, 1.16, 0.089, 0.00 },
176 { BOTTLE, 0.97, 0.120, 0.40 },
177 { BOTTLE, 0.13, 0.300, 0.87 },
178 { BOTTLE, 0.07, 0.300, 0.93 },
179 { BASE, 0.00, 0.280, 0.00 },
180 { BASE, -0.40, 0.120, 0.50 },
181 { BASE, -0.80, 0.280, 1.00 },
185 static lamp_geometry giant_lamp[] = {
186 { CAP, 1.12, 0.105, 0.00 },
187 { BOTTLE, 0.97, 0.130, 0.30 },
188 { BOTTLE, 0.20, 0.300, 0.87 },
189 { BOTTLE, 0.15, 0.300, 0.93 },
190 { BASE, 0.00, 0.230, 0.00 },
191 { BASE, -0.18, 0.140, 0.20 },
192 { BASE, -0.80, 0.280, 1.00 },
196 static lamp_geometry cone_lamp[] = {
197 { CAP, 1.35, 0.001, 0.00 },
198 { CAP, 1.35, 0.020, 0.00 },
199 { CAP, 1.30, 0.055, 0.05 },
200 { BOTTLE, 0.97, 0.120, 0.40 },
201 { BOTTLE, 0.13, 0.300, 0.87 },
202 { BASE, 0.00, 0.300, 0.00 },
203 { BASE, -0.04, 0.320, 0.04 },
204 { BASE, -0.60, 0.420, 0.50 },
208 static lamp_geometry rocket_lamp[] = {
209 { CAP, 1.35, 0.001, 0.00 },
210 { CAP, 1.34, 0.020, 0.00 },
211 { CAP, 1.30, 0.055, 0.05 },
212 { BOTTLE, 0.97, 0.120, 0.40 },
213 { BOTTLE, 0.13, 0.300, 0.87 },
214 { BOTTLE, 0.07, 0.300, 0.93 },
215 { BASE, 0.00, 0.280, 0.00 },
216 { BASE, -0.50, 0.180, 0.50 },
217 { BASE, -0.75, 0.080, 0.75 },
218 { BASE, -0.80, 0.035, 0.80 },
219 { BASE, -0.90, 0.035, 1.00 },
226 GLXContext *glx_context;
228 lamp_geometry *model;
231 trackball_state *trackball;
234 GLfloat max_bottle_radius; /* radius of widest part of the bottle */
236 GLfloat launch_chance; /* how often to percolate */
237 int blobs_per_group; /* how many metaballs we launch at once */
238 Bool just_started_p; /* so we launch some goo right away */
240 int grid_size; /* resolution for marching-cubes */
247 Bool spin_x, spin_y, spin_z; /* spin of the scene overall */
249 int bottle_poly_count; /* polygons in the bottle only */
251 } lavalite_configuration;
253 static lavalite_configuration *bps = NULL;
255 static char *do_spin;
256 static char *do_style;
257 static GLfloat speed;
258 static Bool do_wander;
259 static int resolution;
260 static Bool do_smooth;
261 static Bool do_impatient;
263 static char *lava_color_str, *fluid_color_str, *base_color_str,
265 static char *fluid_tex, *base_tex, *table_tex;
267 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
268 static GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
269 static GLfloat lava_shininess = 128.0;
270 static GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
272 static GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
273 static GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
274 static GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
278 static XrmOptionDescRec opts[] = {
279 { "-style", ".style", XrmoptionSepArg, 0 },
280 { "-spin", ".spin", XrmoptionSepArg, 0 },
281 { "+spin", ".spin", XrmoptionNoArg, "" },
282 { "-speed", ".speed", XrmoptionSepArg, 0 },
283 { "-wander", ".wander", XrmoptionNoArg, "True" },
284 { "+wander", ".wander", XrmoptionNoArg, "False" },
285 { "-resolution", ".resolution", XrmoptionSepArg, 0 },
286 { "-smooth", ".smooth", XrmoptionNoArg, "True" },
287 { "+smooth", ".smooth", XrmoptionNoArg, "False" },
288 { "-impatient", ".impatient", XrmoptionNoArg, "True" },
289 { "+impatient", ".impatient", XrmoptionNoArg, "False" },
291 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
292 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
293 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
294 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
296 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
297 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
298 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
301 static argtype vars[] = {
302 {(caddr_t *) &do_style, "style", "Style", DEF_STYLE, t_String},
303 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
304 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
305 {(caddr_t *) &speed, "speed", "Speed", DEF_SPEED, t_Float},
306 {(caddr_t *) &resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
307 {(caddr_t *) &do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
308 {(caddr_t *) &do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
310 {(caddr_t *) &lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR,t_String},
311 {(caddr_t *) &fluid_color_str,"fluidColor","FluidColor",DEF_FCOLOR,t_String},
312 {(caddr_t *) &base_color_str, "baseColor", "BaseColor", DEF_BCOLOR,t_String},
313 {(caddr_t *) &table_color_str,"tableColor","TableColor",DEF_TCOLOR,t_String},
315 {(caddr_t *) &fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
316 {(caddr_t *) &base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
317 {(caddr_t *) &table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
320 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
323 /* Window management, etc
326 reshape_lavalite (ModeInfo *mi, int width, int height)
328 GLfloat h = (GLfloat) height / (GLfloat) width;
330 glViewport (0, 0, (GLint) width, (GLint) height);
332 glMatrixMode(GL_PROJECTION);
334 gluPerspective (30.0, 1/h, 1.0, 100.0);
336 glMatrixMode(GL_MODELVIEW);
338 gluLookAt( 0.0, 0.0, 30.0,
342 glClear(GL_COLOR_BUFFER_BIT);
351 load_texture (ModeInfo *mi, const char *filename)
353 Display *dpy = mi->dpy;
354 Visual *visual = mi->xgwa.visual;
355 Colormap cmap = mi->xgwa.colormap;
361 !strcasecmp (filename, "(none)"))
363 glDisable (GL_TEXTURE_2D);
367 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
370 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
371 image->width, image->height, 0,
372 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
373 sprintf (buf, "texture: %.100s (%dx%d)",
374 filename, image->width, image->height);
377 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
378 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
379 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
380 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
381 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
382 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
383 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
385 glEnable (GL_TEXTURE_2D);
391 /* Generating the lamp's bottle, caps, and base.
395 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
399 GLfloat step = M_PI * 2 / faces;
403 glFrontFace (up_p ? GL_CW : GL_CCW);
404 glNormal3f (0, (up_p ? 1 : -1), 0);
405 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
410 for (j = 0, th = 0; j <= faces; j++)
412 glTexCoord2f (-j / (GLfloat) faces, 1);
413 glVertex3f (0, z, 0);
415 glTexCoord2f (-j / (GLfloat) faces, 0);
416 glVertex3f (x, z, y);
422 glTexCoord2f (-j / (GLfloat) faces, 0);
423 glVertex3f (x, z, y);
434 draw_tube (GLfloat r0, GLfloat r1,
435 GLfloat z0, GLfloat z1,
436 GLfloat t0, GLfloat t1,
437 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
441 GLfloat x, y, x0=0, y0=0;
442 GLfloat step = M_PI * 2 / faces;
446 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
447 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
459 if (smooth_p) faces++;
461 for (i = 0; i < faces; i++)
463 int nsign = (inside_out_p ? -1 : 1);
466 glNormal3f (x * nsign, z1, y * nsign);
468 glNormal3f (x0 * nsign, z1, y0 * nsign);
470 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
471 glVertex3f (x * r1, z1, y * r1);
473 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
474 glVertex3f (x * r0, z0, y * r0);
485 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
486 glVertex3f (x * r0, z0, y * r0);
488 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
489 glVertex3f (x * r1, z1, y * r1);
501 draw_table (GLfloat z, Bool wire)
504 GLfloat step = M_PI * 2 / faces;
510 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
514 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
518 glTexCoord2f (-0.5, 0.5);
522 for (j = 0, th = 0; j <= faces; j++)
524 GLfloat x = cos (th);
525 GLfloat y = sin (th);
526 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
527 glVertex3f(x*s, z, y*s);
537 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
539 static int coords[2][8][2] = {
560 int maxx = coords[0][countof(coords[0])-1][0];
561 int maxy = coords[0][countof(coords[0])-1][1];
564 for (x = 1; x < countof(coords[0]); x++)
566 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
567 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
568 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
569 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
570 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
571 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
572 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
573 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
579 glNormal3f (0, 0, -1);
580 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
582 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
583 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
584 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
585 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
591 glFrontFace (GL_CCW);
592 glNormal3f (0, 0, -1);
593 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
594 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
595 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
596 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
597 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
603 glFrontFace (GL_CCW);
604 glNormal3f (1, -1, 0); /* #### wrong */
605 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
606 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
607 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
608 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
609 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
616 glNormal3f (-1, 1, 0); /* #### wrong */
617 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
618 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
619 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
620 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
621 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
634 generate_bottle (ModeInfo *mi)
636 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
637 int wire = MI_IS_WIREFRAME(mi);
638 int faces = resolution * 1.5;
639 Bool smooth = do_smooth;
640 Bool have_texture = False;
642 lamp_geometry *top_slice = bp->model;
643 const char *current_texture = 0;
644 lamp_part last_part = 0;
646 if (faces < 3) faces = 3;
647 else if (wire && faces > 20) faces = 20;
648 else if (faces > 60) faces = 60;
650 bp->bottle_poly_count = 0;
652 glNewList (bp->bottle_list, GL_COMPILE);
655 glRotatef (90, 1, 0, 0);
656 glTranslatef (0, -0.5, 0);
658 /* All parts of the lamp use the same specularity and shininess. */
659 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
660 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
664 lamp_geometry *bot_slice = top_slice + 1;
666 const char *texture = 0;
670 glDisable (GL_LIGHT2);
672 switch (top_slice->part)
682 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
689 have_texture = False;
690 if (!wire && texture && texture != current_texture)
692 current_texture = texture;
693 have_texture = load_texture (mi, current_texture);
696 /* Color the discs darker than the tube walls. */
697 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
699 /* Do a top disc if this is the first slice of the CAP or BASE.
701 if ((top_slice->part == CAP && last_part == 0) ||
702 (top_slice->part == BASE && last_part == BOTTLE))
703 bp->bottle_poly_count +=
704 draw_disc (top_slice->radius, top_slice->elevation, faces,
707 /* Do a bottom disc if this is the last slice of the CAP or BASE.
709 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
710 (top_slice->part == BASE && bot_slice->part == 0))
712 lamp_geometry *sl = (bot_slice->part == 0 ? top_slice : bot_slice);
713 bp->bottle_poly_count +=
714 draw_disc (sl->radius, sl->elevation, faces, False, wire);
717 if (bot_slice->part == 0) /* done! */
722 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
724 t0 = top_slice->texture_elevation;
725 t1 = bot_slice->texture_elevation;
727 /* Restart the texture coordinates for the glass.
729 if (top_slice->part == BOTTLE)
731 Bool first_p = (top_slice[-1].part != BOTTLE);
732 Bool last_p = (bot_slice->part != BOTTLE);
737 bp->bottle_poly_count +=
738 draw_tube (top_slice->radius, bot_slice->radius,
739 top_slice->elevation, bot_slice->elevation,
742 (top_slice->part == BOTTLE),
745 last_part = top_slice->part;
749 if (bp->style == ROCKET)
752 for (i = 0; i < 3; i++)
755 glRotatef (120 * i, 0, 1, 0);
756 glTranslatef (0.14, -0.05, 0);
757 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
760 glTranslatef (0, -0.1, 0); /* move floor down a little */
764 have_texture = !wire && load_texture (mi, table_tex);
765 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
766 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
770 glDisable (GL_TEXTURE_2D); /* done with textured objects */
777 /* Generating blobbies
781 bellrand (double extent) /* like frand(), but a bell curve. */
783 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
788 static void move_ball (ModeInfo *mi, metaball *b);
790 /* Bring a ball into play, and re-randomize its values.
793 reset_ball (ModeInfo *mi, metaball *b)
795 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
798 b->R = 0.12 + bellrand(0.10);
800 b->pos_r = bellrand (0.9);
801 b->pos_th = frand(M_PI*2);
804 b->dr = bellrand(TILT);
816 /* returns the first metaball that is not in use, or 0.
819 get_ball (ModeInfo *mi)
821 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
823 for (i = 0; i < bp->nballs; i++)
825 metaball *b = &bp->balls[i];
833 /* Generate the blobs that don't move: the ones at teh top and bottom
834 that are part of the scenery.
837 generate_static_blobs (ModeInfo *mi)
839 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
850 /* the giant blob at the bottom of the bottle.
860 /* the small blob at the top of the bottle.
870 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
872 for (i = 0; i < bp->blobs_per_group; i++)
886 max_bottle_radius (lavalite_configuration *bp)
889 lamp_geometry *slice;
890 for (slice = bp->model; slice->part != 0; slice++)
892 if (slice->part == BOTTLE && slice->radius > r)
893 r = slice->radius; /* top */
894 if (slice[1].radius > r)
895 r = slice[1].radius; /* bottom */
902 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
904 GLfloat topz = -999, botz = -999, topr, botr;
905 lamp_geometry *slice;
908 for (slice = bp->model; slice->part != 0; slice++)
909 if (z > slice->elevation)
912 topz = slice->elevation;
913 topr = slice->radius;
916 if (topz == -999) return 0;
918 for (; slice->part != 0; slice++)
919 if (z > slice->elevation)
921 botz = slice->elevation;
922 botr = slice->radius;
925 if (botz == -999) return 0;
927 ratio = (z - botz) / (topz - botz);
929 return (botr + ((topr - botr) * ratio));
934 move_ball (ModeInfo *mi, metaball *b)
936 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
937 double gravity = GRAVITY;
940 if (b->static_p) return;
952 else if (b->pos_r < 0)
954 b->pos_r = -b->pos_r;
958 real_r = b->pos_r * bottle_radius_at (bp, b->z);
960 b->x = cos (b->pos_th) * real_r;
961 b->y = sin (b->pos_th) * real_r;
963 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
968 /* This function makes sure that balls that are part of a group always stay
969 relatively close to each other.
972 clamp_balls (ModeInfo *mi)
974 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
976 for (i = 0; i < bp->nballs; i++)
978 metaball *b = &bp->balls[i];
979 if (b->alive_p && b->leader)
982 double minz = b->leader->z - zslack;
983 double maxz = b->leader->z + zslack;
985 /* Try to keep the Z values near those of the leader.
986 Don't let it go out of range (above or below) and clamp it
987 if it does. If we've clamped it, make sure dz will be
988 moving it in the right direction (back toward the leader.)
990 We aren't currently clamping r, only z -- doesn't seem to
993 This is kind of flaky, I think. Sometimes you can see
994 the blobbies "twitch". That's no good.
999 if (b->dz < 0) b->dz = -b->dz;
1000 b->z = minz - b->dz;
1005 if (b->dz > 0) b->dz = -b->dz;
1006 b->z = maxz + b->dz;
1014 move_balls (ModeInfo *mi) /* for great justice */
1016 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1018 for (i = 0; i < bp->nballs; i++)
1020 metaball *b = &bp->balls[i];
1030 /* Rendering blobbies using marching cubes.
1034 compute_metaball_influence (lavalite_configuration *bp,
1035 double x, double y, double z,
1036 int nballs, metaball *balls)
1041 for (i = 0; i < nballs; i++)
1043 metaball *b = &balls[i];
1045 double d2, r, R, r2, R2;
1047 if (!b->alive_p) continue;
1054 if (dx > R || dx < -R || /* quick check before multiplying */
1055 dy > R || dy < -R ||
1059 d2 = (dx*dx + dy*dy + dz*dz);
1065 if (d2 <= r2) /* (d <= r) inside the hard radius */
1067 else if (d2 > R2) /* (d > R) outside the radius of influence */
1069 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1071 /* was: vv += 1 - ((d-r) / (R-r)); */
1072 vv += 1 - ((d2-r2) / (R2-r2));
1080 /* callback for marching_cubes() */
1082 obj_init (double grid_size, void *closure)
1084 lavalite_configuration *bp = (lavalite_configuration *) closure;
1085 bp->grid_size = grid_size;
1091 /* Returns True if the given point is outside of the glass tube.
1094 clipped_by_glass_p (double x, double y, double z,
1095 lavalite_configuration *bp)
1097 double d2, or, or2, ir2;
1099 or = bp->max_bottle_radius;
1101 if (x > or || x < -or || /* quick check before multiplying */
1106 or = bottle_radius_at (bp, z);
1110 if (d2 > or2) /* (sqrt(d) > or) */
1115 if (d2 > ir2) /* (sqrt(d) > ir) */
1119 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1120 return (1 - (d2-dr2) / (dr1-dr2));
1128 /* callback for marching_cubes() */
1130 obj_compute (double x, double y, double z, void *closure)
1132 lavalite_configuration *bp = (lavalite_configuration *) closure;
1135 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1139 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1142 clip = clipped_by_glass_p (x, y, z, bp);
1143 if (clip == 0) return 0;
1146 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1150 /* callback for marching_cubes() */
1152 obj_free (void *closure)
1157 /* Send a new blob travelling upward.
1158 This blob will actually be composed of N metaballs that are near
1162 launch_balls (ModeInfo *mi)
1164 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1165 metaball *b0 = get_ball (mi);
1169 reset_ball (mi, b0);
1171 for (i = 0; i < bp->blobs_per_group; i++)
1173 metaball *b1 = get_ball (mi);
1177 reset_ball (mi, b1);
1180 # define FROB(FIELD,AMT) \
1181 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1183 /* FROB (pos_r, 0.7); */
1184 /* FROB (pos_th, 0.7); */
1194 animate_lava (ModeInfo *mi)
1196 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1197 int wire = MI_IS_WIREFRAME(mi);
1198 Bool just_started_p = bp->just_started_p;
1200 double isolevel = 0.3;
1202 /* Maybe bubble a new blobby to the surface.
1204 if (just_started_p ||
1205 frand(1.0) < bp->launch_chance)
1207 bp->just_started_p = False;
1210 if (do_impatient && just_started_p)
1215 for (i = 0; i < bp->nballs; i++)
1217 metaball *b = &bp->balls[i];
1218 if (b->alive_p && !b->static_p && !b->leader &&
1228 glNewList (bp->ball_list, GL_COMPILE);
1231 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1232 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1233 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1235 /* For the blobbies, the origin is on the axis at the bottom of the
1236 glass bottle; and the top of the bottle is +1 on Z.
1238 glTranslatef (0, 0, -0.5);
1240 mi->polygon_count = 0;
1242 double s = 1.0/bp->grid_size;
1244 glTranslatef (-0.5, -0.5, 0);
1246 marching_cubes (resolution, isolevel, wire, do_smooth,
1247 obj_init, obj_compute, obj_free, bp,
1248 &mi->polygon_count);
1252 mi->polygon_count += bp->bottle_poly_count;
1260 /* Startup initialization
1264 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1266 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1268 if (event->xany.type == ButtonPress &&
1269 event->xbutton.button & Button1)
1271 bp->button_down_p = True;
1272 gltrackball_start (bp->trackball,
1273 event->xbutton.x, event->xbutton.y,
1274 MI_WIDTH (mi), MI_HEIGHT (mi));
1277 else if (event->xany.type == ButtonRelease &&
1278 event->xbutton.button & Button1)
1280 bp->button_down_p = False;
1283 else if (event->xany.type == MotionNotify &&
1286 gltrackball_track (bp->trackball,
1287 event->xmotion.x, event->xmotion.y,
1288 MI_WIDTH (mi), MI_HEIGHT (mi));
1297 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1300 a[4] = 1.0; /* alpha */
1302 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1304 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1307 a[0] = c.red / 65536.0;
1308 a[1] = c.green / 65536.0;
1309 a[2] = c.blue / 65536.0;
1314 init_lavalite (ModeInfo *mi)
1316 lavalite_configuration *bp;
1317 int wire = MI_IS_WIREFRAME(mi);
1320 bps = (lavalite_configuration *)
1321 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1323 fprintf(stderr, "%s: out of memory\n", progname);
1327 bp = &bps[MI_SCREEN(mi)];
1330 bp = &bps[MI_SCREEN(mi)];
1332 bp->glx_context = init_GL(mi);
1334 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1340 if (*s == ' ' || *s == '\t') ;
1341 else if (*s == 'x' || *s == 'X') bp->spin_x = 1;
1342 else if (*s == 'y' || *s == 'Y') bp->spin_y = 1;
1343 else if (*s == 'z' || *s == 'Z') bp->spin_z = 1;
1347 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1355 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1356 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1357 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1358 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1359 else if (!strcasecmp (s, "random"))
1361 if (random() & 1) bp->style = CLASSIC; /* half the time */
1362 else bp->style = (random() % ((int) ROCKET+1));
1367 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1373 parse_color (mi, "lava", lava_color_str, lava_color);
1374 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1375 parse_color (mi, "base", base_color_str, base_color);
1376 parse_color (mi, "table", table_color_str, table_color);
1380 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1381 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1382 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1383 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1385 glEnable(GL_LIGHTING);
1386 glEnable(GL_LIGHT0);
1387 glEnable(GL_LIGHT1);
1388 glEnable(GL_DEPTH_TEST);
1389 glEnable(GL_CULL_FACE);
1390 glEnable(GL_NORMALIZE);
1391 glShadeModel(GL_SMOOTH);
1393 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1394 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1395 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1397 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1398 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1399 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1401 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1402 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1403 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1407 Bool spinx=False, spiny=False, spinz=False;
1408 double spin_speed = 0.4;
1409 double wander_speed = 0.03;
1414 if (*s == 'x' || *s == 'X') spinx = True;
1415 else if (*s == 'y' || *s == 'Y') spiny = True;
1416 else if (*s == 'z' || *s == 'Z') spinz = True;
1420 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1427 bp->rot = make_rotator (spinx ? spin_speed : 0,
1428 spiny ? spin_speed : 0,
1429 spinz ? spin_speed : 0,
1431 do_wander ? wander_speed : 0,
1433 bp->rot2 = make_rotator (spin_speed, 0, 0,
1436 bp->trackball = gltrackball_init ();
1438 /* move initial camera position up by around 15 degrees:
1439 in other words, tilt the scene toward the viewer. */
1440 gltrackball_start (bp->trackball, 50, 50, 100, 100);
1441 gltrackball_track (bp->trackball, 50, 5, 100, 100);
1443 /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1444 look like we're looking up at it instead of odwn at it! */
1445 if (bp->style == GIANT)
1446 gltrackball_track (bp->trackball, 50, -12, 100, 100);
1447 else if (bp->style == ROCKET) /* same for rocket, but not as much */
1448 gltrackball_track (bp->trackball, 50, -4, 100, 100);
1453 case CLASSIC: bp->model = classic_lamp; break;
1454 case GIANT: bp->model = giant_lamp; break;
1455 case CONE: bp->model = cone_lamp; break;
1456 case ROCKET: bp->model = rocket_lamp; break;
1457 default: abort(); break;
1460 bp->max_bottle_radius = max_bottle_radius (bp);
1462 bp->launch_chance = speed;
1463 bp->blobs_per_group = BLOBS_PER_GROUP;
1464 bp->just_started_p = True;
1466 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1468 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1470 bp->bottle_list = glGenLists (1);
1471 bp->ball_list = glGenLists (1);
1473 generate_bottle (mi);
1474 generate_static_blobs (mi);
1482 draw_lavalite (ModeInfo *mi)
1484 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1485 Display *dpy = MI_DISPLAY(mi);
1486 Window window = MI_WINDOW(mi);
1488 if (!bp->glx_context)
1491 glMatrixMode (GL_MODELVIEW);
1495 double cx, cy, cz; /* camera position, 0-1. */
1496 double px, py, pz; /* object position, 0-1. */
1497 double rx, ry, rz; /* object rotation, 0-1. */
1499 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1500 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1502 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1503 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1510 #else /* #### this crud doesn't really work yet */
1513 /* We have c[xyz] parameters describing a camera position, but we don't
1514 want to just map those to points in space: the lamp doesn't look very
1515 good from the inside, or from underneath...
1517 Good observation points form a ring around the lamp: basically, a
1518 torus ringing the lamp, parallel to the lamp's floor.
1520 We interpret cz as distance from the origin.
1522 cx is then used as position in the torus (theta).
1526 double cx2, cy2, cz2;
1533 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1534 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1536 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1537 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1548 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1554 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1557 /* Place the lights relative to the object, before the object has
1558 been rotated or wandered within the scene. */
1559 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1560 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1561 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1564 /* Position the lamp in the scene according to the "wander" settings */
1565 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1567 /* Rotate the object according to the "spin" settings */
1568 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1569 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1570 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1572 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1575 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1576 case GIANT: glTranslatef (0, 0, 0.33); break;
1577 case CONE: glTranslatef (0, 0, 0.16); break;
1578 case ROCKET: glTranslatef (0, 0, 0.30);
1579 glScalef (0.85,0.85,0.85); break;
1580 default: abort(); break;
1586 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1587 glCallList (bp->bottle_list);
1588 glCallList (bp->ball_list);
1591 if (mi->fps_p) do_fps (mi);
1594 glXSwapBuffers(dpy, window);