summaryrefslogtreecommitdiffstats
path: root/hacks/glx/gravitywell.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/glx/gravitywell.c')
-rw-r--r--hacks/glx/gravitywell.c767
1 files changed, 767 insertions, 0 deletions
diff --git a/hacks/glx/gravitywell.c b/hacks/glx/gravitywell.c
new file mode 100644
index 0000000..7078764
--- /dev/null
+++ b/hacks/glx/gravitywell.c
@@ -0,0 +1,767 @@
+/* gravitywell, Copyright (c) 2019 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.
+ */
+
+#define DEFAULTS "*delay: 30000 \n" \
+ "*count: 15 \n" \
+ "*gridColor: #00FF00\n" \
+ "*gridColor2: #FF0000\n" \
+ "*showFPS: False \n" \
+ "*wireframe: False \n"
+
+# define release_gw 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#define DEF_SPEED "1.0"
+#define DEF_RESOLUTION "1.0"
+#define DEF_GRID_SIZE "1.0"
+
+#include "xlockmore.h"
+#include "gltrackball.h"
+#include "colors.h"
+#include "hsv.h"
+
+#include <ctype.h>
+
+#define ASSERT(x)
+
+#ifdef USE_GL /* whole file */
+
+typedef struct {
+ GLfloat mass;
+ GLfloat ro2, rm2, ri2; /* outer/middle/inner */
+ GLfloat ro, radius;
+ GLfloat x, y, dx, dy;
+ GLfloat surface_gravity, depth;
+} star;
+
+typedef struct {
+ GLXContext *glx_context;
+ trackball_state *user_trackball;
+ Bool button_down_p;
+ int nstars;
+ star *stars;
+ int grid_w, grid_h;
+ GLfloat *grid;
+ char *segs;
+ GLfloat *vtx, *col;
+ GLfloat color[4];
+ int ncolors;
+ XColor *colors;
+} gw_configuration;
+
+static gw_configuration *bps = NULL;
+
+static GLfloat speed, resolution, grid_size;
+
+#define RESOLUTION_BASE 512
+#define GRID_SIZE_BASE 7
+#define SPEED_BASE 2.5
+#define MASS_EPSILON 0.03
+#define SLOPE_EPSILON 0.06
+#define GRID_SEG 16u /* Power-of-two here is faster. */
+#define MAX_MASS_COLOR 120
+
+static XrmOptionDescRec opts[] = {
+ { "-speed", ".speed", XrmoptionSepArg, 0 },
+ { "-resolution", ".resolution", XrmoptionSepArg, 0 },
+ { "-grid-size", ".gridSize", XrmoptionSepArg, 0 },
+};
+
+static argtype vars[] = {
+ {&speed, "speed", "Speed", DEF_SPEED, t_Float},
+ {&resolution, "resolution", "Resolution", DEF_RESOLUTION, t_Float},
+ {&grid_size, "gridSize", "GridSize", DEF_GRID_SIZE, t_Float},
+};
+
+ENTRYPOINT ModeSpecOpt gw_opts = {
+ countof(opts), opts, countof(vars), vars, NULL};
+
+
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#define WCLIP(x,hi) MIN(MAX((int)(x),0),(hi))
+
+/* Window management, etc
+ */
+ENTRYPOINT void
+reshape_gw (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 * 9/16;
+ y = -height/2;
+ h = height / (GLfloat) width;
+ }
+
+ glViewport (0, y, width, height);
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+ gluPerspective (40, 1/h, 10, 1000);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+ gluLookAt( 0, 0, 30,
+ 0, 0, 0,
+ 0, 1, 0);
+
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+
+ENTRYPOINT Bool
+gw_handle_event (ModeInfo *mi, XEvent *event)
+{
+ gw_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (gltrackball_event_handler (event, bp->user_trackball,
+ MI_WIDTH (mi), MI_HEIGHT (mi),
+ &bp->button_down_p))
+ return True;
+ return False;
+}
+
+
+static void
+parse_color (ModeInfo *mi, char *key, GLfloat color[4])
+{
+ XColor xcolor;
+ char *string = get_string_resource (mi->dpy, key, "Color");
+ if (!XParseColor (mi->dpy, mi->xgwa.colormap, string, &xcolor))
+ {
+ fprintf (stderr, "%s: unparsable color in %s: %s\n", progname,
+ key, string);
+ exit (1);
+ }
+ free (string);
+
+ color[0] = xcolor.red / 65536.0;
+ color[1] = xcolor.green / 65536.0;
+ color[2] = xcolor.blue / 65536.0;
+ color[3] = 1;
+}
+
+
+static void
+new_star (const gw_configuration *bp, star *s)
+{
+ int w = bp->grid_w * GRID_SEG;
+
+ s->radius = 2 * (2 + frand(3) + frand(3) + frand(3));
+ s->mass = s->radius * 150 * (2 + frand(3) + frand(3) + frand(3));
+
+ s->ro2 = s->mass / MASS_EPSILON;
+ s->ro = sqrt (s->ro2);
+ s->rm2 = pow (s->mass * (2.0f / SLOPE_EPSILON), 2.0f / 3.0f);
+ s->ri2 = s->radius * s->radius;
+ if (s->rm2 < s->ri2)
+ s->rm2 = s->ri2;
+ if (s->ro2 < s->rm2)
+ s->ro2 = s->rm2;
+
+ s->x = w * (s == bp->stars ? 0.5 : (0.35 + frand(0.3)));
+ s->dx = ((frand(1.0) - 0.5) * 0.1) / resolution;
+ s->dy = (0.1 + frand(0.6)) / resolution;
+
+ /* What the experienced gravitation would be at the surface of the
+ star, were the mass actually held in a singularity at its center.
+ */
+ s->surface_gravity = s->mass / s->ri2;
+ s->depth = s->surface_gravity;
+}
+
+
+static void
+move_stars (ModeInfo *mi)
+{
+ gw_configuration *bp = &bps[MI_SCREEN(mi)];
+ int w = bp->grid_w * GRID_SEG;
+ int h = bp->grid_h * GRID_SEG;
+ int i;
+
+ for (i = 0; i < bp->nstars; i++)
+ {
+ star *s = &bp->stars[i];
+ /* Move stars off screen until most of their influence fades */
+ GLfloat off = speed * SPEED_BASE * resolution;
+ s->x += s->dx * off;
+ s->y += s->dy * off;
+
+ if (s->x < -s->ro ||
+ s->y < -s->ro ||
+ s->x >= w + s->ro ||
+ s->y >= h + s->ro)
+ {
+ new_star (bp, s);
+ s->y = -s->ro;
+ }
+ }
+}
+
+
+static void
+calc_o (gw_configuration *bp, GLfloat mass, GLfloat cx, GLfloat y02,
+ unsigned from, unsigned to)
+{
+ GLfloat x0 = cx - from * GRID_SEG;
+ GLfloat g0 = mass / (x0*x0 + y02);
+ unsigned x;
+
+ ASSERT (to <= bp->grid_w || to <= bp->grid_h);
+
+ for (x = from; x < to; x++)
+ {
+ GLfloat *g = &bp->grid[x * GRID_SEG];
+ GLfloat g1;
+
+ x0 = cx - (x + 1) * GRID_SEG;
+ g1 = mass / (x0*x0 + y02);
+
+ g[0] += g0;
+ if (bp->segs[x])
+ {
+ GLfloat d = (g1 - g0) / GRID_SEG;
+ unsigned i;
+ for(i = 1; i != GRID_SEG; i++)
+ {
+ g0 += d;
+ g[i] += g0;
+ }
+ }
+ g0 = g1;
+ }
+}
+
+
+static void
+make_hires (gw_configuration *bp, unsigned from, unsigned to, unsigned w)
+{
+ unsigned x;
+
+ /* One bigger than from/to so that there's a good angle between the middle
+ and inner zones.
+
+ Don't make the last GRID_SEG high-res. This keeps the length consistent.
+ */
+ if (from)
+ from--;
+ from = MIN(from / GRID_SEG, w - 1);
+ to = MIN(to / GRID_SEG + 1, w - 1);
+
+ ASSERT (to <= bp->grid_w - 1 || to <= bp->grid_h - 1);
+
+ for (x = from; x < to; x++)
+ {
+ if (! bp->segs[x])
+ {
+ GLfloat *g = &bp->grid[x * GRID_SEG];
+ GLfloat g0 = g[0], g1 = g[GRID_SEG];
+ GLfloat d = (g1 - g0) / GRID_SEG;
+ unsigned i;
+ for (i = 1; i != GRID_SEG; i++)
+ {
+ g0 += d;
+ g[i] = g0;
+ }
+ bp->segs[x] = True;
+ }
+ }
+}
+
+
+static void
+calc_m (gw_configuration *bp, GLfloat mass, GLfloat cx, GLfloat y02,
+ unsigned from, unsigned to)
+{
+ GLfloat *gridp = bp->grid;
+ unsigned x;
+
+ ASSERT (to <= bp->grid_w * GRID_SEG + 1 || to <= bp->grid_h * GRID_SEG + 1);
+
+ for (x = from; x < to; x++)
+ {
+ /* Inverse square of distance from mass as a point source */
+ GLfloat x0 = cx - x;
+ gridp[x] += mass / (x0*x0 + y02);
+ }
+}
+
+
+#define EASE(r) (sin ((r) * M_PI_2))
+
+static void
+draw_row (ModeInfo *mi, int w, int y, Bool swap)
+{
+ gw_configuration *bp = &bps[MI_SCREEN(mi)];
+ int i;
+ int x;
+ int polys;
+ int w2 = w * GRID_SEG;
+
+ GLfloat *vtx_x;
+ GLfloat *vtx_y;
+ GLfloat *gridp = bp->grid;
+ memset (gridp, 0, w2 * sizeof(*gridp));
+ memset (bp->segs, 0, w);
+
+ for (i = 0; i < bp->nstars; i++)
+ {
+ star *s = &bp->stars[i];
+ GLfloat cx, cy;
+ unsigned olo, ohi, mlo, mhi, ilo, ihi;
+ GLfloat mass, max;
+ /* Move stars off screen until most of their influence fades */
+ GLfloat ro, rm, ri;
+
+ GLfloat y0;
+ GLfloat y02;
+
+ if (swap)
+ {
+ cy = s->x;
+ cx = s->y;
+ }
+ else
+ {
+ cx = s->x;
+ cy = s->y;
+ }
+ mass = s->mass;
+ max = s->surface_gravity;
+
+ y0 = cy - y;
+ y02 = y0 * y0;
+
+ if (y02 > s->ro2) continue;
+
+ ro = sqrtf (s->ro2 - y02);
+ olo = WCLIP((cx - ro) / GRID_SEG + 1, w); /* GLfloat -> int */
+ ohi = WCLIP((cx + ro) / GRID_SEG + 1, w);
+
+ rm = s->rm2 > y02 ? sqrtf (s->rm2 - y02) : 0;
+ mlo = WCLIP((cx - rm) + 1, w2);
+ mhi = WCLIP((cx + rm) + 1, w2);
+
+ ASSERT (mlo <= mhi);
+
+ if (mlo != mhi)
+ {
+ ri = s->ri2 > y02 ? sqrtf (s->ri2 - y02) : 0;
+ ilo = WCLIP(cx - ri + 1, w2);
+ ihi = WCLIP(cx + ri + 1, w2);
+
+ mlo -= mlo % GRID_SEG;
+ mhi += GRID_SEG - 1;
+ mhi -= mhi % GRID_SEG;
+
+ /* These go first. */
+ make_hires (bp, mlo, ilo, w);
+ make_hires (bp, ihi, mhi, w);
+
+ calc_m (bp, mass, cx, y02, mlo, ilo);
+ calc_m (bp, mass, cx, y02, ihi, mhi);
+
+ /* This does a bit more work than it needs to. */
+ for (x = ilo; x < ihi; x++)
+ gridp[x] += max;
+ }
+
+ calc_o (bp, mass, cx, y02, olo, mlo / GRID_SEG);
+ calc_o (bp, mass, cx, y02, mhi / GRID_SEG, ohi);
+ }
+
+ if (swap)
+ {
+ vtx_y = bp->vtx;
+ vtx_x = bp->vtx + 1;
+ }
+ else
+ {
+ vtx_x = bp->vtx;
+ vtx_y = bp->vtx + 1;
+ }
+
+# define COLOR_CODE 0
+
+# if COLOR_CODE
+ {
+ unsigned grid_max = bp->grid_w > bp->grid_h ? bp->grid_w : bp->grid_h;
+ GLfloat *color = malloc(sizeof(GLfloat) * 4 * (grid_max * GRID_SEG + 1));
+ glEnableClientState (GL_COLOR_ARRAY);
+ glColorPointer (4, GL_FLOAT, 0, color);
+# endif
+
+ ASSERT (! bp->segs[w - 1]);
+
+ polys = 0;
+ for (x = 0; x != w; x++)
+ {
+ if (! bp->segs[x])
+ {
+ int ci;
+ size_t vp = polys * 3;
+ size_t cp = polys * 4;
+# if COLOR_CODE
+ GLfloat slope = 0;
+ if (x != 0)
+ slope += fabs(gridp[x * GRID_SEG] - gridp[(x - 1) * GRID_SEG]);
+ if (x != w - 1)
+ slope += fabs(gridp[(x + 1) * GRID_SEG] - gridp[x * GRID_SEG]);
+ slope = 1 - (slope / (SLOPE_EPSILON * 2));
+
+ color[cp] = slope;
+ color[cp + 1] = slope;
+ color[cp + 2] = 1;
+ color[cp + 3] = 1;
+# endif
+ vtx_x[vp] = x * GRID_SEG;
+ bp->vtx[vp + 2] = gridp[x * GRID_SEG];
+ polys += 1;
+
+ ci = EASE (bp->vtx[vp + 2] / MAX_MASS_COLOR) * bp->ncolors;
+ bp->col[cp] = bp->colors[ci].red / 65536.0;
+ bp->col[cp+1] = bp->colors[ci].green / 65536.0;
+ bp->col[cp+2] = bp->colors[ci].blue / 65536.0;
+ bp->col[cp+3] = 1;
+ }
+ else
+ {
+ for(i = 0; i != GRID_SEG; i++)
+ {
+ int ci;
+ size_t vp = (polys + i) * 3;
+ size_t cp = (polys + i) * 4;
+# if COLOR_CODE
+ color[cp] = 1;
+ color[cp + 1] = 0.75;
+ color[cp + 2] = 0;
+ color[cp + 3] = 1;
+# endif
+ vtx_x[vp] = x * GRID_SEG + i;
+ bp->vtx[vp + 2] = gridp[x * GRID_SEG + i];
+
+ ci = EASE (bp->vtx[vp + 2] / MAX_MASS_COLOR) * bp->ncolors;
+ bp->col[cp] = bp->colors[ci].red / 65536.0;
+ bp->col[cp+1] = bp->colors[ci].green / 65536.0;
+ bp->col[cp+2] = bp->colors[ci].blue / 65536.0;
+ bp->col[cp+3] = 1;
+ }
+ polys += GRID_SEG;
+ }
+ }
+
+ for (i = 0; i < polys; i++)
+ vtx_y[i * 3] = y; /* + random() * (MASS_EPSILON / (MAXRAND)); */
+
+ mi->polygon_count += polys;
+ glDrawArrays (GL_LINE_STRIP, 0, polys);
+
+# if COLOR_CODE
+ glDisableClientState (GL_COLOR_ARRAY);
+ free (color);
+ }
+# endif
+}
+
+
+ENTRYPOINT void
+init_gw (ModeInfo *mi)
+{
+ gw_configuration *bp;
+ unsigned grid_max, vtx_max;
+ int i;
+ MI_INIT (mi, bps);
+
+ bp = &bps[MI_SCREEN(mi)];
+
+ bp->glx_context = init_GL(mi);
+
+ reshape_gw (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+
+ glShadeModel(GL_SMOOTH);
+
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_NORMALIZE);
+ glEnable(GL_CULL_FACE);
+
+ {
+ int h1, h2;
+ double s1, v1, s2, v2;
+ GLfloat color2[4];
+ parse_color (mi, "gridColor", bp->color);
+ parse_color (mi, "gridColor2", color2);
+ rgb_to_hsv (bp->color[0] * 65536,
+ bp->color[1] * 65536,
+ bp->color[2] * 65536,
+ &h1, &s1, &v1);
+ rgb_to_hsv (color2[0] * 65536,
+ color2[1] * 65536,
+ color2[2] * 65536,
+ &h2, &s2, &v2);
+ bp->ncolors = 128;
+ bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
+ make_color_ramp (0, 0, 0,
+ h1, s1, v1, h2, s2, v2,
+ bp->colors, &bp->ncolors,
+ False, 0, False);
+ }
+
+ bp->user_trackball = gltrackball_init (False);
+
+ bp->grid_w = (RESOLUTION_BASE * resolution) / GRID_SEG;
+ if (bp->grid_w < 2) bp->grid_w = 2;
+ bp->grid_h = bp->grid_w;
+
+ grid_max = bp->grid_w > bp->grid_h ? bp->grid_w : bp->grid_h;
+ vtx_max = grid_max * GRID_SEG;
+ bp->grid = (GLfloat *) calloc (vtx_max, sizeof(*bp->grid));
+ bp->vtx = (GLfloat *) calloc (vtx_max * 3, sizeof(*bp->vtx));
+ bp->col = (GLfloat *) calloc (vtx_max * 4, sizeof(*bp->col));
+ bp->segs = (char *) calloc (grid_max, sizeof(*bp->segs));
+ if (! bp->grid || ! bp->vtx || ! bp->col || ! bp->segs) abort();
+
+ bp->nstars = MI_COUNT(mi);
+ bp->stars = (star *) calloc (bp->nstars, sizeof (star));
+
+ for (i = 0; i < bp->nstars; i++)
+ {
+ star *s = &bp->stars[i];
+ new_star (bp, s);
+ s->y = frand(s->ro * 2 + bp->grid_h * GRID_SEG) - s->ro;
+ }
+
+ /* Let's tilt the floor a little. */
+ gltrackball_reset (bp->user_trackball,
+ -0.4 + frand(0.8),
+ -0.3 + frand(0.2));
+}
+
+
+ENTRYPOINT void
+draw_gw (ModeInfo *mi)
+{
+ gw_configuration *bp = &bps[MI_SCREEN(mi)];
+ int wire = MI_IS_WIREFRAME(mi);
+ Display *dpy = MI_DISPLAY(mi);
+ Window window = MI_WINDOW(mi);
+ int gridmod = grid_size * GRID_SIZE_BASE;
+ int x, y, i;
+ int sample_x, sample_y;
+ GLfloat sample_z = -1;
+
+ if (!bp->glx_context)
+ return;
+
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glPushMatrix ();
+
+# ifdef HAVE_MOBILE
+ glRotatef (current_device_rotation(), 0, 0, 1); /* right side up */
+# endif
+
+ gltrackball_rotate (bp->user_trackball);
+
+#if 0
+ glScalef(0.05/resolution, 0.05/resolution, 0.05/resolution);
+#endif
+
+ glRotatef (90, 1, 0, 0);
+ glTranslatef (-bp->grid_w * (GRID_SEG / 2.0f),
+ -bp->grid_h * (GRID_SEG * 0.75f),
+ 3);
+
+#if 0
+ glColor3f(1,0,0);
+ glPushMatrix();
+ glTranslatef(0,0,0);
+ glScalef (bp->grid_w * GRID_SEG,
+ bp->grid_w * GRID_SEG,
+ bp->grid_w * GRID_SEG);
+ glDisable (GL_FOG);
+ glBegin(GL_LINE_LOOP);
+ glVertex3f(0, 0, 0);
+ glVertex3f(1, 0, 0);
+ glVertex3f(1, 1, 0);
+ glVertex3f(.4, 1, 0);
+ glVertex3f(.5, .5, 0);
+ glVertex3f(.6, 1, 0);
+ glVertex3f(0, 1, 0);
+ glEnd();
+ glPopMatrix();
+ glColor3f(0,1,0);
+ if (!wire) glEnable (GL_FOG);
+#endif
+
+ if (!wire)
+ {
+ GLfloat fog_color[4] = { 0, 0, 0, 1 };
+
+ glLineWidth (2);
+ glEnable (GL_LINE_SMOOTH);
+ glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_BLEND);
+
+ glFogi (GL_FOG_MODE, GL_EXP2);
+ glFogfv (GL_FOG_COLOR, fog_color);
+ glFogf (GL_FOG_DENSITY, 0.005);
+ glEnable (GL_FOG);
+ }
+
+ glEnableClientState (GL_COLOR_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glColorPointer (4, GL_FLOAT, 0, bp->col);
+ glVertexPointer (3, GL_FLOAT, 0, bp->vtx);
+
+ /* Somewhere near the midpoint of the view */
+ sample_x = ((int) (bp->grid_w * GRID_SEG * 0.5) / gridmod) * gridmod;
+ sample_y = ((int) (bp->grid_h * GRID_SEG * 0.75) / GRID_SEG) * GRID_SEG;
+
+ /* Find the cumulative gravitational effect at the midpoint of each star,
+ for the depth of the foot-circle. This duplicates some of the draw_row()
+ logic. */
+ for (i = 0; i < bp->nstars; i++)
+ {
+ star *s0 = &bp->stars[i];
+ GLfloat x0 = s0->x;
+ GLfloat y0 = s0->y;
+ int j;
+ s0->depth = s0->surface_gravity;
+ for (j = 0; j < bp->nstars; j++)
+ {
+ star *s1;
+ GLfloat x1, y1, d2;
+ if (i == j) continue;
+ s1 = &bp->stars[j];
+ x1 = s1->x;
+ y1 = s1->y;
+ d2 = (x1-x0)*(x1-x0) + (y1-y0)*(y1-y0);
+ s0->depth += s1->mass / d2;
+ }
+ }
+
+ mi->polygon_count = 0;
+ for (y = 0; y < (bp->grid_h - 1) * GRID_SEG; y += gridmod)
+ draw_row (mi, bp->grid_w, y, False);
+ for (x = 0; x < (bp->grid_w - 1) * GRID_SEG; x += gridmod)
+ {
+ draw_row (mi, bp->grid_h, x, True);
+ if (x == sample_x)
+ sample_z = bp->grid[sample_y];
+ }
+
+ if (mi->fps_p)
+ {
+ /* Mass of Sol is 2x10^30kg, or 332 kilo-Earths.
+ But I'm not sure what the funniest number to put here is. */
+ /* mi->recursion_depth = (int) sample_z/4; */
+ mi->recursion_depth = (int) (sample_z * 30000);
+ glColor4fv (bp->color);
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, bp->color);
+ glBegin(GL_LINES);
+ glVertex3f (sample_x-0.15, sample_y-0.15, sample_z);
+ glVertex3f (sample_x+0.15, sample_y+0.15, sample_z);
+ glVertex3f (sample_x-0.15, sample_y+0.15, sample_z);
+ glVertex3f (sample_x+0.15, sample_y-0.15, sample_z);
+ glEnd();
+ }
+
+ /* Draw a circle around the "footprint" at the bottom of the gravity well.
+ */
+ for (i = 0; i < bp->nstars; i++)
+ {
+ int steps = 16;
+ star *s = &bp->stars[i];
+ GLfloat th, color[4];
+ int ci;
+ ci = EASE (s->depth / MAX_MASS_COLOR) * bp->ncolors;
+ color[0] = bp->colors[ci].red / 65536.0;
+ color[1] = bp->colors[ci].green / 65536.0;
+ color[2] = bp->colors[ci].blue / 65536.0;
+ color[3] = 1;
+ glColor4fv (color);
+ glMaterialfv (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, color);
+ glPushMatrix();
+ glTranslatef (s->x, s->y, 0);
+ glBegin (GL_LINE_LOOP);
+ for (th = 0; th < M_PI * 2; th += M_PI/steps)
+ glVertex3f (s->radius * cos(th), s->radius * sin(th), s->depth);
+ glEnd();
+ glPopMatrix();
+ mi->polygon_count += steps;
+ }
+
+#if 0
+ {
+ for (i = 0; i < bp->nstars; i++)
+ {
+ star *s = &bp->stars[i];
+ GLfloat maxr = sqrt (s->mass / MASS_EPSILON);
+ GLfloat th;
+ glPushMatrix();
+ glTranslatef (s->x, s->y, 0);
+ glColor3f(0, 0, 1);
+ glBegin (GL_LINE_LOOP);
+ for (th = 0; th < M_PI * 2; th += M_PI/32)
+ glVertex3f (s->radius * cos(th), s->radius * sin(th), 0);
+ glEnd();
+ glColor3f(0, 0, 0.5);
+ glBegin (GL_LINE_LOOP);
+ for (th = 0; th < M_PI * 2; th += M_PI/32)
+ glVertex3f (maxr * cos(th), maxr * sin(th), 0);
+ glEnd();
+ glBegin (GL_LINES);
+ glVertex3f ( 3000 * s->dx, 3000 * s->dy, 0);
+ glVertex3f (-3000 * s->dx, -3000 * s->dy, 0);
+ glEnd();
+ glPopMatrix();
+ }
+ }
+#endif
+
+ glPopMatrix ();
+
+ if (! bp->button_down_p)
+ move_stars (mi);
+
+ if (mi->fps_p) do_fps (mi);
+ glFinish();
+
+ glXSwapBuffers(dpy, window);
+}
+
+
+ENTRYPOINT void
+free_gw (ModeInfo *mi)
+{
+ gw_configuration *bp = &bps[MI_SCREEN(mi)];
+
+ if (!bp->glx_context) return;
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context);
+
+ if (bp->user_trackball) gltrackball_free (bp->user_trackball);
+ if (bp->stars) free (bp->stars);
+ if (bp->grid) free (bp->grid);
+ if (bp->vtx) free (bp->vtx);
+ if (bp->col) free (bp->col);
+ if (bp->segs) free (bp->segs);
+ if (bp->colors) free (bp->colors);
+}
+
+XSCREENSAVER_MODULE_2 ("GravityWell", gravitywell, gw)
+
+#endif /* USE_GL */