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 release_splodesic 0
19 #define countof(x) (sizeof((x))/sizeof((*x)))
21 #include "xlockmore.h"
25 #include "gltrackball.h"
28 #ifdef USE_GL /* whole file */
31 #define DEF_SPIN "True"
32 #define DEF_WANDER "True"
33 #define DEF_SPEED "1.0"
36 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
38 typedef struct { double a, o; } LL; /* latitude + longitude */
40 typedef struct triangle triangle;
44 triangle *neighbors[3];
53 GLXContext *glx_context;
55 trackball_state *trackball;
65 } splodesic_configuration;
67 static splodesic_configuration *bps = NULL;
72 static Bool do_wander;
74 static XrmOptionDescRec opts[] = {
75 { "-spin", ".spin", XrmoptionNoArg, "True" },
76 { "+spin", ".spin", XrmoptionNoArg, "False" },
77 { "-speed", ".speed", XrmoptionSepArg, 0 },
78 { "-depth", ".freq", XrmoptionSepArg, 0 },
79 { "-wander", ".wander", XrmoptionNoArg, "True" },
80 { "+wander", ".wander", XrmoptionNoArg, "False" }
83 static argtype vars[] = {
84 {&do_spin, "spin", "Spin", DEF_SPIN, t_Bool},
85 {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
86 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
87 {&depth_arg, "freq", "Depth", DEF_DEPTH, t_Int},
90 ENTRYPOINT ModeSpecOpt splodesic_opts = {countof(opts), opts, countof(vars), vars, NULL};
93 /* Creates a triangle specified by 3 polar endpoints.
96 make_triangle1 (ModeInfo *mi, LL v1, LL v2, LL v3)
98 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
99 triangle *t = (triangle *) calloc (1, sizeof(*t));
101 t->p[0].x = cos (v1.a) * cos (v1.o);
102 t->p[0].y = cos (v1.a) * sin (v1.o);
103 t->p[0].z = sin (v1.a);
105 t->p[1].x = cos (v2.a) * cos (v2.o);
106 t->p[1].y = cos (v2.a) * sin (v2.o);
107 t->p[1].z = sin (v2.a);
109 t->p[2].x = cos (v3.a) * cos (v3.o);
110 t->p[2].y = cos (v3.a) * sin (v3.o);
111 t->p[2].z = sin (v3.a);
113 t->next = bp->triangles;
119 /* Computes the midpoint of a line between two polar coords.
122 midpoint2 (LL v1, LL v2, LL *vm_ret,
123 XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
129 p1.x = cos (v1.a) * cos (v1.o);
130 p1.y = cos (v1.a) * sin (v1.o);
133 p2.x = cos (v2.a) * cos (v2.o);
134 p2.y = cos (v2.a) * sin (v2.o);
137 pm.x = (p1.x + p2.x) / 2;
138 pm.y = (p1.y + p2.y) / 2;
139 pm.z = (p1.z + p2.z) / 2;
141 vm.o = atan2 (pm.y, pm.x);
142 hyp = sqrt (pm.x * pm.x + pm.y * pm.y);
143 vm.a = atan2 (pm.z, hyp);
152 /* Creates triangular geodesic facets to the given depth.
155 make_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth)
158 make_triangle1 (mi, v1, v2, v3);
162 XYZ p1, p2, p3, p12, p23, p13;
164 midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
165 midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
166 midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
169 make_triangle (mi, v1, v12, v13, depth);
170 make_triangle (mi, v12, v2, v23, depth);
171 make_triangle (mi, v13, v23, v3, depth);
172 make_triangle (mi, v12, v23, v13, depth);
177 /* Creates triangles of a geodesic to the given depth (frequency).
180 make_geodesic (ModeInfo *mi)
182 int depth = depth_arg;
183 GLfloat th0 = atan (0.5); /* lat division: 26.57 deg */
184 GLfloat s = M_PI / 5; /* lon division: 72 deg */
187 for (i = 0; i < 10; i++)
190 GLfloat th2 = s * (i+1);
191 GLfloat th3 = s * (i+2);
193 v1.a = th0; v1.o = th1;
194 v2.a = th0; v2.o = th3;
195 v3.a = -th0; v3.o = th2;
196 vc.a = M_PI/2; vc.o = th2;
198 if (i & 1) /* north */
200 make_triangle (mi, v1, v2, vc, depth);
201 make_triangle (mi, v2, v1, v3, depth);
209 make_triangle (mi, v2, v1, vc, depth);
210 make_triangle (mi, v1, v2, v3, depth);
216 /* Add t1 to the neighbor list of t0. */
218 link_neighbor (int i, int j, triangle *t0, triangle *t1)
223 for (k = 0; k < countof(t0->neighbors); k++)
225 if (t0->neighbors[k] == t1 ||
226 t0->neighbors[k] == 0)
228 t0->neighbors[k] = t1;
232 fprintf (stderr, "%d %d: too many neighbors\n", i, j);
238 feq (GLfloat a, GLfloat b) /* Oh for fuck's sake */
240 const GLfloat e = 0.00001;
242 return (d > -e && d < e);
246 /* Link each triangle to its three neighbors.
249 link_neighbors (ModeInfo *mi)
251 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
252 triangle *t0 = bp->triangles;
255 /* Triangles are neighbors if they share an edge (exactly 2 points).
256 (There must be a faster than N! way to do this...)
258 for (i = 0, t0 = bp->triangles; t0; t0 = t0->next, i++)
263 for (j = i+1, t1 = t0->next; t1; t1 = t1->next, j++)
267 for (ii = 0; ii < 3; ii++)
268 for (jj = 0; jj < 3; jj++)
269 if (feq (t0->p[ii].x, t1->p[jj].x) &&
270 feq (t0->p[ii].y, t1->p[jj].y) &&
271 feq (t0->p[ii].z, t1->p[jj].z))
275 fprintf (stderr, "%d %d: too many matches: %d\n", i, j, count);
280 link_neighbor (i, j, t0, t1);
281 link_neighbor (j, i, t1, t0);
285 if (! (t0->neighbors[0] && t0->neighbors[1] && t0->neighbors[2]))
287 fprintf (stderr, "%d: missing neighbors\n", i);
291 t0->altitude = 60; /* Fall in from space */
296 /* Add thrust to the triangle, and propagate some of that to its neighbors.
299 add_thrust (triangle *t, GLfloat thrust)
305 t->velocity += thrust;
307 /* Eyeballed this to look roughly the same at various depths. Eh. */
309 case 0: dampen = 0.5; break;
310 case 1: dampen = 0.7; break;
311 case 2: dampen = 0.9; break;
312 case 3: dampen = 0.98; break;
313 case 4: dampen = 0.985; break;
314 default: dampen = 0.993; break;
320 add_thrust (t->neighbors[0], thrust);
321 add_thrust (t->neighbors[1], thrust);
322 add_thrust (t->neighbors[2], thrust);
328 tick_triangles (ModeInfo *mi)
330 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
331 GLfloat gravity = 0.1;
335 /* Compute new velocities. */
336 for (i = 0, t = bp->triangles; t; t = t->next, i++)
340 add_thrust (t, t->thrust);
341 t->thrust_duration--;
342 if (t->thrust_duration <= 0)
344 t->thrust_duration = 0;
350 /* Apply new velocities. */
351 for (i = 0, t = bp->triangles; t; t = t->next, i++)
353 t->altitude += t->velocity;
354 t->velocity -= gravity;
360 t->refcount = 0; /* Clear for next time */
364 if (frand(1 / speed) < 0.2)
366 int n = random() % bp->count;
367 for (i = 0, t = bp->triangles; t; t = t->next, i++)
370 t->thrust += gravity * 1.5;
371 t->thrust_duration = 1 + BELLRAND(16);
375 if (bp->ccolor >= bp->ncolors)
381 draw_triangles (ModeInfo *mi)
383 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
384 int wire = MI_IS_WIREFRAME(mi);
388 int c1 = (c0 + bp->ncolors / 2) % bp->ncolors;
390 c[0] = bp->colors[c0].red / 65536.0;
391 c[1] = bp->colors[c0].green / 65536.0;
392 c[2] = bp->colors[c0].blue / 65536.0;
399 glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
401 c[0] = bp->colors[c1].red / 65536.0;
402 c[1] = bp->colors[c1].green / 65536.0;
403 c[2] = bp->colors[c1].blue / 65536.0;
405 glMaterialfv (GL_BACK, GL_AMBIENT_AND_DIFFUSE, c);
408 glFrontFace (GL_CCW);
409 for (t = bp->triangles; t; t = t->next)
411 GLfloat a = t->altitude * 0.25;
415 c.x = t->p[0].x + t->p[1].x + t->p[2].x;
416 c.y = t->p[0].y + t->p[1].y + t->p[2].y;
417 c.z = t->p[0].z + t->p[1].z + t->p[2].z;
419 glTranslatef (a * c.x / 3, a * c.y / 3, a * c.z / 3);
420 glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLES);
421 glNormal3f (c.x, c.y, c.z);
422 glVertex3f (t->p[0].x, t->p[0].y, t->p[0].z);
423 glVertex3f (t->p[1].x, t->p[1].y, t->p[1].z);
424 glVertex3f (t->p[2].x, t->p[2].y, t->p[2].z);
432 /* Window management, etc
435 reshape_splodesic (ModeInfo *mi, int width, int height)
437 GLfloat h = (GLfloat) height / (GLfloat) width;
440 if (width > height * 5) { /* tiny window: show middle */
441 height = width * 9/16;
443 h = height / (GLfloat) width;
446 glViewport (0, y, (GLint) width, (GLint) height);
448 glMatrixMode(GL_PROJECTION);
450 gluPerspective (30.0, 1/h, 1.0, 100.0);
452 glMatrixMode(GL_MODELVIEW);
454 gluLookAt( 0.0, 0.0, 30.0,
458 # ifdef HAVE_MOBILE /* Keep it the same relative size when rotated. */
460 int o = (int) current_device_rotation();
461 if (o != 0 && o != 180 && o != -180)
462 glScalef (1/h, 1/h, 1/h);
466 glClear(GL_COLOR_BUFFER_BIT);
471 splodesic_handle_event (ModeInfo *mi, XEvent *event)
473 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
475 if (gltrackball_event_handler (event, bp->trackball,
476 MI_WIDTH (mi), MI_HEIGHT (mi),
479 else if (event->xany.type == KeyPress)
483 XLookupString (&event->xkey, &c, 1, &keysym, 0);
484 if (c == ' ' || c == '\t')
487 make_smooth_colormap (0, 0, 0,
488 bp->colors, &bp->ncolors,
499 init_splodesic (ModeInfo *mi)
501 splodesic_configuration *bp;
502 int wire = MI_IS_WIREFRAME(mi);
506 bp = &bps[MI_SCREEN(mi)];
508 bp->glx_context = init_GL(mi);
510 reshape_splodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
514 GLfloat pos[4] = {4.0, 1.4, 1.1, 0.0};
515 GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
516 GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
517 GLfloat spc[4] = {1.0, 0.2, 0.2, 1.0};
518 GLfloat cspec[4] = {1, 1, 1, 1};
519 static const GLfloat shiny = 10;
522 glEnable(GL_LIGHTING);
524 glEnable(GL_DEPTH_TEST);
525 glEnable(GL_CULL_FACE);
527 glLightfv(GL_LIGHT0, GL_POSITION, pos);
528 glLightfv(GL_LIGHT0, GL_AMBIENT, amb);
529 glLightfv(GL_LIGHT0, GL_DIFFUSE, dif);
530 glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
531 glLightModeliv (GL_LIGHT_MODEL_TWO_SIDE, &lightmodel);
532 glMaterialfv (GL_FRONT_AND_BACK, GL_SPECULAR, cspec);
533 glMateriali (GL_FRONT_AND_BACK, GL_SHININESS, shiny);
537 double spin_speed = 0.5;
538 double wander_speed = 0.005;
539 double spin_accel = 1.0;
541 bp->rot = make_rotator (do_spin ? spin_speed : 0,
542 do_spin ? spin_speed : 0,
543 do_spin ? spin_speed : 0,
545 do_wander ? wander_speed : 0,
547 bp->trackball = gltrackball_init (True);
551 bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
552 make_smooth_colormap (0, 0, 0,
553 bp->colors, &bp->ncolors,
560 if (depth_arg < 0) depth_arg = 0;
561 if (depth_arg > 10) depth_arg = 10;
569 draw_splodesic (ModeInfo *mi)
571 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
572 Display *dpy = MI_DISPLAY(mi);
573 Window window = MI_WINDOW(mi);
575 if (!bp->glx_context)
578 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
580 glShadeModel(GL_SMOOTH);
582 glEnable(GL_DEPTH_TEST);
583 glEnable(GL_NORMALIZE);
584 glDisable(GL_CULL_FACE);
586 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
592 get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
593 glTranslatef((x - 0.5) * 6,
597 gltrackball_rotate (bp->trackball);
599 get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
600 glRotatef (x * 360, 1.0, 0.0, 0.0);
601 glRotatef (y * 360, 0.0, 1.0, 0.0);
602 glRotatef (z * 360, 0.0, 0.0, 1.0);
605 mi->polygon_count = 0;
613 if (! bp->button_down_p)
619 if (mi->fps_p) do_fps (mi);
622 glXSwapBuffers(dpy, window);
627 free_splodesic (ModeInfo *mi)
629 splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
630 while (bp->triangles)
632 triangle *t = bp->triangles->next;
633 free (bp->triangles);
638 XSCREENSAVER_MODULE ("Splodesic", splodesic)