diff options
author | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
commit | d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch) | |
tree | cbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /hacks/glx/lavalite.c | |
download | xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip |
Original 5.40
Diffstat (limited to 'hacks/glx/lavalite.c')
-rw-r--r-- | hacks/glx/lavalite.c | 1539 |
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 */ |