From http://www.jwz.org/xscreensaver/xscreensaver-5.37.tar.gz
[xscreensaver] / hacks / glx / unknownpleasures.c
1 /* unknownpleasures, Copyright (c) 2013-2014 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  * Translated from Mathematica code by Archery:
12  * http://intothecontinuum.tumblr.com/post/27443100682/in-july-1967-astronomers-at-the-cavendish
13  *
14  * Interestingly, the original image is copyright-free:
15  * http://adamcap.com/2011/05/19/history-of-joy-division-unknown-pleasures-album-art/
16  * https://en.wikipedia.org/wiki/Unknown_Pleasures
17  *
18  * TODO:
19  *
20  * - Performance is not great. Spending half our time in compute_line()
21  *   and half our time in glEnd().  It's a vast number of cos/pow calls,
22  *   and a vast number of polygons.  I'm not sure how much could be cached.
23  *
24  * - There's too low periodicity vertically on the screen.
25  *
26  * - There's too low periodicity in time.
27  *
28  * - Could take advantage of time periodicity for caching: just save every
29  *   poly for an N second loop.  That would be a huge amount of RAM though.
30  *
31  * - At low resolutions, GL_POLYGON_OFFSET_FILL seems to work poorly
32  *   and the lines get blocky.
33  */
34
35
36 #define DEFAULTS        "*delay:        30000       \n" \
37                         "*count:        80          \n" \
38                         "*resolution:   100         \n" \
39                         "*ortho:        True        \n" \
40                         "*showFPS:      False       \n" \
41                         "*wireframe:    False       \n" \
42                         "*geometry:    =800x800"   "\n" \
43
44 # define refresh_unk 0
45 # define release_unk 0
46 #undef countof
47 #define countof(x) (sizeof((x))/sizeof((*x)))
48
49 #include "xlockmore.h"
50 #include "colors.h"
51 #include "gltrackball.h"
52 #include <ctype.h>
53
54 #ifdef USE_GL /* whole file */
55
56 #define DEF_SPEED  "1.0"
57
58
59 typedef struct {
60   GLXContext *glx_context;
61   trackball_state *trackball;
62   Bool button_down_p;
63   Bool orthop;
64   GLfloat resolution;
65   int count;
66   GLfloat t;
67 } unk_configuration;
68
69 static unk_configuration *bps = NULL;
70
71 static GLfloat speed;
72
73 static XrmOptionDescRec opts[] = {
74   { "-speed",        ".speed",      XrmoptionSepArg, 0 },
75   { "-resolution",   ".resolution", XrmoptionSepArg, 0 },
76   { "-ortho",        ".ortho",      XrmoptionNoArg,  "True"  },
77   { "-no-ortho",     ".ortho",      XrmoptionNoArg,  "False" },
78 };
79
80 static argtype vars[] = {
81   {&speed, "speed",  "Speed",  DEF_SPEED,  t_Float},
82 };
83
84 ENTRYPOINT ModeSpecOpt unk_opts = {countof(opts), opts, countof(vars), vars, NULL};
85
86
87
88 /* Window management, etc
89  */
90 ENTRYPOINT void
91 reshape_unk (ModeInfo *mi, int width, int height)
92 {
93   unk_configuration *bp = &bps[MI_SCREEN(mi)];
94   GLfloat h = (GLfloat) height / (GLfloat) width;
95
96   glViewport (0, 0, (GLint) width, (GLint) height);
97
98   if (bp->orthop)
99     {
100       glMatrixMode(GL_PROJECTION);
101       glLoadIdentity();
102       gluPerspective (1.0, 1/h, 690, 710);
103
104       glMatrixMode(GL_MODELVIEW);
105       glLoadIdentity();
106       gluLookAt( 0, 0, 700,
107                  0, 0, 0,
108                  0, 1, 0);
109       if (width < height)
110         glScalef (1/h, 1/h, 1);
111       glTranslatef (0, -0.5, 0);
112     }
113   else
114     {
115       glMatrixMode(GL_PROJECTION);
116       glLoadIdentity();
117       gluPerspective (30.0, 1/h, 20.0, 40.0);
118
119       glMatrixMode(GL_MODELVIEW);
120       glLoadIdentity();
121       gluLookAt( 0.0, 0.0, 30.0,
122                  0.0, 0.0, 0.0,
123                  0.0, 1.0, 0.0);
124       if (width < height)
125         glScalef (1/h, 1/h, 1);
126     }
127
128   glClear(GL_COLOR_BUFFER_BIT);
129 }
130
131
132 ENTRYPOINT Bool
133 unk_handle_event (ModeInfo *mi, XEvent *event)
134 {
135   unk_configuration *bp = &bps[MI_SCREEN(mi)];
136
137   if (event->xany.type == ButtonPress &&
138       (event->xbutton.button == Button4 ||
139        event->xbutton.button == Button5 ||
140        event->xbutton.button == Button6 ||
141        event->xbutton.button == Button7))
142     {
143       int b = event->xbutton.button;
144       int speed = 1;
145       if (b == Button6 || b == Button7)
146         speed *= 3;
147       if (bp->orthop)
148         switch (b) {                            /* swap axes */
149         case Button4: b = Button6; break;
150         case Button5: b = Button7; break;
151         case Button6: b = Button4; break;
152         case Button7: b = Button5; break;
153         }
154       gltrackball_mousewheel (bp->trackball, b, speed, !!event->xbutton.state);
155       return True;
156     }
157   else if (gltrackball_event_handler (event, bp->trackball,
158                                       MI_WIDTH (mi), MI_HEIGHT (mi),
159                                       &bp->button_down_p))
160     return True;
161   else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
162     {
163       bp->orthop = !bp->orthop;
164       reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
165       return True;
166     }
167
168   return False;
169 }
170
171
172
173 ENTRYPOINT void 
174 init_unk (ModeInfo *mi)
175 {
176   unk_configuration *bp;
177
178   MI_INIT (mi, bps, NULL);
179
180   bp = &bps[MI_SCREEN(mi)];
181
182   bp->glx_context = init_GL(mi);
183
184   bp->orthop = get_boolean_resource (MI_DISPLAY (mi), "ortho", "Ortho");
185   bp->resolution = get_float_resource (MI_DISPLAY (mi),
186                                        "resolution", "Resolution");
187   if (bp->resolution < 1) bp->resolution = 1;
188   if (bp->resolution > 300) bp->resolution = 300;
189
190   reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
191
192   bp->count = MI_COUNT(mi);
193   if (bp->count < 1) bp->count = 1;
194
195   bp->trackball = gltrackball_init (False);
196
197   if (MI_COUNT(mi) < 1) MI_COUNT(mi) = 1;
198
199   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
200 }
201
202
203
204 static double
205 R (double f)
206 {
207   /* A simple, fast, deterministic PRNG.
208      ya_rand_init() is too slow for this, and the stream of numbers here
209      doesn't have to be high quality.
210    */
211 #if 1
212   int seed0 = 1613287;
213
214 # else
215   /* Kluge to let me pick a good seed factor by trial and error... */
216   static int seed0 = 0;
217   static int count = 0;
218   if (count++ > 150000000) seed0 = 0, count=0;
219   if (! seed0)
220     {
221       seed0 = (random() & 0xFFFFF);
222       fprintf(stderr, "seed0 = %8x %d\n", seed0, seed0);
223     }
224 # endif
225
226   long seed = seed0 * f;
227   seed = 48271 * (seed % 44488) - 3399 * (seed / 44488);
228   f = (double) seed / 0x7FFFFFFF;
229
230   return f;
231 }
232
233
234 static void
235 compute_line (ModeInfo *mi,
236               int xmin, int xmax, GLfloat xinc,
237               GLfloat res_scale,
238               int y,
239               GLfloat *points)
240 {
241   unk_configuration *bp = &bps[MI_SCREEN(mi)];
242
243   GLfloat fx;
244   int lastx = -999999;
245
246   /* Compute the points on the line */
247
248   for (fx = xmin; fx < xmax; fx += xinc)
249     {
250       int x = fx;
251       int n;
252       GLfloat hsum = 0, vsum = 0;
253
254       if (x == lastx) continue;
255       lastx = x;
256
257       for (n = 1; n <= 30; n++)
258         {
259           /* This sum affects crinkliness of the lines */
260           hsum += (10 *
261                    sin (2 * M_PI
262                         * R (4 * y)
263                         + bp->t
264                         + R (2 * n * y)
265                         * 2 * M_PI)
266                    *  exp (-pow ((.3 * (x / res_scale) + 30
267                                   - 1 * 100 * R (2 * n * y)),
268                                  2)
269                            / 20.0));
270         }
271
272       for (n = 1; n <= 4; n++)
273         {
274           /* This sum affects amplitude of the peaks */
275           vsum += (3 * (1 + R (3 * n * y))
276                    * fabs (sin (bp->t + R (n * y)
277                                 * 2 * M_PI))
278                    * exp (-pow (((x / res_scale) - 1 * 100 * R (n * y)),
279                                 2)
280                           / 20.0));
281         }
282
283       /* Scale of this affects amplitude of the flat parts */
284       points[x - xmin] = (0.2 * sin (2 * M_PI * R (6 * y) + hsum) + vsum);
285     }
286
287 }
288
289
290 ENTRYPOINT void
291 draw_unk (ModeInfo *mi)
292 {
293   unk_configuration *bp = &bps[MI_SCREEN(mi)];
294   Display *dpy = MI_DISPLAY(mi);
295   Window window = MI_WINDOW(mi);
296   int wire = MI_IS_WIREFRAME(mi);
297   GLfloat aspect = 1.5;
298
299   GLfloat res_scale = 4;
300   int xmin = -50 * res_scale;
301   int xmax = 150 * res_scale;
302   GLfloat xinc = 100.0 / (bp->resolution / res_scale);
303   int ymin = 1;
304   int ytop = MI_COUNT(mi);
305   int yinc = 1;
306   int y;
307   GLfloat *points;
308
309   if (xinc < 0.25) xinc = 0.25;
310
311   if (!bp->glx_context)
312     return;
313
314   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
315
316   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
317
318   mi->polygon_count = 0;
319
320   glShadeModel (GL_FLAT);
321   glEnable (GL_DEPTH_TEST);
322   glDisable (GL_CULL_FACE);
323
324   glPushMatrix ();
325
326   glRotatef(current_device_rotation(), 0, 0, 1);
327
328   gltrackball_rotate (bp->trackball);
329   glScalef (10, 10, 10);
330
331   glRotatef (-45, 1, 0, 0);
332   glTranslatef (-0.5, -0.5, 0);
333   if (bp->orthop)
334     glTranslatef (0, 0.05, 0);
335   else
336     glTranslatef (0, 0.15, 0);
337
338   points = (GLfloat *) malloc (sizeof(*points) * (xmax - xmin));
339
340   if (!bp->button_down_p)
341     {
342       double s = 6.3 / 19 / 3;
343 # if 1
344       bp->t -= s * speed;
345       if (bp->t <= 0)
346         bp->t = s * 18 * 3;
347 # else
348       bp->t += s;
349 # endif
350     }
351
352   glLineWidth (2);
353
354   /* Lower the resolution to get a decent frame rate on iPhone 4s */
355   if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640)
356     {
357       ytop *= 0.6;
358       xinc *= 3;
359     }
360
361 # ifdef HAVE_MOBILE
362   /* Lower it even further for iPhone 3 */
363   if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480)
364     {
365       ytop *= 0.8;
366       xinc *= 1.2;
367     }
368
369   /* Performance just sucks on iPad 3, even with a very high xinc. WTF? */
370 /*
371   if (mi->xgwa.width >= 2048 || mi->xgwa.height >= 2048)
372     xinc *= 2;
373 */
374
375 # endif /* USE_MOBILE */
376
377
378   /* Make the image fill the screen a little more fully */
379   if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640)
380     {
381       glScalef (1.2, 1.2, 1.2);
382       glTranslatef (-0.08, 0, 0);
383     }
384
385   if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480)
386     glLineWidth (1);
387
388
389   if (wire)
390     xinc *= 1.3;
391
392   /* Draw back mask */
393   {
394     GLfloat s = 0.99;
395     glDisable (GL_POLYGON_OFFSET_FILL);
396     glColor3f (0, 0, 0);
397     glPushMatrix();
398     glTranslatef (0, (1-aspect)/2, -0.005);
399     glScalef (1, aspect, 1);
400     glTranslatef (0.5, 0.5, 0);
401     glScalef (s, s, 1);
402     glBegin (GL_QUADS);
403     glVertex3f (-0.5, -0.5, 0);
404     glVertex3f ( 0.5, -0.5, 0);
405     glVertex3f ( 0.5,  0.5, 0);
406     glVertex3f (-0.5,  0.5, 0);
407     glEnd();
408     glPopMatrix();
409   }
410
411   if (! wire)
412     {
413       glEnable (GL_LINE_SMOOTH);
414       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
415       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
416       glEnable (GL_BLEND);
417
418       /* So the masking quads don't interfere with the lines */
419       glEnable (GL_POLYGON_OFFSET_FILL);
420       glPolygonOffset (1.0, 1.0);
421     }
422
423   for (y = ymin; y <= ytop; y += yinc)
424     {
425       /* Compute all the verts on the line */
426       compute_line (mi, xmin, xmax, xinc, res_scale, y, points);
427
428       /* Draw the line segments; then draw the black shielding quads. */
429       {
430         GLfloat yy = y / (GLfloat) ytop;
431         int linesp;
432
433         yy = (yy * aspect) - ((aspect - 1) / 2);
434
435         for (linesp = 0; linesp <= 1; linesp++)
436           {
437             GLfloat fx;
438             int lastx = -999999;
439
440             GLfloat c = (linesp || wire ? 1 : 0);
441             glColor3f (c, c, c);
442
443             glBegin (linesp
444                      ? GL_LINE_STRIP
445                      : wire ? GL_LINES : GL_QUAD_STRIP);
446             lastx = -999999;
447             for (fx = xmin; fx < xmax; fx += xinc)
448               {
449                 int x = fx;
450                 GLfloat xx = (x - xmin) / (GLfloat) (xmax - xmin);
451                 GLfloat zz = points [x - xmin];
452
453                 if (x == lastx) continue;
454                 lastx = x;
455
456                 zz /= 80;
457                 glVertex3f (xx, yy, zz);
458                 if (! linesp)
459                   glVertex3f (xx, yy, 0);
460                 mi->polygon_count++;
461               }
462             glEnd ();
463           }
464       }
465     }
466
467   free (points);
468
469   glPopMatrix ();
470
471   if (mi->fps_p) do_fps (mi);
472   glFinish();
473
474   glXSwapBuffers(dpy, window);
475 }
476
477 XSCREENSAVER_MODULE_2 ("UnknownPleasures", unknownpleasures, unk)
478
479 #endif /* USE_GL */