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