From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / glx / unknownpleasures.c
1 /* unknownpleasures, Copyright (c) 2013 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  * http://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 == Button1)
139     {
140       bp->button_down_p = True;
141       gltrackball_start (bp->trackball,
142                          event->xbutton.x, event->xbutton.y,
143                          MI_WIDTH (mi), MI_HEIGHT (mi));
144       return True;
145     }
146   else if (event->xany.type == ButtonRelease &&
147            event->xbutton.button == Button1)
148     {
149       bp->button_down_p = False;
150       return True;
151     }
152   else if (event->xany.type == ButtonPress &&
153            (event->xbutton.button == Button4 ||
154             event->xbutton.button == Button5 ||
155             event->xbutton.button == Button6 ||
156             event->xbutton.button == Button7))
157     {
158       int b = event->xbutton.button;
159       int speed = 1;
160       if (b == Button6 || b == Button7)
161         speed *= 3;
162       if (bp->orthop)
163         switch (b) {                            /* swap axes */
164         case Button4: b = Button6; break;
165         case Button5: b = Button7; break;
166         case Button6: b = Button4; break;
167         case Button7: b = Button5; break;
168         }
169       gltrackball_mousewheel (bp->trackball, b, speed, !!event->xbutton.state);
170       return True;
171     }
172   else if (event->xany.type == MotionNotify &&
173            bp->button_down_p)
174     {
175       gltrackball_track (bp->trackball,
176                          event->xmotion.x, event->xmotion.y,
177                          MI_WIDTH (mi), MI_HEIGHT (mi));
178       return True;
179     }
180   else if (event->xany.type == KeyPress)
181     {
182       KeySym keysym;
183       char c = 0;
184       XLookupString (&event->xkey, &c, 1, &keysym, 0);
185       if (c == ' ')
186         {
187           bp->orthop = !bp->orthop;
188           reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
189           return True;
190         }
191       return False;
192     }
193
194   return False;
195 }
196
197
198
199 ENTRYPOINT void 
200 init_unk (ModeInfo *mi)
201 {
202   unk_configuration *bp;
203
204   if (!bps) {
205     bps = (unk_configuration *)
206       calloc (MI_NUM_SCREENS(mi), sizeof (unk_configuration));
207     if (!bps) {
208       fprintf(stderr, "%s: out of memory\n", progname);
209       exit(1);
210     }
211   }
212
213   bp = &bps[MI_SCREEN(mi)];
214
215   bp->glx_context = init_GL(mi);
216
217   bp->orthop = get_boolean_resource (MI_DISPLAY (mi), "ortho", "Ortho");
218   bp->resolution = get_float_resource (MI_DISPLAY (mi),
219                                        "resolution", "Resolution");
220   if (bp->resolution < 1) bp->resolution = 1;
221   if (bp->resolution > 300) bp->resolution = 300;
222
223   reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
224
225   bp->count = MI_COUNT(mi);
226   if (bp->count < 1) bp->count = 1;
227
228   bp->trackball = gltrackball_init ();
229
230   if (MI_COUNT(mi) < 1) MI_COUNT(mi) = 1;
231
232   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
233 }
234
235
236
237 static double
238 R (double f)
239 {
240   /* A simple, fast, deterministic PRNG.
241      ya_rand_init() is too slow for this, and the stream of numbers here
242      doesn't have to be high quality.
243    */
244 #if 1
245   int seed0 = 1613287;
246
247 # else
248   /* Kluge to let me pick a good seed factor by trial and error... */
249   static int seed0 = 0;
250   static int count = 0;
251   if (count++ > 150000000) seed0 = 0, count=0;
252   if (! seed0)
253     {
254       seed0 = (random() & 0xFFFFF);
255       fprintf(stderr, "seed0 = %8x %d\n", seed0, seed0);
256     }
257 # endif
258
259   long seed = seed0 * f;
260   seed = 48271 * (seed % 44488) - 3399 * (seed / 44488);
261   f = (double) seed / 0x7FFFFFFF;
262
263   return f;
264 }
265
266
267 static void
268 compute_line (ModeInfo *mi,
269               int xmin, int xmax, GLfloat xinc,
270               GLfloat res_scale,
271               int y,
272               GLfloat *points)
273 {
274   unk_configuration *bp = &bps[MI_SCREEN(mi)];
275
276   GLfloat fx;
277   int lastx = -999999;
278
279   /* Compute the points on the line */
280
281   for (fx = xmin; fx < xmax; fx += xinc)
282     {
283       int x = fx;
284       int n;
285       GLfloat hsum = 0, vsum = 0;
286
287       if (x == lastx) continue;
288       lastx = x;
289
290       for (n = 1; n <= 30; n++)
291         {
292           /* This sum affects crinkliness of the lines */
293           hsum += (10 *
294                    sin (2 * M_PI
295                         * R (4 * y)
296                         + bp->t
297                         + R (2 * n * y)
298                         * 2 * M_PI)
299                    *  exp (-pow ((.3 * (x / res_scale) + 30
300                                   - 1 * 100 * R (2 * n * y)),
301                                  2)
302                            / 20.0));
303         }
304
305       for (n = 1; n <= 4; n++)
306         {
307           /* This sum affects amplitude of the peaks */
308           vsum += (3 * (1 + R (3 * n * y))
309                    * fabs (sin (bp->t + R (n * y)
310                                 * 2 * M_PI))
311                    * exp (-pow (((x / res_scale) - 1 * 100 * R (n * y)),
312                                 2)
313                           / 20.0));
314         }
315
316       /* Scale of this affects amplitude of the flat parts */
317       points[x - xmin] = (0.2 * sin (2 * M_PI * R (6 * y) + hsum) + vsum);
318     }
319
320 }
321
322
323 ENTRYPOINT void
324 draw_unk (ModeInfo *mi)
325 {
326   unk_configuration *bp = &bps[MI_SCREEN(mi)];
327   Display *dpy = MI_DISPLAY(mi);
328   Window window = MI_WINDOW(mi);
329   int wire = MI_IS_WIREFRAME(mi);
330   GLfloat aspect = 1.5;
331
332   GLfloat res_scale = 4;
333   int xmin = -50 * res_scale;
334   int xmax = 150 * res_scale;
335   GLfloat xinc = 100.0 / (bp->resolution / res_scale);
336   int ymin = 1;
337   int ytop = MI_COUNT(mi);
338   int yinc = 1;
339   int y;
340   GLfloat *points;
341
342   if (xinc < 0.25) xinc = 0.25;
343
344   if (!bp->glx_context)
345     return;
346
347   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
348
349   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
350
351   mi->polygon_count = 0;
352
353   glShadeModel (GL_FLAT);
354   glEnable (GL_DEPTH_TEST);
355   glDisable (GL_CULL_FACE);
356
357   glPushMatrix ();
358
359   glRotatef(current_device_rotation(), 0, 0, 1);
360
361   gltrackball_rotate (bp->trackball);
362   glScalef (10, 10, 10);
363
364   glRotatef (-45, 1, 0, 0);
365   glTranslatef (-0.5, -0.5, 0);
366   if (bp->orthop)
367     glTranslatef (0, 0.05, 0);
368   else
369     glTranslatef (0, 0.15, 0);
370
371   points = (GLfloat *) malloc (sizeof(*points) * (xmax - xmin));
372
373   if (!bp->button_down_p)
374     {
375       double s = 6.3 / 19 / 3;
376 # if 1
377       bp->t -= s * speed;
378       if (bp->t <= 0)
379         bp->t = s * 18 * 3;
380 # else
381       bp->t += s;
382 # endif
383     }
384
385   glLineWidth (2);
386
387   /* Lower the resolution to get a decent frame rate on iPhone 4s */
388   if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640)
389     {
390       ytop *= 0.6;
391       xinc *= 3;
392     }
393
394 # ifdef USE_IPHONE
395   /* Lower it even further for iPhone 3 */
396   if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480)
397     {
398       ytop *= 0.8;
399       xinc *= 1.2;
400     }
401
402   /* Performance just sucks on iPad 3, even with a very high xinc. WTF? */
403 /*
404   if (mi->xgwa.width >= 2048 || mi->xgwa.height >= 2048)
405     xinc *= 2;
406 */
407
408 # endif /* USE_IPHONE */
409
410
411   /* Make the image fill the screen a little more fully */
412   if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640)
413     {
414       glScalef (1.2, 1.2, 1.2);
415       glTranslatef (-0.08, 0, 0);
416     }
417
418   if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480)
419     glLineWidth (1);
420
421
422   if (wire)
423     xinc *= 1.3;
424
425   /* Draw back mask */
426   {
427     GLfloat s = 0.99;
428     glDisable (GL_POLYGON_OFFSET_FILL);
429     glColor3f (0, 0, 0);
430     glPushMatrix();
431     glTranslatef (0, (1-aspect)/2, -0.005);
432     glScalef (1, aspect, 1);
433     glTranslatef (0.5, 0.5, 0);
434     glScalef (s, s, 1);
435     glBegin (GL_QUADS);
436     glVertex3f (-0.5, -0.5, 0);
437     glVertex3f ( 0.5, -0.5, 0);
438     glVertex3f ( 0.5,  0.5, 0);
439     glVertex3f (-0.5,  0.5, 0);
440     glEnd();
441     glPopMatrix();
442   }
443
444   if (! wire)
445     {
446       glEnable (GL_LINE_SMOOTH);
447       glHint (GL_LINE_SMOOTH_HINT, GL_NICEST);
448       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
449       glEnable (GL_BLEND);
450
451       /* So the masking quads don't interfere with the lines */
452       glEnable (GL_POLYGON_OFFSET_FILL);
453       glPolygonOffset (1.0, 1.0);
454     }
455
456   for (y = ymin; y <= ytop; y += yinc)
457     {
458       /* Compute all the verts on the line */
459       compute_line (mi, xmin, xmax, xinc, res_scale, y, points);
460
461       /* Draw the line segments; then draw the black shielding quads. */
462       {
463         GLfloat yy = y / (GLfloat) ytop;
464         int linesp;
465
466         yy = (yy * aspect) - ((aspect - 1) / 2);
467
468         for (linesp = 0; linesp <= 1; linesp++)
469           {
470             GLfloat fx;
471             int lastx = -999999;
472
473             GLfloat c = (linesp || wire ? 1 : 0);
474             glColor3f (c, c, c);
475
476             glBegin (linesp
477                      ? GL_LINE_STRIP
478                      : wire ? GL_LINES : GL_QUAD_STRIP);
479             lastx = -999999;
480             for (fx = xmin; fx < xmax; fx += xinc)
481               {
482                 int x = fx;
483                 GLfloat xx = (x - xmin) / (GLfloat) (xmax - xmin);
484                 GLfloat zz = points [x - xmin];
485
486                 if (x == lastx) continue;
487                 lastx = x;
488
489                 zz /= 80;
490                 glVertex3f (xx, yy, zz);
491                 if (! linesp)
492                   glVertex3f (xx, yy, 0);
493                 mi->polygon_count++;
494               }
495             glEnd ();
496           }
497       }
498     }
499
500   free (points);
501
502   glPopMatrix ();
503
504   if (mi->fps_p) do_fps (mi);
505   glFinish();
506
507   glXSwapBuffers(dpy, window);
508 }
509
510 XSCREENSAVER_MODULE_2 ("UnknownPleasures", unknownpleasures, unk)
511
512 #endif /* USE_GL */