/* unknownpleasures, Copyright (c) 2013-2014 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.
*
* 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 <ctype.h>
#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 */