/* unknownpleasures, Copyright (c) 2013-2014 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. * * Translated from Mathematica code by Archery: * http://intothecontinuum.tumblr.com/post/27443100682/in-july-1967-astronomers-at-the-cavendish * * Interestingly, the original image is copyright-free: * http://adamcap.com/2011/05/19/history-of-joy-division-unknown-pleasures-album-art/ * https://en.wikipedia.org/wiki/Unknown_Pleasures * * TODO: * * - Performance is not great. Spending half our time in compute_line() * and half our time in glEnd(). It's a vast number of cos/pow calls, * and a vast number of polygons. I'm not sure how much could be cached. * * - There's too low periodicity vertically on the screen. * * - There's too low periodicity in time. * * - Could take advantage of time periodicity for caching: just save every * poly for an N second loop. That would be a huge amount of RAM though. * * - At low resolutions, GL_POLYGON_OFFSET_FILL seems to work poorly * and the lines get blocky. */ #define DEFAULTS "*delay: 30000 \n" \ "*count: 80 \n" \ "*resolution: 100 \n" \ "*ortho: True \n" \ "*showFPS: False \n" \ "*wireframe: False \n" \ "*geometry: =800x800" "\n" \ # define free_unk 0 # define release_unk 0 #undef countof #define countof(x) (sizeof((x))/sizeof((*x))) #include "xlockmore.h" #include "colors.h" #include "gltrackball.h" #include #ifdef USE_GL /* whole file */ #define DEF_SPEED "1.0" typedef struct { GLXContext *glx_context; trackball_state *trackball; Bool button_down_p; Bool orthop; GLfloat resolution; int count; GLfloat t; } unk_configuration; static unk_configuration *bps = NULL; static GLfloat speed; static XrmOptionDescRec opts[] = { { "-speed", ".speed", XrmoptionSepArg, 0 }, { "-resolution", ".resolution", XrmoptionSepArg, 0 }, { "-ortho", ".ortho", XrmoptionNoArg, "True" }, { "-no-ortho", ".ortho", XrmoptionNoArg, "False" }, }; static argtype vars[] = { {&speed, "speed", "Speed", DEF_SPEED, t_Float}, }; ENTRYPOINT ModeSpecOpt unk_opts = {countof(opts), opts, countof(vars), vars, NULL}; /* Window management, etc */ ENTRYPOINT void reshape_unk (ModeInfo *mi, int width, int height) { unk_configuration *bp = &bps[MI_SCREEN(mi)]; GLfloat h = (GLfloat) height / (GLfloat) width; int y = 0; if (width > height * 5) { /* tiny window: show middle */ height = width*1.5; y = -height/2; h = height / (GLfloat) width; } glViewport (0, y, (GLint) width, (GLint) height); if (bp->orthop) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (1.0, 1/h, 690, 710); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0, 0, 700, 0, 0, 0, 0, 1, 0); if (width < height) glScalef (1/h, 1/h, 1); glTranslatef (0, -0.5, 0); } else { glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective (30.0, 1/h, 20.0, 40.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt( 0.0, 0.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); if (width < height) glScalef (1/h, 1/h, 1); } glClear(GL_COLOR_BUFFER_BIT); } ENTRYPOINT Bool unk_handle_event (ModeInfo *mi, XEvent *event) { unk_configuration *bp = &bps[MI_SCREEN(mi)]; if (event->xany.type == ButtonPress && (event->xbutton.button == Button4 || event->xbutton.button == Button5 || event->xbutton.button == Button6 || event->xbutton.button == Button7)) { int b = event->xbutton.button; int speed = 1; if (b == Button6 || b == Button7) speed *= 3; if (bp->orthop) switch (b) { /* swap axes */ case Button4: b = Button6; break; case Button5: b = Button7; break; case Button6: b = Button4; break; case Button7: b = Button5; break; } gltrackball_mousewheel (bp->trackball, b, speed, !!event->xbutton.state); return True; } else if (gltrackball_event_handler (event, bp->trackball, MI_WIDTH (mi), MI_HEIGHT (mi), &bp->button_down_p)) return True; else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event)) { bp->orthop = !bp->orthop; reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); return True; } return False; } ENTRYPOINT void init_unk (ModeInfo *mi) { unk_configuration *bp; MI_INIT (mi, bps); bp = &bps[MI_SCREEN(mi)]; bp->glx_context = init_GL(mi); bp->orthop = get_boolean_resource (MI_DISPLAY (mi), "ortho", "Ortho"); bp->resolution = get_float_resource (MI_DISPLAY (mi), "resolution", "Resolution"); if (bp->resolution < 1) bp->resolution = 1; if (bp->resolution > 300) bp->resolution = 300; reshape_unk (mi, MI_WIDTH(mi), MI_HEIGHT(mi)); bp->count = MI_COUNT(mi); if (bp->count < 1) bp->count = 1; bp->trackball = gltrackball_init (False); if (MI_COUNT(mi) < 1) MI_COUNT(mi) = 1; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } static double R (double f) { /* A simple, fast, deterministic PRNG. ya_rand_init() is too slow for this, and the stream of numbers here doesn't have to be high quality. */ #if 1 int seed0 = 1613287; # else /* Kluge to let me pick a good seed factor by trial and error... */ static int seed0 = 0; static int count = 0; if (count++ > 150000000) seed0 = 0, count=0; if (! seed0) { seed0 = (random() & 0xFFFFF); fprintf(stderr, "seed0 = %8x %d\n", seed0, seed0); } # endif long seed = seed0 * f; seed = 48271 * (seed % 44488) - 3399 * (seed / 44488); f = (double) seed / 0x7FFFFFFF; return f; } static void compute_line (ModeInfo *mi, int xmin, int xmax, GLfloat xinc, GLfloat res_scale, int y, GLfloat *points) { unk_configuration *bp = &bps[MI_SCREEN(mi)]; GLfloat fx; int lastx = -999999; /* Compute the points on the line */ for (fx = xmin; fx < xmax; fx += xinc) { int x = fx; int n; GLfloat hsum = 0, vsum = 0; if (x == lastx) continue; lastx = x; for (n = 1; n <= 30; n++) { /* This sum affects crinkliness of the lines */ hsum += (10 * sin (2 * M_PI * R (4 * y) + bp->t + R (2 * n * y) * 2 * M_PI) * exp (-pow ((.3 * (x / res_scale) + 30 - 1 * 100 * R (2 * n * y)), 2) / 20.0)); } for (n = 1; n <= 4; n++) { /* This sum affects amplitude of the peaks */ vsum += (3 * (1 + R (3 * n * y)) * fabs (sin (bp->t + R (n * y) * 2 * M_PI)) * exp (-pow (((x / res_scale) - 1 * 100 * R (n * y)), 2) / 20.0)); } /* Scale of this affects amplitude of the flat parts */ points[x - xmin] = (0.2 * sin (2 * M_PI * R (6 * y) + hsum) + vsum); } } ENTRYPOINT void draw_unk (ModeInfo *mi) { unk_configuration *bp = &bps[MI_SCREEN(mi)]; Display *dpy = MI_DISPLAY(mi); Window window = MI_WINDOW(mi); int wire = MI_IS_WIREFRAME(mi); GLfloat aspect = 1.5; GLfloat res_scale = 4; int xmin = -50 * res_scale; int xmax = 150 * res_scale; GLfloat xinc = 100.0 / (bp->resolution / res_scale); int ymin = 1; int ytop = MI_COUNT(mi); int yinc = 1; int y; GLfloat *points; if (xinc < 0.25) xinc = 0.25; 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); glEnable (GL_DEPTH_TEST); glDisable (GL_CULL_FACE); glPushMatrix (); glRotatef(current_device_rotation(), 0, 0, 1); gltrackball_rotate (bp->trackball); glScalef (10, 10, 10); glRotatef (-45, 1, 0, 0); glTranslatef (-0.5, -0.5, 0); if (bp->orthop) glTranslatef (0, 0.05, 0); else glTranslatef (0, 0.15, 0); points = (GLfloat *) malloc (sizeof(*points) * (xmax - xmin)); if (!bp->button_down_p) { double s = 6.3 / 19 / 3; # if 1 bp->t -= s * speed; if (bp->t <= 0) bp->t = s * 18 * 3; # else bp->t += s; # endif } glLineWidth (2); /* Lower the resolution to get a decent frame rate on iPhone 4s */ if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640) { ytop *= 0.6; xinc *= 3; } # ifdef HAVE_MOBILE /* Lower it even further for iPhone 3 */ if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480) { ytop *= 0.8; xinc *= 1.2; } /* Performance just sucks on iPad 3, even with a very high xinc. WTF? */ /* if (mi->xgwa.width >= 2048 || mi->xgwa.height >= 2048) xinc *= 2; */ # endif /* HAVE_MOBILE */ /* Make the image fill the screen a little more fully */ if (mi->xgwa.width <= 640 || mi->xgwa.height <= 640) { glScalef (1.2, 1.2, 1.2); glTranslatef (-0.08, 0, 0); } if (mi->xgwa.width <= 480 || mi->xgwa.height <= 480) glLineWidth (1); if (wire) xinc *= 1.3; /* Draw back mask */ { GLfloat s = 0.99; glDisable (GL_POLYGON_OFFSET_FILL); glColor3f (0, 0, 0); glPushMatrix(); glTranslatef (0, (1-aspect)/2, -0.005); glScalef (1, aspect, 1); glTranslatef (0.5, 0.5, 0); glScalef (s, s, 1); glBegin (GL_QUADS); glVertex3f (-0.5, -0.5, 0); glVertex3f ( 0.5, -0.5, 0); glVertex3f ( 0.5, 0.5, 0); glVertex3f (-0.5, 0.5, 0); glEnd(); glPopMatrix(); } if (! wire) { glEnable (GL_LINE_SMOOTH); glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_BLEND); /* So the masking quads don't interfere with the lines */ glEnable (GL_POLYGON_OFFSET_FILL); glPolygonOffset (1.0, 1.0); } for (y = ymin; y <= ytop; y += yinc) { /* Compute all the verts on the line */ compute_line (mi, xmin, xmax, xinc, res_scale, y, points); /* Draw the line segments; then draw the black shielding quads. */ { GLfloat yy = y / (GLfloat) ytop; int linesp; yy = (yy * aspect) - ((aspect - 1) / 2); for (linesp = 0; linesp <= 1; linesp++) { GLfloat fx; int lastx = -999999; GLfloat c = (linesp || wire ? 1 : 0); glColor3f (c, c, c); glBegin (linesp ? GL_LINE_STRIP : wire ? GL_LINES : GL_QUAD_STRIP); lastx = -999999; for (fx = xmin; fx < xmax; fx += xinc) { int x = fx; GLfloat xx = (x - xmin) / (GLfloat) (xmax - xmin); GLfloat zz = points [x - xmin]; if (x == lastx) continue; lastx = x; zz /= 80; glVertex3f (xx, yy, zz); if (! linesp) glVertex3f (xx, yy, 0); mi->polygon_count++; } glEnd (); } } } free (points); glPopMatrix (); if (mi->fps_p) do_fps (mi); glFinish(); glXSwapBuffers(dpy, window); } XSCREENSAVER_MODULE_2 ("UnknownPleasures", unknownpleasures, unk) #endif /* USE_GL */