/* ccurve, Copyright (c) 1998, 1999
* Rick Campbell <rick@campbellcentral.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.
*
*/
/* Draw self-similar linear fractals including the classic ``C Curve''
*
* 16 Aug 1999 Rick Campbell <rick@campbellcentral.org>
* Eliminated sub-windows-with-backing-store-double-buffering crap in
* favor of drawing the new image in a pixmap and then splatting that on
* the window.
*
* 19 Dec 1998 Rick Campbell <rick@campbellcentral.org>
* Original version.
*/
#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "screenhack.h"
#include "colors.h"
#include "erase.h"
#define SQRT3 (1.73205080756887729353)
#define MAXIMUM_COLOR_COUNT (256)
#define EPSILON (1e-5)
typedef struct Position_struct
{
double x;
double y;
}
Position;
typedef struct Segment_struct
{
double angle;
double length;
}
Segment;
struct state {
Display *dpy;
Window window;
int color_count;
int color_index;
Colormap color_map;
XColor colors [MAXIMUM_COLOR_COUNT];
int line_count;
int maximum_lines;
double plot_maximum_x;
double plot_maximum_y;
double plot_minimum_x;
double plot_minimum_y;
int total_lines;
unsigned long int background;
GC context;
Pixmap pixmap;
int width;
int height;
float delay;
float delay2;
int draw_index;
int draw_iterations;
double draw_maximum_x;
double draw_maximum_y;
double draw_minimum_x;
double draw_minimum_y;
int draw_segment_count;
Segment* draw_segments;
double draw_x1;
double draw_y1;
double draw_x2;
double draw_y2;
};
/* normalize alters the sequence to go from (0,0) to (1,0) */
static void
normalized_plot (int segment_count,
Segment* segments,
Position* points)
{
double angle = 0.0;
double cosine = 0.0;
int index = 0;
double length = 0.0;
double sine = 0.0;
double x = 0.0;
double y = 0.0;
for (index = 0; index < segment_count; ++index)
{
Segment* segment = segments + index;
double length = segment->length;
double angle = segment->angle;
x += length * cos (angle);
y += length * sin (angle);
points [index].x = x;
points [index].y = y;
}
angle = -(atan2 (y, x));
cosine = cos (angle);
sine = sin (angle);
length = sqrt ((x * x) + (y * y));
/* rotate and scale */
for (index = 0; index < segment_count; ++index)
{
double temp_x = points [index].x;
double temp_y = points [index].y;
points [index].x = ((temp_x * cosine) + (temp_y * (-sine))) / length;
points [index].y = ((temp_x * sine) + (temp_y * cosine)) / length;
}
}
static void
copy_points (int segment_count,
Position* source,
Position* target)
{
int index = 0;
for (index = 0; index < segment_count; ++index)
{
target [index] = source [index];
}
}
static void
realign (double x1,
double y1,
double x2,
double y2,
int segment_count,
Position* points)
{
double angle = 0.0;
double cosine = 0.0;
double delta_x = 0.0;
double delta_y = 0.0;
int index = 0;
double length = 0.0;
double sine = 0.0;
delta_x = x2 - x1;
delta_y = y2 - y1;
angle = atan2 (delta_y, delta_x);
cosine = cos (angle);
sine = sin (angle);
length = sqrt ((delta_x * delta_x) + (delta_y * delta_y));
/* rotate, scale, then shift */
for (index = 0; index < segment_count; ++index)
{
double temp_x = points [index].x;
double temp_y = points [index].y;
points [index].x
= (length * ((temp_x * cosine) + (temp_y * (-sine)))) + x1;
points [index].y
= (length * ((temp_x * sine) + (temp_y * cosine))) + y1;
}
}
static Bool
self_similar_normalized (struct state *st,
int iterations,
double x1,
double y1,
double x2,
double y2,
double maximum_x,
double maximum_y,
double minimum_x,
double minimum_y,
int segment_count,
Position* points)
{
if (iterations == 0)
{
double delta_x = maximum_x - minimum_x;
double delta_y = maximum_y - minimum_y;
st->color_index = (int)(((double)(st->line_count * st->color_count))
/ ((double)st->total_lines));
++st->line_count;
XSetForeground (st->dpy, st->context, st->colors [st->color_index].pixel);
if (st->plot_maximum_x < x1) st->plot_maximum_x = x1;
if (st->plot_maximum_x < x2) st->plot_maximum_x = x2;
if (st->plot_maximum_y < y1) st->plot_maximum_y = y1;
if (st->plot_maximum_y < y2) st->plot_maximum_y = y2;
if (st->plot_minimum_x > x1) st->plot_minimum_x = x1;
if (st->plot_minimum_x > x2) st->plot_minimum_x = x2;
if (st->plot_minimum_y > y1) st->plot_minimum_y = y1;
if (st->plot_minimum_y > y2) st->plot_minimum_y = y2;
XDrawLine (st->dpy, st->pixmap, st->context,
(int)(((x1 - minimum_x) / delta_x) * st->width),
(int)(((maximum_y - y1) / delta_y) * st->height),
(int)(((x2 - minimum_x) / delta_x) * st->width),
(int)(((maximum_y - y2) / delta_y) * st->height));
}
else
{
int index = 0;
double next_x = 0.0;
double next_y = 0.0;
Position* replacement = (Position*)NULL;
double x = 0.0;
double y = 0.0;
replacement = (Position*)(malloc (segment_count * sizeof (Segment)));
copy_points (segment_count, points, replacement);
assert (fabs ((replacement [segment_count - 1].x) - 1.0) < EPSILON);
assert (fabs (replacement [segment_count - 1].y) < EPSILON);
realign (x1, y1, x2, y2, segment_count, replacement);
/* jwz: I don't understand what these assertions are supposed to
be detecting, but let's just bail on the fractal instead of
crashing. */
/* assert (fabs (x2 - (replacement [segment_count - 1].x)) < EPSILON);
assert (fabs (y2 - (replacement [segment_count - 1].y)) < EPSILON);*/
if (fabs (x2 - (replacement [segment_count - 1].x)) >= EPSILON ||
fabs (y2 - (replacement [segment_count - 1].y)) >= EPSILON) {
free (replacement);
return False;
}
x = x1;
y = y1;
for (index = 0; index < segment_count; ++index)
{
next_x = replacement [index].x;
next_y = replacement [index].y;
if (!self_similar_normalized (st,
iterations - 1, x, y, next_x, next_y,
maximum_x, maximum_y,
minimum_x, minimum_y,
segment_count, points)) {
free(replacement);
return False;
}
x = next_x;
y = next_y;
}
free(replacement);
}
return True;
}
static void
self_similar (struct state *st,
Pixmap pixmap,
GC context,
int width,
int height,
int iterations,
double x1,
double y1,
double x2,
double y2,
double maximum_x,
double maximum_y,
double minimum_x,
double minimum_y,
int segment_count,
Segment* segments)
{
Position* points = (Position*)NULL;
points = (Position*)(malloc (segment_count * sizeof (Position)));
normalized_plot (segment_count, segments, points);
assert (fabs ((points [segment_count - 1].x) - 1.0) < EPSILON);
assert (fabs (points [segment_count - 1].y) < EPSILON);
self_similar_normalized (st, iterations,
x1, y1, x2, y2,
maximum_x, maximum_y,
minimum_x, minimum_y,
segment_count, points);
free((void*)points);
}
static
double
random_double (double base,
double limit,
double epsilon)
{
double range = 0.0;
unsigned int steps = 0;
assert (base < limit);
assert (epsilon > 0.0);
range = limit - base;
steps = (unsigned int)(floor (range / epsilon));
return base + ((random () % steps) * epsilon);
}
static void
select_2_pattern (Segment* segments)
{
if ((random () % 2) == 0)
{
if ((random () % 2) == 0)
{
segments [0].angle = -M_PI_4;
segments [0].length = M_SQRT2;
segments [1].angle = M_PI_4;
segments [1].length = M_SQRT2;
}
else
{
segments [0].angle = M_PI_4;
segments [0].length = M_SQRT2;
segments [1].angle = -M_PI_4;
segments [1].length = M_SQRT2;
}
}
else
{
segments [0].angle
= random_double (M_PI / 6.0, M_PI / 3.0, M_PI / 180.0);
segments [0].length = random_double (0.25, 0.67, 0.001);
if ((random () % 2) == 0)
{
segments [1].angle = -(segments [0].angle);
segments [1].length = segments [0].length;
}
else
{
segments [1].angle = random_double ((-M_PI) / 3.0,
(-M_PI) / 6.0,
M_PI / 180.0);
segments [1].length = random_double (0.25, 0.67, 0.001);
}
}
}
static void
select_3_pattern (Segment* segments)
{
switch (random () % 5)
{
case 0:
if ((random () % 2) == 0)
{
segments [0].angle = M_PI_4;
segments [0].length = M_SQRT2 / 4.0;
segments [1].angle = -M_PI_4;
segments [1].length = M_SQRT2 / 2.0;
segments [2].angle = M_PI_4;
segments [2].length = M_SQRT2 / 4.0;
}
else
{
segments [0].angle = -M_PI_4;
segments [0].length = M_SQRT2 / 4.0;
segments [1].angle = M_PI_4;
segments [1].length = M_SQRT2 / 2.0;
segments [2].angle = -M_PI_4;
segments [2].length = M_SQRT2 / 4.0;
}
break;
case 1:
if ((random () % 2) == 0)
{
segments [0].angle = M_PI / 6.0;
segments [0].length = 1.0;
segments [1].angle = -M_PI_2;
segments [1].length = 1.0;
segments [2].angle = M_PI / 6.0;
segments [2].length = 1.0;
}
else
{
segments [0].angle = -M_PI / 6.0;
segments [0].length = 1.0;
segments [1].angle = M_PI_2;
segments [1].length = 1.0;
segments [2].angle = -M_PI / 6.0;
segments [2].length = 1.0;
}
break;
case 2:
case 3:
case 4:
segments [0].angle
= random_double (M_PI / 6.0, M_PI / 3.0, M_PI / 180.0);
segments [0].length = random_double (0.25, 0.67, 0.001);
segments [1].angle
= random_double (-M_PI / 3.0, -M_PI / 6.0, M_PI / 180.0);
segments [1].length = random_double (0.25, 0.67, 0.001);
if ((random () % 3) == 0)
{
if ((random () % 2) == 0)
{
segments [2].angle = segments [0].angle;
}
else
{
segments [2].angle = -(segments [0].angle);
}
segments [2].length = segments [0].length;
}
else
{
segments [2].angle
= random_double (-M_PI / 3.0, -M_PI / 6.0, M_PI / 180.0);
segments [2].length = random_double (0.25, 0.67, 0.001);
}
break;
}
}
static void
select_4_pattern (Segment* segments)
{
switch (random () % 9)
{
case 0:
if ((random () % 2) == 0)
{
double length = random_double (0.25, 0.50, 0.001);
segments [0].angle = 0.0;
segments [0].length = 0.5;
segments [1].angle = M_PI_2;
segments [1].length = length;
segments [2].angle = -M_PI_2;
segments [2].length = length;
segments [3].angle = 0.0;
segments [3].length = 0.5;
}
else
{
double length = random_double (0.25, 0.50, 0.001);
segments [0].angle = 0.0;
segments [0].length = 0.5;
segments [1].angle = -M_PI_2;
segments [1].length = length;
segments [2].angle = M_PI_2;
segments [2].length = length;
segments [3].angle = 0.0;
segments [3].length = 0.5;
}
break;
case 1:
if ((random () % 2) == 0)
{
segments [0].angle = 0.0;
segments [0].length = 0.5;
segments [1].angle = M_PI_2;
segments [1].length = 0.45;
segments [2].angle = -M_PI_2;
segments [2].length = 0.45;
segments [3].angle = 0.0;
segments [3].length = 0.5;
}
else
{
segments [0].angle = 0.0;
segments [0].length = 0.5;
segments [1].angle = -M_PI_2;
segments [1].length = 0.45;
segments [2].angle = M_PI_2;
segments [2].length = 0.45;
segments [3].angle = 0.0;
segments [3].length = 0.5;
}
break;
case 2:
if ((random () % 2) == 0)
{
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = (5.0 * M_PI) / 12.0;
segments [1].length = 1.2;
segments [2].angle = (-5.0 * M_PI) / 12.0;
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
else
{
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = (-5.0 * M_PI) / 12.0;
segments [1].length = 1.2;
segments [2].angle = (5.0 * M_PI) / 12.0;
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
break;
case 3:
if ((random () % 2) == 0)
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = angle;
segments [1].length = 1.2;
segments [2].angle = (-angle);
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
else
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = (-angle);
segments [1].length = 1.2;
segments [2].angle = angle;
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
break;
case 4:
if ((random () % 2) == 0)
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = angle;
segments [1].length = 1.2;
segments [2].angle = (-angle);
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
else
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = (-angle);
segments [1].length = 1.2;
segments [2].angle = angle;
segments [2].length = 1.2;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
break;
case 5:
if ((random () % 2) == 0)
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
double length = random_double (0.25, 0.50, 0.001);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = angle;
segments [1].length = length;
segments [2].angle = (-angle);
segments [2].length = length;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
else
{
double angle
= random_double (M_PI / 4.0,
M_PI_2,
M_PI / 180.0);
double length = random_double (0.25, 0.50, 0.001);
segments [0].angle = 0.0;
segments [0].length = 1.0;
segments [1].angle = (-angle);
segments [1].length = length;
segments [2].angle = angle;
segments [2].length = length;
segments [3].angle = 0.0;
segments [3].length = 1.0;
}
break;
case 6:
case 7:
case 8:
segments [0].angle
= random_double (M_PI / 12.0, (11.0 * M_PI) / 12.0, 0.001);
segments [0].length = random_double (0.25, 0.50, 0.001);
segments [1].angle
= random_double (M_PI / 12.0, (11.0 * M_PI) / 12.0, 0.001);
segments [1].length = random_double (0.25, 0.50, 0.001);
if ((random () % 3) == 0)
{
segments [2].angle
= random_double (M_PI / 12.0, (11.0 * M_PI) / 12.0, 0.001);
segments [2].length = random_double (0.25, 0.50, 0.001);
segments [3].angle
= random_double (M_PI / 12.0, (11.0 * M_PI) / 12.0, 0.001);
segments [3].length = random_double (0.25, 0.50, 0.001);
}
else
{
if ((random () % 2) == 0)
{
segments [2].angle = -(segments [1].angle);
segments [2].length = segments [1].length;
segments [3].angle = -(segments [0].angle);
segments [3].length = segments [0].length;
}
else
{
segments [2].angle = segments [1].angle;
segments [2].length = segments [1].length;
segments [3].angle = segments [0].angle;
segments [3].length = segments [0].length;
}
}
break;
}
}
static void
select_pattern (int segment_count,
Segment* segments)
{
switch (segment_count)
{
case 2:
select_2_pattern (segments);
break;
case 3:
select_3_pattern (segments);
break;
case 4:
select_4_pattern (segments);
break;
default:
fprintf (stderr, "\nBad segment count, must be 2, 3, or 4.\n");
exit (1);
}
}
#define Y_START (0.5)
static void *
ccurve_init (Display *dpy, Window window)
{
struct state *st = (struct state *) calloc (1, sizeof(*st));
unsigned long int black = 0;
int depth = 0;
XWindowAttributes hack_attributes;
XGCValues values;
unsigned long int white = 0;
st->dpy = dpy;
st->window = window;
st->delay = get_float_resource (st->dpy, "delay", "Integer");
st->delay2 = get_float_resource (st->dpy, "pause", "Integer");
st->maximum_lines = get_integer_resource (st->dpy, "limit", "Integer");
black = BlackPixel (st->dpy, DefaultScreen (st->dpy));
white = WhitePixel (st->dpy, DefaultScreen (st->dpy));
st->background = black;
XGetWindowAttributes (st->dpy, st->window, &hack_attributes);
st->width = hack_attributes.width;
st->height = hack_attributes.height;
depth = hack_attributes.depth;
st->color_map = hack_attributes.colormap;
st->pixmap = XCreatePixmap (st->dpy, st->window, st->width, st->height, depth);
values.foreground = white;
values.background = black;
st->context = XCreateGC (st->dpy, st->window, GCForeground | GCBackground,
&values);
st->color_count = MAXIMUM_COLOR_COUNT;
make_color_loop (hack_attributes.screen, hack_attributes.visual,
st->color_map,
0, 1, 1,
120, 1, 1,
240, 1, 1,
st->colors, &st->color_count, True, False);
if (st->color_count <= 0)
{
st->color_count = 1;
st->colors [0].red = st->colors [0].green = st->colors [0].blue = 0xFFFF;
XAllocColor (st->dpy, st->color_map, &st->colors [0]);
}
st->draw_maximum_x = 1.20;
st->draw_maximum_y = 0.525;
st->draw_minimum_x = -0.20;
st->draw_minimum_y = -0.525;
st->draw_x2 = 1.0;
return st;
}
static unsigned long
ccurve_draw (Display *dpy, Window window, void *closure)
{
struct state *st = (struct state *) closure;
static const int lengths [] = { 4, 4, 4, 4, 4, 3, 3, 3, 2 };
if (st->draw_index == 0)
{
st->draw_segment_count
= lengths [random () % (sizeof (lengths) / sizeof (int))];
st->draw_segments
= (Segment*)(malloc ((st->draw_segment_count) * sizeof (Segment)));
select_pattern (st->draw_segment_count, st->draw_segments);
st->draw_iterations = floor (log (st->maximum_lines)
/ log (((double)(st->draw_segment_count))));
if ((random () % 3) != 0)
{
double factor = 0.45;
st->draw_x1 += random_double (-factor, factor, 0.001);
st->draw_y1 += random_double (-factor, factor, 0.001);
st->draw_x2 += random_double (-factor, factor, 0.001);
st->draw_y2 += random_double (-factor, factor, 0.001);
}
/* background = (random () % 2) ? black : white; */
}
/* for (st->draw_index = 0; st->draw_index < st->draw_iterations; ++st->draw_index) */
{
double delta_x = 0.0;
double delta_y = 0.0;
XSetForeground (st->dpy, st->context, st->background);
XFillRectangle (st->dpy, st->pixmap, st->context, 0, 0, st->width, st->height);
st->line_count = 0;
st->total_lines = (int)(pow ((double)(st->draw_segment_count),
(double)st->draw_index));
st->plot_maximum_x = -1000.00;
st->plot_maximum_y = -1000.00;
st->plot_minimum_x = 1000.00;
st->plot_minimum_y = 1000.00;
self_similar (st, st->pixmap, st->context, st->width, st->height, st->draw_index,
st->draw_x1, st->draw_y1, st->draw_x2, st->draw_y2,
st->draw_maximum_x,
st->draw_maximum_y,
st->draw_minimum_x,
st->draw_minimum_y,
st->draw_segment_count, st->draw_segments);
delta_x = st->plot_maximum_x - st->plot_minimum_x;
delta_y = st->plot_maximum_y - st->plot_minimum_y;
st->draw_maximum_x = st->plot_maximum_x + (delta_x * 0.2);
st->draw_maximum_y = st->plot_maximum_y + (delta_y * 0.2);
st->draw_minimum_x = st->plot_minimum_x - (delta_x * 0.2);
st->draw_minimum_y = st->plot_minimum_y - (delta_y * 0.2);
delta_x = st->draw_maximum_x - st->draw_minimum_x;
delta_y = st->draw_maximum_y - st->draw_minimum_y;
if ((delta_y / delta_x) > (((double)st->height) / ((double)st->width)))
{
double new_delta_x
= (delta_y * ((double)st->width)) / ((double)st->height);
st->draw_minimum_x -= (new_delta_x - delta_x) / 2.0;
st->draw_maximum_x += (new_delta_x - delta_x) / 2.0;
}
else
{
double new_delta_y
= (delta_x * ((double)st->height)) / ((double)st->width);
st->draw_minimum_y -= (new_delta_y - delta_y) / 2.0;
st->draw_maximum_y += (new_delta_y - delta_y) / 2.0;
}
XCopyArea (st->dpy, st->pixmap, st->window, st->context, 0, 0, st->width, st->height,
0, 0);
}
st->draw_index++;
/* #### mi->recursion_depth = st->draw_index; */
if (st->draw_index >= st->draw_iterations)
{
st->draw_index = 0;
free((void*)st->draw_segments);
st->draw_segments = 0;
return (int) (1000000 * st->delay);
}
else
return (int) (1000000 * st->delay2);
}
static void
ccurve_reshape (Display *dpy, Window window, void *closure,
unsigned int w, unsigned int h)
{
struct state *st = (struct state *) closure;
XWindowAttributes xgwa;
st->width = w;
st->height = h;
XGetWindowAttributes (st->dpy, st->window, &xgwa);
XFreePixmap (dpy, st->pixmap);
st->pixmap = XCreatePixmap (st->dpy, st->window, st->width, st->height,
xgwa.depth);
}
static Bool
ccurve_event (Display *dpy, Window window, void *closure, XEvent *event)
{
struct state *st = (struct state *) closure;
if (screenhack_event_helper (dpy, window, event))
{
st->draw_index = 0;
return True;
}
return False;
}
static void
ccurve_free (Display *dpy, Window window, void *closure)
{
}
static const char *ccurve_defaults [] =
{
".background: black",
".foreground: white",
".delay: 3",
".pause: 0.4",
".limit: 200000",
#ifdef HAVE_MOBILE
"*ignoreRotation: True",
#endif
0
};
static XrmOptionDescRec ccurve_options [] =
{
{ "-delay", ".delay", XrmoptionSepArg, 0 },
{ "-pause", ".pause", XrmoptionSepArg, 0 },
{ "-limit", ".limit", XrmoptionSepArg, 0 },
{ 0, 0, 0, 0 }
};
XSCREENSAVER_MODULE ("CCurve", ccurve)