From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / glx / quasicrystal.c
1 /* quasicrystal, 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  * Overlapping sine waves create interesting plane-tiling interference
12  * patterns.  Created by jwz, Jul 2013.  Inspired by
13  * http://mainisusuallyafunction.blogspot.com/2011/10/quasicrystals-as-sums-of-waves-in-plane.html
14  */
15
16
17 #define DEFAULTS        "*delay:        30000       \n" \
18                         "*spin:         True        \n" \
19                         "*wander:       True        \n" \
20                         "*symmetric:    True        \n" \
21                         "*count:        17          \n" \
22                         "*contrast:     30          \n" \
23                         "*showFPS:      False       \n" \
24                         "*wireframe:    False       \n" \
25                         "*suppressRotationAnimation: True\n" \
26
27 # define refresh_quasicrystal 0
28 # define release_quasicrystal 0
29 #undef countof
30 #define countof(x) (sizeof((x))/sizeof((*x)))
31
32 #include "xlockmore.h"
33 #include "colors.h"
34 #include "rotator.h"
35 #include <ctype.h>
36
37 #ifdef USE_GL /* whole file */
38
39 #define DEF_SPEED  "1.0"
40
41 typedef struct {
42   rotator *rot, *rot2;
43   GLuint texid;
44 } plane;
45
46 typedef struct {
47   GLXContext *glx_context;
48   Bool button_down_p;
49   Bool symmetric_p;
50   GLfloat contrast;
51   int count;
52   int ncolors, ccolor;
53   XColor *colors;
54   plane *planes;
55   int mousex, mousey;
56
57 } quasicrystal_configuration;
58
59 static quasicrystal_configuration *bps = NULL;
60
61 static GLfloat speed;
62
63 static XrmOptionDescRec opts[] = {
64   { "-spin",         ".spin",      XrmoptionNoArg, "True"  },
65   { "+spin",         ".spin",      XrmoptionNoArg, "False" },
66   { "-wander",       ".wander",    XrmoptionNoArg, "True"  },
67   { "+wander",       ".wander",    XrmoptionNoArg, "False" },
68   { "-symmetry",     ".symmetric", XrmoptionNoArg, "True"   },
69   { "-no-symmetry",  ".symmetric", XrmoptionNoArg, "False"  },
70   { "-speed",        ".speed",     XrmoptionSepArg, 0 },
71   { "-contrast",     ".contrast",  XrmoptionSepArg, 0 },
72 };
73
74 static argtype vars[] = {
75   {&speed,     "speed",  "Speed",  DEF_SPEED,  t_Float},
76 };
77
78 ENTRYPOINT ModeSpecOpt quasicrystal_opts = {countof(opts), opts, countof(vars), vars, NULL};
79
80
81
82 /* Window management, etc
83  */
84 ENTRYPOINT void
85 reshape_quasicrystal (ModeInfo *mi, int width, int height)
86 {
87   GLfloat h = (GLfloat) height / (GLfloat) width;
88
89   glViewport (0, 0, (GLint) width, (GLint) height);
90
91   glMatrixMode(GL_PROJECTION);
92   glLoadIdentity();
93   glOrtho (0, 1, 1, 0, -1, 1);
94
95   glMatrixMode(GL_MODELVIEW);
96   glLoadIdentity();
97   glTranslatef (0.5, 0.5, 0);
98   glScalef (h, 1, 1);
99   if (width > height)
100     glScalef (1/h, 1/h, 1);
101   glTranslatef (-0.5, -0.5, 0);
102   glClear(GL_COLOR_BUFFER_BIT);
103 }
104
105
106 ENTRYPOINT Bool
107 quasicrystal_handle_event (ModeInfo *mi, XEvent *event)
108 {
109   quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)];
110
111   if (event->xany.type == ButtonPress &&
112       event->xbutton.button == Button1)
113     {
114       bp->button_down_p = True;
115       bp->mousex = event->xbutton.x;
116       bp->mousey = event->xbutton.y;
117       return True;
118     }
119   else if (event->xany.type == ButtonRelease &&
120            event->xbutton.button == Button1)
121     {
122       bp->button_down_p = False;
123       return True;
124     }
125   else if (event->xany.type == ButtonPress &&   /* wheel up or right */
126            (event->xbutton.button == Button4 ||
127             event->xbutton.button == Button7))
128     {
129     UP:
130       if (bp->contrast <= 0) return False;
131       bp->contrast--;
132       return True;
133     }
134   else if (event->xany.type == ButtonPress &&   /* wheel down or left */
135            (event->xbutton.button == Button5 ||
136             event->xbutton.button == Button6))
137     {
138     DOWN:
139       if (bp->contrast >= 100) return False;
140       bp->contrast++;
141       return True;
142     }
143   else if (event->xany.type == MotionNotify &&
144            bp->button_down_p)
145     {
146       /* Dragging up and down tweaks contrast */
147
148       int dx = event->xmotion.x - bp->mousex;
149       int dy = event->xmotion.y - bp->mousey;
150
151       if (abs(dy) > abs(dx))
152         {
153           bp->contrast += dy / 40.0;
154           if (bp->contrast < 0)   bp->contrast = 0;
155           if (bp->contrast > 100) bp->contrast = 100;
156         }
157
158       bp->mousex = event->xmotion.x;
159       bp->mousey = event->xmotion.y;
160       return True;
161     }
162   else
163     {
164       if (event->xany.type == KeyPress)
165         {
166           KeySym keysym;
167           char c = 0;
168           XLookupString (&event->xkey, &c, 1, &keysym, 0);
169           if (c == '<' || c == ',' || c == '-' || c == '_' ||
170               keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
171             goto UP;
172           else if (c == '>' || c == '.' || c == '=' || c == '+' ||
173                    keysym == XK_Right || keysym == XK_Down ||
174                    keysym == XK_Next)
175             goto DOWN;
176         }
177
178       return screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event);
179     }
180
181   return False;
182 }
183
184
185
186 ENTRYPOINT void 
187 init_quasicrystal (ModeInfo *mi)
188 {
189   quasicrystal_configuration *bp;
190   int wire = MI_IS_WIREFRAME(mi);
191   unsigned char *tex_data = 0;
192   int tex_width;
193   int i;
194
195   if (!bps) {
196     bps = (quasicrystal_configuration *)
197       calloc (MI_NUM_SCREENS(mi), sizeof (quasicrystal_configuration));
198     if (!bps) {
199       fprintf(stderr, "%s: out of memory\n", progname);
200       exit(1);
201     }
202   }
203
204   bp = &bps[MI_SCREEN(mi)];
205
206   bp->glx_context = init_GL(mi);
207
208   reshape_quasicrystal (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
209
210   glDisable (GL_DEPTH_TEST);
211   glEnable (GL_CULL_FACE);
212
213   bp->count = MI_COUNT(mi);
214   if (bp->count < 1) bp->count = 1;
215
216   if (! wire)
217     {
218       unsigned char *o;
219
220       tex_width = 4096;
221       glGetIntegerv (GL_MAX_TEXTURE_SIZE, &tex_width);
222       if (tex_width > 4096) tex_width = 4096;
223
224       tex_data = (unsigned char *) calloc (4, tex_width);
225       o = tex_data;
226       for (i = 0; i < tex_width; i++)
227         {
228           unsigned char y = 255 * (1 + sin (i * M_PI * 2 / tex_width)) / 2;
229           *o++ = y;
230           *o++ = y;
231           *o++ = y;
232           *o++ = 255;
233         }
234     }
235
236   bp->symmetric_p =
237     get_boolean_resource (MI_DISPLAY (mi), "symmetry", "Symmetry");
238
239   bp->contrast = get_float_resource (MI_DISPLAY (mi), "contrast", "Contrast");
240   if (bp->contrast < 0 || bp->contrast > 100) 
241     {
242       fprintf (stderr, "%s: contrast must be between 0 and 100%%.\n", progname);
243       bp->contrast = 0;
244     }
245
246   {
247     Bool spinp   = get_boolean_resource (MI_DISPLAY (mi), "spin", "Spin");
248     Bool wanderp = get_boolean_resource (MI_DISPLAY (mi), "wander", "Wander");
249     double spin_speed   = 0.01;
250     double wander_speed = 0.0001;
251     double spin_accel   = 10.0;
252     double scale_speed  = 0.005;
253
254     bp->planes = (plane *) calloc (sizeof (*bp->planes), bp->count);
255
256     bp->ncolors = 256;  /* ncolors affects color-cycling speed */
257     bp->colors = (XColor *) calloc (bp->ncolors, sizeof(XColor));
258     make_smooth_colormap (0, 0, 0, bp->colors, &bp->ncolors,
259                           False, 0, False);
260     bp->ccolor = 0;
261
262     for (i = 0;  i < bp->count; i++)
263       {
264         plane *p = &bp->planes[i];
265         p->rot = make_rotator (0, 0,
266                                spinp ? spin_speed : 0,
267                                spin_accel,
268                                wanderp ? wander_speed : 0,
269                                True);
270         p->rot2 = make_rotator (0, 0,
271                                 0, 0,
272                                scale_speed,
273                                True);
274         if (! wire)
275           {
276             clear_gl_error();
277
278             glGenTextures (1, &p->texid);
279             glBindTexture (GL_TEXTURE_1D, p->texid);
280             glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
281             glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA,
282                           tex_width, 0,
283                           GL_RGBA,
284                           /* GL_UNSIGNED_BYTE, */
285                           GL_UNSIGNED_INT_8_8_8_8_REV,
286                           tex_data);
287             check_gl_error("texture");
288
289             glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
290             glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
291
292             glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
293             glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
294
295             glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
296           }
297       }
298   }
299
300   if (tex_data) free (tex_data);
301
302   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
303 }
304
305
306 ENTRYPOINT void
307 draw_quasicrystal (ModeInfo *mi)
308 {
309   quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)];
310   Display *dpy = MI_DISPLAY(mi);
311   Window window = MI_WINDOW(mi);
312   int wire = MI_IS_WIREFRAME(mi);
313   double r=0, ps=0;
314   int i;
315
316   if (!bp->glx_context)
317     return;
318
319   glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
320
321   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
322
323   mi->polygon_count = 0;
324
325   glShadeModel(GL_FLAT);
326   glDisable(GL_DEPTH_TEST);
327   glDisable(GL_CULL_FACE);
328   glDisable (GL_LIGHTING);
329   if (!wire)
330     {
331       glEnable (GL_TEXTURE_1D);
332 # ifdef HAVE_JWZGLES
333       glEnable (GL_TEXTURE_2D);  /* jwzgles needs this, bleh. */
334 # endif
335     }
336
337   glEnable (GL_BLEND);
338   glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
339
340   glPushMatrix ();
341   glTranslatef (0.5, 0.5, 0);
342   glScalef (3, 3, 3);
343
344   if (wire) glScalef (0.2, 0.2, 0.2);
345
346   for (i = 0;  i < bp->count; i++)
347     {
348       plane *p = &bp->planes[i];
349       double x, y, z;
350       double scale = (wire ? 10 : 700.0 / bp->count);
351       double pscale;
352
353       glPushMatrix();
354
355       get_position (p->rot, &x, &y, &z, !bp->button_down_p);
356       glTranslatef((x - 0.5) * 0.3333,
357                    (y - 0.5) * 0.3333,
358                    0);
359
360       /* With -symmetry, keep the planes' scales in sync.
361          Otherwise, they scale independently.
362        */
363       if (bp->symmetric_p && i > 0)
364         pscale = ps;
365       else
366         {
367           get_position (p->rot2, &x, &y, &z, !bp->button_down_p);
368           pscale = 1 + (4 * z);
369           ps = pscale;
370         }
371
372       scale *= pscale;
373
374
375       /* With -symmetry, evenly distribute the planes' rotation.
376          Otherwise, they rotate independently.
377        */
378       if (bp->symmetric_p && i > 0)
379         z = r + (i * M_PI * 2 / bp->count);
380       else
381         {
382           get_rotation (p->rot, &x, &y, &z, !bp->button_down_p);
383           r = z;
384         }
385
386
387       glRotatef (z * 360, 0, 0, 1);
388       glTranslatef (-0.5, -0.5, 0);
389
390       glColor4f (1, 1, 1, (wire ? 0.5 : 1.0 / bp->count));
391
392       if (!wire)
393         glBindTexture (GL_TEXTURE_1D, p->texid);
394
395       glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
396       glNormal3f (0, 0, 1);
397       glTexCoord2f (-scale/2,  scale/2); glVertex3f (0, 1, 0);
398       glTexCoord2f ( scale/2,  scale/2); glVertex3f (1, 1, 0);
399       glTexCoord2f ( scale/2, -scale/2); glVertex3f (1, 0, 0);
400       glTexCoord2f (-scale/2, -scale/2); glVertex3f (0, 0, 0);
401       glEnd();
402
403       if (wire)
404         {
405           float j;
406           glDisable (GL_TEXTURE_1D);
407           glColor4f (1, 1, 1, 1.0 / bp->count);
408           for (j = 0; j < 1; j += (1 / scale))
409             {
410               glBegin (GL_LINES);
411               glVertex3f (j, 0, 0);
412               glVertex3f (j, 1, 0);
413               mi->polygon_count++;
414               glEnd();
415             }
416         }
417
418       glPopMatrix();
419
420       mi->polygon_count++;
421     }
422
423   /* Colorize the grayscale image. */
424   {
425     GLfloat c[4];
426     c[0] = bp->colors[bp->ccolor].red   / 65536.0;
427     c[1] = bp->colors[bp->ccolor].green / 65536.0;
428     c[2] = bp->colors[bp->ccolor].blue  / 65536.0;
429     c[3] = 1;
430
431     /* Brighten the colors. */
432     c[0] = (0.6666 + c[0]/3);
433     c[1] = (0.6666 + c[1]/3);
434     c[2] = (0.6666 + c[2]/3);
435
436     glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR);
437     glDisable (GL_TEXTURE_1D);
438     glColor4fv (c);
439     glTranslatef (-0.5, -0.5, 0);
440     glBegin (GL_QUADS);
441     glVertex3f (0, 1, 0);
442     glVertex3f (1, 1, 0);
443     glVertex3f (1, 0, 0);
444     glVertex3f (0, 0, 0);
445     glEnd();
446     mi->polygon_count++;
447   }
448
449   /* Clip the colors to simulate contrast. */
450
451   if (bp->contrast > 0)
452     {
453       /* If c > 0, map 0 - 100 to 0.5 - 1.0, and use (s & ~d) */
454       GLfloat c = 1 - (bp->contrast / 2 / 100.0);
455       glDisable (GL_TEXTURE_1D);
456       glDisable (GL_BLEND);
457       glEnable (GL_COLOR_LOGIC_OP);
458       glLogicOp (GL_AND_REVERSE);
459       glColor4f (c, c, c, 1);
460       glBegin (GL_QUADS);
461       glVertex3f (0, 1, 0);
462       glVertex3f (1, 1, 0);
463       glVertex3f (1, 0, 0);
464       glVertex3f (0, 0, 0);
465       glEnd();
466       mi->polygon_count++;
467       glDisable (GL_COLOR_LOGIC_OP);
468     }
469
470   /* Rotate colors. */
471   bp->ccolor++;
472   if (bp->ccolor >= bp->ncolors)
473     bp->ccolor = 0;
474
475
476   glPopMatrix ();
477
478   if (mi->fps_p) do_fps (mi);
479   glFinish();
480
481   glXSwapBuffers(dpy, window);
482 }
483
484 XSCREENSAVER_MODULE ("QuasiCrystal", quasicrystal)
485
486 #endif /* USE_GL */