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