From http://www.jwz.org/xscreensaver/xscreensaver-5.40.tar.gz
[xscreensaver] / hacks / glx / cubestorm.c
1 /* cubestorm, Copyright (c) 2003-2018 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                         "*count:      " DEF_COUNT   "\n" \
14                         "*showFPS:      False       \n" \
15                         "*fpsSolid:     True        \n" \
16                         "*wireframe:    False       \n" \
17                         "*suppressRotationAnimation: True\n" \
18
19
20 # define free_cube 0
21 # define release_cube 0
22 #undef countof
23 #define countof(x) (sizeof((x))/sizeof((*x)))
24
25 #include "xlockmore.h"
26 #include "colors.h"
27 #include "rotator.h"
28 #include "gltrackball.h"
29 #include <ctype.h>
30
31 #ifdef USE_GL /* whole file */
32
33 #define DEF_SPIN        "True"
34 #define DEF_WANDER      "True"
35 #define DEF_SPEED       "1.0"
36 #define DEF_THICKNESS   "0.06"
37 #define DEF_COUNT       "4"
38 #define DEF_LENGTH      "200"
39
40 typedef struct {
41   GLfloat px, py, pz;
42   GLfloat rx, ry, rz;
43   int ccolor;
44 } histcube;
45
46 typedef struct {
47   rotator *rot;
48   int ccolor;
49 } subcube;
50
51 typedef struct {
52   GLXContext *glx_context;
53   trackball_state *trackball;
54   Bool button_down_p;
55   Bool clear_p;
56
57   GLuint cube_list;
58
59   int ncolors;
60   XColor *colors;
61
62   subcube *subcubes;
63
64   int hist_size, hist_count;
65   histcube *hist;
66
67 } cube_configuration;
68
69 static cube_configuration *bps = NULL;
70
71 static Bool do_spin;
72 static Bool do_wander;
73 static GLfloat speed;
74 static GLfloat thickness;
75 static int max_length;
76
77 static XrmOptionDescRec opts[] = {
78   { "-spin",   ".spin",   XrmoptionNoArg, "True" },
79   { "+spin",   ".spin",   XrmoptionNoArg, "False" },
80   { "-wander", ".wander", XrmoptionNoArg, "True" },
81   { "+wander", ".wander", XrmoptionNoArg, "False" },
82   { "-speed",  ".speed",  XrmoptionSepArg, 0 },
83   { "-db",     ".doubleBuffer", XrmoptionNoArg, "True"},
84   { "+db",     ".doubleBuffer", XrmoptionNoArg, "False"},
85   { "-thickness", ".thickness", XrmoptionSepArg, 0 },
86   { "-length", ".length", XrmoptionSepArg, 0 },
87 };
88
89 static argtype vars[] = {
90   {&do_spin,   "spin",   "Spin",   DEF_SPIN,   t_Bool},
91   {&do_wander, "wander", "Wander", DEF_WANDER, t_Bool},
92   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
93   {&thickness,  "thickness", "Thickness", DEF_THICKNESS, t_Float},
94   {&max_length, "length",    "Length",    DEF_LENGTH,    t_Int},
95 };
96
97 ENTRYPOINT ModeSpecOpt cube_opts = {countof(opts), opts, countof(vars), vars, NULL};
98
99
100 static void
101 draw_face (ModeInfo *mi)
102 {
103   int wire = MI_IS_WIREFRAME(mi);
104
105   int i;
106   GLfloat t = thickness / 2;
107   GLfloat a = -0.5;
108   GLfloat b =  0.5;
109
110   if (t <= 0) t = 0.001;
111   else if (t > 0.5) t = 0.5;
112
113   glPushMatrix();
114   glFrontFace(GL_CW);
115
116   for (i = 0; i < 4; i++)
117     {
118       glNormal3f (0, 0, -1);
119       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
120       glVertex3f (a,   a,   a);
121       glVertex3f (b,   a,   a);
122       glVertex3f (b-t, a+t, a);
123       glVertex3f (a+t, a+t, a);
124       glEnd();
125
126       glNormal3f (0, 1, 0);
127       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
128       glVertex3f (b-t, a+t, a);
129       glVertex3f (b-t, a+t, a+t);
130       glVertex3f (a+t, a+t, a+t);
131       glVertex3f (a+t, a+t, a);
132       glEnd();
133
134       glRotatef(90, 0, 0, 1);
135     }
136   glPopMatrix();
137 }
138
139 static void
140 draw_faces (ModeInfo *mi)
141 {
142   glPushMatrix();
143   draw_face (mi);
144   glRotatef (90,  0, 1, 0); draw_face (mi);
145   glRotatef (90,  0, 1, 0); draw_face (mi);
146   glRotatef (90,  0, 1, 0); draw_face (mi);
147   glRotatef (90,  1, 0, 0); draw_face (mi);
148   glRotatef (180, 1, 0, 0); draw_face (mi);
149   glPopMatrix();
150 }
151
152
153 static void
154 new_cube_colors (ModeInfo *mi)
155 {
156   cube_configuration *bp = &bps[MI_SCREEN(mi)];
157   int i;
158   bp->ncolors = 128;
159   if (bp->colors) free (bp->colors);
160   bp->colors = (XColor *) calloc(bp->ncolors, sizeof(XColor));
161   make_smooth_colormap (0, 0, 0,
162                         bp->colors, &bp->ncolors,
163                         False, 0, False);
164   for (i = 0; i < MI_COUNT(mi); i++)
165     bp->subcubes[i].ccolor = random() % bp->ncolors;
166 }
167
168
169 /* Window management, etc
170  */
171 ENTRYPOINT void
172 reshape_cube (ModeInfo *mi, int width, int height)
173 {
174   GLfloat h = (GLfloat) height / (GLfloat) width;
175   int y = 0;
176
177   if (width > height * 5) {   /* tiny window: show middle */
178     height = width * 9/16;
179     y = -height/2;
180     h = height / (GLfloat) width;
181   }
182
183   glViewport (0, y, (GLint) width, (GLint) height);
184
185   glMatrixMode(GL_PROJECTION);
186   glLoadIdentity();
187   gluPerspective (30.0, 1/h, 1.0, 100.0);
188
189   glMatrixMode(GL_MODELVIEW);
190   glLoadIdentity();
191   gluLookAt( 0.0, 0.0, 45.0,
192              0.0, 0.0, 0.0,
193              0.0, 1.0, 0.0);
194
195 # ifdef HAVE_MOBILE     /* Keep it the same relative size when rotated. */
196   {
197     int o = (int) current_device_rotation();
198     if (o != 0 && o != 180 && o != -180)
199       glScalef (1/h, 1/h, 1/h);
200   }
201 # endif
202
203   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
204 }
205
206
207 ENTRYPOINT Bool
208 cube_handle_event (ModeInfo *mi, XEvent *event)
209 {
210   cube_configuration *bp = &bps[MI_SCREEN(mi)];
211
212   if (gltrackball_event_handler (event, bp->trackball,
213                                  MI_WIDTH (mi), MI_HEIGHT (mi),
214                                  &bp->button_down_p))
215     return True;
216   else if (event->xany.type == KeyPress)
217     {
218       KeySym keysym;
219       char c = 0;
220       XLookupString (&event->xkey, &c, 1, &keysym, 0);
221       if (c == ' ')
222         {
223           glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
224           return True;
225         }
226       else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
227         goto DEF;
228     }
229   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
230     {
231     DEF:
232       new_cube_colors (mi);
233       return True;
234     }
235   return False;
236 }
237
238
239 ENTRYPOINT void 
240 init_cube (ModeInfo *mi)
241 {
242   cube_configuration *bp;
243   int wire = MI_IS_WIREFRAME(mi);
244   int i;
245
246   MI_INIT (mi, bps);
247
248   bp = &bps[MI_SCREEN(mi)];
249
250   bp->glx_context = init_GL(mi);
251
252   if (MI_COUNT(mi) <= 0) MI_COUNT(mi) = 1;
253
254   bp->trackball = gltrackball_init (True);
255   bp->subcubes = (subcube *) calloc (MI_COUNT(mi), sizeof(subcube));
256
257   bp->hist_count = 0;
258   bp->hist_size = 100;
259   bp->hist = (histcube *) malloc (bp->hist_size * sizeof(*bp->hist));
260
261   for (i = 0; i < MI_COUNT(mi); i++)
262     {
263       double wander_speed, spin_speed, spin_accel;
264
265       if (i == 0)
266         {
267           wander_speed = 0.05 * speed;
268           spin_speed   = 10.0 * speed;
269           spin_accel   = 4.0  * speed;
270         }
271       else
272         {
273           wander_speed = 0;
274           spin_speed   = 4.0 * speed;
275           spin_accel   = 2.0 * speed;
276         }
277
278       bp->subcubes[i].rot = make_rotator (do_spin ? spin_speed : 0,
279                                           do_spin ? spin_speed : 0,
280                                           do_spin ? spin_speed : 0,
281                                           spin_accel,
282                                           do_wander ? wander_speed : 0,
283                                           True);
284     }
285
286   bp->colors = 0;
287   new_cube_colors (mi);
288
289   reshape_cube (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
290
291   if (!wire)
292     {
293       GLfloat pos[4] = {1.0, 1.0, 1.0, 0.0};
294       GLfloat amb[4] = {0.0, 0.0, 0.0, 1.0};
295       GLfloat dif[4] = {1.0, 1.0, 1.0, 1.0};
296       GLfloat spc[4] = {0.0, 1.0, 1.0, 1.0};
297
298       glEnable(GL_LIGHTING);
299       glEnable(GL_LIGHT0);
300       glEnable(GL_DEPTH_TEST);
301       glEnable(GL_CULL_FACE);
302
303       glLightfv(GL_LIGHT0, GL_POSITION, pos);
304       glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
305       glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
306       glLightfv(GL_LIGHT0, GL_SPECULAR, spc);
307     }
308
309   bp->cube_list = glGenLists (1);
310   glNewList (bp->cube_list, GL_COMPILE);
311   draw_faces (mi);
312   glEndList ();
313
314   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
315 }
316
317
318 /* Originally, this program achieved the "accumulating cubes" effect by
319    simply not clearing the depth or color buffers between frames.  That
320    doesn't work on modern systems, particularly mobile: you can no longer
321    rely on your buffers being unmolested once you have yielded.  So now we
322    must save and re-render every polygon.  Noof has the same problem and
323    solves it by taking a screenshot of the frame buffer into a texture, but
324    cubestorm needs to restore the depth buffer as well as the color buffer.
325  */
326 static void
327 push_hist (ModeInfo *mi)
328 {
329   cube_configuration *bp = &bps[MI_SCREEN(mi)];
330   double px, py, pz;
331   double rx = 0, ry = 0, rz = 0;
332   int i;
333
334   if (bp->hist_count > max_length &&
335       bp->hist_count > MI_COUNT(mi) &&
336       !bp->button_down_p)
337     {
338       /* Drop history off of the end. */
339       memmove (bp->hist,
340                bp->hist + MI_COUNT(mi), 
341                (bp->hist_count - MI_COUNT(mi)) * sizeof(*bp->hist));
342       bp->hist_count -= MI_COUNT(mi);
343     }
344
345   if (bp->hist_count + MI_COUNT(mi) >= bp->hist_size)
346     {
347       bp->hist_size = bp->hist_count + MI_COUNT(mi) + 100;
348       bp->hist = (histcube *)
349         realloc (bp->hist, bp->hist_size * sizeof(*bp->hist));
350     }
351   
352   get_position (bp->subcubes[0].rot, &px, &py, &pz, !bp->button_down_p);
353
354   for (i = 0; i < MI_COUNT(mi); i++)
355     {
356       subcube  *sc = &bp->subcubes[i];
357       histcube *hc = &bp->hist[bp->hist_count];
358       double rx2, ry2, rz2;
359
360       get_rotation (sc->rot, &rx2, &ry2, &rz2, !bp->button_down_p);
361
362       if (i == 0)  /* N+1 cubes rotate relative to cube 0 */
363         rx = rx2, ry = ry2, rz = rz2;
364       else
365         rx2 += rx, ry2 += ry, rz2 += rz;
366
367       hc->px = px;
368       hc->py = py;
369       hc->pz = pz;
370       hc->rx = rx2;
371       hc->ry = ry2;
372       hc->rz = rz2;
373       hc->ccolor = sc->ccolor;
374       sc->ccolor++;
375       if (sc->ccolor >= bp->ncolors)
376         sc->ccolor = 0;
377       bp->hist_count++;
378     }
379 }
380
381
382 ENTRYPOINT void
383 draw_cube (ModeInfo *mi)
384 {
385   cube_configuration *bp = &bps[MI_SCREEN(mi)];
386   Display *dpy = MI_DISPLAY(mi);
387   Window window = MI_WINDOW(mi);
388   int wire = MI_IS_WIREFRAME(mi);
389   int i;
390
391   if (!bp->glx_context)
392     return;
393
394   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
395   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
396
397   glShadeModel(GL_SMOOTH);
398
399   glEnable(GL_DEPTH_TEST);
400   glEnable(GL_NORMALIZE);
401   glEnable(GL_CULL_FACE);
402
403   if (bp->clear_p)   /* we're in "no vapor trails" mode */
404     {
405       bp->hist_count = 0;
406       if (! (random() % (int) (25 / speed)))
407         bp->clear_p = False;
408     }
409   else
410     {
411       if (! (random() % (int) (200 / speed)))
412         {
413           bp->clear_p = True;
414           new_cube_colors (mi);
415         }
416     }
417
418   push_hist (mi);
419   mi->polygon_count = 0;
420   for (i = 0; i < bp->hist_count; i++)
421     {
422       GLfloat bcolor[4] = {0.0, 0.0, 0.0, 1.0};
423       GLfloat bspec[4]  = {1.0, 1.0, 1.0, 1.0};
424       GLfloat bshiny    = 128.0;
425
426       histcube *hc = &bp->hist[i];
427
428       glPushMatrix();
429       glScalef (1.1, 1.1, 1.1);
430
431       glTranslatef((hc->px - 0.5) * 15,
432                    (hc->py - 0.5) * 15,
433                    (hc->pz - 0.5) * 30);
434       gltrackball_rotate (bp->trackball);
435
436       glScalef (4.0, 4.0, 4.0);
437
438       glRotatef (hc->rx * 360, 1.0, 0.0, 0.0);
439       glRotatef (hc->ry * 360, 0.0, 1.0, 0.0);
440       glRotatef (hc->rz * 360, 0.0, 0.0, 1.0);
441
442       bcolor[0] = bp->colors[hc->ccolor].red   / 65536.0;
443       bcolor[1] = bp->colors[hc->ccolor].green / 65536.0;
444       bcolor[2] = bp->colors[hc->ccolor].blue  / 65536.0;
445
446       if (wire)
447         glColor3f (bcolor[0], bcolor[1], bcolor[2]);
448       else
449         {
450           glMaterialfv (GL_FRONT, GL_SPECULAR,            bspec);
451           glMateriali  (GL_FRONT, GL_SHININESS,           bshiny);
452           glMaterialfv (GL_FRONT, GL_AMBIENT_AND_DIFFUSE, bcolor);
453         }
454
455       glCallList (bp->cube_list);
456       mi->polygon_count += (4 * 2 * 6);
457
458       glPopMatrix();
459     }
460
461   if (mi->fps_p) do_fps (mi);
462   glFinish();
463
464   glXSwapBuffers(dpy, window);
465 }
466
467 XSCREENSAVER_MODULE_2 ("CubeStorm", cubestorm, cube)
468
469 #endif /* USE_GL */