summaryrefslogtreecommitdiffstats
path: root/hacks/glx/lavalite.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/lavalite.c')
-rw-r--r--hacks/glx/lavalite.c1539
1 files changed, 1539 insertions, 0 deletions
diff --git a/hacks/glx/lavalite.c b/hacks/glx/lavalite.c
new file mode 100644
index 0000000..73d5805
--- /dev/null
+++ b/hacks/glx/lavalite.c
@@ -0,0 +1,1539 @@
+/* lavalite --- 3D Simulation a Lava Lite, written by jwz.
+ *
+ * This software Copyright (c) 2002-2017 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * LAVAŽ, LAVA LITEŽ, LAVA WORLD INTERNATIONALŽ and the configuration of the
+ * LAVAŽ brand motion lamp are registered trademarks of Haggerty Enterprises,
+ * Inc. The configuration of the globe and base of the motion lamp are
+ * registered trademarks of Haggerty Enterprises, Inc. in the U.S.A. and in
+ * other countries around the world.
+ *
+ * Official Lava Lite web site: http://www.lavaworld.com/
+ *
+ * Implementation details:
+ *
+ * The blobs are generated using metaballs. For an explanation of what
+ * those are, see http://astronomy.swin.edu.au/~pbourke/modelling/implicitsurf/
+ * or http://www.fifi.org/doc/povray-manual/pov30005.htm
+ *
+ * Basically, each bubble of lava is a few (4) overlapping spherical metaballs
+ * of various sizes following similar but slightly different steep, slow
+ * parabolic arcs from the bottom to the top and back.
+ *
+ * We then polygonize the surface of the lava using the marching squares
+ * algorithm implemented in marching.c.
+ *
+ * Like real lavalites, this program is very slow.
+ *
+ * Surprisingly, it's loading the CPU and not the graphics engine: the speed
+ * bottleneck is in *computing* the scene rather than *rendering* it. We
+ * actually don't use a huge number of polygons, but computing the mesh for
+ * the surface takes a lot of cycles.
+ *
+ * I eliminated all the square roots, but there is still a lot of
+ * floating-point multiplication in here. I tried optimizing away the
+ * fp divisions, but that didn't seem to make a difference.
+ *
+ * -style lamp shape: classic, giant, cone, rocket, or random.
+ * -speed frequency at which new blobs launch.
+ * -resolution density of the polygon mesh.
+ * -count max number of blobs allowed at once.
+ * -wander, -spin whether to roll the scene around the screen.
+ * -lava-color color of the blobbies
+ * -fluid-color color of the stuff the blobbies float in
+ * -base-color color of the base of the lamp
+ * -table-color color of the table under the lamp
+ * -impatient at startup, skip forward in the animation so
+ * that at least one blob is fully exposed.
+ *
+ * TODO:
+ *
+ * - make the table look better, somehow.
+ * - should the lava be emissive? should the one at the bottom be
+ * more brightly colored than the ones at the top? light distance?
+ * - is there some way to put a specular reflection on the front glass
+ * itself? Maybe render it twice with FRONT/BACK tweaked, or alpha
+ * with depth buffering turned off?
+ */
+
+#define DEFAULTS "*delay: 30000 \n" \
+ "*showFPS: False \n" \
+ "*wireframe: False \n" \
+ "*geometry: 600x900\n" \
+ "*count: " DEF_COUNT " \n" \
+
+# define free_lavalite 0
+# define release_lavalite 0
+
+
+#define BLOBS_PER_GROUP 4
+
+#define GRAVITY 0.000013 /* odwnward acceleration */
+#define CONVECTION 0.005 /* initial upward velocity (bell curve) */
+#define TILT 0.00166666 /* horizontal velocity (bell curve) */
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef ABS
+#define ABS(n) ((n)<0?-(n):(n))
+#undef SIGNOF
+#define SIGNOF(n) ((n)<0?-1:1)
+
+#include "xlockmore.h"
+#include "marching.h"
+#include "rotator.h"
+#include "gltrackball.h"
+#include "ximage-loader.h"
+#include <ctype.h>
+
+#ifdef USE_GL /* whole file */
+
+
+#define DEF_SPIN "Z"
+#define DEF_WANDER "False"
+#define DEF_SPEED "0.003"
+#define DEF_RESOLUTION "40"
+#define DEF_SMOOTH "True"
+#define DEF_COUNT "3"
+#define DEF_STYLE "random"
+#define DEF_IMPATIENT "False"
+#define DEF_LCOLOR "#FF0000" /* lava */
+#define DEF_FCOLOR "#00AAFF" /* fluid */
+#define DEF_BCOLOR "#666666" /* base */
+#define DEF_TCOLOR "#000000" /*"#00FF00"*/ /* table */
+
+#define DEF_FTEX "(none)"
+#define DEF_BTEX "(none)"
+#define DEF_TTEX "(none)"
+
+typedef struct metaball metaball;
+
+struct metaball {
+
+ Bool alive_p;
+ Bool static_p;
+
+ double r; /* hard radius */
+ double R; /* radius of field of influence */
+
+ double z; /* vertical position */
+ double pos_r; /* position on horizontal circle */
+ double pos_th; /* position on horizontal circle */
+ double dr, dz; /* current velocity */
+
+ double x, y; /* h planar position - compused from the above */
+
+ metaball *leader; /* stay close to this other ball */
+};
+
+
+typedef enum { CLASSIC = 0, GIANT, CONE, ROCKET } lamp_style;
+typedef enum { CAP = 100, BOTTLE, BASE } lamp_part;
+
+typedef struct {
+ lamp_part part;
+ GLfloat elevation;
+ GLfloat radius;
+ GLfloat texture_elevation;
+} lamp_geometry;
+
+static const lamp_geometry classic_lamp[] = {
+ { CAP, 1.16, 0.089, 0.00 },
+ { BOTTLE, 0.97, 0.120, 0.40 },
+ { BOTTLE, 0.13, 0.300, 0.87 },
+ { BOTTLE, 0.07, 0.300, 0.93 },
+ { BASE, 0.00, 0.280, 0.00 },
+ { BASE, -0.40, 0.120, 0.50 },
+ { BASE, -0.80, 0.280, 1.00 },
+ { 0, 0, 0, 0 },
+};
+
+static const lamp_geometry giant_lamp[] = {
+ { CAP, 1.12, 0.105, 0.00 },
+ { BOTTLE, 0.97, 0.130, 0.30 },
+ { BOTTLE, 0.20, 0.300, 0.87 },
+ { BOTTLE, 0.15, 0.300, 0.93 },
+ { BASE, 0.00, 0.230, 0.00 },
+ { BASE, -0.18, 0.140, 0.20 },
+ { BASE, -0.80, 0.280, 1.00 },
+ { 0, 0, 0, 0 },
+};
+
+static const lamp_geometry cone_lamp[] = {
+ { CAP, 1.35, 0.001, 0.00 },
+ { CAP, 1.35, 0.020, 0.00 },
+ { CAP, 1.30, 0.055, 0.05 },
+ { BOTTLE, 0.97, 0.120, 0.40 },
+ { BOTTLE, 0.13, 0.300, 0.87 },
+ { BASE, 0.00, 0.300, 0.00 },
+ { BASE, -0.04, 0.320, 0.04 },
+ { BASE, -0.60, 0.420, 0.50 },
+ { 0, 0, 0, 0 },
+};
+
+static const lamp_geometry rocket_lamp[] = {
+ { CAP, 1.35, 0.001, 0.00 },
+ { CAP, 1.34, 0.020, 0.00 },
+ { CAP, 1.30, 0.055, 0.05 },
+ { BOTTLE, 0.97, 0.120, 0.40 },
+ { BOTTLE, 0.13, 0.300, 0.87 },
+ { BOTTLE, 0.07, 0.300, 0.93 },
+ { BASE, 0.00, 0.280, 0.00 },
+ { BASE, -0.50, 0.180, 0.50 },
+ { BASE, -0.75, 0.080, 0.75 },
+ { BASE, -0.80, 0.035, 0.80 },
+ { BASE, -0.90, 0.035, 1.00 },
+ { 0, 0, 0, 0 },
+};
+
+
+
+typedef struct {
+ GLXContext *glx_context;
+ lamp_style style;
+ const lamp_geometry *model;
+ rotator *rot;
+ rotator *rot2;
+ trackball_state *trackball;
+ Bool button_down_p;
+
+ GLfloat max_bottle_radius; /* radius of widest part of the bottle */
+
+ GLfloat launch_chance; /* how often to percolate */
+ int blobs_per_group; /* how many metaballs we launch at once */
+ Bool just_started_p; /* so we launch some goo right away */
+
+ int grid_size; /* resolution for marching-cubes */
+ int nballs;
+ metaball *balls;
+
+ GLuint bottle_list;
+ GLuint ball_list;
+
+ int bottle_poly_count; /* polygons in the bottle only */
+
+} lavalite_configuration;
+
+static lavalite_configuration *bps = NULL;
+
+static char *do_spin;
+static char *do_style;
+static GLfloat speed;
+static Bool do_wander;
+static int resolution;
+static Bool do_smooth;
+static Bool do_impatient;
+
+static char *lava_color_str, *fluid_color_str, *base_color_str,
+ *table_color_str;
+static char *fluid_tex, *base_tex, *table_tex;
+
+static GLfloat lava_color[4], fluid_color[4], base_color[4], table_color[4];
+
+static const GLfloat lava_spec[4] = {1.0, 1.0, 1.0, 1.0};
+static const GLfloat lava_shininess = 128.0;
+static const GLfloat foot_color[4] = {0.2, 0.2, 0.2, 1.0};
+
+static const GLfloat light0_pos[4] = {-0.6, 0.0, 1.0, 0.0};
+static const GLfloat light1_pos[4] = { 1.0, 0.0, 0.2, 0.0};
+static const GLfloat light2_pos[4] = { 0.6, 0.0, 1.0, 0.0};
+
+
+
+static XrmOptionDescRec opts[] = {
+ { "-style", ".style", XrmoptionSepArg, 0 },
+ { "-spin", ".spin", XrmoptionSepArg, 0 },
+ { "+spin", ".spin", XrmoptionNoArg, "" },
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-wander", ".wander", XrmoptionNoArg, "True" },
+ { "+wander", ".wander", XrmoptionNoArg, "False" },
+ { "-resolution", ".resolution", XrmoptionSepArg, 0 },
+ { "-smooth", ".smooth", XrmoptionNoArg, "True" },
+ { "+smooth", ".smooth", XrmoptionNoArg, "False" },
+ { "-impatient", ".impatient", XrmoptionNoArg, "True" },
+ { "+impatient", ".impatient", XrmoptionNoArg, "False" },
+
+ { "-lava-color", ".lavaColor", XrmoptionSepArg, 0 },
+ { "-fluid-color", ".fluidColor", XrmoptionSepArg, 0 },
+ { "-base-color", ".baseColor", XrmoptionSepArg, 0 },
+ { "-table-color", ".tableColor", XrmoptionSepArg, 0 },
+
+ { "-fluid-texture",".fluidTexture", XrmoptionSepArg, 0 },
+ { "-base-texture", ".baseTexture", XrmoptionSepArg, 0 },
+ { "-table-texture",".tableTexture", XrmoptionSepArg, 0 },
+};
+
+static argtype vars[] = {
+ {&do_style, "style", "Style", DEF_STYLE, t_String},
+ {&do_spin, "spin", "Spin", DEF_SPIN, t_String},
+ {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
+ {&speed, "speed", "Speed", DEF_SPEED, t_Float},
+ {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Int},
+ {&do_smooth, "smooth", "Smooth", DEF_SMOOTH, t_Bool},
+ {&do_impatient, "impatient", "Impatient", DEF_IMPATIENT, t_Bool},
+
+ {&lava_color_str, "lavaColor", "LavaColor", DEF_LCOLOR, t_String},
+ {&fluid_color_str, "fluidColor", "FluidColor", DEF_FCOLOR, t_String},
+ {&base_color_str, "baseColor", "BaseColor", DEF_BCOLOR, t_String},
+ {&table_color_str, "tableColor", "TableColor", DEF_TCOLOR, t_String},
+
+ {&fluid_tex, "fluidTexture", "FluidTexture", DEF_FTEX, t_String},
+ {&base_tex, "baseTexture", "BaseTexture", DEF_BTEX, t_String},
+ {&table_tex, "tableTexture", "BaseTexture", DEF_TTEX, t_String},
+};
+
+ENTRYPOINT ModeSpecOpt lavalite_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_lavalite (ModeInfo *mi, int width, int height)
+{
+ GLfloat h = (GLfloat) height / (GLfloat) width;
+ int y = 0;
+
+ if (width > height * 5) { /* tiny window: show middle */
+ height = width * 3;
+ y = -height/2;
+ h = height / (GLfloat) width;
+ }
+
+ glViewport (0, y, (GLint) width, (GLint) height);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective (30.0, 1/h, 1.0, 100.0);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ gluLookAt( 0.0, 0.0, 30.0,
+ 0.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+
+/* Textures
+ */
+
+static Bool
+load_texture (ModeInfo *mi, const char *filename)
+{
+ Display *dpy = mi->dpy;
+ Visual *visual = mi->xgwa.visual;
+ char buf[1024];
+ XImage *image;
+
+ if (!filename ||
+ !*filename ||
+ !strcasecmp (filename, "(none)"))
+ {
+ glDisable (GL_TEXTURE_2D);
+ return False;
+ }
+
+ image = file_to_ximage (dpy, visual, filename);
+ if (!image) return False;
+
+ clear_gl_error();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
+ image->width, image->height, 0,
+ GL_RGBA, GL_UNSIGNED_BYTE, image->data);
+ sprintf (buf, "texture: %.100s (%dx%d)",
+ filename, image->width, image->height);
+ check_gl_error(buf);
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 4);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, image->width);
+ glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+
+ glEnable (GL_TEXTURE_2D);
+ return True;
+}
+
+
+
+/* Generating the lamp's bottle, caps, and base.
+ */
+
+static int
+draw_disc (GLfloat r, GLfloat z, int faces, Bool up_p, Bool wire)
+{
+ int j;
+ GLfloat th;
+ GLfloat step = M_PI * 2 / faces;
+ int polys = 0;
+ GLfloat x, y;
+
+ glFrontFace (up_p ? GL_CW : GL_CCW);
+ glNormal3f (0, (up_p ? 1 : -1), 0);
+ glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
+
+ x = r;
+ y = 0;
+
+ for (j = 0, th = 0; j <= faces; j++)
+ {
+ glTexCoord2f (-j / (GLfloat) faces, 1);
+ glVertex3f (0, z, 0);
+
+ glTexCoord2f (-j / (GLfloat) faces, 0);
+ glVertex3f (x, z, y);
+
+ th += step;
+ x = r * cos (th);
+ y = r * sin (th);
+
+ glTexCoord2f (-j / (GLfloat) faces, 0);
+ glVertex3f (x, z, y);
+
+ polys++;
+ }
+ glEnd();
+
+ return polys;
+}
+
+
+static int
+draw_tube (GLfloat r0, GLfloat r1,
+ GLfloat z0, GLfloat z1,
+ GLfloat t0, GLfloat t1,
+ int faces, Bool inside_out_p, Bool smooth_p, Bool wire)
+{
+ int polys = 0;
+ GLfloat th;
+ GLfloat x, y, x0=0, y0=0;
+ GLfloat step = M_PI * 2 / faces;
+ GLfloat s2 = step/2;
+ int i;
+
+ glFrontFace (inside_out_p ? GL_CW : GL_CCW);
+ glBegin (wire ? GL_LINES : (smooth_p ? GL_QUAD_STRIP : GL_QUADS));
+
+ th = 0;
+ x = 1;
+ y = 0;
+
+ if (!smooth_p)
+ {
+ x0 = cos (s2);
+ y0 = sin (s2);
+ }
+
+ if (smooth_p) faces++;
+
+ for (i = 0; i < faces; i++)
+ {
+ int nsign = (inside_out_p ? -1 : 1);
+
+ if (smooth_p)
+ glNormal3f (x * nsign, z1, y * nsign);
+ else
+ glNormal3f (x0 * nsign, z1, y0 * nsign);
+
+ glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t1);
+ glVertex3f (x * r1, z1, y * r1);
+
+ glTexCoord2f (nsign * -i / (GLfloat) faces, 1-t0);
+ glVertex3f (x * r0, z0, y * r0);
+
+ th += step;
+ x = cos (th);
+ y = sin (th);
+
+ if (!smooth_p)
+ {
+ x0 = cos (th + s2);
+ y0 = sin (th + s2);
+
+ glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t0);
+ glVertex3f (x * r0, z0, y * r0);
+
+ glTexCoord2f (nsign * -(i+1) / (double) faces, 1-t1);
+ glVertex3f (x * r1, z1, y * r1);
+ }
+
+ polys++;
+ }
+ glEnd();
+
+ return polys;
+}
+
+
+static int
+draw_table (GLfloat z, Bool wire)
+{
+ GLfloat faces = 6;
+ GLfloat step = M_PI * 2 / faces;
+ GLfloat s = 8;
+ GLfloat th;
+ int j;
+ int polys = 0;
+
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
+
+ glFrontFace(GL_CW);
+ glNormal3f(0, 1, 0);
+ glBegin(wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
+
+ if (! wire)
+ {
+ glTexCoord2f (-0.5, 0.5);
+ glVertex3f(0, z, 0);
+ }
+
+ for (j = 0, th = 0; j <= faces; j++)
+ {
+ GLfloat x = cos (th);
+ GLfloat y = sin (th);
+ glTexCoord2f (-(x+1)/2.0, (y+1)/2.0);
+ glVertex3f(x*s, z, y*s);
+ th += step;
+ polys++;
+ }
+ glEnd();
+ return polys;
+}
+
+
+static int
+draw_wing (GLfloat w, GLfloat h, GLfloat d, Bool wire)
+{
+ static const int coords[2][8][2] = {
+ { { 0, 0 },
+ { 10, 10 },
+ { 20, 23 },
+ { 30, 41 },
+ { 40, 64 },
+ { 45, 81 },
+ { 50, 103 },
+ { 53, 134 } },
+ { { 0, 54 },
+ { 10, 57 },
+ { 20, 64 },
+ { 30, 75 },
+ { 40, 92 },
+ { 45, 104 },
+ { 50, 127 },
+ { 51, 134 }
+ }
+ };
+
+ int polys = 0;
+ int maxx = coords[0][countof(coords[0])-1][0];
+ int maxy = coords[0][countof(coords[0])-1][1];
+ unsigned int x;
+
+ for (x = 1; x < countof(coords[0]); x++)
+ {
+ GLfloat px0 = (GLfloat) coords[0][x-1][0] / maxx * w;
+ GLfloat py0 = (GLfloat) coords[0][x-1][1] / maxy * h;
+ GLfloat px1 = (GLfloat) coords[1][x-1][0] / maxx * w;
+ GLfloat py1 = (GLfloat) coords[1][x-1][1] / maxy * h;
+ GLfloat px2 = (GLfloat) coords[0][x ][0] / maxx * w;
+ GLfloat py2 = (GLfloat) coords[0][x ][1] / maxy * h;
+ GLfloat px3 = (GLfloat) coords[1][x ][0] / maxx * w;
+ GLfloat py3 = (GLfloat) coords[1][x ][1] / maxy * h;
+ GLfloat zz = d/2;
+
+ /* Left side
+ */
+ glFrontFace (GL_CW);
+ glNormal3f (0, 0, -1);
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+
+ glTexCoord2f (px0, py0); glVertex3f (px0, -py0, -zz);
+ glTexCoord2f (px1, py1); glVertex3f (px1, -py1, -zz);
+ glTexCoord2f (px3, py3); glVertex3f (px3, -py3, -zz);
+ glTexCoord2f (px2, py2); glVertex3f (px2, -py2, -zz);
+ polys++;
+ glEnd();
+
+ /* Right side
+ */
+ glFrontFace (GL_CCW);
+ glNormal3f (0, 0, -1);
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
+ glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
+ glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
+ glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
+ polys++;
+ glEnd();
+
+ /* Top edge
+ */
+ glFrontFace (GL_CCW);
+ glNormal3f (1, -1, 0); /* #### wrong */
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glTexCoord2f(px0, py0); glVertex3f (px0, -py0, -zz);
+ glTexCoord2f(px0, py0); glVertex3f (px0, -py0, zz);
+ glTexCoord2f(px2, py2); glVertex3f (px2, -py2, zz);
+ glTexCoord2f(px2, py2); glVertex3f (px2, -py2, -zz);
+ polys++;
+ glEnd();
+
+ /* Bottom edge
+ */
+ glFrontFace (GL_CW);
+ glNormal3f (-1, 1, 0); /* #### wrong */
+ glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
+ glTexCoord2f(px1, py1); glVertex3f (px1, -py1, -zz);
+ glTexCoord2f(px1, py1); glVertex3f (px1, -py1, zz);
+ glTexCoord2f(px3, py3); glVertex3f (px3, -py3, zz);
+ glTexCoord2f(px3, py3); glVertex3f (px3, -py3, -zz);
+ polys++;
+ glEnd();
+
+
+ }
+
+ return polys;
+
+}
+
+
+static void
+generate_bottle (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ int faces = resolution * 1.5;
+ Bool smooth = do_smooth;
+
+ const lamp_geometry *top_slice = bp->model;
+ const char *current_texture = 0;
+ lamp_part last_part = 0;
+
+ if (faces < 3) faces = 3;
+ else if (wire && faces > 20) faces = 20;
+ else if (faces > 60) faces = 60;
+
+ bp->bottle_poly_count = 0;
+
+ glNewList (bp->bottle_list, GL_COMPILE);
+ glPushMatrix();
+
+ glRotatef (90, 1, 0, 0);
+ glTranslatef (0, -0.5, 0);
+
+ /* All parts of the lamp use the same specularity and shininess. */
+ glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
+ glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
+
+ while (1)
+ {
+ const lamp_geometry *bot_slice = top_slice + 1;
+
+ const char *texture = 0;
+ GLfloat *color = 0;
+ GLfloat t0, t1;
+
+ glDisable (GL_LIGHT2);
+
+ switch (top_slice->part)
+ {
+ case CAP:
+ case BASE:
+ texture = base_tex;
+ color = base_color;
+ break;
+ case BOTTLE:
+ texture = fluid_tex;
+ color = fluid_color;
+ if (!wire) glEnable (GL_LIGHT2); /* light2 affects only fluid */
+ break;
+ default:
+ abort();
+ break;
+ }
+
+ if (!wire && texture && texture != current_texture)
+ {
+ current_texture = texture;
+ load_texture (mi, current_texture);
+ }
+
+ /* Color the discs darker than the tube walls. */
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, foot_color);
+
+ /* Do a top disc if this is the first slice of the CAP or BASE.
+ */
+ if ((top_slice->part == CAP && last_part == 0) ||
+ (top_slice->part == BASE && last_part == BOTTLE))
+ bp->bottle_poly_count +=
+ draw_disc (top_slice->radius, top_slice->elevation, faces,
+ True, wire);
+
+ /* Do a bottom disc if this is the last slice of the CAP or BASE.
+ */
+ if ((top_slice->part == CAP && bot_slice->part == BOTTLE) ||
+ (top_slice->part == BASE && bot_slice->part == 0))
+ {
+ const lamp_geometry *sl = (bot_slice->part == 0
+ ? top_slice : bot_slice);
+ bp->bottle_poly_count +=
+ draw_disc (sl->radius, sl->elevation, faces, False, wire);
+ }
+
+ if (bot_slice->part == 0) /* done! */
+ break;
+
+ /* Do a tube or cone
+ */
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
+
+ t0 = top_slice->texture_elevation;
+ t1 = bot_slice->texture_elevation;
+
+ /* Restart the texture coordinates for the glass.
+ */
+ if (top_slice->part == BOTTLE)
+ {
+ Bool first_p = (top_slice[-1].part != BOTTLE);
+ Bool last_p = (bot_slice->part != BOTTLE);
+ if (first_p) t0 = 0;
+ if (last_p) t1 = 1;
+ }
+
+ bp->bottle_poly_count +=
+ draw_tube (top_slice->radius, bot_slice->radius,
+ top_slice->elevation, bot_slice->elevation,
+ t0, t1,
+ faces,
+ (top_slice->part == BOTTLE),
+ smooth, wire);
+
+ last_part = top_slice->part;
+ top_slice++;
+ }
+
+ if (bp->style == ROCKET)
+ {
+ int i;
+ for (i = 0; i < 3; i++)
+ {
+ glPushMatrix();
+ glRotatef (120 * i, 0, 1, 0);
+ glTranslatef (0.14, -0.05, 0);
+ bp->bottle_poly_count += draw_wing (0.4, 0.95, 0.02, wire);
+ glPopMatrix();
+ }
+ glTranslatef (0, -0.1, 0); /* move floor down a little */
+ }
+
+
+ if (!wire) load_texture (mi, table_tex);
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, table_color);
+ bp->bottle_poly_count += draw_table (top_slice->elevation, wire);
+
+
+ glPopMatrix ();
+ glDisable (GL_TEXTURE_2D); /* done with textured objects */
+ glEndList ();
+}
+
+
+
+
+/* Generating blobbies
+ */
+
+static double
+bellrand (double extent) /* like frand(), but a bell curve. */
+{
+ return (((frand(extent) + frand(extent) + frand(extent)) / 3)
+ - (extent/2));
+}
+
+
+static void move_ball (ModeInfo *mi, metaball *b);
+
+/* Bring a ball into play, and re-randomize its values.
+ */
+static void
+reset_ball (ModeInfo *mi, metaball *b)
+{
+/* lavalite_configuration *bp = &bps[MI_SCREEN(mi)]; */
+
+ b->r = 0.00001;
+ b->R = 0.12 + bellrand(0.10);
+
+ b->pos_r = bellrand (0.9);
+ b->pos_th = frand(M_PI*2);
+ b->z = 0;
+
+ b->dr = bellrand(TILT);
+ b->dz = CONVECTION;
+
+ b->leader = 0;
+
+ if (!b->alive_p)
+ b->alive_p = True;
+
+ move_ball (mi, b);
+}
+
+
+/* returns the first metaball that is not in use, or 0.
+ */
+static metaball *
+get_ball (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < bp->nballs; i++)
+ {
+ metaball *b = &bp->balls[i];
+ if (!b->alive_p)
+ return b;
+ }
+ return 0;
+}
+
+
+/* Generate the blobs that don't move: the ones at teh top and bottom
+ that are part of the scenery.
+ */
+static void
+generate_static_blobs (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ metaball *b0, *b1;
+ int i;
+
+ b0 = get_ball (mi);
+ if (!b0) abort();
+ b0->static_p = True;
+ b0->alive_p = True;
+ b0->R = 0.6;
+ b0->r = 0.3;
+
+ /* the giant blob at the bottom of the bottle.
+ */
+ b0->pos_r = 0;
+ b0->pos_th = 0;
+ b0->dr = 0;
+ b0->dz = 0;
+ b0->x = 0;
+ b0->y = 0;
+ b0->z = -0.43;
+
+ /* the small blob at the top of the bottle.
+ */
+ b1 = get_ball (mi);
+ if (!b1) abort();
+
+ *b1 = *b0;
+ b1->R = 0.16;
+ b1->r = 0.135;
+ b1->z = 1.078;
+
+ /* Some extra blobs at the bottom of the bottle, to jumble the surface.
+ */
+ for (i = 0; i < bp->blobs_per_group; i++)
+ {
+ b1 = get_ball (mi);
+ if (!b1) abort();
+ reset_ball (mi, b1);
+ b1->static_p = True;
+ b1->z = frand(0.04);
+ b1->dr = 0;
+ b1->dz = 0;
+ }
+}
+
+
+static GLfloat
+max_bottle_radius (lavalite_configuration *bp)
+{
+ GLfloat r = 0;
+ const lamp_geometry *slice;
+ for (slice = bp->model; slice->part != 0; slice++)
+ {
+ if (slice->part == BOTTLE && slice->radius > r)
+ r = slice->radius; /* top */
+ if (slice[1].radius > r)
+ r = slice[1].radius; /* bottom */
+ }
+ return r;
+}
+
+
+static GLfloat
+bottle_radius_at (lavalite_configuration *bp, GLfloat z)
+{
+ GLfloat topz = -999, botz = -999, topr = 0, botr = 0;
+ const lamp_geometry *slice;
+ GLfloat ratio;
+
+ for (slice = bp->model; slice->part != 0; slice++)
+ if (z > slice->elevation)
+ {
+ slice--;
+ topz = slice->elevation;
+ topr = slice->radius;
+ break;
+ }
+ if (topz == -999) return 0;
+
+ for (; slice->part != 0; slice++)
+ if (z > slice->elevation)
+ {
+ botz = slice->elevation;
+ botr = slice->radius;
+ break;
+ }
+ if (botz == -999) return 0;
+
+ ratio = (z - botz) / (topz - botz);
+
+ return (botr + ((topr - botr) * ratio));
+}
+
+
+static void
+move_ball (ModeInfo *mi, metaball *b)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ double gravity = GRAVITY;
+ double real_r;
+
+ if (b->static_p) return;
+
+ b->pos_r += b->dr;
+ b->z += b->dz;
+
+ b->dz -= gravity;
+
+ if (b->pos_r > 0.9)
+ {
+ b->pos_r = 0.9;
+ b->dr = -b->dr;
+ }
+ else if (b->pos_r < 0)
+ {
+ b->pos_r = -b->pos_r;
+ b->dr = -b->dr;
+ }
+
+ real_r = b->pos_r * bottle_radius_at (bp, b->z);
+
+ b->x = cos (b->pos_th) * real_r;
+ b->y = sin (b->pos_th) * real_r;
+
+ if (b->z < -b->R) /* dropped below bottom of glass - turn it off */
+ b->alive_p = False;
+}
+
+
+/* This function makes sure that balls that are part of a group always stay
+ relatively close to each other.
+ */
+static void
+clamp_balls (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < bp->nballs; i++)
+ {
+ metaball *b = &bp->balls[i];
+ if (b->alive_p && b->leader)
+ {
+ double zslack = 0.1;
+ double minz = b->leader->z - zslack;
+ double maxz = b->leader->z + zslack;
+
+ /* Try to keep the Z values near those of the leader.
+ Don't let it go out of range (above or below) and clamp it
+ if it does. If we've clamped it, make sure dz will be
+ moving it in the right direction (back toward the leader.)
+
+ We aren't currently clamping r, only z -- doesn't seem to
+ be needed.
+
+ This is kind of flaky, I think. Sometimes you can see
+ the blobbies "twitch". That's no good.
+ */
+
+ if (b->z < minz)
+ {
+ if (b->dz < 0) b->dz = -b->dz;
+ b->z = minz - b->dz;
+ }
+
+ if (b->z > maxz)
+ {
+ if (b->dz > 0) b->dz = -b->dz;
+ b->z = maxz + b->dz;
+ }
+ }
+ }
+}
+
+
+static void
+move_balls (ModeInfo *mi) /* for great justice */
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < bp->nballs; i++)
+ {
+ metaball *b = &bp->balls[i];
+ if (b->alive_p)
+ move_ball (mi, b);
+ }
+
+ clamp_balls (mi);
+}
+
+
+
+/* Rendering blobbies using marching cubes.
+ */
+
+static double
+compute_metaball_influence (lavalite_configuration *bp,
+ double x, double y, double z,
+ int nballs, metaball *balls)
+{
+ double vv = 0;
+ int i;
+
+ for (i = 0; i < nballs; i++)
+ {
+ metaball *b = &balls[i];
+ double dx, dy, dz;
+ double d2, r, R, r2, R2;
+
+ if (!b->alive_p) continue;
+
+ dx = x - b->x;
+ dy = y - b->y;
+ dz = z - b->z;
+ R = b->R;
+
+ if (dx > R || dx < -R || /* quick check before multiplying */
+ dy > R || dy < -R ||
+ dz > R || dz < -R)
+ continue;
+
+ d2 = (dx*dx + dy*dy + dz*dz);
+ r = b->r;
+
+ r2 = r*r;
+ R2 = R*R;
+
+ if (d2 <= r2) /* (d <= r) inside the hard radius */
+ vv += 1;
+ else if (d2 > R2) /* (d > R) outside the radius of influence */
+ ;
+ else /* somewhere in between: linear drop-off from r=1 to R=0 */
+ {
+ /* was: vv += 1 - ((d-r) / (R-r)); */
+ vv += 1 - ((d2-r2) / (R2-r2));
+ }
+ }
+
+ return vv;
+}
+
+
+/* callback for marching_cubes() */
+static void *
+obj_init (double grid_size, void *closure)
+{
+ lavalite_configuration *bp = (lavalite_configuration *) closure;
+ bp->grid_size = grid_size;
+
+ return closure;
+}
+
+
+/* Returns True if the given point is outside of the glass tube.
+ */
+static double
+clipped_by_glass_p (double x, double y, double z,
+ lavalite_configuration *bp)
+{
+ double d2, or, or2, ir2;
+
+ or = bp->max_bottle_radius;
+
+ if (x > or || x < -or || /* quick check before multiplying */
+ y > or || y < -or)
+ return 0;
+
+ d2 = (x*x + y*y);
+ or = bottle_radius_at (bp, z);
+
+ or2 = or*or;
+
+ if (d2 > or2) /* (sqrt(d) > or) */
+ return 0;
+
+ ir2 = or2 * 0.7;
+
+ if (d2 > ir2) /* (sqrt(d) > ir) */
+ {
+ double dr1 = or2;
+ double dr2 = ir2;
+ /* was: (1 - (d-ratio2) / (ratio1-ratio2)) */
+ return (1 - (d2-dr2) / (dr1-dr2));
+ }
+
+ return 1;
+}
+
+
+
+/* callback for marching_cubes() */
+static double
+obj_compute (double x, double y, double z, void *closure)
+{
+ lavalite_configuration *bp = (lavalite_configuration *) closure;
+ double clip;
+
+ x /= bp->grid_size; /* convert from 0-N to 0-1. */
+ y /= bp->grid_size;
+ z /= bp->grid_size;
+
+ x -= 0.5; /* X and Y range from -.5 to +.5; z ranges from 0-1. */
+ y -= 0.5;
+
+ clip = clipped_by_glass_p (x, y, z, bp);
+ if (clip == 0) return 0;
+
+ return (clip *
+ compute_metaball_influence (bp, x, y, z, bp->nballs, bp->balls));
+}
+
+
+/* callback for marching_cubes() */
+static void
+obj_free (void *closure)
+{
+}
+
+
+/* Send a new blob travelling upward.
+ This blob will actually be composed of N metaballs that are near
+ each other.
+ */
+static void
+launch_balls (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ metaball *b0 = get_ball (mi);
+ int i;
+
+ if (!b0) return;
+ reset_ball (mi, b0);
+
+ for (i = 0; i < bp->blobs_per_group; i++)
+ {
+ metaball *b1 = get_ball (mi);
+ if (!b1) break;
+ *b1 = *b0;
+
+ reset_ball (mi, b1);
+ b1->leader = b0;
+
+# define FROB(FIELD,AMT) \
+ b1->FIELD += (bellrand(AMT) * b0->FIELD)
+
+ /* FROB (pos_r, 0.7); */
+ /* FROB (pos_th, 0.7); */
+ FROB (dr, 0.8);
+ FROB (dz, 0.6);
+# undef FROB
+ }
+
+}
+
+
+static void
+animate_lava (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ Bool just_started_p = bp->just_started_p;
+
+ double isolevel = 0.3;
+
+ /* Maybe bubble a new blobby to the surface.
+ */
+ if (just_started_p ||
+ frand(1.0) < bp->launch_chance)
+ {
+ bp->just_started_p = False;
+ launch_balls (mi);
+
+ if (do_impatient && just_started_p)
+ while (1)
+ {
+ int i;
+ move_balls (mi);
+ for (i = 0; i < bp->nballs; i++)
+ {
+ metaball *b = &bp->balls[i];
+ if (b->alive_p && !b->static_p && !b->leader &&
+ b->z > 0.5)
+ goto DONE;
+ }
+ }
+ DONE: ;
+ }
+
+ move_balls (mi);
+
+ glNewList (bp->ball_list, GL_COMPILE);
+ glPushMatrix();
+
+ glMaterialfv (GL_FRONT, GL_SPECULAR, lava_spec);
+ glMateriali (GL_FRONT, GL_SHININESS, lava_shininess);
+ glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, lava_color);
+
+ /* For the blobbies, the origin is on the axis at the bottom of the
+ glass bottle; and the top of the bottle is +1 on Z.
+ */
+ glTranslatef (0, 0, -0.5);
+
+ mi->polygon_count = 0;
+ {
+ double s;
+ if (bp->grid_size == 0) bp->grid_size = 1; /* first time through */
+ s = 1.0/bp->grid_size;
+
+ glPushMatrix();
+ glTranslatef (-0.5, -0.5, 0);
+ glScalef (s, s, s);
+ marching_cubes (resolution, isolevel, wire, do_smooth,
+ obj_init, obj_compute, obj_free, bp,
+ &mi->polygon_count);
+ glPopMatrix();
+ }
+
+ mi->polygon_count += bp->bottle_poly_count;
+
+ glPopMatrix();
+ glEndList ();
+}
+
+
+
+/* Startup initialization
+ */
+
+ENTRYPOINT Bool
+lavalite_handle_event (ModeInfo *mi, XEvent *event)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, bp->trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &bp->button_down_p))
+ return True;
+
+ return False;
+}
+
+
+static void
+parse_color (ModeInfo *mi, const char *name, const char *s, GLfloat *a)
+{
+ XColor c;
+ a[3] = 1.0; /* alpha */
+
+ if (! XParseColor (MI_DISPLAY(mi), MI_COLORMAP(mi), s, &c))
+ {
+ fprintf (stderr, "%s: can't parse %s color %s", progname, name, s);
+ exit (1);
+ }
+ a[0] = c.red / 65536.0;
+ a[1] = c.green / 65536.0;
+ a[2] = c.blue / 65536.0;
+}
+
+
+ENTRYPOINT void
+init_lavalite (ModeInfo *mi)
+{
+ lavalite_configuration *bp;
+ int wire = MI_IS_WIREFRAME(mi);
+
+ MI_INIT (mi, bps);
+
+ bp = &bps[MI_SCREEN(mi)];
+
+ bp->glx_context = init_GL(mi);
+
+ reshape_lavalite (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+ {
+ char *s = do_style;
+ if (!s || !*s || !strcasecmp (s, "classic")) bp->style = CLASSIC;
+ else if (!strcasecmp (s, "giant")) bp->style = GIANT;
+ else if (!strcasecmp (s, "cone")) bp->style = CONE;
+ else if (!strcasecmp (s, "rocket")) bp->style = ROCKET;
+ else if (!strcasecmp (s, "random"))
+ {
+ if (random() & 1) bp->style = CLASSIC; /* half the time */
+ else bp->style = (random() % ((int) ROCKET+1));
+ }
+ else
+ {
+ fprintf (stderr,
+ "%s: style must be Classic, Giant, Cone, or Rocket (not \"%s\")\n",
+ progname, s);
+ exit (1);
+ }
+ }
+
+ parse_color (mi, "lava", lava_color_str, lava_color);
+ parse_color (mi, "fluid", fluid_color_str, fluid_color);
+ parse_color (mi, "base", base_color_str, base_color);
+ parse_color (mi, "table", table_color_str, table_color);
+
+ if (!wire)
+ {
+ GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
+ GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
+ GLfloat spc0[4] = {0.0, 1.0, 1.0, 1.0};
+ GLfloat spc1[4] = {1.0, 0.0, 1.0, 1.0};
+
+ glEnable(GL_LIGHTING);
+ glEnable(GL_LIGHT0);
+ glEnable(GL_LIGHT1);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_NORMALIZE);
+ glShadeModel(GL_SMOOTH);
+
+ glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT0, GL_SPECULAR, spc0);
+
+ glLightfv(GL_LIGHT1, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT1, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT1, GL_SPECULAR, spc1);
+
+ glLightfv(GL_LIGHT2, GL_AMBIENT, amb);
+ glLightfv(GL_LIGHT2, GL_DIFFUSE, dif);
+ glLightfv(GL_LIGHT2, GL_SPECULAR, spc0);
+ }
+
+ {
+ Bool spinx=False, spiny=False, spinz=False;
+ double spin_speed = 0.4;
+ double wander_speed = 0.03;
+
+ char *s = do_spin;
+ while (*s)
+ {
+ if (*s == 'x' || *s == 'X') spinx = True;
+ else if (*s == 'y' || *s == 'Y') spiny = True;
+ else if (*s == 'z' || *s == 'Z') spinz = True;
+ else if (*s == '0') ;
+ else
+ {
+ fprintf (stderr,
+ "%s: spin must contain only the characters X, Y, or Z (not \"%s\")\n",
+ progname, do_spin);
+ exit (1);
+ }
+ s++;
+ }
+
+ bp->rot = make_rotator (spinx ? spin_speed : 0,
+ spiny ? spin_speed : 0,
+ spinz ? spin_speed : 0,
+ 1.0,
+ do_wander ? wander_speed : 0,
+ False);
+ bp->rot2 = make_rotator (spin_speed, 0, 0,
+ 1, 0.1,
+ False);
+ bp->trackball = gltrackball_init (False);
+
+ /* Tilt the scene a bit: lean the normal lamps toward the viewer,
+ and the huge lamps away. */
+ gltrackball_reset (bp->trackball,
+ -0.3 + frand(0.6),
+ (bp->style == ROCKET || bp->style == GIANT
+ ? frand (0.2)
+ : -frand (0.6)));
+ }
+
+ switch (bp->style)
+ {
+ case CLASSIC: bp->model = classic_lamp; break;
+ case GIANT: bp->model = giant_lamp; break;
+ case CONE: bp->model = cone_lamp; break;
+ case ROCKET: bp->model = rocket_lamp; break;
+ default: abort(); break;
+ }
+
+ bp->max_bottle_radius = max_bottle_radius (bp);
+
+ bp->launch_chance = speed;
+ bp->blobs_per_group = BLOBS_PER_GROUP;
+ bp->just_started_p = True;
+
+ bp->nballs = (((MI_COUNT (mi) + 1) * bp->blobs_per_group)
+ + 2);
+ bp->balls = (metaball *) calloc (sizeof(*bp->balls), bp->nballs+1);
+
+ bp->bottle_list = glGenLists (1);
+ bp->ball_list = glGenLists (1);
+
+ generate_bottle (mi);
+ generate_static_blobs (mi);
+}
+
+
+/* Render one frame
+ */
+
+ENTRYPOINT void
+draw_lavalite (ModeInfo *mi)
+{
+ lavalite_configuration *bp = &bps[MI_SCREEN(mi)];
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+
+ if (!bp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
+
+ glMatrixMode (GL_MODELVIEW);
+ glPushMatrix ();
+
+ {
+ double cx, cy, cz; /* camera position, 0-1. */
+ double px, py, pz; /* object position, 0-1. */
+ double rx, ry, rz; /* object rotation, 0-1. */
+
+ get_position (bp->rot2, 0, &cy, &cz, !bp->button_down_p);
+ get_rotation (bp->rot2, &cx, 0, 0, !bp->button_down_p);
+
+ get_position (bp->rot, &px, &py, &pz, !bp->button_down_p);
+ get_rotation (bp->rot, &rx, &ry, &rz, !bp->button_down_p);
+
+#if 1
+ cx = 0.5;
+ cy = 0.5;
+ cz = 1.0;
+
+#else /* #### this crud doesn't really work yet */
+
+
+ /* We have c[xyz] parameters describing a camera position, but we don't
+ want to just map those to points in space: the lamp doesn't look very
+ good from the inside, or from underneath...
+
+ Good observation points form a ring around the lamp: basically, a
+ torus ringing the lamp, parallel to the lamp's floor.
+
+ We interpret cz as distance from the origin.
+ cy as elevation.
+ cx is then used as position in the torus (theta).
+ */
+
+ {
+ double cx2, cy2, cz2;
+ double d;
+
+ cx2 = 0.5;
+ cy2 = 0.5;
+ cz2 = 1.0;
+
+ cy2 = (cy * 0.4); /* cam elevation: 0.0 (table) - 0.4 up. */
+ d = 0.9 + cz; /* cam distance: 0.9 - 1.9. */
+
+ cz2 = 0.5 + (d * cos (cx * M_PI * 2));
+ cx2 = 0.5 + (d * sin (cx * M_PI * 2));
+
+
+ cx = cx2;
+ cy = cy2;
+ cz = cz2;
+ }
+#endif /* 0 */
+
+ glLoadIdentity();
+ glRotatef(current_device_rotation(), 0, 0, 1);
+
+ gluLookAt ((cx - 0.5) * 8, /* Position the camera */
+ (cy - 0.5) * 8,
+ (cz - 0.5) * 8,
+ 0, 0, 0,
+ 0, 1, 0);
+
+ gltrackball_rotate (bp->trackball); /* Apply mouse-based camera position */
+
+ glRotatef (-90, 1, 0, 0); /* Right side up */
+
+
+ /* Place the lights relative to the object, before the object has
+ been rotated or wandered within the scene. */
+ glLightfv(GL_LIGHT0, GL_POSITION, light0_pos);
+ glLightfv(GL_LIGHT1, GL_POSITION, light1_pos);
+ glLightfv(GL_LIGHT2, GL_POSITION, light2_pos);
+
+
+ /* Position the lamp in the scene according to the "wander" settings */
+ glTranslatef ((px - 0.5), (py - 0.5), (pz - 0.5));
+
+ /* Rotate the object according to the "spin" settings */
+ glRotatef (rx * 360, 1.0, 0.0, 0.0);
+ glRotatef (ry * 360, 0.0, 1.0, 0.0);
+ glRotatef (rz * 360, 0.0, 0.0, 1.0);
+
+ /* Move the lamp up slightly: make 0,0 be at its vertical center. */
+ switch (bp->style)
+ {
+ case CLASSIC: glTranslatef (0, 0, 0.33); break;
+ case GIANT: glTranslatef (0, 0, 0.33); break;
+ case CONE: glTranslatef (0, 0, 0.16); break;
+ case ROCKET: glTranslatef (0, 0, 0.30);
+ glScalef (0.85,0.85,0.85); break;
+ default: abort(); break;
+ }
+ }
+
+ animate_lava (mi);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ glCallList (bp->bottle_list);
+ glCallList (bp->ball_list);
+ glPopMatrix ();
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+XSCREENSAVER_MODULE ("Lavalite", lavalite)
+
+#endif /* USE_GL */