From http://www.jwz.org/xscreensaver/xscreensaver-5.36.tar.gz
[xscreensaver] / hacks / glx / splodesic.c
1 /* splodesic, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
2  *
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 
9  * implied warranty.
10  */
11
12 #define DEFAULTS        "*delay:        30000       \n" \
13                         "*showFPS:      False       \n" \
14                         "*wireframe:    False       \n" \
15                         "*suppressRotationAnimation: True\n" \
16
17 # define refresh_splodesic 0
18 #undef countof
19 #define countof(x) (sizeof((x))/sizeof((*x)))
20
21 #include "xlockmore.h"
22 #include "colors.h"
23 #include "normals.h"
24 #include "rotator.h"
25 #include "gltrackball.h"
26 #include <ctype.h>
27
28 #ifdef USE_GL /* whole file */
29
30
31 #define DEF_SPIN        "True"
32 #define DEF_WANDER      "True"
33 #define DEF_SPEED       "1.0"
34 #define DEF_DEPTH       "4"
35
36 #define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
37
38 typedef struct { double a, o; } LL;     /* latitude + longitude */
39
40 typedef struct triangle triangle;
41 struct triangle {
42   XYZ p[3];
43   triangle *next;
44   triangle *neighbors[3];
45   GLfloat altitude;
46   GLfloat velocity;
47   GLfloat thrust;
48   int thrust_duration;
49   int refcount;
50 };
51
52 typedef struct {
53   GLXContext *glx_context;
54   rotator *rot;
55   trackball_state *trackball;
56   Bool button_down_p;
57
58   int count;
59   triangle *triangles;
60
61   int ncolors;
62   XColor *colors;
63   int ccolor;
64
65 } splodesic_configuration;
66
67 static splodesic_configuration *bps = NULL;
68
69 static Bool do_spin;
70 static GLfloat speed;
71 static int depth_arg;
72 static Bool do_wander;
73
74 static XrmOptionDescRec opts[] = {
75   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
76   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
77   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
78   { "-depth",  ".depth",  XrmoptionSepArg, 0 },
79   { "-wander", ".wander", XrmoptionNoArg, "True" },
80   { "+wander", ".wander", XrmoptionNoArg, "False" }
81 };
82
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, "depth",  "Depth",  DEF_DEPTH,  t_Int},
88 };
89
90 ENTRYPOINT ModeSpecOpt splodesic_opts = {countof(opts), opts, countof(vars), vars, NULL};
91
92
93 /* Creates a triangle specified by 3 polar endpoints.
94  */
95 static void
96 make_triangle1 (ModeInfo *mi, LL v1, LL v2, LL v3)
97 {
98   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
99   triangle *t = (triangle *) calloc (1, sizeof(*t));
100
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);
104
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);
108
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);
112
113   t->next = bp->triangles;
114   bp->triangles = t;
115   bp->count++;
116 }
117
118
119 /* Computes the midpoint of a line between two polar coords.
120  */
121 static void
122 midpoint2 (LL v1, LL v2, LL *vm_ret,
123            XYZ *p1_ret, XYZ *p2_ret, XYZ *pm_ret)
124 {
125   XYZ p1, p2, pm;
126   LL vm;
127   GLfloat hyp;
128
129   p1.x = cos (v1.a) * cos (v1.o);
130   p1.y = cos (v1.a) * sin (v1.o);
131   p1.z = sin (v1.a);
132
133   p2.x = cos (v2.a) * cos (v2.o);
134   p2.y = cos (v2.a) * sin (v2.o);
135   p2.z = sin (v2.a);
136
137   pm.x = (p1.x + p2.x) / 2;
138   pm.y = (p1.y + p2.y) / 2;
139   pm.z = (p1.z + p2.z) / 2;
140
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);
144
145   *p1_ret = p1;
146   *p2_ret = p2;
147   *pm_ret = pm;
148   *vm_ret = vm;
149 }
150
151
152 /* Creates triangular geodesic facets to the given depth.
153  */
154 static void
155 make_triangle (ModeInfo *mi, LL v1, LL v2, LL v3, int depth)
156 {
157   if (depth <= 0)
158     make_triangle1 (mi, v1, v2, v3);
159   else
160     {
161       LL v12, v23, v13;
162       XYZ p1, p2, p3, p12, p23, p13;
163
164       midpoint2 (v1, v2, &v12, &p1, &p2, &p12);
165       midpoint2 (v2, v3, &v23, &p2, &p3, &p23);
166       midpoint2 (v1, v3, &v13, &p1, &p3, &p13);
167       depth--;
168
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);
173     }
174 }
175
176
177 /* Creates triangles of a geodesic to the given depth (frequency).
178  */
179 static void
180 make_geodesic (ModeInfo *mi)
181 {
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    */
185   int i;
186
187   for (i = 0; i < 10; i++)
188     {
189       GLfloat th1 = s * i;
190       GLfloat th2 = s * (i+1);
191       GLfloat th3 = s * (i+2);
192       LL v1, v2, v3, vc;
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;
197
198       if (i & 1)                        /* north */
199         {
200           make_triangle (mi, v1, v2, vc, depth);
201           make_triangle (mi, v2, v1, v3, depth);
202         }
203       else                              /* south */
204         {
205           v1.a = -v1.a;
206           v2.a = -v2.a;
207           v3.a = -v3.a;
208           vc.a = -vc.a;
209           make_triangle (mi, v2, v1, vc, depth);
210           make_triangle (mi, v1, v2, v3, depth);
211         }
212     }
213 }
214
215
216 /* Add t1 to the neighbor list of t0. */
217 static void
218 link_neighbor (int i, int j, triangle *t0, triangle *t1)
219 {
220   int k;
221   if (t0 == t1)
222     return;
223   for (k = 0; k < countof(t0->neighbors); k++)
224     {
225       if (t0->neighbors[k] == t1 ||
226           t0->neighbors[k] == 0)
227         {
228           t0->neighbors[k] = t1;
229           return;
230         }
231     }
232   fprintf (stderr, "%d %d: too many neighbors\n", i, j);
233   abort();
234 }
235
236
237 static int
238 feq (GLfloat a, GLfloat b)      /* Oh for fuck's sake */
239 {
240   const GLfloat e = 0.00001;
241   GLfloat d = a - b;
242   return (d > -e && d < e);
243 }
244
245
246 /* Link each triangle to its three neighbors.
247  */
248 static void
249 link_neighbors (ModeInfo *mi)
250 {
251   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
252   triangle *t0 = bp->triangles;
253   int i;
254
255   /* Triangles are neighbors if they share an edge (exactly 2 points).
256      (There must be a faster than N! way to do this...)
257    */
258   for (i = 0, t0 = bp->triangles; t0; t0 = t0->next, i++)
259     {
260       triangle *t1;
261       int j;
262
263       for (j = i+1, t1 = t0->next; t1; t1 = t1->next, j++)
264         {
265           int count = 0;
266           int ii, jj;
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))
272                 count++;
273           if (count >= 3)
274             {
275               fprintf (stderr, "%d %d: too many matches: %d\n", i, j, count);
276               abort();
277             }
278           if (count == 2)
279             {
280               link_neighbor (i, j, t0, t1);
281               link_neighbor (j, i, t1, t0);
282             }
283         }
284
285       if (! (t0->neighbors[0] && t0->neighbors[1] && t0->neighbors[2]))
286         {
287           fprintf (stderr, "%d: missing neighbors\n", i);
288           abort();
289         }
290
291       t0->altitude = 60;  /* Fall in from space */
292     }
293 }
294
295
296 /* Add thrust to the triangle, and propagate some of that to its neighbors.
297  */
298 static void
299 add_thrust (triangle *t, GLfloat thrust)
300 {
301   GLfloat dampen = 0;
302   if (t->refcount)
303     return;
304   t->refcount++;
305   t->velocity += thrust;
306
307   /* Eyeballed this to look roughly the same at various depths. Eh. */
308   switch (depth_arg) {
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;
315   }
316
317   thrust *= dampen;
318   if (thrust > 0.1)
319     {
320       add_thrust (t->neighbors[0], thrust);
321       add_thrust (t->neighbors[1], thrust);
322       add_thrust (t->neighbors[2], thrust);
323     }
324 }
325
326
327 static void
328 tick_triangles (ModeInfo *mi)
329 {
330   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
331   GLfloat gravity = 0.1;
332   triangle *t;
333   int i;
334
335   /* Compute new velocities. */
336   for (i = 0, t = bp->triangles; t; t = t->next, i++)
337     {
338       if (t->thrust > 0)
339         {
340           add_thrust (t, t->thrust);
341           t->thrust_duration--;
342           if (t->thrust_duration <= 0)
343             {
344               t->thrust_duration = 0;
345               t->thrust = 0;
346             }
347         }
348     }
349
350   /* Apply new velocities. */
351   for (i = 0, t = bp->triangles; t; t = t->next, i++)
352     {
353       t->altitude += t->velocity;
354       t->velocity -= gravity;
355       if (t->altitude < 0)
356         {
357           t->velocity = 0;
358           t->altitude = 0;
359         }
360       t->refcount = 0;  /* Clear for next time */
361     }
362
363   /* Add eruptions. */
364   if (frand(1 / speed) < 0.2)
365     {
366       int n = random() % bp->count;
367       for (i = 0, t = bp->triangles; t; t = t->next, i++)
368         if (i == n)
369           break;
370       t->thrust += gravity * 1.5;
371       t->thrust_duration = 1 + BELLRAND(16);
372     }
373
374   bp->ccolor++;
375   if (bp->ccolor >= bp->ncolors)
376     bp->ccolor = 0;
377 }
378
379
380 static void
381 draw_triangles (ModeInfo *mi)
382 {
383   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
384   int wire = MI_IS_WIREFRAME(mi);
385   triangle *t;
386   GLfloat c[4];
387   int c0 = bp->ccolor;
388   int c1 = (c0 + bp->ncolors / 2) % bp->ncolors;
389
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;
393   c[3] = 1;
394
395   if (wire)
396     glColor4fv (c);
397   else
398     {
399       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c);
400
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;
404       c[3] = 1;
405       glMaterialfv (GL_BACK, GL_AMBIENT_AND_DIFFUSE, c);
406     }
407
408   glFrontFace (GL_CCW);
409   for (t = bp->triangles; t; t = t->next)
410     {
411       GLfloat a = t->altitude * 0.25;
412       XYZ c;
413       glPushMatrix();
414
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;
418       if (a > 0)
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);
425       glEnd();
426       mi->polygon_count++;
427       glPopMatrix();
428     }
429 }
430
431
432 /* Window management, etc
433  */
434 ENTRYPOINT void
435 reshape_splodesic (ModeInfo *mi, int width, int height)
436 {
437   GLfloat h = (GLfloat) height / (GLfloat) width;
438
439   glViewport (0, 0, (GLint) width, (GLint) height);
440
441   glMatrixMode(GL_PROJECTION);
442   glLoadIdentity();
443   gluPerspective (30.0, 1/h, 1.0, 100.0);
444
445   glMatrixMode(GL_MODELVIEW);
446   glLoadIdentity();
447   gluLookAt( 0.0, 0.0, 30.0,
448              0.0, 0.0, 0.0,
449              0.0, 1.0, 0.0);
450
451 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
452   {
453     int o = (int) current_device_rotation();
454     if (o != 0 && o != 180 && o != -180)
455       glScalef (1/h, 1/h, 1/h);
456   }
457 # endif
458
459   glClear(GL_COLOR_BUFFER_BIT);
460 }
461
462
463 ENTRYPOINT Bool
464 splodesic_handle_event (ModeInfo *mi, XEvent *event)
465 {
466   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
467
468   if (gltrackball_event_handler (event, bp->trackball,
469                                  MI_WIDTH (mi), MI_HEIGHT (mi),
470                                  &bp->button_down_p))
471     return True;
472   else if (event->xany.type == KeyPress)
473     {
474       KeySym keysym;
475       char c = 0;
476       XLookupString (&event->xkey, &c, 1, &keysym, 0);
477       if (c == ' ' || c == '\t')
478         {
479           bp->ncolors = 1024;
480           make_smooth_colormap (0, 0, 0,
481                                 bp->colors, &bp->ncolors,
482                                 False, 0, False);
483           return True;
484         }
485     }
486
487   return False;
488 }
489
490
491 ENTRYPOINT void 
492 init_splodesic (ModeInfo *mi)
493 {
494   splodesic_configuration *bp;
495   int wire = MI_IS_WIREFRAME(mi);
496
497   if (!bps) {
498     bps = (splodesic_configuration *)
499       calloc (MI_NUM_SCREENS(mi), sizeof (splodesic_configuration));
500     if (!bps) {
501       fprintf(stderr, "%s: out of memory\n", progname);
502       exit(1);
503     }
504   }
505
506   bp = &bps[MI_SCREEN(mi)];
507
508   bp->glx_context = init_GL(mi);
509
510   reshape_splodesic (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
511
512   if (!wire)
513     {
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;
520       int lightmodel = 1;
521
522       glEnable(GL_LIGHTING);
523       glEnable(GL_LIGHT0);
524       glEnable(GL_DEPTH_TEST);
525       glEnable(GL_CULL_FACE);
526
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);
534     }
535
536   {
537     double spin_speed   = 0.5;
538     double wander_speed = 0.005;
539     double spin_accel   = 1.0;
540
541     bp->rot = make_rotator (do_spin ? spin_speed : 0,
542                             do_spin ? spin_speed : 0,
543                             do_spin ? spin_speed : 0,
544                             spin_accel,
545                             do_wander ? wander_speed : 0,
546                             True);
547     bp->trackball = gltrackball_init (True);
548   }
549
550   bp->ncolors = 1024;
551   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
552   make_smooth_colormap (0, 0, 0,
553                         bp->colors, &bp->ncolors,
554                         False, 0, False);
555
556 # ifdef HAVE_MOBILE
557   depth_arg--;
558 # endif
559
560   if (depth_arg < 0)  depth_arg = 0;
561   if (depth_arg > 10) depth_arg = 10;
562
563   make_geodesic (mi);
564   link_neighbors (mi);
565 }
566
567
568 ENTRYPOINT void
569 draw_splodesic (ModeInfo *mi)
570 {
571   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
572   Display *dpy = MI_DISPLAY(mi);
573   Window window = MI_WINDOW(mi);
574
575   if (!bp->glx_context)
576     return;
577
578   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
579
580   glShadeModel(GL_SMOOTH);
581
582   glEnable(GL_DEPTH_TEST);
583   glEnable(GL_NORMALIZE);
584   glDisable(GL_CULL_FACE);
585
586   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
587
588   glPushMatrix ();
589
590   {
591     double x, y, z;
592     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
593     glTranslatef((x - 0.5) * 6,
594                  (y - 0.5) * 6,
595                  (z - 0.5) * 8);
596
597     gltrackball_rotate (bp->trackball);
598
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);
603   }
604
605   mi->polygon_count = 0;
606
607 # ifdef HAVE_MOBILE
608   glScalef (3, 3, 3);
609 #else
610   glScalef (4, 4, 4);
611 # endif
612
613   if (! bp->button_down_p)
614     tick_triangles (mi);
615   draw_triangles (mi);
616
617   glPopMatrix ();
618
619   if (mi->fps_p) do_fps (mi);
620   glFinish();
621
622   glXSwapBuffers(dpy, window);
623 }
624
625
626 ENTRYPOINT void
627 release_splodesic (ModeInfo *mi)
628 {
629   splodesic_configuration *bp = &bps[MI_SCREEN(mi)];
630   while (bp->triangles)
631     {
632       triangle *t = bp->triangles->next;
633       free (bp->triangles);
634       bp->triangles = t;
635     }
636 }
637
638 XSCREENSAVER_MODULE ("Splodesic", splodesic)
639
640 #endif /* USE_GL */