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