/* quasicrystal, Copyright (c) 2013 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that * the above copyright notice appear in all copies and that both that * copyright notice and this permission notice appear in supporting * documentation. No representations are made about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. * * Overlapping sine waves create interesting plane-tiling interference * patterns. Created by jwz, Jul 2013. Inspired by * http://mainisusuallyafunction.blogspot.com/2011/10/quasicrystals-as-sums-of-waves-in-plane.html */ #define DEFAULTS "*delay: 30000 \n" \ "*spin: True \n" \ "*wander: True \n" \ "*symmetric: True \n" \ "*count: 17 \n" \ "*contrast: 30 \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*suppressRotationAnimation: True\n" \ # define release_quasicrystal 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #include "xlockmore.h" #include "colors.h" #include "rotator.h" #include #ifdef USE_GL /* whole file */ #define DEF_SPEED "1.0" typedef struct { rotator *rot, *rot2; GLuint texid; } plane; typedef struct { GLXContext *glx_context; Bool button_down_p; Bool symmetric_p; GLfloat contrast; int count; int ncolors, ccolor; XColor *colors; plane *planes; int mousex, mousey; } quasicrystal_configuration; static quasicrystal_configuration *bps = NULL; static GLfloat speed; static XrmOptionDescRec opts[] = { { "-spin", ".spin", XrmoptionNoArg, "True" }, { "+spin", ".spin", XrmoptionNoArg, "False" }, { "-wander", ".wander", XrmoptionNoArg, "True" }, { "+wander", ".wander", XrmoptionNoArg, "False" }, { "-symmetry", ".symmetric", XrmoptionNoArg, "True" }, { "-symmetric", ".symmetric", XrmoptionNoArg, "True" }, { "-no-symmetry", ".symmetric", XrmoptionNoArg, "False" }, { "-nonsymmetric", ".symmetric", XrmoptionNoArg, "False" }, { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-contrast", ".contrast", XrmoptionSepArg, 0 }, }; static argtype vars[] = { {&speed, "speed", "Speed", DEF_SPEED, t_Float}, }; ENTRYPOINT ModeSpecOpt quasicrystal_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Window management, etc */ ENTRYPOINT void reshape_quasicrystal (ModeInfo *mi, int width, int height) { GLfloat h = (GLfloat) height / (GLfloat) width; glViewport (0, 0, (GLint) width, (GLint) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho (0, 1, 1, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef (0.5, 0.5, 0); glScalef (h, 1, 1); if (width > height) glScalef (1/h, 1/h, 1); glTranslatef (-0.5, -0.5, 0); glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool quasicrystal_handle_event (ModeInfo *mi, XEvent *event) { quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)]; if (event->xany.type == ButtonPress && event->xbutton.button == Button1) { bp->button_down_p = True; bp->mousex = event->xbutton.x; bp->mousey = event->xbutton.y; return True; } else if (event->xany.type == ButtonRelease && event->xbutton.button == Button1) { bp->button_down_p = False; return True; } else if (event->xany.type == ButtonPress && /* wheel up or right */ (event->xbutton.button == Button4 || event->xbutton.button == Button7)) { UP: if (bp->contrast <= 0) return False; bp->contrast--; return True; } else if (event->xany.type == ButtonPress && /* wheel down or left */ (event->xbutton.button == Button5 || event->xbutton.button == Button6)) { DOWN: if (bp->contrast >= 100) return False; bp->contrast++; return True; } else if (event->xany.type == MotionNotify && bp->button_down_p) { /* Dragging up and down tweaks contrast */ int dx = event->xmotion.x - bp->mousex; int dy = event->xmotion.y - bp->mousey; if (abs(dy) > abs(dx)) { bp->contrast += dy / 40.0; if (bp->contrast < 0) bp->contrast = 0; if (bp->contrast > 100) bp->contrast = 100; } bp->mousex = event->xmotion.x; bp->mousey = event->xmotion.y; return True; } else { if (event->xany.type == KeyPress) { KeySym keysym; char c = 0; XLookupString (&event->xkey, &c, 1, &keysym, 0); if (c == '<' || c == ',' || c == '-' || c == '_' || keysym == XK_Left || keysym == XK_Up || keysym == XK_Prior) goto UP; else if (c == '>' || c == '.' || c == '=' || c == '+' || keysym == XK_Right || keysym == XK_Down || keysym == XK_Next) goto DOWN; } return screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event); } return False; } ENTRYPOINT void init_quasicrystal (ModeInfo *mi) { quasicrystal_configuration *bp; int wire = MI_IS_WIREFRAME(mi); unsigned char *tex_data = 0; int tex_width; int i; MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); reshape_quasicrystal (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); glDisable (GL_DEPTH_TEST); glEnable (GL_CULL_FACE); bp->count = MI_COUNT(mi); if (bp->count < 1) bp->count = 1; if (! wire) { unsigned char *o; tex_width = 4096; glGetIntegerv (GL_MAX_TEXTURE_SIZE, &tex_width); if (tex_width > 4096) tex_width = 4096; tex_data = (unsigned char *) calloc (4, tex_width); o = tex_data; for (i = 0; i < tex_width; i++) { unsigned char y = 255 * (1 + sin (i * M_PI * 2 / tex_width)) / 2; *o++ = y; *o++ = y; *o++ = y; *o++ = 255; } } bp->symmetric_p = get_boolean_resource (MI_DISPLAY (mi), "symmetric", "Symmetric"); bp->contrast = get_float_resource (MI_DISPLAY (mi), "contrast", "Contrast"); if (bp->contrast < 0 || bp->contrast > 100) { fprintf (stderr, "%s: contrast must be between 0 and 100%%.\n", progname); bp->contrast = 0; } { Bool spinp = get_boolean_resource (MI_DISPLAY (mi), "spin", "Spin"); Bool wanderp = get_boolean_resource (MI_DISPLAY (mi), "wander", "Wander"); double spin_speed = 0.01; double wander_speed = 0.0001; double spin_accel = 10.0; double scale_speed = 0.005; bp->planes = (plane *) calloc (sizeof (*bp->planes), bp->count); bp->ncolors = 256; /* ncolors affects color-cycling speed */ bp->colors = (XColor *) calloc (bp->ncolors, sizeof(XColor)); make_smooth_colormap (0, 0, 0, bp->colors, &bp->ncolors, False, 0, False); bp->ccolor = 0; for (i = 0; i < bp->count; i++) { plane *p = &bp->planes[i]; p->rot = make_rotator (0, 0, spinp ? spin_speed : 0, spin_accel, wanderp ? wander_speed : 0, True); p->rot2 = make_rotator (0, 0, 0, 0, scale_speed, True); if (! wire) { clear_gl_error(); glGenTextures (1, &p->texid); glBindTexture (GL_TEXTURE_1D, p->texid); glPixelStorei (GL_UNPACK_ALIGNMENT, 1); glTexImage1D (GL_TEXTURE_1D, 0, GL_RGBA, tex_width, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex_data); check_gl_error("texture"); glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_1D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); } } } if (tex_data) free (tex_data); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } ENTRYPOINT void draw_quasicrystal (ModeInfo *mi) { quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int wire = MI_IS_WIREFRAME(mi); double r=0, ps=0; int i; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); mi->polygon_count = 0; glShadeModel(GL_FLAT); glDisable(GL_DEPTH_TEST); glDisable(GL_CULL_FACE); glDisable (GL_LIGHTING); if (!wire) { glEnable (GL_TEXTURE_1D); # ifdef HAVE_JWZGLES glEnable (GL_TEXTURE_2D); /* jwzgles needs this, bleh. */ # endif } glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glPushMatrix (); glTranslatef (0.5, 0.5, 0); glScalef (3, 3, 3); if (wire) glScalef (0.2, 0.2, 0.2); for (i = 0; i < bp->count; i++) { plane *p = &bp->planes[i]; double x, y, z; double scale = (wire ? 10 : 700.0 / bp->count); double pscale; glPushMatrix(); get_position (p->rot, &x, &y, &z, !bp->button_down_p); glTranslatef((x - 0.5) * 0.3333, (y - 0.5) * 0.3333, 0); /* With -symmetry, keep the planes' scales in sync. Otherwise, they scale independently. */ if (bp->symmetric_p && i > 0) pscale = ps; else { get_position (p->rot2, &x, &y, &z, !bp->button_down_p); pscale = 1 + (4 * z); ps = pscale; } scale *= pscale; /* With -symmetry, evenly distribute the planes' rotation. Otherwise, they rotate independently. */ if (bp->symmetric_p && i > 0) z = r + (i * M_PI * 2 / bp->count); else { get_rotation (p->rot, &x, &y, &z, !bp->button_down_p); r = z; } glRotatef (z * 360, 0, 0, 1); glTranslatef (-0.5, -0.5, 0); glColor4f (1, 1, 1, (wire ? 0.5 : 1.0 / bp->count)); if (!wire) glBindTexture (GL_TEXTURE_1D, p->texid); glBegin (wire ? GL_LINE_LOOP : GL_QUADS); glNormal3f (0, 0, 1); glTexCoord2f (-scale/2, scale/2); glVertex3f (0, 1, 0); glTexCoord2f ( scale/2, scale/2); glVertex3f (1, 1, 0); glTexCoord2f ( scale/2, -scale/2); glVertex3f (1, 0, 0); glTexCoord2f (-scale/2, -scale/2); glVertex3f (0, 0, 0); glEnd(); if (wire) { float j; glDisable (GL_TEXTURE_1D); glColor4f (1, 1, 1, 1.0 / bp->count); for (j = 0; j < 1; j += (1 / scale)) { glBegin (GL_LINES); glVertex3f (j, 0, 0); glVertex3f (j, 1, 0); mi->polygon_count++; glEnd(); } } glPopMatrix(); mi->polygon_count++; } /* Colorize the grayscale image. */ { GLfloat c[4]; c[0] = bp->colors[bp->ccolor].red / 65536.0; c[1] = bp->colors[bp->ccolor].green / 65536.0; c[2] = bp->colors[bp->ccolor].blue / 65536.0; c[3] = 1; /* Brighten the colors. */ c[0] = (0.6666 + c[0]/3); c[1] = (0.6666 + c[1]/3); c[2] = (0.6666 + c[2]/3); glBlendFunc (GL_DST_COLOR, GL_SRC_COLOR); glDisable (GL_TEXTURE_1D); glColor4fv (c); glTranslatef (-0.5, -0.5, 0); glBegin (GL_QUADS); glVertex3f (0, 1, 0); glVertex3f (1, 1, 0); glVertex3f (1, 0, 0); glVertex3f (0, 0, 0); glEnd(); mi->polygon_count++; } /* Clip the colors to simulate contrast. */ if (bp->contrast > 0) { /* If c > 0, map 0 - 100 to 0.5 - 1.0, and use (s & ~d) */ GLfloat c = 1 - (bp->contrast / 2 / 100.0); glDisable (GL_TEXTURE_1D); glDisable (GL_BLEND); glEnable (GL_COLOR_LOGIC_OP); glLogicOp (GL_AND_REVERSE); glColor4f (c, c, c, 1); glBegin (GL_QUADS); glVertex3f (0, 1, 0); glVertex3f (1, 1, 0); glVertex3f (1, 0, 0); glVertex3f (0, 0, 0); glEnd(); mi->polygon_count++; glDisable (GL_COLOR_LOGIC_OP); } /* Rotate colors. */ bp->ccolor++; if (bp->ccolor >= bp->ncolors) bp->ccolor = 0; glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } ENTRYPOINT void free_quasicrystal (ModeInfo *mi) { quasicrystal_configuration *bp = &bps[MI_SCREEN(mi)]; int i; if (!bp->glx_context) return; glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *bp->glx_context); for (i = 0; i < bp->count; i++) { free_rotator (bp->planes[i].rot); free_rotator (bp->planes[i].rot2); if (bp->planes[i].texid) glDeleteTextures (1, &bp->planes[i].texid); } if (bp->planes) free (bp->planes); if (bp->colors) free (bp->colors); } XSCREENSAVER_MODULE ("QuasiCrystal", quasicrystal) #endif /* USE_GL */