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 "*geometry: 640x640 \n" \
100 "*count: " DEF_COUNT " \n" \
101 "*style: " DEF_STYLE " \n" \
102 "*speed: " DEF_SPEED " \n" \
103 "*spin: " DEF_SPIN "\n" \
104 "*wander: " DEF_WANDER "\n" \
105 "*resolution: " DEF_RESOLUTION "\n" \
106 "*smooth: " DEF_SMOOTH "\n" \
107 "*impatient: " DEF_IMPATIENT " \n" \
108 "*geometry: 600x900\n" \
109 "*lavaColor: " DEF_LCOLOR "\n" \
110 "*fluidColor: " DEF_FCOLOR "\n" \
111 "*baseColor: " DEF_BCOLOR "\n" \
112 "*tableColor: " DEF_TCOLOR "\n" \
113 "*fluidTexture: " DEF_FTEX "\n" \
114 "*baseTexture: " DEF_BTEX "\n" \
115 "*tableTexture: " DEF_TTEX "\n" \
118 #define BLOBS_PER_GROUP 4
120 #define GRAVITY 0.000013 /* odwnward acceleration */
121 #define CONVECTION 0.005 /* initial upward velocity (bell curve) */
122 #define TILT 0.00166666 /* horizontal velocity (bell curve) */
125 #define countof(x) (sizeof((x))/sizeof((*x)))
128 #define ABS(n) ((n)<0?-(n):(n))
130 #define SIGNOF(n) ((n)<0?-1:1)
132 #include "xlockmore.h"
133 #include "marching.h"
135 #include "gltrackball.h"
136 #include "xpm-ximage.h"
139 #ifdef USE_GL /* whole file */
144 typedef struct metaball metaball;
151 double r; /* hard radius */
152 double R; /* radius of field of influence */
154 double z; /* vertical position */
155 double pos_r; /* position on horizontal circle */
156 double pos_th; /* position on horizontal circle */
157 double dr, dz; /* current velocity */
159 double x, y; /* h planar position - compused from the above */
161 metaball *leader; /* stay close to this other ball */
165 typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
166 typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
172 GLfloat texture_elevation;
175 static lamp_geometry classic_lamp[] = {
176 { CAP, 1.16, 0.089, 0.00 },
177 { BOTTLE, 0.97, 0.120, 0.40 },
178 { BOTTLE, 0.13, 0.300, 0.87 },
179 { BOTTLE, 0.07, 0.300, 0.93 },
180 { BASE, 0.00, 0.280, 0.00 },
181 { BASE, -0.40, 0.120, 0.50 },
182 { BASE, -0.80, 0.280, 1.00 },
186 static lamp_geometry giant_lamp[] = {
187 { CAP, 1.12, 0.105, 0.00 },
188 { BOTTLE, 0.97, 0.130, 0.30 },
189 { BOTTLE, 0.20, 0.300, 0.87 },
190 { BOTTLE, 0.15, 0.300, 0.93 },
191 { BASE, 0.00, 0.230, 0.00 },
192 { BASE, -0.18, 0.140, 0.20 },
193 { BASE, -0.80, 0.280, 1.00 },
197 static lamp_geometry cone_lamp[] = {
198 { CAP, 1.35, 0.001, 0.00 },
199 { CAP, 1.35, 0.020, 0.00 },
200 { CAP, 1.30, 0.055, 0.05 },
201 { BOTTLE, 0.97, 0.120, 0.40 },
202 { BOTTLE, 0.13, 0.300, 0.87 },
203 { BASE, 0.00, 0.300, 0.00 },
204 { BASE, -0.04, 0.320, 0.04 },
205 { BASE, -0.60, 0.420, 0.50 },
209 static lamp_geometry rocket_lamp[] = {
210 { CAP, 1.35, 0.001, 0.00 },
211 { CAP, 1.34, 0.020, 0.00 },
212 { CAP, 1.30, 0.055, 0.05 },
213 { BOTTLE, 0.97, 0.120, 0.40 },
214 { BOTTLE, 0.13, 0.300, 0.87 },
215 { BOTTLE, 0.07, 0.300, 0.93 },
216 { BASE, 0.00, 0.280, 0.00 },
217 { BASE, -0.50, 0.180, 0.50 },
218 { BASE, -0.75, 0.080, 0.75 },
219 { BASE, -0.80, 0.035, 0.80 },
220 { BASE, -0.90, 0.035, 1.00 },
227 GLXContext *glx_context;
229 lamp_geometry *model;
232 trackball_state *trackball;
235 GLfloat max_bottle_radius; /* radius of widest part of the bottle */
237 GLfloat launch_chance; /* how often to percolate */
238 int blobs_per_group; /* how many metaballs we launch at once */
239 Bool just_started_p; /* so we launch some goo right away */
241 int grid_size; /* resolution for marching-cubes */
248 int bottle_poly_count; /* polygons in the bottle only */
250 } lavalite_configuration;
252 static lavalite_configuration *bps = NULL;
254 static char *do_spin;
255 static char *do_style;
256 static GLfloat speed;
257 static Bool do_wander;
258 static int resolution;
259 static Bool do_smooth;
260 static Bool do_impatient;
262 static char *lava_color_str, *fluid_color_str, *base_color_str,
264 static char *fluid_tex, *base_tex, *table_tex;
266 static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
267 static GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
268 static GLfloat lava_shininess = 128.0;
269 static GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
271 static GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
272 static GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
273 static GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
277 static XrmOptionDescRec opts[] = {
278 { "-style", ".style", XrmoptionSepArg, 0 },
279 { "-spin", ".spin", XrmoptionSepArg, 0 },
280 { "+spin", ".spin", XrmoptionNoArg, "" },
281 { "-speed", ".speed", XrmoptionSepArg, 0 },
282 { "-wander", ".wander", XrmoptionNoArg, "True" },
283 { "+wander", ".wander", XrmoptionNoArg, "False" },
284 { "-resolution", ".resolution", XrmoptionSepArg, 0 },
285 { "-smooth", ".smooth", XrmoptionNoArg, "True" },
286 { "+smooth", ".smooth", XrmoptionNoArg, "False" },
287 { "-impatient", ".impatient", XrmoptionNoArg, "True" },
288 { "+impatient", ".impatient", XrmoptionNoArg, "False" },
290 { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
291 { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
292 { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
293 { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
295 { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
296 { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
297 { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
300 static argtype vars[] = {
301 {&do_style, "style", "Style", DEF_STYLE, t_String},
302 {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
303 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
304 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
305 {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
306 {&do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
307 {&do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
309 {&lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR, t_String},
310 {&fluid_color_str, "fluidColor", "FluidColor", DEF_FCOLOR, t_String},
311 {&base_color_str, "baseColor", "BaseColor", DEF_BCOLOR, t_String},
312 {&table_color_str, "tableColor", "TableColor", DEF_TCOLOR, t_String},
314 {&fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
315 {&base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
316 {&table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
319 ModeSpecOpt sws_opts = {countof(opts), opts, countof(vars), vars, NULL};
322 /* Window management, etc
325 reshape_lavalite (ModeInfo *mi, int width, int height)
327 GLfloat h = (GLfloat) height / (GLfloat) width;
329 glViewport (0, 0, (GLint) width, (GLint) height);
331 glMatrixMode(GL_PROJECTION);
333 gluPerspective (30.0, 1/h, 1.0, 100.0);
335 glMatrixMode(GL_MODELVIEW);
337 gluLookAt( 0.0, 0.0, 30.0,
341 glClear(GL_COLOR_BUFFER_BIT);
350 load_texture (ModeInfo *mi, const char *filename)
352 Display *dpy = mi->dpy;
353 Visual *visual = mi->xgwa.visual;
354 Colormap cmap = mi->xgwa.colormap;
360 !strcasecmp (filename, "(none)"))
362 glDisable (GL_TEXTURE_2D);
366 image = xpm_file_to_ximage (dpy, visual, cmap, filename);
369 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
370 image->width, image->height, 0,
371 GL_RGBA, GL_UNSIGNED_BYTE, image->data);
372 sprintf (buf, "texture: %.100s (%dx%d)",
373 filename, image->width, image->height);
376 glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
377 glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
378 glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
379 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
380 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
381 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
382 glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
384 glEnable (GL_TEXTURE_2D);
390 /* Generating the lamp's bottle, caps, and base.
394 draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
398 GLfloat step = M_PI * 2 / faces;
402 glFrontFace (up_p ? GL_CW : GL_CCW);
403 glNormal3f (0, (up_p ? 1 : -1), 0);
404 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
409 for (j = 0, th = 0; j <= faces; j++)
411 glTexCoord2f (-j / (GLfloat) faces, 1);
412 glVertex3f (0, z, 0);
414 glTexCoord2f (-j / (GLfloat) faces, 0);
415 glVertex3f (x, z, y);
421 glTexCoord2f (-j / (GLfloat) faces, 0);
422 glVertex3f (x, z, y);
433 draw_tube (GLfloat r0, GLfloat r1,
434 GLfloat z0, GLfloat z1,
435 GLfloat t0, GLfloat t1,
436 int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
440 GLfloat x, y, x0=0, y0=0;
441 GLfloat step = M_PI * 2 / faces;
445 glFrontFace (inside_out_p ? GL_CW : GL_CCW);
446 glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
458 if (smooth_p) faces++;
460 for (i = 0; i < faces; i++)
462 int nsign = (inside_out_p ? -1 : 1);
465 glNormal3f (x * nsign, z1, y * nsign);
467 glNormal3f (x0 * nsign, z1, y0 * nsign);
469 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
470 glVertex3f (x * r1, z1, y * r1);
472 glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
473 glVertex3f (x * r0, z0, y * r0);
484 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
485 glVertex3f (x * r0, z0, y * r0);
487 glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
488 glVertex3f (x * r1, z1, y * r1);
500 draw_table (GLfloat z, Bool wire)
503 GLfloat step = M_PI * 2 / faces;
509 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
513 glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
517 glTexCoord2f (-0.5, 0.5);
521 for (j = 0, th = 0; j <= faces; j++)
523 GLfloat x = cos (th);
524 GLfloat y = sin (th);
525 glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
526 glVertex3f(x*s, z, y*s);
536 draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
538 static int coords[2][8][2] = {
559 int maxx = coords[0][countof(coords[0])-1][0];
560 int maxy = coords[0][countof(coords[0])-1][1];
563 for (x = 1; x < countof(coords[0]); x++)
565 GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
566 GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
567 GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
568 GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
569 GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
570 GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
571 GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
572 GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
578 glNormal3f (0, 0, -1);
579 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
581 glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
582 glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
583 glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
584 glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
590 glFrontFace (GL_CCW);
591 glNormal3f (0, 0, -1);
592 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
593 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
594 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
595 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
596 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
602 glFrontFace (GL_CCW);
603 glNormal3f (1, -1, 0); /* #### wrong */
604 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
605 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
606 glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
607 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
608 glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
615 glNormal3f (-1, 1, 0); /* #### wrong */
616 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
617 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
618 glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
619 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
620 glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
633 generate_bottle (ModeInfo *mi)
635 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
636 int wire = MI_IS_WIREFRAME(mi);
637 int faces = resolution * 1.5;
638 Bool smooth = do_smooth;
639 Bool have_texture = False;
641 lamp_geometry *top_slice = bp->model;
642 const char *current_texture = 0;
643 lamp_part last_part = 0;
645 if (faces < 3) faces = 3;
646 else if (wire && faces > 20) faces = 20;
647 else if (faces > 60) faces = 60;
649 bp->bottle_poly_count = 0;
651 glNewList (bp->bottle_list, GL_COMPILE);
654 glRotatef (90, 1, 0, 0);
655 glTranslatef (0, -0.5, 0);
657 /* All parts of the lamp use the same specularity and shininess. */
658 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
659 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
663 lamp_geometry *bot_slice = top_slice + 1;
665 const char *texture = 0;
669 glDisable (GL_LIGHT2);
671 switch (top_slice->part)
681 if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
688 have_texture = False;
689 if (!wire && texture && texture != current_texture)
691 current_texture = texture;
692 have_texture = load_texture (mi, current_texture);
695 /* Color the discs darker than the tube walls. */
696 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
698 /* Do a top disc if this is the first slice of the CAP or BASE.
700 if ((top_slice->part == CAP && last_part == 0) ||
701 (top_slice->part == BASE && last_part == BOTTLE))
702 bp->bottle_poly_count +=
703 draw_disc (top_slice->radius, top_slice->elevation, faces,
706 /* Do a bottom disc if this is the last slice of the CAP or BASE.
708 if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
709 (top_slice->part == BASE && bot_slice->part == 0))
711 lamp_geometry *sl = (bot_slice->part == 0 ? top_slice : bot_slice);
712 bp->bottle_poly_count +=
713 draw_disc (sl->radius, sl->elevation, faces, False, wire);
716 if (bot_slice->part == 0) /* done! */
721 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
723 t0 = top_slice->texture_elevation;
724 t1 = bot_slice->texture_elevation;
726 /* Restart the texture coordinates for the glass.
728 if (top_slice->part == BOTTLE)
730 Bool first_p = (top_slice[-1].part != BOTTLE);
731 Bool last_p = (bot_slice->part != BOTTLE);
736 bp->bottle_poly_count +=
737 draw_tube (top_slice->radius, bot_slice->radius,
738 top_slice->elevation, bot_slice->elevation,
741 (top_slice->part == BOTTLE),
744 last_part = top_slice->part;
748 if (bp->style == ROCKET)
751 for (i = 0; i < 3; i++)
754 glRotatef (120 * i, 0, 1, 0);
755 glTranslatef (0.14, -0.05, 0);
756 bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
759 glTranslatef (0, -0.1, 0); /* move floor down a little */
763 have_texture = !wire && load_texture (mi, table_tex);
764 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
765 bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
769 glDisable (GL_TEXTURE_2D); /* done with textured objects */
776 /* Generating blobbies
780 bellrand (double extent) /* like frand(), but a bell curve. */
782 return (((frand(extent) + frand(extent) + frand(extent)) / 3)
787 static void move_ball (ModeInfo *mi, metaball *b);
789 /* Bring a ball into play, and re-randomize its values.
792 reset_ball (ModeInfo *mi, metaball *b)
794 /* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
797 b->R = 0.12 + bellrand(0.10);
799 b->pos_r = bellrand (0.9);
800 b->pos_th = frand(M_PI*2);
803 b->dr = bellrand(TILT);
815 /* returns the first metaball that is not in use, or 0.
818 get_ball (ModeInfo *mi)
820 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
822 for (i = 0; i < bp->nballs; i++)
824 metaball *b = &bp->balls[i];
832 /* Generate the blobs that don't move: the ones at teh top and bottom
833 that are part of the scenery.
836 generate_static_blobs (ModeInfo *mi)
838 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
849 /* the giant blob at the bottom of the bottle.
859 /* the small blob at the top of the bottle.
869 /* Some extra blobs at the bottom of the bottle, to jumble the surface.
871 for (i = 0; i < bp->blobs_per_group; i++)
885 max_bottle_radius (lavalite_configuration *bp)
888 lamp_geometry *slice;
889 for (slice = bp->model; slice->part != 0; slice++)
891 if (slice->part == BOTTLE && slice->radius > r)
892 r = slice->radius; /* top */
893 if (slice[1].radius > r)
894 r = slice[1].radius; /* bottom */
901 bottle_radius_at (lavalite_configuration *bp, GLfloat z)
903 GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
904 lamp_geometry *slice;
907 for (slice = bp->model; slice->part != 0; slice++)
908 if (z > slice->elevation)
911 topz = slice->elevation;
912 topr = slice->radius;
915 if (topz == -999) return 0;
917 for (; slice->part != 0; slice++)
918 if (z > slice->elevation)
920 botz = slice->elevation;
921 botr = slice->radius;
924 if (botz == -999) return 0;
926 ratio = (z - botz) / (topz - botz);
928 return (botr + ((topr - botr) * ratio));
933 move_ball (ModeInfo *mi, metaball *b)
935 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
936 double gravity = GRAVITY;
939 if (b->static_p) return;
951 else if (b->pos_r < 0)
953 b->pos_r = -b->pos_r;
957 real_r = b->pos_r * bottle_radius_at (bp, b->z);
959 b->x = cos (b->pos_th) * real_r;
960 b->y = sin (b->pos_th) * real_r;
962 if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
967 /* This function makes sure that balls that are part of a group always stay
968 relatively close to each other.
971 clamp_balls (ModeInfo *mi)
973 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
975 for (i = 0; i < bp->nballs; i++)
977 metaball *b = &bp->balls[i];
978 if (b->alive_p && b->leader)
981 double minz = b->leader->z - zslack;
982 double maxz = b->leader->z + zslack;
984 /* Try to keep the Z values near those of the leader.
985 Don't let it go out of range (above or below) and clamp it
986 if it does. If we've clamped it, make sure dz will be
987 moving it in the right direction (back toward the leader.)
989 We aren't currently clamping r, only z -- doesn't seem to
992 This is kind of flaky, I think. Sometimes you can see
993 the blobbies "twitch". That's no good.
998 if (b->dz < 0) b->dz = -b->dz;
1004 if (b->dz > 0) b->dz = -b->dz;
1005 b->z = maxz + b->dz;
1013 move_balls (ModeInfo *mi) /* for great justice */
1015 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1017 for (i = 0; i < bp->nballs; i++)
1019 metaball *b = &bp->balls[i];
1029 /* Rendering blobbies using marching cubes.
1033 compute_metaball_influence (lavalite_configuration *bp,
1034 double x, double y, double z,
1035 int nballs, metaball *balls)
1040 for (i = 0; i < nballs; i++)
1042 metaball *b = &balls[i];
1044 double d2, r, R, r2, R2;
1046 if (!b->alive_p) continue;
1053 if (dx > R || dx < -R || /* quick check before multiplying */
1054 dy > R || dy < -R ||
1058 d2 = (dx*dx + dy*dy + dz*dz);
1064 if (d2 <= r2) /* (d <= r) inside the hard radius */
1066 else if (d2 > R2) /* (d > R) outside the radius of influence */
1068 else /* somewhere in between: linear drop-off from r=1 to R=0 */
1070 /* was: vv += 1 - ((d-r) / (R-r)); */
1071 vv += 1 - ((d2-r2) / (R2-r2));
1079 /* callback for marching_cubes() */
1081 obj_init (double grid_size, void *closure)
1083 lavalite_configuration *bp = (lavalite_configuration *) closure;
1084 bp->grid_size = grid_size;
1090 /* Returns True if the given point is outside of the glass tube.
1093 clipped_by_glass_p (double x, double y, double z,
1094 lavalite_configuration *bp)
1096 double d2, or, or2, ir2;
1098 or = bp->max_bottle_radius;
1100 if (x > or || x < -or || /* quick check before multiplying */
1105 or = bottle_radius_at (bp, z);
1109 if (d2 > or2) /* (sqrt(d) > or) */
1114 if (d2 > ir2) /* (sqrt(d) > ir) */
1118 /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
1119 return (1 - (d2-dr2) / (dr1-dr2));
1127 /* callback for marching_cubes() */
1129 obj_compute (double x, double y, double z, void *closure)
1131 lavalite_configuration *bp = (lavalite_configuration *) closure;
1134 x /= bp->grid_size; /* convert from 0-N to 0-1. */
1138 x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
1141 clip = clipped_by_glass_p (x, y, z, bp);
1142 if (clip == 0) return 0;
1145 compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
1149 /* callback for marching_cubes() */
1151 obj_free (void *closure)
1156 /* Send a new blob travelling upward.
1157 This blob will actually be composed of N metaballs that are near
1161 launch_balls (ModeInfo *mi)
1163 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1164 metaball *b0 = get_ball (mi);
1168 reset_ball (mi, b0);
1170 for (i = 0; i < bp->blobs_per_group; i++)
1172 metaball *b1 = get_ball (mi);
1176 reset_ball (mi, b1);
1179 # define FROB(FIELD,AMT) \
1180 b1->FIELD += (bellrand(AMT) * b0->FIELD)
1182 /* FROB (pos_r, 0.7); */
1183 /* FROB (pos_th, 0.7); */
1193 animate_lava (ModeInfo *mi)
1195 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1196 int wire = MI_IS_WIREFRAME(mi);
1197 Bool just_started_p = bp->just_started_p;
1199 double isolevel = 0.3;
1201 /* Maybe bubble a new blobby to the surface.
1203 if (just_started_p ||
1204 frand(1.0) < bp->launch_chance)
1206 bp->just_started_p = False;
1209 if (do_impatient && just_started_p)
1214 for (i = 0; i < bp->nballs; i++)
1216 metaball *b = &bp->balls[i];
1217 if (b->alive_p && !b->static_p && !b->leader &&
1227 glNewList (bp->ball_list, GL_COMPILE);
1230 glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
1231 glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
1232 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
1234 /* For the blobbies, the origin is on the axis at the bottom of the
1235 glass bottle; and the top of the bottle is +1 on Z.
1237 glTranslatef (0, 0, -0.5);
1239 mi->polygon_count = 0;
1242 if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
1243 s = 1.0/bp->grid_size;
1246 glTranslatef (-0.5, -0.5, 0);
1248 marching_cubes (resolution, isolevel, wire, do_smooth,
1249 obj_init, obj_compute, obj_free, bp,
1250 &mi->polygon_count);
1254 mi->polygon_count += bp->bottle_poly_count;
1262 /* Startup initialization
1266 lavalite_handle_event (ModeInfo *mi, XEvent *event)
1268 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1270 if (event->xany.type == ButtonPress &&
1271 event->xbutton.button & Button1)
1273 bp->button_down_p = True;
1274 gltrackball_start (bp->trackball,
1275 event->xbutton.x, event->xbutton.y,
1276 MI_WIDTH (mi), MI_HEIGHT (mi));
1279 else if (event->xany.type == ButtonRelease &&
1280 event->xbutton.button & Button1)
1282 bp->button_down_p = False;
1285 else if (event->xany.type == MotionNotify &&
1288 gltrackball_track (bp->trackball,
1289 event->xmotion.x, event->xmotion.y,
1290 MI_WIDTH (mi), MI_HEIGHT (mi));
1299 parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
1302 a[4] = 1.0; /* alpha */
1304 if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
1306 fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
1309 a[0] = c.red / 65536.0;
1310 a[1] = c.green / 65536.0;
1311 a[2] = c.blue / 65536.0;
1316 init_lavalite (ModeInfo *mi)
1318 lavalite_configuration *bp;
1319 int wire = MI_IS_WIREFRAME(mi);
1322 bps = (lavalite_configuration *)
1323 calloc (MI_NUM_SCREENS(mi), sizeof (lavalite_configuration));
1325 fprintf(stderr, "%s: out of memory\n", progname);
1329 bp = &bps[MI_SCREEN(mi)];
1332 bp = &bps[MI_SCREEN(mi)];
1334 bp->glx_context = init_GL(mi);
1336 reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1340 if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
1341 else if (!strcasecmp (s, "giant")) bp->style = GIANT;
1342 else if (!strcasecmp (s, "cone")) bp->style = CONE;
1343 else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
1344 else if (!strcasecmp (s, "random"))
1346 if (random() & 1) bp->style = CLASSIC; /* half the time */
1347 else bp->style = (random() % ((int) ROCKET+1));
1352 "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
1358 parse_color (mi, "lava", lava_color_str, lava_color);
1359 parse_color (mi, "fluid", fluid_color_str, fluid_color);
1360 parse_color (mi, "base", base_color_str, base_color);
1361 parse_color (mi, "table", table_color_str, table_color);
1365 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
1366 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
1367 GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
1368 GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
1370 glEnable(GL_LIGHTING);
1371 glEnable(GL_LIGHT0);
1372 glEnable(GL_LIGHT1);
1373 glEnable(GL_DEPTH_TEST);
1374 glEnable(GL_CULL_FACE);
1375 glEnable(GL_NORMALIZE);
1376 glShadeModel(GL_SMOOTH);
1378 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
1379 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
1380 glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
1382 glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
1383 glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
1384 glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
1386 glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
1387 glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
1388 glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
1392 Bool spinx=False, spiny=False, spinz=False;
1393 double spin_speed = 0.4;
1394 double wander_speed = 0.03;
1399 if (*s == 'x' || *s == 'X') spinx = True;
1400 else if (*s == 'y' || *s == 'Y') spiny = True;
1401 else if (*s == 'z' || *s == 'Z') spinz = True;
1405 "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
1412 bp->rot = make_rotator (spinx ? spin_speed : 0,
1413 spiny ? spin_speed : 0,
1414 spinz ? spin_speed : 0,
1416 do_wander ? wander_speed : 0,
1418 bp->rot2 = make_rotator (spin_speed, 0, 0,
1421 bp->trackball = gltrackball_init ();
1423 /* move initial camera position up by around 15 degrees:
1424 in other words, tilt the scene toward the viewer. */
1425 gltrackball_start (bp->trackball, 50, 50, 100, 100);
1426 gltrackball_track (bp->trackball, 50, 5, 100, 100);
1428 /* Oh, but if it's the "Giant" model, tilt the scene away: make it
1429 look like we're looking up at it instead of odwn at it! */
1430 if (bp->style == GIANT)
1431 gltrackball_track (bp->trackball, 50, -12, 100, 100);
1432 else if (bp->style == ROCKET) /* same for rocket, but not as much */
1433 gltrackball_track (bp->trackball, 50, -4, 100, 100);
1438 case CLASSIC: bp->model = classic_lamp; break;
1439 case GIANT: bp->model = giant_lamp; break;
1440 case CONE: bp->model = cone_lamp; break;
1441 case ROCKET: bp->model = rocket_lamp; break;
1442 default: abort(); break;
1445 bp->max_bottle_radius = max_bottle_radius (bp);
1447 bp->launch_chance = speed;
1448 bp->blobs_per_group = BLOBS_PER_GROUP;
1449 bp->just_started_p = True;
1451 bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
1453 bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
1455 bp->bottle_list = glGenLists (1);
1456 bp->ball_list = glGenLists (1);
1458 generate_bottle (mi);
1459 generate_static_blobs (mi);
1467 draw_lavalite (ModeInfo *mi)
1469 lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
1470 Display *dpy = MI_DISPLAY(mi);
1471 Window window = MI_WINDOW(mi);
1473 if (!bp->glx_context)
1476 glMatrixMode (GL_MODELVIEW);
1480 double cx, cy, cz; /* camera position, 0-1. */
1481 double px, py, pz; /* object position, 0-1. */
1482 double rx, ry, rz; /* object rotation, 0-1. */
1484 get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
1485 get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
1487 get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
1488 get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
1495 #else /* #### this crud doesn't really work yet */
1498 /* We have c[xyz] parameters describing a camera position, but we don't
1499 want to just map those to points in space: the lamp doesn't look very
1500 good from the inside, or from underneath...
1502 Good observation points form a ring around the lamp: basically, a
1503 torus ringing the lamp, parallel to the lamp's floor.
1505 We interpret cz as distance from the origin.
1507 cx is then used as position in the torus (theta).
1511 double cx2, cy2, cz2;
1518 cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
1519 d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
1521 cz2 = 0.5 + (d * cos (cx * M_PI * 2));
1522 cx2 = 0.5 + (d * sin (cx * M_PI * 2));
1533 gluLookAt ((cx - 0.5) * 8, /* Position the camera */
1539 gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
1542 /* Place the lights relative to the object, before the object has
1543 been rotated or wandered within the scene. */
1544 glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
1545 glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
1546 glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
1549 /* Position the lamp in the scene according to the "wander" settings */
1550 glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
1552 /* Rotate the object according to the "spin" settings */
1553 glRotatef (rx * 360, 1.0, 0.0, 0.0);
1554 glRotatef (ry * 360, 0.0, 1.0, 0.0);
1555 glRotatef (rz * 360, 0.0, 0.0, 1.0);
1557 /* Move the lamp up slightly: make 0,0 be at its vertical center. */
1560 case CLASSIC: glTranslatef (0, 0, 0.33); break;
1561 case GIANT: glTranslatef (0, 0, 0.33); break;
1562 case CONE: glTranslatef (0, 0, 0.16); break;
1563 case ROCKET: glTranslatef (0, 0, 0.30);
1564 glScalef (0.85,0.85,0.85); break;
1565 default: abort(); break;
1571 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1572 glCallList (bp->bottle_list);
1573 glCallList (bp->ball_list);
1576 if (mi->fps_p) do_fps (mi);
1579 glXSwapBuffers(dpy, window);