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