/* voronoi, Copyright (c) 2007-2018 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.
*/
#define DEFAULTS "*delay: 20000 \n" \
"*showFPS: False \n" \
"*suppressRotationAnimation: True\n" \
# define release_voronoi 0
#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))
#define BELLRAND(n) ((frand((n)) + frand((n)) + frand((n))) / 3)
#include "xlockmore.h"
#include <ctype.h>
#ifdef USE_GL /* whole file */
#define DEF_POINTS "25"
#define DEF_POINT_SIZE "9"
#define DEF_POINT_SPEED "1.0"
#define DEF_POINT_DELAY "0.05"
#define DEF_ZOOM_SPEED "1.0"
#define DEF_ZOOM_DELAY "15"
typedef struct node {
GLfloat x, y;
GLfloat dx, dy;
GLfloat ddx, ddy;
struct node *next;
GLfloat color[4], color2[4];
int rot;
} node;
typedef struct {
GLXContext *glx_context;
node *nodes;
int nnodes;
node *dragging;
int ncolors;
XColor *colors;
int point_size;
enum { MODE_WAITING, MODE_ADDING, MODE_ZOOMING } mode;
int adding;
double last_time;
GLfloat zooming; /* 1.0 starting zoom, 0.0 no longer zooming. */
GLfloat zoom_toward[2];
} voronoi_configuration;
static voronoi_configuration *vps = NULL;
/* command line arguments */
static int npoints;
static GLfloat point_size, point_speed, point_delay;
static GLfloat zoom_speed, zoom_delay;
static XrmOptionDescRec opts[] = {
{ "-points", ".points", XrmoptionSepArg, 0 },
{ "-point-size", ".pointSize", XrmoptionSepArg, 0 },
{ "-point-speed", ".pointSpeed", XrmoptionSepArg, 0 },
{ "-point-delay", ".pointDelay", XrmoptionSepArg, 0 },
{ "-zoom-speed", ".zoomSpeed", XrmoptionSepArg, 0 },
{ "-zoom-delay", ".zoomDelay", XrmoptionSepArg, 0 },
};
static argtype vars[] = {
{&npoints, "points", "Points", DEF_POINTS, t_Int},
{&point_size, "pointSize", "PointSize", DEF_POINT_SIZE, t_Float},
{&point_speed, "pointSpeed", "PointSpeed", DEF_POINT_SPEED, t_Float},
{&point_delay, "pointDelay", "PointDelay", DEF_POINT_DELAY, t_Float},
{&zoom_speed, "zoomSpeed", "ZoomSpeed", DEF_ZOOM_SPEED, t_Float},
{&zoom_delay, "zoomDelay", "ZoomDelay", DEF_ZOOM_DELAY, t_Float},
};
ENTRYPOINT ModeSpecOpt voronoi_opts =
{countof(opts), opts, countof(vars), vars, NULL};
/* Returns the current time in seconds as a double.
*/
static double
double_time (void)
{
struct timeval now;
# ifdef GETTIMEOFDAY_TWO_ARGS
struct timezone tzp;
gettimeofday(&now, &tzp);
# else
gettimeofday(&now);
# endif
return (now.tv_sec + ((double) now.tv_usec * 0.000001));
}
static node *
add_node (voronoi_configuration *vp, GLfloat x, GLfloat y)
{
node *nn = (node *) calloc (1, sizeof (*nn));
int i;
nn->x = x;
nn->y = y;
i = random() % vp->ncolors;
nn->color[0] = vp->colors[i].red / 65536.0;
nn->color[1] = vp->colors[i].green / 65536.0;
nn->color[2] = vp->colors[i].blue / 65536.0;
nn->color[3] = 1.0;
nn->color2[0] = nn->color[0] * 0.7;
nn->color2[1] = nn->color[1] * 0.7;
nn->color2[2] = nn->color[2] * 0.7;
nn->color2[3] = 1.0;
nn->ddx = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
nn->ddy = frand (0.000001 * point_speed) * (random() & 1 ? 1 : -1);
nn->rot = (random() % 360) * (random() & 1 ? 1 : -1);
nn->next = vp->nodes;
vp->nodes = nn;
vp->nnodes++;
return nn;
}
static int
cone (void)
{
int i;
int faces = 64;
GLfloat step = M_PI * 2 / faces;
GLfloat th = 0;
GLfloat x = 1;
GLfloat y = 0;
glBegin(GL_TRIANGLE_FAN);
glVertex3f (0, 0, 1);
for (i = 0; i < faces; i++)
{
glVertex3f (x, y, 0);
th += step;
x = cos (th);
y = sin (th);
}
glVertex3f (1, 0, 0);
glEnd();
return faces;
}
static void
move_points (voronoi_configuration *vp)
{
node *nn;
for (nn = vp->nodes; nn; nn = nn->next)
{
if (nn == vp->dragging) continue;
nn->x += nn->dx;
nn->y += nn->dy;
if (vp->mode == MODE_WAITING)
{
nn->dx += nn->ddx;
nn->dy += nn->ddy;
}
}
}
static void
prune_points (voronoi_configuration *vp)
{
node *nn;
node *prev = 0;
int lim = 5;
for (nn = vp->nodes; nn; prev = nn, nn = (nn ? nn->next : 0))
if (nn->x < -lim || nn->x > lim ||
nn->y < -lim || nn->y > lim)
{
if (prev)
prev->next = nn->next;
else
vp->nodes = nn->next;
free (nn);
vp->nnodes--;
nn = prev;
}
}
static void
zoom_points (voronoi_configuration *vp)
{
node *nn;
GLfloat tick = sin (vp->zooming * M_PI);
GLfloat scale = 1 + (tick * 0.02 * zoom_speed);
vp->zooming -= (0.01 * zoom_speed);
if (vp->zooming < 0) vp->zooming = 0;
if (vp->zooming <= 0) return;
if (scale < 1) scale = 1;
for (nn = vp->nodes; nn; nn = nn->next)
{
GLfloat x = nn->x - vp->zoom_toward[0];
GLfloat y = nn->y - vp->zoom_toward[1];
x *= scale;
y *= scale;
nn->x = x + vp->zoom_toward[0];
nn->y = y + vp->zoom_toward[1];
}
}
static void
draw_cells (ModeInfo *mi)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
node *nn;
int lim = 5;
for (nn = vp->nodes; nn; nn = nn->next)
{
if (nn->x < -lim || nn->x > lim ||
nn->y < -lim || nn->y > lim)
continue;
glPushMatrix();
glTranslatef (nn->x, nn->y, 0);
glScalef (lim*2, lim*2, 1);
glColor4fv (nn->color);
mi->polygon_count += cone ();
glPopMatrix();
}
glClear (GL_DEPTH_BUFFER_BIT);
if (vp->point_size <= 0)
;
else if (vp->point_size < 3)
{
glPointSize (vp->point_size);
for (nn = vp->nodes; nn; nn = nn->next)
{
glBegin (GL_POINTS);
glColor4fv (nn->color2);
glVertex2f (nn->x, nn->y);
glEnd();
mi->polygon_count++;
}
}
else
{
for (nn = vp->nodes; nn; nn = nn->next)
{
int w = MI_WIDTH (mi);
int h = MI_HEIGHT (mi);
int s = vp->point_size;
int i;
glColor4fv (nn->color2);
glPushMatrix();
glTranslatef (nn->x, nn->y, 0);
glScalef (1.0 / w * s, 1.0 / h * s, 1);
glLineWidth (vp->point_size / 10);
nn->rot += (nn->rot < 0 ? -1 : 1);
glRotatef (nn->rot, 0, 0, 1);
glRotatef (180, 0, 0, 1);
for (i = 0; i < 5; i++)
{
glBegin (GL_TRIANGLES);
glVertex2f (0, 1);
glVertex2f (-0.2, 0);
glVertex2f ( 0.2, 0);
glEnd ();
glRotatef (360.0/5, 0, 0, 1);
mi->polygon_count++;
}
glPopMatrix();
}
}
#if 0
glPushMatrix();
glColor3f(1,1,1);
glBegin(GL_LINE_LOOP);
glVertex3f(0,0,0);
glVertex3f(1,0,0);
glVertex3f(1,1,0);
glVertex3f(0,1,0);
glEnd();
glScalef(0.25, 0.25, 1);
glBegin(GL_LINE_LOOP);
glVertex3f(0,0,0);
glVertex3f(1,0,0);
glVertex3f(1,1,0);
glVertex3f(0,1,0);
glEnd();
glPopMatrix();
#endif
}
/* Window management, etc
*/
ENTRYPOINT void
reshape_voronoi (ModeInfo *mi, int width, int height)
{
/* voronoi_configuration *vp = &vps[MI_SCREEN(mi)];*/
glViewport (0, 0, (GLint) width, (GLint) height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho (0, 1, 1, 0, -1, 1);
# ifdef HAVE_MOBILE /* So much WTF */
{
int rot = current_device_rotation();
glTranslatef (0.5, 0.5, 0);
// glScalef(0.19, 0.19, 0.19);
if (rot == 180 || rot == -180) {
glTranslatef (1, 1, 0);
} else if (rot == 90 || rot == -270) {
glRotatef (180, 0, 0, 1);
glTranslatef (0, 1, 0);
} else if (rot == -90 || rot == 270) {
glRotatef (180, 0, 0, 1);
glTranslatef (1, 0, 0);
}
glTranslatef(-0.5, -0.5, 0);
}
# endif
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT);
}
static node *
find_node (ModeInfo *mi, GLfloat x, GLfloat y)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
int ps = (vp->point_size < 5 ? 5 : vp->point_size);
GLfloat hysteresis = (1.0 / MI_WIDTH (mi)) * ps;
node *nn;
for (nn = vp->nodes; nn; nn = nn->next)
if (nn->x > x - hysteresis && nn->x < x + hysteresis &&
nn->y > y - hysteresis && nn->y < y + hysteresis)
return nn;
return 0;
}
ENTRYPOINT Bool
voronoi_handle_event (ModeInfo *mi, XEvent *event)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
if (event->xany.type == ButtonPress)
{
GLfloat x = (GLfloat) event->xbutton.x / MI_WIDTH (mi);
GLfloat y = (GLfloat) event->xbutton.y / MI_HEIGHT (mi);
node *nn = find_node (mi, x, y);
if (!nn)
nn = add_node (vp, x, y);
vp->dragging = nn;
return True;
}
else if (event->xany.type == ButtonRelease && vp->dragging)
{
vp->dragging = 0;
return True;
}
else if (event->xany.type == MotionNotify && vp->dragging)
{
vp->dragging->x = (GLfloat) event->xmotion.x / MI_WIDTH (mi);
vp->dragging->y = (GLfloat) event->xmotion.y / MI_HEIGHT (mi);
return True;
}
return False;
}
static void
state_change (ModeInfo *mi)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
double now = double_time();
if (vp->dragging)
{
vp->last_time = now;
vp->adding = 0;
vp->zooming = 0;
return;
}
switch (vp->mode)
{
case MODE_WAITING:
if (vp->last_time + zoom_delay <= now)
{
node *tn = vp->nodes;
vp->zoom_toward[0] = (tn ? tn->x : 0.5);
vp->zoom_toward[1] = (tn ? tn->y : 0.5);
vp->mode = MODE_ZOOMING;
vp->zooming = 1;
vp->last_time = now;
}
break;
case MODE_ADDING:
if (vp->last_time + point_delay <= now)
{
add_node (vp,
BELLRAND(0.5) + 0.25,
BELLRAND(0.5) + 0.25);
vp->last_time = now;
vp->adding--;
if (vp->adding <= 0)
{
vp->adding = 0;
vp->mode = MODE_WAITING;
vp->last_time = now;
}
}
break;
case MODE_ZOOMING:
{
zoom_points (vp);
if (vp->zooming <= 0)
{
vp->mode = MODE_ADDING;
vp->adding = npoints;
vp->last_time = now;
}
}
break;
default:
abort();
}
}
ENTRYPOINT void
init_voronoi (ModeInfo *mi)
{
voronoi_configuration *vp;
MI_INIT (mi, vps);
vp = &vps[MI_SCREEN(mi)];
vp->glx_context = init_GL(mi);
vp->point_size = point_size;
if (vp->point_size < 0) vp->point_size = 10;
if (MI_WIDTH(mi) > 2560) vp->point_size *= 2; /* Retina displays */
vp->ncolors = 128;
vp->colors = (XColor *) calloc (vp->ncolors, sizeof(XColor));
make_smooth_colormap (0, 0, 0,
vp->colors, &vp->ncolors,
False, False, False);
reshape_voronoi (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
vp->mode = MODE_ADDING;
vp->adding = npoints * 2;
vp->last_time = 0;
}
ENTRYPOINT void
draw_voronoi (ModeInfo *mi)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
Display *dpy = MI_DISPLAY(mi);
Window window = MI_WINDOW(mi);
if (!vp->glx_context)
return;
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *vp->glx_context);
glShadeModel(GL_FLAT);
glEnable(GL_POINT_SMOOTH);
/* glEnable(GL_LINE_SMOOTH);*/
/* glEnable(GL_POLYGON_SMOOTH);*/
glEnable (GL_DEPTH_TEST);
glDepthFunc (GL_LEQUAL);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mi->polygon_count = 0;
draw_cells (mi);
move_points (vp);
prune_points (vp);
state_change (mi);
if (mi->fps_p) do_fps (mi);
glFinish();
glXSwapBuffers(dpy, window);
}
ENTRYPOINT void
free_voronoi (ModeInfo *mi)
{
voronoi_configuration *vp = &vps[MI_SCREEN(mi)];
node *n;
if (!vp->glx_context) return;
glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *vp->glx_context);
if (vp->colors) free (vp->colors);
n = vp->nodes;
while (n) {
node *n2 = n->next;
free (n);
n = n2;
}
}
XSCREENSAVER_MODULE ("Voronoi", voronoi)
#endif /* USE_GL */