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