/*
* pedal
*
* Based on a program for some old PDP-11 Graphics Display Processors
* at CMU.
*
* X version by
*
* Dale Moore <Dale.Moore@cs.cmu.edu>
* 24-Jun-1994
*
* Copyright (c) 1994, by Carnegie Mellon University. Permission to use,
* copy, modify, distribute, and sell this software and its documentation
* for any purpose is hereby granted without fee, provided fnord 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 fnord this software
* for any purpose. It is provided "as is" without express or implied
* warranty.
*/
#include <math.h>
#include <stdlib.h>
#include "screenhack.h"
#include "erase.h"
/* If MAXLINES is too big, we might not be able to get it
* to the X server in the 2byte length field. Must be less
* than 16k
*/
#define MAXLINES (16 * 1024)
#define MAXPOINTS MAXLINES
/*
* If the pedal has only this many lines, it must be ugly and we dont
* want to see it.
*/
#define MINLINES 7
struct state {
Display *dpy;
Window window;
XPoint *points;
int sizex, sizey;
int delay;
int maxlines;
GC gc;
XColor foreground, background;
Colormap cmap;
eraser_state *eraser;
int erase_p;
};
/*
* Routine (Macro actually)
* mysin
* Description:
* Assume that degrees is .. oh 360... meaning that
* there are 360 degress in a circle. Then this function
* would return the sin of the angle in degrees. But lets
* say that they are really big degrees, with 4 big degrees
* the same as one regular degree. Then this routine
* would be called mysin(t, 90) and would return sin(t degrees * 4)
*/
#define mysin(t, degrees) sin(t * 2 * M_PI / (degrees))
#define mycos(t, degrees) cos(t * 2 * M_PI / (degrees))
/*
* Macro:
* rand_range
* Description:
* Return a random number between a inclusive and b exclusive.
* rand (3, 6) returns 3 or 4 or 5, but not 6.
*/
#define rand_range(a, b) (a + random() % (b - a))
static int
gcd(int m, int n) /* Greatest Common Divisor (also Greates common factor). */
{
int r;
for (;;) {
r = m % n;
if (r == 0) return (n);
m = n;
n = r;
}
}
static int numlines (int a, int b, int d)
/*
* Description:
*
* Given parameters a and b, how many lines will we have to draw?
*
* Algorithm:
*
* This algorithm assumes that r = sin (theta * a), where we
* evaluate theta on multiples of b.
*
* LCM (i, j) = i * j / GCD (i, j);
*
* So, at LCM (b, 360) we start over again. But since we
* got to LCM (b, 360) by steps of b, the number of lines is
* LCM (b, 360) / b.
*
* If a is odd, then at 180 we cross over and start the
* negative. Someone should write up an elegant way of proving
* this. Why? Because I'm not convinced of it myself.
*
*/
{
#define odd(x) (x & 1)
#define even(x) (!odd(x))
if ( odd(a) && odd(b) && even(d)) d /= 2;
return (d / gcd (d, b));
#undef odd
}
static int
compute_pedal(struct state *st, XPoint *points, int maxpoints)
/*
* Description:
*
* Basically, it's combination spirograph and string art.
* Instead of doing lines, we just use a complex polygon,
* and use an even/odd rule for filling in between.
*
* The spirograph, in mathematical terms is a polar
* plot of the form r = sin (theta * c);
* The string art of this is that we evaluate that
* function only on certain multiples of theta. That is
* we let theta advance in some random increment. And then
* we draw a straight line between those two adjacent points.
*
* Eventually, the lines will start repeating themselves
* if we've evaluated theta on some rational portion of the
* whole.
*
* The number of lines generated is limited to the
* ratio of the increment we put on theta to the whole.
* If we say that there are 360 degrees in a circle, then we
* will never have more than 360 lines.
*
* Return:
*
* The number of points.
*
*/
{
int a, b, d; /* These describe a unique pedal */
double r;
int theta = 0;
XPoint *pp = st->points;
int count;
int numpoints;
/* Just to make sure that this division is not done inside the loop */
int h_width = st->sizex / 2, h_height = st->sizey / 2 ;
for (;;) {
d = rand_range (MINLINES, st->maxlines);
a = rand_range (1, d);
b = rand_range (1, d);
numpoints = numlines(a, b, d);
if (numpoints > MINLINES) break;
}
/* it might be nice to try to move as much sin and cos computing
* (or at least the argument computing) out of the loop.
*/
for (count = numpoints; count-- ; )
{
r = mysin (theta * a, d);
/* Convert from polar to cartesian coordinates */
/* We could round the results, but coercing seems just fine */
pp->x = mysin (theta, d) * r * h_width + h_width;
pp->y = mycos (theta, d) * r * h_height + h_height;
/* Advance index into array */
pp++;
/* Advance theta */
theta += b;
theta %= d;
}
return(numpoints);
}
static void *
pedal_init (Display *dpy, Window window)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
XGCValues gcv;
XWindowAttributes xgwa;
st->dpy = dpy;
st->window = window;
st->delay = get_integer_resource (st->dpy, "delay", "Integer");
if (st->delay < 0) st->delay = 0;
st->maxlines = get_integer_resource (st->dpy, "maxlines", "Integer");
if (st->maxlines < MINLINES) st->maxlines = MINLINES;
else if (st->maxlines > MAXLINES) st->maxlines = MAXLINES;
st->points = (XPoint *)malloc(sizeof(XPoint) * st->maxlines);
XGetWindowAttributes (st->dpy, st->window, &xgwa);
st->sizex = xgwa.width;
st->sizey = xgwa.height;
st->cmap = xgwa.colormap;
gcv.function = GXcopy;
gcv.foreground = get_pixel_resource (st->dpy, st->cmap, "foreground", "Foreground");
gcv.background = get_pixel_resource (st->dpy, st->cmap, "background", "Background");
st->gc = XCreateGC (st->dpy, st->window, GCForeground | GCBackground |GCFunction, &gcv);
return st;
}
/*
* Since the XFillPolygon doesn't require that the last
* point == first point, the number of points is the same
* as the number of lines. We just let XFillPolygon supply
* the line from the last point to the first point.
*
*/
static unsigned long
pedal_draw (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
int numpoints;
int erase_delay = 10000;
int long_delay = 1000000 * st->delay;
if (st->erase_p || st->eraser) {
st->eraser = erase_window (dpy, window, st->eraser);
st->erase_p = 0;
return (st->eraser ? erase_delay : 1000000);
}
numpoints = compute_pedal(st, st->points, st->maxlines);
/* Pick a new foreground color (added by jwz) */
if (! mono_p)
{
XColor color;
hsv_to_rgb (random()%360, 1.0, 1.0,
&color.red, &color.green, &color.blue);
if (XAllocColor (st->dpy, st->cmap, &color))
{
XSetForeground (st->dpy, st->gc, color.pixel);
XFreeColors (st->dpy, st->cmap, &st->foreground.pixel, 1, 0);
st->foreground.red = color.red;
st->foreground.green = color.green;
st->foreground.blue = color.blue;
st->foreground.pixel = color.pixel;
}
}
XFillPolygon (st->dpy, st->window, st->gc, st->points, numpoints,
Complex, CoordModeOrigin);
st->erase_p = 1;
return long_delay;
}
static void
pedal_reshape (Display *dpy, Window window, void *closure,
unsigned int w, unsigned int h)
{
struct state *st = (struct state *) closure;
st->sizex = w;
st->sizey = h;
}
static Bool
pedal_event (Display *dpy, Window window, void *closure, XEvent *event)
{
struct state *st = (struct state *) closure;
if (screenhack_event_helper (dpy, window, event))
{
st->erase_p = 1;
return True;
}
return False;
}
static void
pedal_free (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
free (st);
}
/*
* If we are trying to save the screen, the background
* should be dark.
*/
static const char *pedal_defaults [] = {
".background: black",
".foreground: white",
"*fpsSolid: true",
"*delay: 5",
"*maxlines: 1000",
#ifdef HAVE_MOBILE
"*ignoreRotation: True",
#endif
0
};
static XrmOptionDescRec pedal_options [] = {
{ "-delay", ".delay", XrmoptionSepArg, 0 },
{ "-maxlines", ".maxlines", XrmoptionSepArg, 0 },
{ "-foreground", ".foreground", XrmoptionSepArg, 0 },
{ "-background", ".background", XrmoptionSepArg, 0 },
{ 0, 0, 0, 0 }
};
XSCREENSAVER_MODULE ("Pedal", pedal)