1 /* quasicrystal, Copyright (c) 2013 Jamie Zawinski <jwz@jwz.org>
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
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
17 #define DEFAULTS "*delay: 30000 \n" \
20 "*symmetric: True \n" \
23 "*showFPS: False \n" \
24 "*wireframe: False \n" \
25 "*suppressRotationAnimation: True\n" \
27 # define free_quasicrystal 0
28 # define release_quasicrystal 0
30 #define countof(x) (sizeof((x))/sizeof((*x)))
32 #include "xlockmore.h"
37 #ifdef USE_GL /* whole file */
39 #define DEF_SPEED "1.0"
47 GLXContext *glx_context;
57 } quasicrystal_configuration;
59 static quasicrystal_configuration *bps = NULL;
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 },
74 static argtype vars[] = {
75 {&speed, "speed", "Speed", DEF_SPEED, t_Float},
78 ENTRYPOINT ModeSpecOpt quasicrystal_opts = {countof(opts), opts, countof(vars), vars, NULL};
82 /* Window management, etc
85 reshape_quasicrystal (ModeInfo *mi, int width, int height)
87 GLfloat h = (GLfloat) height / (GLfloat) width;
89 glViewport (0, 0, (GLint) width, (GLint) height);
91 glMatrixMode(GL_PROJECTION);
93 glOrtho (0, 1, 1, 0, -1, 1);
95 glMatrixMode(GL_MODELVIEW);
97 glTranslatef (0.5, 0.5, 0);
100 glScalef (1/h, 1/h, 1);
101 glTranslatef (-0.5, -0.5, 0);
102 glClear(GL_COLOR_BUFFER_BIT);
107 quasicrystal_handle_event (ModeInfo *mi, XEvent *event)
109 quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)];
111 if (event->xany.type == ButtonPress &&
112 event->xbutton.button == Button1)
114 bp->button_down_p = True;
115 bp->mousex = event->xbutton.x;
116 bp->mousey = event->xbutton.y;
119 else if (event->xany.type == ButtonRelease &&
120 event->xbutton.button == Button1)
122 bp->button_down_p = False;
125 else if (event->xany.type == ButtonPress && /* wheel up or right */
126 (event->xbutton.button == Button4 ||
127 event->xbutton.button == Button7))
130 if (bp->contrast <= 0) return False;
134 else if (event->xany.type == ButtonPress && /* wheel down or left */
135 (event->xbutton.button == Button5 ||
136 event->xbutton.button == Button6))
139 if (bp->contrast >= 100) return False;
143 else if (event->xany.type == MotionNotify &&
146 /* Dragging up and down tweaks contrast */
148 int dx = event->xmotion.x - bp->mousex;
149 int dy = event->xmotion.y - bp->mousey;
151 if (abs(dy) > abs(dx))
153 bp->contrast += dy / 40.0;
154 if (bp->contrast < 0) bp->contrast = 0;
155 if (bp->contrast > 100) bp->contrast = 100;
158 bp->mousex = event->xmotion.x;
159 bp->mousey = event->xmotion.y;
164 if (event->xany.type == KeyPress)
168 XLookupString (&event->xkey, &c, 1, &keysym, 0);
169 if (c == '<' || c == ',' || c == '-' || c == '_' ||
170 keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior)
172 else if (c == '>' || c == '.' || c == '=' || c == '+' ||
173 keysym == XK_Right || keysym == XK_Down ||
178 return screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event);
187 init_quasicrystal (ModeInfo *mi)
189 quasicrystal_configuration *bp;
190 int wire = MI_IS_WIREFRAME(mi);
191 unsigned char *tex_data = 0;
197 bp = &bps[MI_SCREEN(mi)];
199 bp->glx_context = init_GL(mi);
201 reshape_quasicrystal (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
203 glDisable (GL_DEPTH_TEST);
204 glEnable (GL_CULL_FACE);
206 bp->count = MI_COUNT(mi);
207 if (bp->count < 1) bp->count = 1;
214 glGetIntegerv (GL_MAX_TEXTURE_SIZE, &tex_width);
215 if (tex_width > 4096) tex_width = 4096;
217 tex_data = (unsigned char *) calloc (4, tex_width);
219 for (i = 0; i < tex_width; i++)
221 unsigned char y = 255 * (1 + sin (i * M_PI * 2 / tex_width)) / 2;
230 get_boolean_resource (MI_DISPLAY (mi), "symmetry", "Symmetry");
232 bp->contrast = get_float_resource (MI_DISPLAY (mi), "contrast", "Contrast");
233 if (bp->contrast < 0 || bp->contrast > 100)
235 fprintf (stderr, "%s: contrast must be between 0 and 100%%.\n", progname);
240 Bool spinp = get_boolean_resource (MI_DISPLAY (mi), "spin", "Spin");
241 Bool wanderp = get_boolean_resource (MI_DISPLAY (mi), "wander", "Wander");
242 double spin_speed = 0.01;
243 double wander_speed = 0.0001;
244 double spin_accel = 10.0;
245 double scale_speed = 0.005;
247 bp->planes = (plane *) calloc (sizeof (*bp->planes), bp->count);
249 bp->ncolors = 256; /* ncolors affects color-cycling speed */
250 bp->colors = (XColor *) calloc (bp->ncolors, sizeof(XColor));
251 make_smooth_colormap (0, 0, 0, bp->colors, &bp->ncolors,
255 for (i = 0; i < bp->count; i++)
257 plane *p = &bp->planes[i];
258 p->rot = make_rotator (0, 0,
259 spinp ? spin_speed : 0,
261 wanderp ? wander_speed : 0,
263 p->rot2 = make_rotator (0, 0,
271 glGenTextures (1, &p->texid);
272 glBindTexture (GL_TEXTURE_1D, p->texid);
273 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
274 glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA,
277 /* GL_UNSIGNED_BYTE, */
278 GL_UNSIGNED_INT_8_8_8_8_REV,
280 check_gl_error("texture");
282 glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT);
283 glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT);
285 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
286 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
288 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
293 if (tex_data) free (tex_data);
295 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
300 draw_quasicrystal (ModeInfo *mi)
302 quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)];
303 Display *dpy = MI_DISPLAY(mi);
304 Window window = MI_WINDOW(mi);
305 int wire = MI_IS_WIREFRAME(mi);
309 if (!bp->glx_context)
312 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(bp->glx_context));
314 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
316 mi->polygon_count = 0;
318 glShadeModel(GL_FLAT);
319 glDisable(GL_DEPTH_TEST);
320 glDisable(GL_CULL_FACE);
321 glDisable (GL_LIGHTING);
324 glEnable (GL_TEXTURE_1D);
326 glEnable (GL_TEXTURE_2D); /* jwzgles needs this, bleh. */
331 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
334 glTranslatef (0.5, 0.5, 0);
337 if (wire) glScalef (0.2, 0.2, 0.2);
339 for (i = 0; i < bp->count; i++)
341 plane *p = &bp->planes[i];
343 double scale = (wire ? 10 : 700.0 / bp->count);
348 get_position (p->rot, &x, &y, &z, !bp->button_down_p);
349 glTranslatef((x - 0.5) * 0.3333,
353 /* With -symmetry, keep the planes' scales in sync.
354 Otherwise, they scale independently.
356 if (bp->symmetric_p && i > 0)
360 get_position (p->rot2, &x, &y, &z, !bp->button_down_p);
361 pscale = 1 + (4 * z);
368 /* With -symmetry, evenly distribute the planes' rotation.
369 Otherwise, they rotate independently.
371 if (bp->symmetric_p && i > 0)
372 z = r + (i * M_PI * 2 / bp->count);
375 get_rotation (p->rot, &x, &y, &z, !bp->button_down_p);
380 glRotatef (z * 360, 0, 0, 1);
381 glTranslatef (-0.5, -0.5, 0);
383 glColor4f (1, 1, 1, (wire ? 0.5 : 1.0 / bp->count));
386 glBindTexture (GL_TEXTURE_1D, p->texid);
388 glBegin (wire ? GL_LINE_LOOP : GL_QUADS);
389 glNormal3f (0, 0, 1);
390 glTexCoord2f (-scale/2, scale/2); glVertex3f (0, 1, 0);
391 glTexCoord2f ( scale/2, scale/2); glVertex3f (1, 1, 0);
392 glTexCoord2f ( scale/2, -scale/2); glVertex3f (1, 0, 0);
393 glTexCoord2f (-scale/2, -scale/2); glVertex3f (0, 0, 0);
399 glDisable (GL_TEXTURE_1D);
400 glColor4f (1, 1, 1, 1.0 / bp->count);
401 for (j = 0; j < 1; j += (1 / scale))
404 glVertex3f (j, 0, 0);
405 glVertex3f (j, 1, 0);
416 /* Colorize the grayscale image. */
419 c[0] = bp->colors[bp->ccolor].red / 65536.0;
420 c[1] = bp->colors[bp->ccolor].green / 65536.0;
421 c[2] = bp->colors[bp->ccolor].blue / 65536.0;
424 /* Brighten the colors. */
425 c[0] = (0.6666 + c[0]/3);
426 c[1] = (0.6666 + c[1]/3);
427 c[2] = (0.6666 + c[2]/3);
429 glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR);
430 glDisable (GL_TEXTURE_1D);
432 glTranslatef (-0.5, -0.5, 0);
434 glVertex3f (0, 1, 0);
435 glVertex3f (1, 1, 0);
436 glVertex3f (1, 0, 0);
437 glVertex3f (0, 0, 0);
442 /* Clip the colors to simulate contrast. */
444 if (bp->contrast > 0)
446 /* If c > 0, map 0 - 100 to 0.5 - 1.0, and use (s & ~d) */
447 GLfloat c = 1 - (bp->contrast / 2 / 100.0);
448 glDisable (GL_TEXTURE_1D);
449 glDisable (GL_BLEND);
450 glEnable (GL_COLOR_LOGIC_OP);
451 glLogicOp (GL_AND_REVERSE);
452 glColor4f (c, c, c, 1);
454 glVertex3f (0, 1, 0);
455 glVertex3f (1, 1, 0);
456 glVertex3f (1, 0, 0);
457 glVertex3f (0, 0, 0);
460 glDisable (GL_COLOR_LOGIC_OP);
465 if (bp->ccolor >= bp->ncolors)
471 if (mi->fps_p) do_fps (mi);
474 glXSwapBuffers(dpy, window);
477 XSCREENSAVER_MODULE ("QuasiCrystal", quasicrystal)