From http://www.jwz.org/xscreensaver/xscreensaver-5.22.tar.gz
[xscreensaver] / hacks / glx / quasicrystal.c
diff --git a/hacks/glx/quasicrystal.c b/hacks/glx/quasicrystal.c
new file mode 100644 (file)
index 0000000..d01d1ed
--- /dev/null
@@ -0,0 +1,441 @@
+/* quasicrystal, Copyright (c) 2013 Jamie Zawinski <jwz@jwz.org>
+ *
+ * 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" \
+
+# define refresh_quasicrystal 0
+# define release_quasicrystal 0
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#include "xlockmore.h"
+#include "colors.h"
+#include "rotator.h"
+#include <ctype.h>
+
+#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"   },
+  { "-no-symmetry",  ".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 == 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;
+    }
+
+  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;
+
+  if (!bps) {
+    bps = (quasicrystal_configuration *)
+      calloc (MI_NUM_SCREENS(mi), sizeof (quasicrystal_configuration));
+    if (!bps) {
+      fprintf(stderr, "%s: out of memory\n", progname);
+      exit(1);
+    }
+  }
+
+  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;
+      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), "symmetry", "Symmetry");
+
+  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, */
+                          GL_UNSIGNED_INT_8_8_8_8_REV,
+                          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, ps;
+  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);
+
+  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)
+        {
+          glEnable (GL_TEXTURE_1D);
+          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);
+}
+
+XSCREENSAVER_MODULE ("QuasiCrystal", quasicrystal)
+
+#endif /* USE_GL */