1 /* splodesic, Copyright (c) 2016 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 refresh_splodesic 0
18 # define release_splodesic 0
20 #define countof(x) (sizeof((x))/sizeof((*x)))
22 #include "xlockmore.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"
37 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
39 typedef struct { double a, o; } LL; /* latitude + longitude */
41 typedef struct triangle triangle;
45 triangle *neighbors[3];
54 GLXContext *glx_context;
56 trackball_state *trackball;
66 } splodesic_configuration;
68 static splodesic_configuration *bps = NULL;
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 { "-depth", ".freq", XrmoptionSepArg, 0 },
80 { "-wander", ".wander", XrmoptionNoArg, "True" },
81 { "+wander", ".wander", XrmoptionNoArg, "False" }
84 static argtype vars[] = {
85 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
86 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
87 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
88 {&depth_arg, "freq", "Depth", DEF_DEPTH, t_Int},
91 ENTRYPOINT ModeSpecOpt splodesic_opts = {countof(opts), opts, countof(vars), vars, NULL};
94 /* Creates a triangle specified by 3 polar endpoints.
97 make_triangle1 (ModeInfo *mi, LL v1, LL v2, LL v3)
99 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
100 triangle *t = (triangle *) calloc (1, sizeof(*t));
102 t->p[0].x = cos (v1.a) * cos (v1.o);
103 t->p[0].y = cos (v1.a) * sin (v1.o);
104 t->p[0].z = sin (v1.a);
106 t->p[1].x = cos (v2.a) * cos (v2.o);
107 t->p[1].y = cos (v2.a) * sin (v2.o);
108 t->p[1].z = sin (v2.a);
110 t->p[2].x = cos (v3.a) * cos (v3.o);
111 t->p[2].y = cos (v3.a) * sin (v3.o);
112 t->p[2].z = sin (v3.a);
114 t->next = bp->triangles;
120 /* Computes the midpoint of a line between two polar coords.
123 midpoint2 (LL v1, LL v2, LL *vm_ret,
124 XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
130 p1.x = cos (v1.a) * cos (v1.o);
131 p1.y = cos (v1.a) * sin (v1.o);
134 p2.x = cos (v2.a) * cos (v2.o);
135 p2.y = cos (v2.a) * sin (v2.o);
138 pm.x = (p1.x + p2.x) / 2;
139 pm.y = (p1.y + p2.y) / 2;
140 pm.z = (p1.z + p2.z) / 2;
142 vm.o = atan2 (pm.y, pm.x);
143 hyp = sqrt (pm.x * pm.x + pm.y * pm.y);
144 vm.a = atan2 (pm.z, hyp);
153 /* Creates triangular geodesic facets to the given depth.
156 make_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth)
159 make_triangle1 (mi, v1, v2, v3);
163 XYZ p1, p2, p3, p12, p23, p13;
165 midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
166 midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
167 midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
170 make_triangle (mi, v1, v12, v13, depth);
171 make_triangle (mi, v12, v2, v23, depth);
172 make_triangle (mi, v13, v23, v3, depth);
173 make_triangle (mi, v12, v23, v13, depth);
178 /* Creates triangles of a geodesic to the given depth (frequency).
181 make_geodesic (ModeInfo *mi)
183 int depth = depth_arg;
184 GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */
185 GLfloat s = M_PI / 5; /* lon division: 72 deg */
188 for (i = 0; i < 10; i++)
191 GLfloat th2 = s * (i+1);
192 GLfloat th3 = s * (i+2);
194 v1.a = th0; v1.o = th1;
195 v2.a = th0; v2.o = th3;
196 v3.a = -th0; v3.o = th2;
197 vc.a = M_PI/2; vc.o = th2;
199 if (i & 1) /* north */
201 make_triangle (mi, v1, v2, vc, depth);
202 make_triangle (mi, v2, v1, v3, depth);
210 make_triangle (mi, v2, v1, vc, depth);
211 make_triangle (mi, v1, v2, v3, depth);
217 /* Add t1 to the neighbor list of t0. */
219 link_neighbor (int i, int j, triangle *t0, triangle *t1)
224 for (k = 0; k < countof(t0->neighbors); k++)
226 if (t0->neighbors[k] == t1 ||
227 t0->neighbors[k] == 0)
229 t0->neighbors[k] = t1;
233 fprintf (stderr, "%d %d: too many neighbors\n", i, j);
239 feq (GLfloat a, GLfloat b) /* Oh for fuck's sake */
241 const GLfloat e = 0.00001;
243 return (d > -e && d < e);
247 /* Link each triangle to its three neighbors.
250 link_neighbors (ModeInfo *mi)
252 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
253 triangle *t0 = bp->triangles;
256 /* Triangles are neighbors if they share an edge (exactly 2 points).
257 (There must be a faster than N! way to do this...)
259 for (i = 0, t0 = bp->triangles; t0; t0 = t0->next, i++)
264 for (j = i+1, t1 = t0->next; t1; t1 = t1->next, j++)
268 for (ii = 0; ii < 3; ii++)
269 for (jj = 0; jj < 3; jj++)
270 if (feq (t0->p[ii].x, t1->p[jj].x) &&
271 feq (t0->p[ii].y, t1->p[jj].y) &&
272 feq (t0->p[ii].z, t1->p[jj].z))
276 fprintf (stderr, "%d %d: too many matches: %d\n", i, j, count);
281 link_neighbor (i, j, t0, t1);
282 link_neighbor (j, i, t1, t0);
286 if (! (t0->neighbors[0] && t0->neighbors[1] && t0->neighbors[2]))
288 fprintf (stderr, "%d: missing neighbors\n", i);
292 t0->altitude = 60; /* Fall in from space */
297 /* Add thrust to the triangle, and propagate some of that to its neighbors.
300 add_thrust (triangle *t, GLfloat thrust)
306 t->velocity += thrust;
308 /* Eyeballed this to look roughly the same at various depths. Eh. */
310 case 0: dampen = 0.5; break;
311 case 1: dampen = 0.7; break;
312 case 2: dampen = 0.9; break;
313 case 3: dampen = 0.98; break;
314 case 4: dampen = 0.985; break;
315 default: dampen = 0.993; break;
321 add_thrust (t->neighbors[0], thrust);
322 add_thrust (t->neighbors[1], thrust);
323 add_thrust (t->neighbors[2], thrust);
329 tick_triangles (ModeInfo *mi)
331 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
332 GLfloat gravity = 0.1;
336 /* Compute new velocities. */
337 for (i = 0, t = bp->triangles; t; t = t->next, i++)
341 add_thrust (t, t->thrust);
342 t->thrust_duration--;
343 if (t->thrust_duration <= 0)
345 t->thrust_duration = 0;
351 /* Apply new velocities. */
352 for (i = 0, t = bp->triangles; t; t = t->next, i++)
354 t->altitude += t->velocity;
355 t->velocity -= gravity;
361 t->refcount = 0; /* Clear for next time */
365 if (frand(1 / speed) < 0.2)
367 int n = random() % bp->count;
368 for (i = 0, t = bp->triangles; t; t = t->next, i++)
371 t->thrust += gravity * 1.5;
372 t->thrust_duration = 1 + BELLRAND(16);
376 if (bp->ccolor >= bp->ncolors)
382 draw_triangles (ModeInfo *mi)
384 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
385 int wire = MI_IS_WIREFRAME(mi);
389 int c1 = (c0 + bp->ncolors / 2) % bp->ncolors;
391 c[0] = bp->colors[c0].red / 65536.0;
392 c[1] = bp->colors[c0].green / 65536.0;
393 c[2] = bp->colors[c0].blue / 65536.0;
400 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
402 c[0] = bp->colors[c1].red / 65536.0;
403 c[1] = bp->colors[c1].green / 65536.0;
404 c[2] = bp->colors[c1].blue / 65536.0;
406 glMaterialfv (GL_BACK, GL_AMBIENT_AND_DIFFUSE, c);
409 glFrontFace (GL_CCW);
410 for (t = bp->triangles; t; t = t->next)
412 GLfloat a = t->altitude * 0.25;
416 c.x = t->p[0].x + t->p[1].x + t->p[2].x;
417 c.y = t->p[0].y + t->p[1].y + t->p[2].y;
418 c.z = t->p[0].z + t->p[1].z + t->p[2].z;
420 glTranslatef (a * c.x / 3, a * c.y / 3, a * c.z / 3);
421 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
422 glNormal3f (c.x, c.y, c.z);
423 glVertex3f (t->p[0].x, t->p[0].y, t->p[0].z);
424 glVertex3f (t->p[1].x, t->p[1].y, t->p[1].z);
425 glVertex3f (t->p[2].x, t->p[2].y, t->p[2].z);
433 /* Window management, etc
436 reshape_splodesic (ModeInfo *mi, int width, int height)
438 GLfloat h = (GLfloat) height / (GLfloat) width;
440 glViewport (0, 0, (GLint) width, (GLint) height);
442 glMatrixMode(GL_PROJECTION);
444 gluPerspective (30.0, 1/h, 1.0, 100.0);
446 glMatrixMode(GL_MODELVIEW);
448 gluLookAt( 0.0, 0.0, 30.0,
452 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
454 int o = (int) current_device_rotation();
455 if (o != 0 && o != 180 && o != -180)
456 glScalef (1/h, 1/h, 1/h);
460 glClear(GL_COLOR_BUFFER_BIT);
465 splodesic_handle_event (ModeInfo *mi, XEvent *event)
467 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
469 if (gltrackball_event_handler (event, bp->trackball,
470 MI_WIDTH (mi), MI_HEIGHT (mi),
473 else if (event->xany.type == KeyPress)
477 XLookupString (&event->xkey, &c, 1, &keysym, 0);
478 if (c == ' ' || c == '\t')
481 make_smooth_colormap (0, 0, 0,
482 bp->colors, &bp->ncolors,
492 static void free_splodesic (ModeInfo *mi);
495 init_splodesic (ModeInfo *mi)
497 splodesic_configuration *bp;
498 int wire = MI_IS_WIREFRAME(mi);
500 MI_INIT (mi, bps, free_splodesic);
502 bp = &bps[MI_SCREEN(mi)];
504 bp->glx_context = init_GL(mi);
506 reshape_splodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
510 GLfloat pos[4] = {4.0, 1.4, 1.1, 0.0};
511 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
512 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
513 GLfloat spc[4] = {1.0, 0.2, 0.2, 1.0};
514 GLfloat cspec[4] = {1, 1, 1, 1};
515 static const GLfloat shiny = 10;
518 glEnable(GL_LIGHTING);
520 glEnable(GL_DEPTH_TEST);
521 glEnable(GL_CULL_FACE);
523 glLightfv(GL_LIGHT0, GL_POSITION, pos);
524 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
525 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
526 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
527 glLightModeliv (GL_LIGHT_MODEL_TWO_SIDE, &lightmodel);
528 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, cspec);
529 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
533 double spin_speed = 0.5;
534 double wander_speed = 0.005;
535 double spin_accel = 1.0;
537 bp->rot = make_rotator (do_spin ? spin_speed : 0,
538 do_spin ? spin_speed : 0,
539 do_spin ? spin_speed : 0,
541 do_wander ? wander_speed : 0,
543 bp->trackball = gltrackball_init (True);
547 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
548 make_smooth_colormap (0, 0, 0,
549 bp->colors, &bp->ncolors,
556 if (depth_arg < 0) depth_arg = 0;
557 if (depth_arg > 10) depth_arg = 10;
565 draw_splodesic (ModeInfo *mi)
567 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
568 Display *dpy = MI_DISPLAY(mi);
569 Window window = MI_WINDOW(mi);
571 if (!bp->glx_context)
574 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
576 glShadeModel(GL_SMOOTH);
578 glEnable(GL_DEPTH_TEST);
579 glEnable(GL_NORMALIZE);
580 glDisable(GL_CULL_FACE);
582 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
588 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
589 glTranslatef((x - 0.5) * 6,
593 gltrackball_rotate (bp->trackball);
595 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
596 glRotatef (x * 360, 1.0, 0.0, 0.0);
597 glRotatef (y * 360, 0.0, 1.0, 0.0);
598 glRotatef (z * 360, 0.0, 0.0, 1.0);
601 mi->polygon_count = 0;
609 if (! bp->button_down_p)
615 if (mi->fps_p) do_fps (mi);
618 glXSwapBuffers(dpy, window);
623 free_splodesic (ModeInfo *mi)
625 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
626 while (bp->triangles)
628 triangle *t = bp->triangles->next;
629 free (bp->triangles);
634 XSCREENSAVER_MODULE ("Splodesic", splodesic)