1 /* crumbler, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org>
3 * Permission to use, copy, modify, distribute, and sell this software and its
4 * documentation for any purpose is hereby granted without fee, provided that
5 * the above copyright notice appear in all copies and that both that
6 * copyright notice and this permission notice appear in supporting
7 * documentation. No representations are made about the suitability of this
8 * software for any purpose. It is provided "as is" without express or
12 #define DEFAULTS "*delay: 30000 \n" \
13 "*showFPS: False \n" \
14 "*wireframe: False \n" \
15 "*suppressRotationAnimation: True\n" \
17 # define free_crumbler 0
18 # define release_crumbler 0
20 #define countof(x) (sizeof((x))/sizeof((*x)))
22 #include "xlockmore.h"
25 #include "quickhull.h"
26 #include "gltrackball.h"
29 #ifdef USE_GL /* whole file */
32 #define DEF_SPIN "True"
33 #define DEF_WANDER "True"
34 #define DEF_SPEED "1.0"
35 #define DEF_DENSITY "1.0"
36 #define DEF_FRACTURE "0"
39 #define RANDSIGN() ((random() & 1) ? 1 : -1)
42 qh_vertex_t *verts; /* interior point cloud */
44 qh_vertex_t min, max; /* enclosing box */
53 GLXContext *glx_context;
55 trackball_state *trackball;
56 enum { IDLE, SPLIT, PAUSE, FLEE, ZOOM } state;
65 } crumbler_configuration;
67 static crumbler_configuration *bps = NULL;
71 static GLfloat density;
73 static Bool do_wander;
75 static XrmOptionDescRec opts[] = {
76 { "-spin", ".spin", XrmoptionNoArg, "True" },
77 { "+spin", ".spin", XrmoptionNoArg, "False" },
78 { "-speed", ".speed", XrmoptionSepArg, 0 },
79 { "-density", ".density", XrmoptionSepArg, 0 },
80 { "-fracture",".fracture",XrmoptionSepArg, 0 },
81 { "-wander", ".wander", XrmoptionNoArg, "True" },
82 { "+wander", ".wander", XrmoptionNoArg, "False" }
85 static argtype vars[] = {
86 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
87 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
88 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
89 {&density, "density", "Density", DEF_DENSITY, t_Float},
90 {&fracture, "fracture","Fracture",DEF_FRACTURE,t_Int},
93 ENTRYPOINT ModeSpecOpt crumbler_opts = {countof(opts), opts, countof(vars), vars, NULL};
96 /* Create a roughly spherical cloud of N random points.
99 make_point_cloud (qh_vertex_t *verts, int nverts)
104 verts[i].x = (0.5 - frand(1.0));
105 verts[i].y = (0.5 - frand(1.0));
106 verts[i].z = (0.5 - frand(1.0));
107 if ((verts[i].x * verts[i].x +
108 verts[i].y * verts[i].y +
109 verts[i].z * verts[i].z)
119 chunk *c = (chunk *) calloc (1, sizeof(*c));
120 c->dlist = glGenLists (1);
121 c->color_shift = 1 + (random() % 3) * RANDSIGN();
126 render_chunk (ModeInfo *mi, chunk *c)
128 int wire = MI_IS_WIREFRAME(mi);
133 c->polygon_count = 0;
134 c->min.x = c->min.y = c->min.z = 999999;
135 c->max.x = c->max.y = c->max.z = -999999;
138 fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
142 for (i = 0; i < c->nverts; i++)
144 if (c->verts[i].x < c->min.x) c->min.x = c->verts[i].x;
145 if (c->verts[i].y < c->min.y) c->min.y = c->verts[i].y;
146 if (c->verts[i].z < c->min.z) c->min.z = c->verts[i].z;
147 if (c->verts[i].x > c->max.x) c->max.x = c->verts[i].x;
148 if (c->verts[i].y > c->max.y) c->max.y = c->verts[i].y;
149 if (c->verts[i].z > c->max.z) c->max.z = c->verts[i].z;
152 c->mid.x = (c->max.x + c->min.x) / 2;
153 c->mid.y = (c->max.y + c->min.y) / 2;
154 c->mid.z = (c->max.z + c->min.z) / 2;
156 /* midpoint as normalized vector from origin */
157 d = sqrt (c->mid.x * c->mid.x +
158 c->mid.y * c->mid.y +
159 c->mid.z * c->mid.z);
160 c->vec.x = c->mid.x / d;
161 c->vec.y = c->mid.y / d;
162 c->vec.z = c->mid.z / d;
166 fprintf (stderr, "%s: nverts %d\n", progname, c->nverts);
170 m = qh_quickhull3d (c->verts, c->nverts);
172 glNewList (c->dlist, GL_COMPILE);
173 if (! wire) glBegin (GL_TRIANGLES);
174 for (i = 0, j = 0; i < m.nindices; i += 3, j++)
176 qh_vertex_t *v0 = &m.vertices[m.indices[i+0]];
177 qh_vertex_t *v1 = &m.vertices[m.indices[i+1]];
178 qh_vertex_t *v2 = &m.vertices[m.indices[i+2]];
179 qh_vec3_t *n = &m.normals[m.normalindices[j]];
181 if (i+2 >= m.nindices) abort();
182 if (j >= m.nnormals) abort();
184 glNormal3f (n->x, n->y, n->z);
185 if (wire) glBegin(GL_LINE_LOOP);
186 glVertex3f (v0->x, v0->y, v0->z);
187 glVertex3f (v1->x, v1->y, v1->z);
188 glVertex3f (v2->x, v2->y, v2->z);
199 for (i = 0; i < c->nverts; i++)
201 if (i > 0 && i == c->onverts)
207 glVertex3f (c->verts[i].x, c->verts[i].y, c->verts[i].z);
219 free_chunk (chunk *c)
222 glDeleteLists (c->dlist, 1);
227 /* Make sure the chunk contains at least N points.
228 As we subdivide, the number of points is reduced.
229 This adds new points to the interior that do not
230 affect the shape of the outer hull.
233 pad_chunk (chunk *c, int min)
235 /* Allocate a new array of size N
236 Copy the old points into it
238 pick two random points
239 add a point that is somewhere along the line between them
240 (that point will still be inside the old hull)
244 if (c->nverts >= min) return;
245 if (c->nverts <= 3) abort();
246 verts = (qh_vertex_t *) calloc (min, sizeof(*verts));
248 memcpy (verts, c->verts, c->nverts * sizeof(*verts));
255 j0 = random() % c->nverts;
257 j1 = random() % c->nverts;
260 r = 0.2 + frand(0.6);
262 # define R(F) v.F = c->verts[j0].F + \
263 r * (fabs (c->verts[j1].F - c->verts[j0].F)) \
264 * (c->verts[j0].F > c->verts[j1].F ? -1 : 1)
270 /* Sometimes quickhull.c is giving us concave and un-closed polygons.
271 Maybe it gets confused if there are duplicate points? So reject
272 this point if it is within epsilon of any earlier point.
274 # if 0 /* Nope, that's not it. */
278 for (j = 0; j < i; j++)
281 double X = c->verts[j].x - v.x;
282 double Y = c->verts[j].y - v.y;
283 double Z = c->verts[j].z - v.z;
284 double d2 = X*X + Y*Y + Z*Z;
287 /* fprintf (stderr, "## REJ %f\n",d2); */
300 fprintf (stdout, " int n = %d;\n", min);
301 fprintf (stdout, " qh_vertex_t v[] = {");
302 for (i = 0; i < min; i++)
303 fprintf(stdout,"{%f,%f,%f},", verts[i].x, verts[i].y, verts[i].z);
304 fprintf (stdout, "};\n\n");
309 c->onverts = c->nverts;
313 qh_vertex_t *verts2 = (qh_vertex_t *) calloc (n, sizeof(*verts2));
314 memcpy (verts2, v, n * sizeof(*verts2));
323 /* Returns a list of N new chunks.
326 split_chunk (ModeInfo *mi, chunk *c, int nchunks)
328 /* Pick N key-points from the cloud.
331 It goes in chunk N if it is closest to key-point N.
336 crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
337 chunk **chunks = (chunk **) calloc (nchunks, sizeof(*chunks));
338 int *keys = (int *) calloc (nchunks, sizeof(*keys));
342 for (i = 0; i < nchunks; i++)
344 /* Fill keys with random numbers that are not duplicates. */
346 if (nchunks >= c->nverts)
348 fprintf (stderr, "%s: nverts %d nchunks %d\n", progname,
353 keys[i] = random() % c->nverts;
354 for (j = 0; j < i; j++)
355 if (keys[i] == keys[j])
365 chunks[i]->nverts = 0;
366 c2->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c2->verts));
367 c2->color = (c->color + (random() % (1 + (bp->ncolors / 3)))
371 /* Add the verts to the approprate chunks
373 for (i = 0; i < c->nverts; i++)
375 qh_vertex_t *v0 = &c->verts[i];
376 int target_chunk = -1;
377 double target_d2 = 9999999;
378 for (j = 0; j < nchunks; j++)
380 qh_vertex_t *v1 = &c->verts[keys[j]];
381 double X = v1->x - v0->x;
382 double Y = v1->y - v0->y;
383 double Z = v1->z - v0->z;
384 double d2 = X*X + Y*Y + Z*Z;
391 if (target_chunk == -1) abort();
393 c2 = chunks[target_chunk];
394 c2->verts[c2->nverts++] = *v0;
397 for (i = 0; i < nchunks; i++)
400 if (i == 0) /* The one we're gonna keep */
401 pad_chunk (c2, c->nverts);
402 render_chunk (mi, c2);
411 tick_crumbler (ModeInfo *mi)
413 crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
416 if (bp->button_down_p) return;
419 case IDLE: ts = 0.02; break;
420 case SPLIT: ts = 0.01; break;
421 case PAUSE: ts = 0.008; break;
422 case FLEE: ts = 0.005; break;
423 case ZOOM: ts = 0.03; break;
424 default: abort(); break;
427 bp->tick += ts * speed;
429 if (bp->tick < 1) return;
432 bp->state = (bp->state + 1) % (ZOOM + 1);
437 chunk *c = bp->chunks[0];
440 /* We already animated it zooming to full size. Now make it real. */
441 GLfloat X = (c->max.x - c->min.x);
442 GLfloat Y = (c->max.y - c->min.y);
443 GLfloat Z = (c->max.z - c->min.z);
444 GLfloat s = 1 / MAX(X, MAX(Y, Z));
446 for (i = 0; i < c->nverts; i++)
453 /* Re-render it to move the verts in the display list too.
454 This also recomputes min, max and mid.
456 render_chunk (mi, c);
462 chunk *c = bp->chunks[0];
463 int frac = (fracture >= 2 ? fracture : 2 + (2 * (random() % 5)));
464 chunk **chunks = split_chunk (mi, c, frac);
465 if (bp->nchunks != 1) abort();
466 if (bp->ghost) abort();
478 if (bp->ghost) free_chunk (bp->ghost);
484 chunk *c = bp->chunks[0];
486 for (i = 1; i < bp->nchunks; i++)
487 free_chunk (bp->chunks[i]);
490 /* We already animated the remaining chunk moving toward the origin.
493 for (i = 0; i < c->nverts; i++)
495 c->verts[i].x -= c->mid.x;
496 c->verts[i].y -= c->mid.y;
497 c->verts[i].z -= c->mid.z;
500 /* Re-render it to move the verts in the display list too.
501 This also recomputes min, max and mid (now 0).
503 render_chunk (mi, c);
507 default: abort(); break;
515 return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
520 ease_ratio (GLfloat r)
523 if (r <= 0) return 0;
524 else if (r >= 1) return 1;
525 else if (r <= ease) return ease * ease_fn (r / ease);
526 else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
531 /* Window management, etc
534 reshape_crumbler (ModeInfo *mi, int width, int height)
536 GLfloat h = (GLfloat) height / (GLfloat) width;
539 if (width > height * 5) { /* tiny window: show middle */
540 height = width * 9/16;
542 h = height / (GLfloat) width;
545 glViewport (0, y, (GLint) width, (GLint) height);
547 glMatrixMode(GL_PROJECTION);
549 gluPerspective (30.0, 1/h, 1.0, 100.0);
551 glMatrixMode(GL_MODELVIEW);
553 gluLookAt( 0.0, 0.0, 30.0,
557 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
559 int o = (int) current_device_rotation();
560 if (o != 0 && o != 180 && o != -180)
561 glScalef (1/h, 1/h, 1/h);
565 glClear(GL_COLOR_BUFFER_BIT);
570 crumbler_handle_event (ModeInfo *mi, XEvent *event)
572 crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
574 if (gltrackball_event_handler (event, bp->trackball,
575 MI_WIDTH (mi), MI_HEIGHT (mi),
584 init_crumbler (ModeInfo *mi)
586 crumbler_configuration *bp;
587 int wire = MI_IS_WIREFRAME(mi);
591 bp = &bps[MI_SCREEN(mi)];
593 bp->glx_context = init_GL(mi);
595 reshape_crumbler (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
599 GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
600 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
601 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
602 GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
604 glEnable(GL_LIGHTING);
606 glEnable(GL_DEPTH_TEST);
607 glEnable(GL_CULL_FACE);
609 glLightfv(GL_LIGHT0, GL_POSITION, pos);
610 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
611 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
612 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
615 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
619 double spin_speed = 0.5 * speed;
620 double spin_accel = 0.3;
621 double wander_speed = 0.01 * speed;
623 bp->rot = make_rotator (do_spin ? spin_speed : 0,
624 do_spin ? spin_speed : 0,
625 do_spin ? spin_speed : 0,
627 do_wander ? wander_speed : 0,
629 bp->trackball = gltrackball_init (True);
633 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
634 make_smooth_colormap (0, 0, 0,
635 bp->colors, &bp->ncolors,
638 /* brighter colors, please... */
639 for (i = 0; i < bp->ncolors; i++)
642 # define R(F) F = 65535 * (0.3 + 0.7 * ((F) / 65535.0))
643 R (bp->colors[i].red);
644 R (bp->colors[i].green);
645 R (bp->colors[i].blue);
652 bp->chunks = (chunk **) calloc (bp->nchunks, sizeof(*bp->chunks));
655 c->nverts = 4500 * density;
656 c->verts = (qh_vertex_t *) calloc (c->nverts, sizeof(*c->verts));
657 make_point_cloud (c->verts, c->nverts);
659 /* Let's shrink it to a point then zoom in. */
662 for (i = 0; i < c->nverts; i++)
664 c->verts[i].x /= 500;
665 c->verts[i].y /= 500;
666 c->verts[i].z /= 500;
669 render_chunk (mi, c);
675 draw_chunk (ModeInfo *mi, chunk *c, GLfloat alpha)
677 crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
680 color[0] = bp->colors[c->color].red / 65536.0;
681 color[1] = bp->colors[c->color].green / 65536.0;
682 color[2] = bp->colors[c->color].blue / 65536.0;
685 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
687 c->color += c->color_shift;
688 while (c->color < 0) c->color += bp->ncolors;
689 while (c->color >= bp->ncolors) c->color -= bp->ncolors;
691 glCallList (c->dlist);
692 mi->polygon_count += c->polygon_count;
697 draw_crumbler (ModeInfo *mi)
699 int wire = MI_IS_WIREFRAME(mi);
700 crumbler_configuration *bp = &bps[MI_SCREEN(mi)];
701 Display *dpy = MI_DISPLAY(mi);
702 Window window = MI_WINDOW(mi);
706 static const GLfloat bspec[4] = {1.0, 1.0, 1.0, 1.0};
707 static const GLfloat bshiny = 128.0;
709 if (!bp->glx_context)
712 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
716 glShadeModel(GL_SMOOTH);
717 glEnable(GL_DEPTH_TEST);
718 glEnable(GL_NORMALIZE);
719 glEnable(GL_CULL_FACE);
721 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
727 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
728 glTranslatef((x - 0.5) * 8,
732 gltrackball_rotate (bp->trackball);
734 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
735 glRotatef (x * 360, 1.0, 0.0, 0.0);
736 glRotatef (y * 360, 0.0, 1.0, 0.0);
737 glRotatef (z * 360, 0.0, 0.0, 1.0);
740 mi->polygon_count = 0;
742 glMaterialfv (GL_FRONT, GL_SPECULAR, bspec);
743 glMateriali (GL_FRONT, GL_SHININESS, bshiny);
746 glScalef (10, 10, 10);
748 glScalef (13, 13, 13);
751 for (i = 0; i < bp->nchunks; i++)
753 chunk *c = bp->chunks[i];
760 GLfloat r = ease_ratio (bp->tick);
761 /* Move everybody toward the origin, so that chunk #0 ends up
763 glTranslatef (-r * c->mid.x,
768 /* Move this chunk away from the center, along a vector from
769 the origin to its midpoint. */
771 glTranslatef (c->vec.x * d2, c->vec.y * d2, c->vec.z * d2);
779 chunk *c = bp->chunks[0];
780 GLfloat X = (c->max.x - c->min.x);
781 GLfloat Y = (c->max.y - c->min.y);
782 GLfloat Z = (c->max.z - c->min.z);
783 GLfloat size0 = MAX(X, MAX(Y, Z));
785 GLfloat r = 1 - ease_ratio (bp->tick);
786 GLfloat s = 1 / (size0 + r * (size1 - size0));
795 draw_chunk (mi, c, alpha);
799 /* Draw the old one, fading out. */
800 if (!wire && bp->state == SPLIT && bp->ghost)
803 /* alpha = 1 - bp->tick; */
805 /* s = 0.7 + (0.3 * ease_ratio (1-bp->tick)); */
806 s = 2 * ease_ratio ((1-bp->tick) / 2);
809 draw_chunk (mi, bp->ghost, alpha);
814 if (mi->fps_p) do_fps (mi);
817 glXSwapBuffers(dpy, window);
820 XSCREENSAVER_MODULE ("Crumbler", crumbler)