From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / cubetwist.c
1 /* cubetwist, Copyright (c) 2016-2017 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_cube 0
18 # define release_cube 0
19 #undef countof
20 #define countof(x) (sizeof((x))/sizeof((*x)))
21
22 #include "xlockmore.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_FLAT         "True"
35 #define DEF_THICKNESS    "0.0"
36 #define DEF_DISPLACEMENT "0.0"
37
38 typedef struct cube cube;
39 struct cube {
40   GLfloat size, thickness;
41   XYZ pos, rot;
42   GLfloat color[4];
43   cube *next;
44 };
45
46 typedef struct oscillator oscillator;
47 struct oscillator {
48   double ratio, from, to, speed, *var;
49   int remaining;
50   oscillator *next;
51 };
52
53 typedef struct {
54   GLXContext *glx_context;
55   rotator *rot;
56   trackball_state *trackball;
57   Bool button_down_p;
58   cube *cubes;
59   oscillator *oscillators;
60 } cube_configuration;
61
62 static cube_configuration *bps = NULL;
63
64 static Bool do_flat;
65 static Bool do_spin;
66 static GLfloat speed;
67 static GLfloat thickness;
68 static GLfloat displacement;
69 static Bool do_wander;
70
71 static XrmOptionDescRec opts[] = {
72   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
73   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
74   { "-wander", ".wander", XrmoptionNoArg, "True" },
75   { "+wander", ".wander", XrmoptionNoArg, "False" },
76   { "-flat",   ".flat",   XrmoptionNoArg, "True" },
77   { "+flat",   ".flat",   XrmoptionNoArg, "False" },
78   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
79   { "-thickness",  ".thickness",  XrmoptionSepArg, 0 },
80   { "-displacement",  ".displacement",  XrmoptionSepArg, 0 },
81 };
82
83 static argtype vars[] = {
84   {&do_flat,   "flat",   "flat",   DEF_FLAT,   t_Bool},
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   {&thickness, "thickness", "Thickness", DEF_THICKNESS, t_Float},
89   {&displacement, "displacement", "Displacement", DEF_DISPLACEMENT, t_Float},
90 };
91
92 ENTRYPOINT ModeSpecOpt cube_opts = {countof(opts), opts, countof(vars), vars, NULL};
93
94
95 static int
96 draw_strut (ModeInfo *mi, cube *c)
97 {
98   int wire = MI_IS_WIREFRAME(mi);
99   int polys = 0;
100
101   glPushMatrix();
102   glFrontFace (GL_CW);
103   glNormal3f (0, 0, -1);
104   glTranslatef (-c->size/2, -c->size/2, -c->size/2);
105
106   glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
107   glVertex3f (0, 0, 0);
108   glVertex3f (c->size, 0, 0);
109   glVertex3f (c->size - c->thickness, c->thickness, 0);
110   glVertex3f (c->thickness, c->thickness, 0);
111   glEnd();
112   polys += 2;
113
114   glNormal3f (0, 1, 0);
115   glBegin (wire ? GL_LINE_LOOP : GL_TRIANGLE_FAN);
116   glVertex3f (c->thickness, c->thickness, 0);
117   glVertex3f (c->size - c->thickness, c->thickness, 0);
118   glVertex3f (c->size - c->thickness, c->thickness, c->thickness);
119   glVertex3f (c->thickness, c->thickness, c->thickness);
120   glEnd();
121   polys += 2;
122   glPopMatrix();
123
124   return polys;
125 }
126
127
128 static int
129 draw_cubes (ModeInfo *mi, cube *c)
130 {
131   int polys = 0;
132   int i, j;
133
134   glColor4fv (c->color);
135   glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, c->color);
136
137   glPushMatrix();
138   for (j = 0; j < 6; j++)
139     {
140       for (i = 0; i < 4; i++)
141         {
142           polys += draw_strut (mi, c);
143           glRotatef (90, 0, 0, 1);
144         }
145       if (j == 3)
146         glRotatef (90, 0, 0, 1);
147       if (j < 4)
148         glRotatef (90, 0, 1, 0);
149       else
150         glRotatef (180, 1, 0, 0);
151     }
152   glPopMatrix();
153
154   if (c->next)
155     {
156       /* This leaves rotations on the prevailing matrix stack, but since
157          this is a tail-call, that's fine.  Don't blow the matrix stack. */
158       glRotatef (c->rot.x, 1, 0, 0);
159       glRotatef (c->rot.y, 0, 1, 0);
160       glRotatef (c->rot.z, 0, 0, 1);
161       glTranslatef (c->pos.x, c->pos.y, c->pos.z);
162       c->next->pos = c->pos;
163       c->next->rot = c->rot;
164       polys += draw_cubes (mi, c->next);
165     }
166
167   check_gl_error("cubetwist");
168   return polys;
169 }
170
171
172 static void
173 make_cubes (ModeInfo *mi)
174 {
175   cube_configuration *bp = &bps[MI_SCREEN(mi)];
176   GLfloat step = 2 * (thickness + displacement);
177   GLfloat size = 1.0;
178   cube *tail = 0;
179   GLfloat cc[4], cstep;
180   int depth = 0;
181   cube *c;
182
183   cc[0] = 0.3 + frand(0.7);
184   cc[1] = 0.3 + frand(0.7);
185   cc[2] = 0.3 + frand(0.7);
186   cc[3] = 1;
187
188   if (bp->cubes) abort();
189   while (1)
190     {
191       cube *c = (cube *) calloc (1, sizeof (*c));
192       c->size = size;
193       c->thickness = thickness;
194       if (tail)
195         tail->next = c;
196       else
197         bp->cubes = c;
198       tail = c;
199
200       depth++;
201       size -= step;
202       if (size <= step)
203         break;
204     }
205
206   cstep = 0.8 / depth;
207   for (c = bp->cubes; c; c = c->next)
208     {
209       memcpy (c->color, cc, sizeof(cc));
210       cc[0] -= cstep;
211       cc[1] -= cstep;
212       cc[2] -= cstep;
213     }
214 }
215
216
217 static GLfloat
218 ease_fn (GLfloat r)
219 {
220   return cos ((r/2 + 1) * M_PI) + 1; /* Smooth curve up, end at slope 1. */
221 }
222
223
224 static GLfloat
225 ease_ratio (GLfloat r)
226 {
227   GLfloat ease = 0.5;
228   if      (r <= 0)     return 0;
229   else if (r >= 1)     return 1;
230   else if (r <= ease)  return     ease * ease_fn (r / ease);
231   else if (r > 1-ease) return 1 - ease * ease_fn ((1 - r) / ease);
232   else                 return r;
233 }
234
235
236 static void
237 tick_oscillators (ModeInfo *mi)
238 {
239   cube_configuration *bp = &bps[MI_SCREEN(mi)];
240   oscillator *prev = 0;
241   oscillator *a = bp->oscillators;
242   GLfloat tick = 0.1 / speed;
243
244   while (a)
245     {
246       oscillator *next = a->next;
247       a->ratio += tick * a->speed;
248       if (a->ratio > 1)
249         a->ratio = 1;
250
251       *a->var = a->from + (a->to - a->from) * ease_ratio (a->ratio);
252
253       if (a->ratio < 1)                 /* mid cycle */
254         prev = a;
255       else if (--a->remaining <= 0)     /* ended, and expired */
256         {
257           if (prev)
258             prev->next = next;
259           else
260             bp->oscillators = next;
261           free (a);
262         }
263       else                              /* keep going the other way */
264         {
265           GLfloat swap = a->from;
266           a->from = a->to;
267           a->to = swap;
268           a->ratio = 0;
269           prev = a;
270         }
271
272       a = next;
273     }
274 }
275
276
277 static void
278 add_oscillator (ModeInfo *mi, double *var, GLfloat speed, GLfloat to,
279                 int repeat)
280 {
281   cube_configuration *bp = &bps[MI_SCREEN(mi)];
282   oscillator *a;
283
284   /* If an oscillator is already running on this variable, don't
285      add another. */
286   for (a = bp->oscillators; a && a->next; a = a->next)
287     if (a->var == var)
288       return;
289
290   a = (oscillator *) calloc (1, sizeof (*a));
291   if (repeat <= 0) abort();
292   a->ratio = 0;
293   a->from = *var;
294   a->to = to;
295   a->speed = speed;
296   a->var = var;
297   a->remaining = repeat;
298   a->next = bp->oscillators;
299   bp->oscillators = a;
300 # if 0
301   fprintf (stderr, "%s: %3d %6.2f -> %6.2f %s\n",
302            progname, repeat, *var, to,
303            (var == &bp->midpoint.z ? "z" :
304             var == &bp->tilt ? "tilt" :
305             var == &bp->axial_radius ? "r" :
306             var == &bp->speed ? "speed" : "?"));
307 # endif
308 }
309
310
311 #undef RANDSIGN
312 #define RANDSIGN() ((random() & 1) ? 1 : -1)
313
314 static void
315 add_random_oscillator (ModeInfo *mi)
316 {
317   cube_configuration *bp = &bps[MI_SCREEN(mi)];
318   cube *c = bp->cubes;
319   double s1 = speed * 0.07;
320   double s2 = speed * 0.3;
321   double disp = (thickness + displacement);
322   int c1 = 1 + ((random() % 4) ? 0 : (random() % 3));
323   int c2 = 2;
324   int n = random() % 6;
325
326   switch (n) {
327   case 0: add_oscillator (mi, &c->rot.x, s1, 90 * RANDSIGN(), c1); break;
328   case 1: add_oscillator (mi, &c->rot.y, s1, 90 * RANDSIGN(), c1); break;
329   case 2: add_oscillator (mi, &c->rot.z, s1, 90 * RANDSIGN(), c1); break;
330   case 3: add_oscillator (mi, &c->pos.x, s2, disp * RANDSIGN(), c2); break;
331   case 4: add_oscillator (mi, &c->pos.y, s2, disp * RANDSIGN(), c2); break;
332   case 5: add_oscillator (mi, &c->pos.z, s2, disp * RANDSIGN(), c2); break;
333   default: abort(); break;
334   }
335 }
336
337
338 /* Window management, etc
339  */
340 ENTRYPOINT void
341 reshape_cube (ModeInfo *mi, int width, int height)
342 {
343   GLfloat h = (GLfloat) height / (GLfloat) width;
344
345   glViewport (0, 0, (GLint) width, (GLint) height);
346
347   glMatrixMode(GL_PROJECTION);
348   glLoadIdentity();
349   gluPerspective (30.0, 1/h, 1.0, 100.0);
350
351   glMatrixMode(GL_MODELVIEW);
352   glLoadIdentity();
353   gluLookAt( 0.0, 0.0, 30.0,
354              0.0, 0.0, 0.0,
355              0.0, 1.0, 0.0);
356
357 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
358   {
359     int o = (int) current_device_rotation();
360     if (o != 0 && o != 180 && o != -180)
361       glScalef (1/h, 1/h, 1/h);
362   }
363 # endif
364
365   glClear(GL_COLOR_BUFFER_BIT);
366 }
367
368
369 ENTRYPOINT Bool
370 cube_handle_event (ModeInfo *mi, XEvent *event)
371 {
372   cube_configuration *bp = &bps[MI_SCREEN(mi)];
373
374   if (gltrackball_event_handler (event, bp->trackball,
375                                  MI_WIDTH (mi), MI_HEIGHT (mi),
376                                  &bp->button_down_p))
377     return True;
378   else if (event->xany.type == KeyPress)
379     {
380       KeySym keysym;
381       char c = 0;
382       XLookupString (&event->xkey, &c, 1, &keysym, 0);
383       if (c == ' ' || c == '\t')
384         {
385           while (bp->cubes)
386             {
387               cube *c = bp->cubes->next;
388               free (bp->cubes);
389               bp->cubes = c;
390             }
391
392           while (bp->oscillators)
393             {
394               oscillator *o = bp->oscillators->next;
395               free (bp->oscillators);
396               bp->oscillators = o;
397             }
398
399           if (random() & 1)
400             {
401               thickness = 0.03 + frand(0.02);
402               displacement = (random() & 1) ? 0 : (thickness / 3);
403             }
404           else
405             {
406               thickness = 0.001 + frand(0.02);
407               displacement = 0;
408             }
409
410           make_cubes (mi);
411
412           return True;
413         }
414     }
415
416   return False;
417 }
418
419
420 static void free_cube (ModeInfo *mi);
421
422 ENTRYPOINT void 
423 init_cube (ModeInfo *mi)
424 {
425   cube_configuration *bp;
426   int wire = MI_IS_WIREFRAME(mi);
427
428   MI_INIT (mi, bps, free_cube);
429
430   bp = &bps[MI_SCREEN(mi)];
431
432   bp->glx_context = init_GL(mi);
433
434   reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
435
436   if (!wire && !do_flat)
437     {
438       GLfloat color[4] = {1, 1, 1, 1};
439       GLfloat cspec[4] = {1, 1, 0, 1};
440       static const GLfloat shiny = 30;
441
442       static GLfloat pos0[4] = { 0.5, -1, -0.5, 0};
443       static GLfloat pos1[4] = {-0.75, -1, 0, 0};
444       static GLfloat amb[4] = {0, 0, 0, 1};
445       static GLfloat dif[4] = {1, 1, 1, 1};
446       static GLfloat spc[4] = {1, 1, 1, 1};
447
448       glEnable(GL_LIGHTING);
449       glEnable(GL_LIGHT0);
450       glEnable(GL_LIGHT1);
451       glEnable(GL_DEPTH_TEST);
452       glEnable(GL_CULL_FACE);
453
454       glLightfv(GL_LIGHT0, GL_POSITION, pos0);
455       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
456       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
457       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
458
459       glLightfv(GL_LIGHT1, GL_POSITION, pos1);
460       glLightfv(GL_LIGHT1, GL_AMBIENT,  amb);
461       glLightfv(GL_LIGHT1, GL_DIFFUSE,  dif);
462       glLightfv(GL_LIGHT1, GL_SPECULAR, spc);
463
464       glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, color);
465       glMaterialfv (GL_FRONT, GL_SPECULAR,  cspec);
466       glMateriali  (GL_FRONT, GL_SHININESS, shiny);
467     }
468
469   {
470     double spin_speed   = 0.05;
471     double wander_speed = 0.005;
472     double spin_accel   = 1.0;
473
474     bp->rot = make_rotator (do_spin ? spin_speed : 0,
475                             do_spin ? spin_speed : 0,
476                             do_spin ? spin_speed : 0,
477                             spin_accel,
478                             do_wander ? wander_speed : 0,
479                             True);
480     bp->trackball = gltrackball_init (True);
481   }
482
483   if (thickness > 0.5)
484     thickness = 0.5;
485   if (displacement > 0.5)
486     displacement = 0.5;
487
488   if (thickness <= 0.0001)
489     {
490       if (random() & 1)
491         {
492           thickness = 0.03 + frand(0.02);
493           displacement = (random() & 1) ? 0 : (thickness / 3);
494         }
495       else
496         {
497           thickness = 0.001 + frand(0.02);
498           displacement = 0;
499         }
500     }
501
502   make_cubes (mi);
503 }
504
505
506 ENTRYPOINT void
507 draw_cube (ModeInfo *mi)
508 {
509   cube_configuration *bp = &bps[MI_SCREEN(mi)];
510   Display *dpy = MI_DISPLAY(mi);
511   Window window = MI_WINDOW(mi);
512
513   if (!bp->glx_context)
514     return;
515
516   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
517
518   glShadeModel(GL_SMOOTH);
519   glEnable(GL_DEPTH_TEST);
520   glEnable(GL_NORMALIZE);
521   glEnable(GL_CULL_FACE);
522
523   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
524
525   glPushMatrix ();
526
527   glScalef(1.1, 1.1, 1.1);
528
529   {
530     double x, y, z;
531     get_position (bp->rot, &x, &y, &z, !bp->button_down_p);
532     glTranslatef((x - 0.5) * 4,
533                  (y - 0.5) * 4,
534                  (z - 0.5) * 2);
535
536     gltrackball_rotate (bp->trackball);
537
538     get_rotation (bp->rot, &x, &y, &z, !bp->button_down_p);
539     glRotatef (x * 360, 1.0, 0.0, 0.0);
540     glRotatef (y * 360, 0.0, 1.0, 0.0);
541     glRotatef (z * 360, 0.0, 0.0, 1.0);
542   }
543
544   mi->polygon_count = 0;
545
546   glScalef (6, 6, 6);
547
548   mi->polygon_count = draw_cubes (mi, bp->cubes);
549   glPopMatrix ();
550
551   if (!bp->button_down_p)
552     tick_oscillators (mi);
553
554   if (! bp->oscillators &&
555       !bp->button_down_p &&
556       !(random() % 60))
557     {
558       bp->cubes->pos.x = bp->cubes->pos.y = bp->cubes->pos.z = 0;
559       bp->cubes->rot.x = bp->cubes->rot.y = bp->cubes->rot.z = 0;
560       add_random_oscillator (mi);
561     }
562
563   if (mi->fps_p) do_fps (mi);
564   glFinish();
565
566   glXSwapBuffers(dpy, window);
567 }
568
569 static void
570 free_cube (ModeInfo *mi)
571 {
572   cube_configuration *bp = &bps[MI_SCREEN(mi)];
573   while (bp->cubes)
574     {
575       cube *c = bp->cubes->next;
576       free (bp->cubes);
577       bp->cubes = c;
578     }
579
580   while (bp->oscillators)
581     {
582       oscillator *o = bp->oscillators->next;
583       free (bp->oscillators);
584       bp->oscillators = o;
585     }
586 }
587
588
589 XSCREENSAVER_MODULE_2 ("CubeTwist", cubetwist, cube)
590
591 #endif /* USE_GL */