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 int bottle_poly_count; /* polygons in the bottle only */
249 } lavalite_configuration;
251 static lavalite_configuration *bps = NULL;
253 static char *do_spin;
254 static char *do_style;
255 static GLfloat speed;
256 static Bool do_wander;
257 static int resolution;
258 static Bool do_smooth;
259 static Bool do_impatient;
261 static char *lava_color_str, *fluid_color_str, *base_color_str,
263 static char *fluid_tex, *base_tex, *table_tex;
265 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
266 static GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
267 static GLfloat lava_shininess = 128.0;
268 static GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
270 static GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
271 static GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
272 static GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
276 static XrmOptionDescRec opts[] = {
277 { "-style", ".style", XrmoptionSepArg, 0 },
278 { "-spin", ".spin", XrmoptionSepArg, 0 },
279 { "+spin", ".spin", XrmoptionNoArg, "" },
280 { "-speed", ".speed", XrmoptionSepArg, 0 },
281 { "-wander", ".wander", XrmoptionNoArg, "True" },
282 { "+wander", ".wander", XrmoptionNoArg, "False" },
283 { "-resolution", ".resolution", XrmoptionSepArg, 0 },
284 { "-smooth", ".smooth", XrmoptionNoArg, "True" },
285 { "+smooth", ".smooth", XrmoptionNoArg, "False" },
286 { "-impatient", ".impatient", XrmoptionNoArg, "True" },
287 { "+impatient", ".impatient", XrmoptionNoArg, "False" },
289 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
290 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
291 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
292 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
294 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
295 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
296 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
299 static argtype vars[] = {
300 {(caddr_t *) &do_style, "style", "Style", DEF_STYLE, t_String},
301 {(caddr_t *) &do_spin, "spin", "Spin", DEF_SPIN, t_String},
302 {(caddr_t *) &do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
303 {(caddr_t *) &speed, "speed", "Speed", DEF_SPEED, t_Float},
304 {(caddr_t *) &resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
305 {(caddr_t *) &do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
306 {(caddr_t *) &do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
308 {(caddr_t *) &lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR,t_String},
309 {(caddr_t *) &fluid_color_str,"fluidColor","FluidColor",DEF_FCOLOR,t_String},
310 {(caddr_t *) &base_color_str, "baseColor", "BaseColor", DEF_BCOLOR,t_String},
311 {(caddr_t *) &table_color_str,"tableColor","TableColor",DEF_TCOLOR,t_String},
313 {(caddr_t *) &fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
314 {(caddr_t *) &base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
315 {(caddr_t *) &table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
318 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
321 /* Window management, etc
324 reshape_lavalite (ModeInfo *mi, int width, int height)
326 GLfloat h = (GLfloat) height / (GLfloat) width;
328 glViewport (0, 0, (GLint) width, (GLint) height);
330 glMatrixMode(GL_PROJECTION);
332 gluPerspective (30.0, 1/h, 1.0, 100.0);
334 glMatrixMode(GL_MODELVIEW);
336 gluLookAt( 0.0, 0.0, 30.0,
340 glClear(GL_COLOR_BUFFER_BIT);
349 load_texture (ModeInfo *mi, const char *filename)
351 Display *dpy = mi->dpy;
352 Visual *visual = mi->xgwa.visual;
353 Colormap cmap = mi->xgwa.colormap;
359 !strcasecmp (filename, "(none)"))
361 glDisable (GL_TEXTURE_2D);
365 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
368 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
369 image->width, image->height, 0,
370 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
371 sprintf (buf, "texture: %.100s (%dx%d)",
372 filename, image->width, image->height);
375 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
376 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
377 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
378 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
379 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
380 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
381 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
383 glEnable (GL_TEXTURE_2D);
389 /* Generating the lamp's bottle, caps, and base.
393 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
397 GLfloat step = M_PI * 2 / faces;
401 glFrontFace (up_p ? GL_CW : GL_CCW);
402 glNormal3f (0, (up_p ? 1 : -1), 0);
403 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
408 for (j = 0, th = 0; j <= faces; j++)
410 glTexCoord2f (-j / (GLfloat) faces, 1);
411 glVertex3f (0, z, 0);
413 glTexCoord2f (-j / (GLfloat) faces, 0);
414 glVertex3f (x, z, y);
420 glTexCoord2f (-j / (GLfloat) faces, 0);
421 glVertex3f (x, z, y);
432 draw_tube (GLfloat r0, GLfloat r1,
433 GLfloat z0, GLfloat z1,
434 GLfloat t0, GLfloat t1,
435 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
439 GLfloat x, y, x0=0, y0=0;
440 GLfloat step = M_PI * 2 / faces;
444 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
445 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
457 if (smooth_p) faces++;
459 for (i = 0; i < faces; i++)
461 int nsign = (inside_out_p ? -1 : 1);
464 glNormal3f (x * nsign, z1, y * nsign);
466 glNormal3f (x0 * nsign, z1, y0 * nsign);
468 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
469 glVertex3f (x * r1, z1, y * r1);
471 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
472 glVertex3f (x * r0, z0, y * r0);
483 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
484 glVertex3f (x * r0, z0, y * r0);
486 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
487 glVertex3f (x * r1, z1, y * r1);
499 draw_table (GLfloat z, Bool wire)
502 GLfloat step = M_PI * 2 / faces;
508 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
512 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
516 glTexCoord2f (-0.5, 0.5);
520 for (j = 0, th = 0; j <= faces; j++)
522 GLfloat x = cos (th);
523 GLfloat y = sin (th);
524 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
525 glVertex3f(x*s, z, y*s);
535 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
537 static int coords[2][8][2] = {
558 int maxx = coords[0][countof(coords[0])-1][0];
559 int maxy = coords[0][countof(coords[0])-1][1];
562 for (x = 1; x < countof(coords[0]); x++)
564 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
565 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
566 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
567 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
568 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
569 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
570 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
571 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
577 glNormal3f (0, 0, -1);
578 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
580 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
581 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
582 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
583 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
589 glFrontFace (GL_CCW);
590 glNormal3f (0, 0, -1);
591 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
592 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
593 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
594 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
595 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
601 glFrontFace (GL_CCW);
602 glNormal3f (1, -1, 0); /* #### wrong */
603 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
604 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
605 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
606 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
607 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
614 glNormal3f (-1, 1, 0); /* #### wrong */
615 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
616 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
617 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
618 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
619 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
632 generate_bottle (ModeInfo *mi)
634 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
635 int wire = MI_IS_WIREFRAME(mi);
636 int faces = resolution * 1.5;
637 Bool smooth = do_smooth;
638 Bool have_texture = False;
640 lamp_geometry *top_slice = bp->model;
641 const char *current_texture = 0;
642 lamp_part last_part = 0;
644 if (faces < 3) faces = 3;
645 else if (wire && faces > 20) faces = 20;
646 else if (faces > 60) faces = 60;
648 bp->bottle_poly_count = 0;
650 glNewList (bp->bottle_list, GL_COMPILE);
653 glRotatef (90, 1, 0, 0);
654 glTranslatef (0, -0.5, 0);
656 /* All parts of the lamp use the same specularity and shininess. */
657 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
658 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
662 lamp_geometry *bot_slice = top_slice + 1;
664 const char *texture = 0;
668 glDisable (GL_LIGHT2);
670 switch (top_slice->part)
680 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
687 have_texture = False;
688 if (!wire && texture && texture != current_texture)
690 current_texture = texture;
691 have_texture = load_texture (mi, current_texture);
694 /* Color the discs darker than the tube walls. */
695 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
697 /* Do a top disc if this is the first slice of the CAP or BASE.
699 if ((top_slice->part == CAP && last_part == 0) ||
700 (top_slice->part == BASE && last_part == BOTTLE))
701 bp->bottle_poly_count +=
702 draw_disc (top_slice->radius, top_slice->elevation, faces,
705 /* Do a bottom disc if this is the last slice of the CAP or BASE.
707 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
708 (top_slice->part == BASE && bot_slice->part == 0))
710 lamp_geometry *sl = (bot_slice->part == 0 ? top_slice : bot_slice);
711 bp->bottle_poly_count +=
712 draw_disc (sl->radius, sl->elevation, faces, False, wire);
715 if (bot_slice->part == 0) /* done! */
720 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
722 t0 = top_slice->texture_elevation;
723 t1 = bot_slice->texture_elevation;
725 /* Restart the texture coordinates for the glass.
727 if (top_slice->part == BOTTLE)
729 Bool first_p = (top_slice[-1].part != BOTTLE);
730 Bool last_p = (bot_slice->part != BOTTLE);
735 bp->bottle_poly_count +=
736 draw_tube (top_slice->radius, bot_slice->radius,
737 top_slice->elevation, bot_slice->elevation,
740 (top_slice->part == BOTTLE),
743 last_part = top_slice->part;
747 if (bp->style == ROCKET)
750 for (i = 0; i < 3; i++)
753 glRotatef (120 * i, 0, 1, 0);
754 glTranslatef (0.14, -0.05, 0);
755 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
758 glTranslatef (0, -0.1, 0); /* move floor down a little */
762 have_texture = !wire && load_texture (mi, table_tex);
763 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
764 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
768 glDisable (GL_TEXTURE_2D); /* done with textured objects */
775 /* Generating blobbies
779 bellrand (double extent) /* like frand(), but a bell curve. */
781 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
786 static void move_ball (ModeInfo *mi, metaball *b);
788 /* Bring a ball into play, and re-randomize its values.
791 reset_ball (ModeInfo *mi, metaball *b)
793 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
796 b->R = 0.12 + bellrand(0.10);
798 b->pos_r = bellrand (0.9);
799 b->pos_th = frand(M_PI*2);
802 b->dr = bellrand(TILT);
814 /* returns the first metaball that is not in use, or 0.
817 get_ball (ModeInfo *mi)
819 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
821 for (i = 0; i < bp->nballs; i++)
823 metaball *b = &bp->balls[i];
831 /* Generate the blobs that don't move: the ones at teh top and bottom
832 that are part of the scenery.
835 generate_static_blobs (ModeInfo *mi)
837 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
848 /* the giant blob at the bottom of the bottle.
858 /* the small blob at the top of the bottle.
868 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
870 for (i = 0; i < bp->blobs_per_group; i++)
884 max_bottle_radius (lavalite_configuration *bp)
887 lamp_geometry *slice;
888 for (slice = bp->model; slice->part != 0; slice++)
890 if (slice->part == BOTTLE && slice->radius > r)
891 r = slice->radius; /* top */
892 if (slice[1].radius > r)
893 r = slice[1].radius; /* bottom */
900 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
902 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
903 lamp_geometry *slice;
906 for (slice = bp->model; slice->part != 0; slice++)
907 if (z > slice->elevation)
910 topz = slice->elevation;
911 topr = slice->radius;
914 if (topz == -999) return 0;
916 for (; slice->part != 0; slice++)
917 if (z > slice->elevation)
919 botz = slice->elevation;
920 botr = slice->radius;
923 if (botz == -999) return 0;
925 ratio = (z - botz) / (topz - botz);
927 return (botr + ((topr - botr) * ratio));
932 move_ball (ModeInfo *mi, metaball *b)
934 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
935 double gravity = GRAVITY;
938 if (b->static_p) return;
950 else if (b->pos_r < 0)
952 b->pos_r = -b->pos_r;
956 real_r = b->pos_r * bottle_radius_at (bp, b->z);
958 b->x = cos (b->pos_th) * real_r;
959 b->y = sin (b->pos_th) * real_r;
961 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
966 /* This function makes sure that balls that are part of a group always stay
967 relatively close to each other.
970 clamp_balls (ModeInfo *mi)
972 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
974 for (i = 0; i < bp->nballs; i++)
976 metaball *b = &bp->balls[i];
977 if (b->alive_p && b->leader)
980 double minz = b->leader->z - zslack;
981 double maxz = b->leader->z + zslack;
983 /* Try to keep the Z values near those of the leader.
984 Don't let it go out of range (above or below) and clamp it
985 if it does. If we've clamped it, make sure dz will be
986 moving it in the right direction (back toward the leader.)
988 We aren't currently clamping r, only z -- doesn't seem to
991 This is kind of flaky, I think. Sometimes you can see
992 the blobbies "twitch". That's no good.
997 if (b->dz < 0) b->dz = -b->dz;
1003 if (b->dz > 0) b->dz = -b->dz;
1004 b->z = maxz + b->dz;
1012 move_balls (ModeInfo *mi) /* for great justice */
1014 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1016 for (i = 0; i < bp->nballs; i++)
1018 metaball *b = &bp->balls[i];
1028 /* Rendering blobbies using marching cubes.
1032 compute_metaball_influence (lavalite_configuration *bp,
1033 double x, double y, double z,
1034 int nballs, metaball *balls)
1039 for (i = 0; i < nballs; i++)
1041 metaball *b = &balls[i];
1043 double d2, r, R, r2, R2;
1045 if (!b->alive_p) continue;
1052 if (dx > R || dx < -R || /* quick check before multiplying */
1053 dy > R || dy < -R ||
1057 d2 = (dx*dx + dy*dy + dz*dz);
1063 if (d2 <= r2) /* (d <= r) inside the hard radius */
1065 else if (d2 > R2) /* (d > R) outside the radius of influence */
1067 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1069 /* was: vv += 1 - ((d-r) / (R-r)); */
1070 vv += 1 - ((d2-r2) / (R2-r2));
1078 /* callback for marching_cubes() */
1080 obj_init (double grid_size, void *closure)
1082 lavalite_configuration *bp = (lavalite_configuration *) closure;
1083 bp->grid_size = grid_size;
1089 /* Returns True if the given point is outside of the glass tube.
1092 clipped_by_glass_p (double x, double y, double z,
1093 lavalite_configuration *bp)
1095 double d2, or, or2, ir2;
1097 or = bp->max_bottle_radius;
1099 if (x > or || x < -or || /* quick check before multiplying */
1104 or = bottle_radius_at (bp, z);
1108 if (d2 > or2) /* (sqrt(d) > or) */
1113 if (d2 > ir2) /* (sqrt(d) > ir) */
1117 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1118 return (1 - (d2-dr2) / (dr1-dr2));
1126 /* callback for marching_cubes() */
1128 obj_compute (double x, double y, double z, void *closure)
1130 lavalite_configuration *bp = (lavalite_configuration *) closure;
1133 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1137 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1140 clip = clipped_by_glass_p (x, y, z, bp);
1141 if (clip == 0) return 0;
1144 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1148 /* callback for marching_cubes() */
1150 obj_free (void *closure)
1155 /* Send a new blob travelling upward.
1156 This blob will actually be composed of N metaballs that are near
1160 launch_balls (ModeInfo *mi)
1162 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1163 metaball *b0 = get_ball (mi);
1167 reset_ball (mi, b0);
1169 for (i = 0; i < bp->blobs_per_group; i++)
1171 metaball *b1 = get_ball (mi);
1175 reset_ball (mi, b1);
1178 # define FROB(FIELD,AMT) \
1179 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1181 /* FROB (pos_r, 0.7); */
1182 /* FROB (pos_th, 0.7); */
1192 animate_lava (ModeInfo *mi)
1194 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1195 int wire = MI_IS_WIREFRAME(mi);
1196 Bool just_started_p = bp->just_started_p;
1198 double isolevel = 0.3;
1200 /* Maybe bubble a new blobby to the surface.
1202 if (just_started_p ||
1203 frand(1.0) < bp->launch_chance)
1205 bp->just_started_p = False;
1208 if (do_impatient && just_started_p)
1213 for (i = 0; i < bp->nballs; i++)
1215 metaball *b = &bp->balls[i];
1216 if (b->alive_p && !b->static_p && !b->leader &&
1226 glNewList (bp->ball_list, GL_COMPILE);
1229 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1230 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1231 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1233 /* For the blobbies, the origin is on the axis at the bottom of the
1234 glass bottle; and the top of the bottle is +1 on Z.
1236 glTranslatef (0, 0, -0.5);
1238 mi->polygon_count = 0;
1240 double s = 1.0/bp->grid_size;
1242 glTranslatef (-0.5, -0.5, 0);
1244 marching_cubes (resolution, isolevel, wire, do_smooth,
1245 obj_init, obj_compute, obj_free, bp,
1246 &mi->polygon_count);
1250 mi->polygon_count += bp->bottle_poly_count;
1258 /* Startup initialization
1262 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1264 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1266 if (event->xany.type == ButtonPress &&
1267 event->xbutton.button & Button1)
1269 bp->button_down_p = True;
1270 gltrackball_start (bp->trackball,
1271 event->xbutton.x, event->xbutton.y,
1272 MI_WIDTH (mi), MI_HEIGHT (mi));
1275 else if (event->xany.type == ButtonRelease &&
1276 event->xbutton.button & Button1)
1278 bp->button_down_p = False;
1281 else if (event->xany.type == MotionNotify &&
1284 gltrackball_track (bp->trackball,
1285 event->xmotion.x, event->xmotion.y,
1286 MI_WIDTH (mi), MI_HEIGHT (mi));
1295 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1298 a[4] = 1.0; /* alpha */
1300 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1302 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1305 a[0] = c.red / 65536.0;
1306 a[1] = c.green / 65536.0;
1307 a[2] = c.blue / 65536.0;
1312 init_lavalite (ModeInfo *mi)
1314 lavalite_configuration *bp;
1315 int wire = MI_IS_WIREFRAME(mi);
1318 bps = (lavalite_configuration *)
1319 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1321 fprintf(stderr, "%s: out of memory\n", progname);
1325 bp = &bps[MI_SCREEN(mi)];
1328 bp = &bps[MI_SCREEN(mi)];
1330 bp->glx_context = init_GL(mi);
1332 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1336 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1337 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1338 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1339 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1340 else if (!strcasecmp (s, "random"))
1342 if (random() & 1) bp->style = CLASSIC; /* half the time */
1343 else bp->style = (random() % ((int) ROCKET+1));
1348 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1354 parse_color (mi, "lava", lava_color_str, lava_color);
1355 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1356 parse_color (mi, "base", base_color_str, base_color);
1357 parse_color (mi, "table", table_color_str, table_color);
1361 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1362 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1363 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1364 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1366 glEnable(GL_LIGHTING);
1367 glEnable(GL_LIGHT0);
1368 glEnable(GL_LIGHT1);
1369 glEnable(GL_DEPTH_TEST);
1370 glEnable(GL_CULL_FACE);
1371 glEnable(GL_NORMALIZE);
1372 glShadeModel(GL_SMOOTH);
1374 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1375 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1376 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1378 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1379 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1380 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1382 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1383 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1384 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1388 Bool spinx=False, spiny=False, spinz=False;
1389 double spin_speed = 0.4;
1390 double wander_speed = 0.03;
1395 if (*s == 'x' || *s == 'X') spinx = True;
1396 else if (*s == 'y' || *s == 'Y') spiny = True;
1397 else if (*s == 'z' || *s == 'Z') spinz = True;
1401 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1408 bp->rot = make_rotator (spinx ? spin_speed : 0,
1409 spiny ? spin_speed : 0,
1410 spinz ? spin_speed : 0,
1412 do_wander ? wander_speed : 0,
1414 bp->rot2 = make_rotator (spin_speed, 0, 0,
1417 bp->trackball = gltrackball_init ();
1419 /* move initial camera position up by around 15 degrees:
1420 in other words, tilt the scene toward the viewer. */
1421 gltrackball_start (bp->trackball, 50, 50, 100, 100);
1422 gltrackball_track (bp->trackball, 50, 5, 100, 100);
1424 /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1425 look like we're looking up at it instead of odwn at it! */
1426 if (bp->style == GIANT)
1427 gltrackball_track (bp->trackball, 50, -12, 100, 100);
1428 else if (bp->style == ROCKET) /* same for rocket, but not as much */
1429 gltrackball_track (bp->trackball, 50, -4, 100, 100);
1434 case CLASSIC: bp->model = classic_lamp; break;
1435 case GIANT: bp->model = giant_lamp; break;
1436 case CONE: bp->model = cone_lamp; break;
1437 case ROCKET: bp->model = rocket_lamp; break;
1438 default: abort(); break;
1441 bp->max_bottle_radius = max_bottle_radius (bp);
1443 bp->launch_chance = speed;
1444 bp->blobs_per_group = BLOBS_PER_GROUP;
1445 bp->just_started_p = True;
1447 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1449 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1451 bp->bottle_list = glGenLists (1);
1452 bp->ball_list = glGenLists (1);
1454 generate_bottle (mi);
1455 generate_static_blobs (mi);
1463 draw_lavalite (ModeInfo *mi)
1465 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1466 Display *dpy = MI_DISPLAY(mi);
1467 Window window = MI_WINDOW(mi);
1469 if (!bp->glx_context)
1472 glMatrixMode (GL_MODELVIEW);
1476 double cx, cy, cz; /* camera position, 0-1. */
1477 double px, py, pz; /* object position, 0-1. */
1478 double rx, ry, rz; /* object rotation, 0-1. */
1480 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1481 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1483 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1484 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1491 #else /* #### this crud doesn't really work yet */
1494 /* We have c[xyz] parameters describing a camera position, but we don't
1495 want to just map those to points in space: the lamp doesn't look very
1496 good from the inside, or from underneath...
1498 Good observation points form a ring around the lamp: basically, a
1499 torus ringing the lamp, parallel to the lamp's floor.
1501 We interpret cz as distance from the origin.
1503 cx is then used as position in the torus (theta).
1507 double cx2, cy2, cz2;
1514 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1515 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1517 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1518 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1529 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1535 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1538 /* Place the lights relative to the object, before the object has
1539 been rotated or wandered within the scene. */
1540 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1541 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1542 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1545 /* Position the lamp in the scene according to the "wander" settings */
1546 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1548 /* Rotate the object according to the "spin" settings */
1549 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1550 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1551 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1553 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1556 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1557 case GIANT: glTranslatef (0, 0, 0.33); break;
1558 case CONE: glTranslatef (0, 0, 0.16); break;
1559 case ROCKET: glTranslatef (0, 0, 0.30);
1560 glScalef (0.85,0.85,0.85); break;
1561 default: abort(); break;
1567 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1568 glCallList (bp->bottle_list);
1569 glCallList (bp->ball_list);
1572 if (mi->fps_p) do_fps (mi);
1575 glXSwapBuffers(dpy, window);