X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fcrumbler.c;fp=hacks%2Fglx%2Fcrumbler.c;h=cf21a94e1cd5d0716001e89ca0fe9aa2a088b138;hp=0000000000000000000000000000000000000000;hb=78add6e627ee5f10e1fa6f3852602ea5066eee5a;hpb=39809ded547bdbb08207d3e514950425215b4410 diff --git a/hacks/glx/crumbler.c b/hacks/glx/crumbler.c new file mode 100644 index 00000000..cf21a94e --- /dev/null +++ b/hacks/glx/crumbler.c @@ -0,0 +1,822 @@ +/* crumbler, Copyright (c) 2018 Jamie Zawinski + * + * 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" \ + "*showFPS: False \n" \ + "*wireframe: False \n" \ + "*suppressRotationAnimation: True\n" \ + +# define free_crumbler 0 +# define release_crumbler 0 +#undef countof +#define countof(x) (sizeof((x))/sizeof((*x))) + +#include "xlockmore.h" +#include "colors.h" +#include "rotator.h" +#include "quickhull.h" +#include "gltrackball.h" +#include + +#ifdef USE_GL /* whole file */ + + +#define DEF_SPIN "True" +#define DEF_WANDER "True" +#define DEF_SPEED "1.0" +#define DEF_DENSITY "1.0" +#define DEF_FRACTURE "0" + +#undef RANDSIGN +#define RANDSIGN() ((random() & 1) ? 1 : -1) + +typedef struct { + qh_vertex_t *verts; /* interior point cloud */ + int nverts, onverts; + qh_vertex_t min, max; /* enclosing box */ + qh_vertex_t mid, vec; + int polygon_count; + GLuint dlist; + int color; + int color_shift; +} chunk; + +typedef struct { + GLXContext *glx_context; + rotator *rot; + trackball_state *trackball; + enum { IDLE, SPLIT, PAUSE, FLEE, ZOOM } state; + GLfloat tick; + Bool button_down_p; + int nchunks; + chunk **chunks; + chunk *ghost; + + int ncolors; + XColor *colors; +} crumbler_configuration; + +static crumbler_configuration *bps = NULL; + +static Bool do_spin; +static GLfloat speed; +static GLfloat density; +static int fracture; +static Bool do_wander; + +static XrmOptionDescRec opts[] = { + { "-spin", ".spin", XrmoptionNoArg, "True" }, + { "+spin", ".spin", XrmoptionNoArg, "False" }, + { "-speed", ".speed", XrmoptionSepArg, 0 }, + { "-density", ".density", XrmoptionSepArg, 0 }, + { "-fracture",".fracture",XrmoptionSepArg, 0 }, + { "-wander", ".wander", XrmoptionNoArg, "True" }, + { "+wander", ".wander", XrmoptionNoArg, "False" } +}; + +static argtype vars[] = { + {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool}, + {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool}, + {&speed, "speed", "Speed", DEF_SPEED, t_Float}, + {&density, "density", "Density", DEF_DENSITY, t_Float}, + {&fracture, "fracture","Fracture",DEF_FRACTURE,t_Int}, +}; + +ENTRYPOINT ModeSpecOpt crumbler_opts = {countof(opts), opts, countof(vars), vars, NULL}; + + +/* Create a roughly spherical cloud of N random points. + */ +static void +make_point_cloud (qh_vertex_t *verts, int nverts) +{ + int i = 0; + while (i < nverts) + { + verts[i].x = (0.5 - frand(1.0)); + verts[i].y = (0.5 - frand(1.0)); + verts[i].z = (0.5 - frand(1.0)); + if ((verts[i].x * verts[i].x + + verts[i].y * verts[i].y + + verts[i].z * verts[i].z) + < 0.25) + i++; + } +} + + +static chunk * +make_chunk (void) +{ + chunk *c = (chunk *) calloc (1, sizeof(*c)); + c->dlist = glGenLists (1); + c->color_shift = 1 + (random() % 3) * RANDSIGN(); + return c; +} + +static void +render_chunk (ModeInfo *mi, chunk *c) +{ + int wire = MI_IS_WIREFRAME(mi); + int i, j; + qh_mesh_t m; + GLfloat d; + + c->polygon_count = 0; + c->min.x = c->min.y = c->min.z = 999999; + c->max.x = c->max.y = c->max.z = -999999; + if (c->nverts <= 3) + { + fprintf (stderr, "%s: nverts %d\n", progname, c->nverts); + abort(); + } + + for (i = 0; i < c->nverts; i++) + { + if (c->verts[i].x < c->min.x) c->min.x = c->verts[i].x; + if (c->verts[i].y < c->min.y) c->min.y = c->verts[i].y; + if (c->verts[i].z < c->min.z) c->min.z = c->verts[i].z; + if (c->verts[i].x > c->max.x) c->max.x = c->verts[i].x; + if (c->verts[i].y > c->max.y) c->max.y = c->verts[i].y; + if (c->verts[i].z > c->max.z) c->max.z = c->verts[i].z; + } + + c->mid.x = (c->max.x + c->min.x) / 2; + c->mid.y = (c->max.y + c->min.y) / 2; + c->mid.z = (c->max.z + c->min.z) / 2; + + /* midpoint as normalized vector from origin */ + d = sqrt (c->mid.x * c->mid.x + + c->mid.y * c->mid.y + + c->mid.z * c->mid.z); + c->vec.x = c->mid.x / d; + c->vec.y = c->mid.y / d; + c->vec.z = c->mid.z / d; + + if (c->nverts <= 3) + { + fprintf (stderr, "%s: nverts %d\n", progname, c->nverts); + abort(); + } + + m = qh_quickhull3d (c->verts, c->nverts); + + glNewList (c->dlist, GL_COMPILE); + if (! wire) glBegin (GL_TRIANGLES); + for (i = 0, j = 0; i < m.nindices; i += 3, j++) + { + qh_vertex_t *v0 = &m.vertices[m.indices[i+0]]; + qh_vertex_t *v1 = &m.vertices[m.indices[i+1]]; + qh_vertex_t *v2 = &m.vertices[m.indices[i+2]]; + qh_vec3_t *n = &m.normals[m.normalindices[j]]; + + if (i+2 >= m.nindices) abort(); + if (j >= m.nnormals) abort(); + + glNormal3f (n->x, n->y, n->z); + if (wire) glBegin(GL_LINE_LOOP); + glVertex3f (v0->x, v0->y, v0->z); + glVertex3f (v1->x, v1->y, v1->z); + glVertex3f (v2->x, v2->y, v2->z); + if (wire) glEnd(); + c->polygon_count++; + } + if (! wire) glEnd(); + + if (wire) + { + glPointSize (1); + glColor3f (0, 1, 0); + glBegin (GL_POINTS); + for (i = 0; i < c->nverts; i++) + { + if (i > 0 && i == c->onverts) + { + glEnd(); + glColor3f (1, 0, 0); + glBegin (GL_POINTS); + } + glVertex3f (c->verts[i].x, c->verts[i].y, c->verts[i].z); + } + glEnd(); + } + + glEndList(); + + qh_free_mesh (m); +} + + +static void +free_chunk (chunk *c) +{ + free (c->verts); + glDeleteLists (c->dlist, 1); + free (c); +} + + +/* Make sure the chunk contains at least N points. + As we subdivide, the number of points is reduced. + This adds new points to the interior that do not + affect the shape of the outer hull. + */ +static void +pad_chunk (chunk *c, int min) +{ + /* Allocate a new array of size N + Copy the old points into it + while size < N + pick two random points + add a point that is somewhere along the line between them + (that point will still be inside the old hull) + */ + qh_vertex_t *verts; + int i; + if (c->nverts >= min) return; + if (c->nverts <= 3) abort(); + verts = (qh_vertex_t *) calloc (min, sizeof(*verts)); + if (!verts) abort(); + memcpy (verts, c->verts, c->nverts * sizeof(*verts)); + i = c->nverts; + while (i < min) + { + qh_vertex_t v; + int j0, j1; + GLfloat r; + j0 = random() % c->nverts; + do { + j1 = random() % c->nverts; + } while (j0 == j1); + + r = 0.2 + frand(0.6); +# undef R +# define R(F) v.F = c->verts[j0].F + \ + r * (fabs (c->verts[j1].F - c->verts[j0].F)) \ + * (c->verts[j0].F > c->verts[j1].F ? -1 : 1) + R(x); + R(y); + R(z); +# undef R + + /* Sometimes quickhull.c is giving us concave and un-closed polygons. + Maybe it gets confused if there are duplicate points? So reject + this point if it is within epsilon of any earlier point. + */ +# if 0 /* Nope, that's not it. */ + { + Bool ok = True; + int j; + for (j = 0; j < i; j++) + { + + double X = c->verts[j].x - v.x; + double Y = c->verts[j].y - v.y; + double Z = c->verts[j].z - v.z; + double d2 = X*X + Y*Y + Z*Z; + if (d2 < 0.0001) + { + /* fprintf (stderr, "## REJ %f\n",d2); */ + ok = False; + break; + } + } + if (! ok) continue; + } +# endif + + verts[i++] = v; + } + +#if 0 + fprintf (stdout, " int n = %d;\n", min); + fprintf (stdout, " qh_vertex_t v[] = {"); + for (i = 0; i < min; i++) + fprintf(stdout,"{%f,%f,%f},", verts[i].x, verts[i].y, verts[i].z); + fprintf (stdout, "};\n\n"); +#endif + + free (c->verts); + c->verts = verts; + c->onverts = c->nverts; + c->nverts = min; + +#if 0 + qh_vertex_t *verts2 = (qh_vertex_t *) calloc (n, sizeof(*verts2)); + memcpy (verts2, v, n * sizeof(*verts2)); + free (c->verts); + c->verts = verts2; + c->onverts = 0; + c->nverts = n; +#endif +} + + +/* Returns a list of N new chunks. + */ +static chunk ** +split_chunk (ModeInfo *mi, chunk *c, int nchunks) +{ + /* Pick N key-points from the cloud. + Create N new chunks. + For each old point: + It goes in chunk N if it is closest to key-point N. + Free old chunk. + for each new chunk + render_chunk + */ + crumbler_configuration *bp = &bps[MI_SCREEN(mi)]; + chunk **chunks = (chunk **) calloc (nchunks, sizeof(*chunks)); + int *keys = (int *) calloc (nchunks, sizeof(*keys)); + int i, j; + chunk *c2; + + for (i = 0; i < nchunks; i++) + { + /* Fill keys with random numbers that are not duplicates. */ + Bool ok = True; + if (nchunks >= c->nverts) + { + fprintf (stderr, "%s: nverts %d nchunks %d\n", progname, + c->nverts, nchunks); + abort(); + } + do { + keys[i] = random() % c->nverts; + for (j = 0; j < i; j++) + if (keys[i] == keys[j]) + { + ok = False; + break; + } + ok = True; + } while (!ok); + + c2 = make_chunk(); + chunks[i] = c2; + chunks[i]->nverts = 0; + c2->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c2->verts)); + c2->color = (c->color + (random() % (1 + (bp->ncolors / 3))) + % bp->ncolors); + } + + /* Add the verts to the approprate chunks + */ + for (i = 0; i < c->nverts; i++) + { + qh_vertex_t *v0 = &c->verts[i]; + int target_chunk = -1; + double target_d2 = 9999999; + for (j = 0; j < nchunks; j++) + { + qh_vertex_t *v1 = &c->verts[keys[j]]; + double X = v1->x - v0->x; + double Y = v1->y - v0->y; + double Z = v1->z - v0->z; + double d2 = X*X + Y*Y + Z*Z; + if (d2 < target_d2) + { + target_d2 = d2; + target_chunk = j; + } + } + if (target_chunk == -1) abort(); + + c2 = chunks[target_chunk]; + c2->verts[c2->nverts++] = *v0; + } + + for (i = 0; i < nchunks; i++) + { + c2 = chunks[i]; + if (i == 0) /* The one we're gonna keep */ + pad_chunk (c2, c->nverts); + render_chunk (mi, c2); + } + + free (keys); + return chunks; +} + + +static void +tick_crumbler (ModeInfo *mi) +{ + crumbler_configuration *bp = &bps[MI_SCREEN(mi)]; + GLfloat ts; + + if (bp->button_down_p) return; + + switch (bp->state) { + case IDLE: ts = 0.02; break; + case SPLIT: ts = 0.01; break; + case PAUSE: ts = 0.008; break; + case FLEE: ts = 0.005; break; + case ZOOM: ts = 0.03; break; + default: abort(); break; + } + + bp->tick += ts * speed; + + if (bp->tick < 1) return; + + bp->tick = 0; + bp->state = (bp->state + 1) % (ZOOM + 1); + + switch (bp->state) { + case IDLE: + { + chunk *c = bp->chunks[0]; + int i; + + /* We already animated it zooming to full size. Now make it real. */ + GLfloat X = (c->max.x - c->min.x); + GLfloat Y = (c->max.y - c->min.y); + GLfloat Z = (c->max.z - c->min.z); + GLfloat s = 1 / MAX(X, MAX(Y, Z)); + + for (i = 0; i < c->nverts; i++) + { + c->verts[i].x *= s; + c->verts[i].y *= s; + c->verts[i].z *= s; + } + + /* Re-render it to move the verts in the display list too. + This also recomputes min, max and mid. + */ + render_chunk (mi, c); + break; + } + + case SPLIT: + { + chunk *c = bp->chunks[0]; + int frac = (fracture >= 2 ? fracture : 2 + (2 * (random() % 5))); + chunk **chunks = split_chunk (mi, c, frac); + if (bp->nchunks != 1) abort(); + if (bp->ghost) abort(); + bp->ghost = c; + free (bp->chunks); + bp->chunks = chunks; + bp->nchunks = frac; + break; + } + + case PAUSE: + break; + + case FLEE: + if (bp->ghost) free_chunk (bp->ghost); + bp->ghost = 0; + break; + + case ZOOM: + { + chunk *c = bp->chunks[0]; + int i; + for (i = 1; i < bp->nchunks; i++) + free_chunk (bp->chunks[i]); + bp->nchunks = 1; + + /* We already animated the remaining chunk moving toward the origin. + Make it real. + */ + for (i = 0; i < c->nverts; i++) + { + c->verts[i].x -= c->mid.x; + c->verts[i].y -= c->mid.y; + c->verts[i].z -= c->mid.z; + } + + /* Re-render it to move the verts in the display list too. + This also recomputes min, max and mid (now 0). + */ + render_chunk (mi, c); + break; + } + + default: abort(); break; + } +} + + +static GLfloat +ease_fn (GLfloat r) +{ + return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */ +} + + +static GLfloat +ease_ratio (GLfloat r) +{ + GLfloat ease = 0.35; + if (r <= 0) return 0; + else if (r >= 1) return 1; + else if (r <= ease) return ease * ease_fn (r / ease); + else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease); + else return r; +} + + +/* Window management, etc + */ +ENTRYPOINT void +reshape_crumbler (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, (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); + +# ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */ + { + int o = (int) current_device_rotation(); + if (o != 0 && o != 180 && o != -180) + glScalef (1/h, 1/h, 1/h); + } +# endif + + glClear(GL_COLOR_BUFFER_BIT); +} + + +ENTRYPOINT Bool +crumbler_handle_event (ModeInfo *mi, XEvent *event) +{ + crumbler_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; +} + + +ENTRYPOINT void +init_crumbler (ModeInfo *mi) +{ + crumbler_configuration *bp; + int wire = MI_IS_WIREFRAME(mi); + int i; + + MI_INIT (mi, bps); + bp = &bps[MI_SCREEN(mi)]; + + bp->glx_context = init_GL(mi); + + reshape_crumbler (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); + + if (!wire) + { + GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0}; + GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0}; + GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0}; + GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0}; + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + + glLightfv(GL_LIGHT0, GL_POSITION, pos); + glLightfv(GL_LIGHT0, GL_AMBIENT, amb); + glLightfv(GL_LIGHT0, GL_DIFFUSE, dif); + glLightfv(GL_LIGHT0, GL_SPECULAR, spc); + + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + { + double spin_speed = 0.5 * speed; + double spin_accel = 0.3; + double wander_speed = 0.01 * speed; + + bp->rot = make_rotator (do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + do_spin ? spin_speed : 0, + spin_accel, + do_wander ? wander_speed : 0, + True); + bp->trackball = gltrackball_init (True); + } + + bp->ncolors = 1024; + bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor)); + make_smooth_colormap (0, 0, 0, + bp->colors, &bp->ncolors, + False, 0, False); + + /* brighter colors, please... */ + for (i = 0; i < bp->ncolors; i++) + { +# undef R +# define R(F) F = 65535 * (0.3 + 0.7 * ((F) / 65535.0)) + R (bp->colors[i].red); + R (bp->colors[i].green); + R (bp->colors[i].blue); +# undef R + } + + { + chunk *c; + bp->nchunks = 1; + bp->chunks = (chunk **) calloc (bp->nchunks, sizeof(*bp->chunks)); + c = make_chunk(); + bp->chunks[0] = c; + c->nverts = 4500 * density; + c->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c->verts)); + make_point_cloud (c->verts, c->nverts); + + /* Let's shrink it to a point then zoom in. */ + bp->state = ZOOM; + bp->tick = 0; + for (i = 0; i < c->nverts; i++) + { + c->verts[i].x /= 500; + c->verts[i].y /= 500; + c->verts[i].z /= 500; + } + + render_chunk (mi, c); + } +} + + +static void +draw_chunk (ModeInfo *mi, chunk *c, GLfloat alpha) +{ + crumbler_configuration *bp = &bps[MI_SCREEN(mi)]; + GLfloat color[4]; + + color[0] = bp->colors[c->color].red / 65536.0; + color[1] = bp->colors[c->color].green / 65536.0; + color[2] = bp->colors[c->color].blue / 65536.0; + color[3] = alpha; + glColor4fv (color); + glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color); + + c->color += c->color_shift; + while (c->color < 0) c->color += bp->ncolors; + while (c->color >= bp->ncolors) c->color -= bp->ncolors; + + glCallList (c->dlist); + mi->polygon_count += c->polygon_count; +} + + +ENTRYPOINT void +draw_crumbler (ModeInfo *mi) +{ + int wire = MI_IS_WIREFRAME(mi); + crumbler_configuration *bp = &bps[MI_SCREEN(mi)]; + Display *dpy = MI_DISPLAY(mi); + Window window = MI_WINDOW(mi); + GLfloat alpha = 1; + int i; + + static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0}; + static const GLfloat bshiny = 128.0; + + if (!bp->glx_context) + return; + + glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context)); + + tick_crumbler (mi); + + glShadeModel(GL_SMOOTH); + glEnable(GL_DEPTH_TEST); + glEnable(GL_NORMALIZE); + glEnable(GL_CULL_FACE); + + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glPushMatrix (); + + { + double x, y, z; + get_position (bp->rot, &x, &y, &z, !bp->button_down_p); + glTranslatef((x - 0.5) * 8, + (y - 0.5) * 8, + (z - 0.5) * 15); + + gltrackball_rotate (bp->trackball); + + get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p); + glRotatef (x * 360, 1.0, 0.0, 0.0); + glRotatef (y * 360, 0.0, 1.0, 0.0); + glRotatef (z * 360, 0.0, 0.0, 1.0); + } + + mi->polygon_count = 0; + + glMaterialfv (GL_FRONT, GL_SPECULAR, bspec); + glMateriali (GL_FRONT, GL_SHININESS, bshiny); + + if (do_wander) + glScalef (10, 10, 10); + else + glScalef (13, 13, 13); + + alpha = 1; + for (i = 0; i < bp->nchunks; i++) + { + chunk *c = bp->chunks[i]; + + glPushMatrix(); + + switch (bp->state) { + case FLEE: + { + GLfloat r = ease_ratio (bp->tick); + /* Move everybody toward the origin, so that chunk #0 ends up + centered there. */ + glTranslatef (-r * c->mid.x, + -r * c->mid.y, + -r * c->mid.z); + if (i != 0) + { + /* Move this chunk away from the center, along a vector from + the origin to its midpoint. */ + GLfloat d2 = r * 6; + glTranslatef (c->vec.x * d2, c->vec.y * d2, c->vec.z * d2); + alpha = 1 - r; + } + } + break; + + case ZOOM: + { + chunk *c = bp->chunks[0]; + GLfloat X = (c->max.x - c->min.x); + GLfloat Y = (c->max.y - c->min.y); + GLfloat Z = (c->max.z - c->min.z); + GLfloat size0 = MAX(X, MAX(Y, Z)); + GLfloat size1 = 1.0; + GLfloat r = 1 - ease_ratio (bp->tick); + GLfloat s = 1 / (size0 + r * (size1 - size0)); + glScalef (s, s, s); + } + break; + + default: + break; + } + + draw_chunk (mi, c, alpha); + glPopMatrix(); + } + + /* Draw the old one, fading out. */ + if (!wire && bp->state == SPLIT && bp->ghost) + { + GLfloat s; + /* alpha = 1 - bp->tick; */ + alpha = 1; + /* s = 0.7 + (0.3 * ease_ratio (1-bp->tick)); */ + s = 2 * ease_ratio ((1-bp->tick) / 2); + s *= 1.01; + glScalef (s, s, s); + draw_chunk (mi, bp->ghost, alpha); + } + + glPopMatrix (); + + if (mi->fps_p) do_fps (mi); + glFinish(); + + glXSwapBuffers(dpy, window); +} + +XSCREENSAVER_MODULE ("Crumbler", crumbler) + +#endif /* USE_GL */