/* -*- Mode: C; tab-width: 4 -*- */
/* kaleid --- Brewster's Kaleidoscope */

#if 0
static const char sccsid[] = "@(#)kaleid.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 *
 *  kaleid.c - Brewster's Kaleidoscope  (Sir David Brewster invented the
 *     kaleidoscope in 1816 and patented it in 1817.)
 *
 *  Copyright (c) 1998 by Robert Adam, II  <raii@comm.net>
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 1998: Written
 *
 *
 *  -batchcount n     number of pens [default 4]
 *
 *  -cycle n          percentage of black in the pattern (0%-95%)
 *
 *  -size n          symmetry mode [default -9]
 *                         <0     = random from 0 to -number
 *                         >0     = mirrored                  (-alternate)
 *                                  rotated                   (+alternate)
 *
 *
 *  -/+disconnected   turn on/off disconnected pen movement
 *
 *  -/+serial         turn on/off sequential allocation of colors
 *
 *  -/+alternate      turn on/off alternate display mode
 *
 *  -/+spiral         turn on/off spiral mode
 *
 *  -/+spots          turn on/off spots mode
 *
 *  -/+quad           turn on/off quad mirrored/rotated mode similar
 *                       to size 4, works with alternate
 *
 *  -/+oct            turn on/off oct mirrored/rotated moded similar
 *                       to size 8, works with alternate
 *
 *  -/+linear         select Cartesian/Polar coordinate mode. Cartesian
 *                       uses straight lines similar to -oct and -quad
 *                       mode instead of the curved lines of Polar mode.
 *                       [default off]
 *
 */

#ifdef STANDALONE
#define MODE_kaleid
#define DEFAULTS "*delay: 80000 \n" \
	"*count: 4 \n" \
	"*cycles: 40 \n" \
	"*size: -9 \n" \
	"*ncolors: 200 \n" \
	"*fullrandom: False \n" \

# define free_kaleid 0
# define reshape_kaleid 0
# define kaleid_handle_event 0
#define UNIFORM_COLORS
#define BRIGHT_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#endif /* STANDALONE */

#ifdef MODE_kaleid

#include <math.h>

#define DEF_DISCONNECTED  "True"
#define DEF_SERIAL        "False"
#define DEF_ALTERNATE     "False"
#define DEF_QUAD          "False"
#define DEF_OCT           "False"
#define DEF_LINEAR        "False"
#define DEF_SPIRAL        "False"
#define DEF_SPOTS         "False"

static Bool Disconnected;
static Bool Serial;
static Bool Alternate;
static Bool Quad;
static Bool Oct;
static Bool Linear;
static Bool Spiral;
static Bool Spots;

static XrmOptionDescRec opts[] =
{
   {(char *) "-disconnected", (char *) ".kaleid.disconnected", XrmoptionNoArg, (caddr_t) "on"},
  {(char *) "+disconnected", (char *) ".kaleid.disconnected", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-serial", (char *) ".kaleid.serial", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+serial", (char *) ".kaleid.serial", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-alternate", (char *) ".kaleid.alternate", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+alternate", (char *) ".kaleid.alternate", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-spiral", (char *) ".kaleid.spiral", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+spiral", (char *) ".kaleid.spiral", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-spots", (char *) ".kaleid.spots", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+spots", (char *) ".kaleid.spots", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-quad", (char *) ".kaleid.quad", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+quad", (char *) ".kaleid.quad", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-oct", (char *) ".kaleid.oct", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+oct", (char *) ".kaleid.oct", XrmoptionNoArg, (caddr_t) "off"},
	{(char *) "-linear", (char *) ".kaleid.linear", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+linear", (char *) ".kaleid.linear", XrmoptionNoArg, (caddr_t) "off"}
};

static argtype vars[] =
{
	{(void *) & Disconnected, (char *) "disconnected", (char *) "Disconnected", (char *) DEF_DISCONNECTED, t_Bool},
	{(void *) & Serial, (char *) "serial", (char *) "Serial", (char *) DEF_SERIAL, t_Bool},
	{(void *) & Alternate, (char *) "alternate", (char *) "Alternate", (char *) DEF_ALTERNATE, t_Bool},
	{(void *) & Spiral, (char *) "spiral", (char *) "Spiral", (char *) DEF_SPIRAL, t_Bool},
	{(void *) & Spots, (char *) "spots", (char *) "Spots", (char *) DEF_SPOTS, t_Bool},
	{(void *) & Quad, (char *) "quad", (char *) "Quad", (char *) DEF_QUAD, t_Bool},
	{(void *) & Oct, (char *) "oct", (char *) "Oct", (char *) DEF_OCT, t_Bool},
	{(void *) & Linear, (char *) "linear", (char *) "Linear", (char *) DEF_LINEAR, t_Bool}
};

static OptionStruct desc[] =
{
	{(char *) "-/+disconnected", (char *) "turn on/off disconnected pen movement"},
	{(char *) "-/+serial", (char *) "turn on/off sequential color selection"},
	{(char *) "-/+alternate", (char *) "turn on/off alternate display mode"},
	{(char *) "-/+spiral", (char *) "turn on/off angular bounding mode"},
	{(char *) "-/+spots", (char *) "turn on/off circle mode"},
	{(char *) "-/+quad", (char *) "turn on/off quad mirrored display mode"},
	{(char *) "-/+oct", (char *) "turn on/off oct mirrored display mode"},
	{(char *) "-/+linear", (char *) "select Cartesian/Polar coordinate display mode"}
};

ENTRYPOINT ModeSpecOpt kaleid_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   kaleid_description =
{
	"kaleid", "init_kaleid", "draw_kaleid", "release_kaleid",
	"refresh_kaleid", "init_kaleid", (char *) NULL, &kaleid_opts,
	80000, 4, 40, -9, 64, 0.6, "",
	"Shows Brewster's Kaleidoscope", 0, NULL
};

#endif

#define INTRAND(min,max) (NRAND(((max+1)-(min)))+(min))

#define MINPENS 1
#define MINSIZE 1
#define QUAD (-4)
#define OCT (-8)

#define SpotsMult           3

#define MinVelocity         6
#define MaxVelocity         4

#define MinRadialVelocity   6
#define MaxRadialVelocity   30

#define MinAngularVelocity  5
#define MaxAngularVelocity  3

#define WidthPercent  25
#define ChangeChance  2

#define ToRadians 0.017453293
#define ToDegrees 57.29577951
#define PolarToCartX(r, t) ((r) * cos((t) * ToRadians))
#define PolarToCartY(r, t) ((r) * sin((t) * ToRadians))
#define CartToRadius(x, y) (sqrt(((x)*(x)) + ((y)*(y))))
#define CartToAngle(x,y)   ((((x) == 0.0) && ((y) == 0.0)) ? 0.0 : (atan2((y),(x)) * ToDegrees))

typedef struct {
	double      cx;
	double      cy;
	double      ox;
	double      oy;
	double      xv;
	double      yv;
	int         curlwidth;
	int         pix;
	int         White;
	Bool        RadiusOut;
	Bool        AngleOut;
	Bool        DeferredChange;
} penstruct;

typedef struct {
	penstruct  *pen;

	int         PercentBlack;
	int         PenCount;
	int         maxlwidth;
	double      width, widthby2;
	double      height, heightby2;
	double      radius;
	double      slice;
	int         bouncetype;
	int         modetype;
	Bool        alternate, disconnected, serial, linear, spiral, spots;
} kaleidstruct;

static kaleidstruct *kaleids = (kaleidstruct *) NULL;

/*-
 *
 */
static void
QuadMirrored(
		    kaleidstruct * kp,
		    int pn,
		    XSegment segs[4]
)
{
	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	if (kp->pen[pn].cx < 0.0) {
		kp->pen[pn].cx = -kp->pen[pn].cx;
		kp->pen[pn].xv = -kp->pen[pn].xv;
	} else if (kp->pen[pn].cx >= kp->widthby2) {
		kp->pen[pn].cx = (kp->widthby2 - 1.0)
			- (kp->pen[pn].cx - kp->widthby2);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	}
	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
	if (kp->pen[pn].cy < 0.0) {
		kp->pen[pn].cy = -kp->pen[pn].cy;
		kp->pen[pn].yv = -kp->pen[pn].yv;
	} else if (kp->pen[pn].cy >= kp->heightby2) {
		kp->pen[pn].cy = (kp->heightby2 - 1.0)
			- (kp->pen[pn].cy - kp->heightby2);
		kp->pen[pn].yv = -kp->pen[pn].yv;
	}
	segs[0].x1 = (int) kp->pen[pn].ox;
	segs[0].y1 = (int) kp->pen[pn].oy;
	segs[0].x2 = (int) kp->pen[pn].cx;
	segs[0].y2 = (int) kp->pen[pn].cy;

	segs[1].x1 = (int) (kp->width - kp->pen[pn].ox);
	segs[1].y1 = (int) kp->pen[pn].oy;
	segs[1].x2 = (int) (kp->width - kp->pen[pn].cx);
	segs[1].y2 = (int) kp->pen[pn].cy;

	segs[2].x1 = (int) (kp->width - kp->pen[pn].ox);
	segs[2].y1 = (int) (kp->height - kp->pen[pn].oy);
	segs[2].x2 = (int) (kp->width - kp->pen[pn].cx);
	segs[2].y2 = (int) (kp->height - kp->pen[pn].cy);

	segs[3].x1 = (int) kp->pen[pn].ox;
	segs[3].y1 = (int) (kp->height - kp->pen[pn].oy);
	segs[3].x2 = (int) kp->pen[pn].cx;
	segs[3].y2 = (int) (kp->height - kp->pen[pn].cy);

}

/*-
 *
 */
static void
QuadRotated(
		   kaleidstruct * kp,
		   int pn,
		   XSegment segs[4]
)
{
	double      oxscaled2y;
	double      oyscaled2x;
	double      cxscaled2y;
	double      cyscaled2x;

	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	if (kp->pen[pn].cx < 0.0) {
		kp->pen[pn].cx = -kp->pen[pn].cx;
		kp->pen[pn].xv = -kp->pen[pn].xv;
	} else if (kp->pen[pn].cx >= kp->widthby2) {
		kp->pen[pn].cx = (kp->widthby2 - 1.0)
			- (kp->pen[pn].cx - kp->widthby2);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	}
	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
	if (kp->pen[pn].cy < 0.0) {
		kp->pen[pn].cy = -kp->pen[pn].cy;
		kp->pen[pn].yv = -kp->pen[pn].yv;
	} else if (kp->pen[pn].cy >= kp->heightby2) {
		kp->pen[pn].cy = (kp->heightby2 - 1.0)
			- (kp->pen[pn].cy - kp->heightby2);
		kp->pen[pn].yv = -kp->pen[pn].yv;
	}
	segs[0].x1 = (int) kp->pen[pn].ox;
	segs[0].y1 = (int) kp->pen[pn].oy;
	segs[0].x2 = (int) kp->pen[pn].cx;
	segs[0].y2 = (int) kp->pen[pn].cy;

	oxscaled2y = ((kp->pen[pn].ox * kp->heightby2) / kp->widthby2);
	oyscaled2x = ((kp->pen[pn].oy * kp->widthby2) / kp->heightby2);
	cxscaled2y = ((kp->pen[pn].cx * kp->heightby2) / kp->widthby2);
	cyscaled2x = ((kp->pen[pn].cy * kp->widthby2) / kp->heightby2);

	segs[1].x1 = (int) (kp->width - oyscaled2x);
	segs[1].y1 = (int) oxscaled2y;
	segs[1].x2 = (int) (kp->width - cyscaled2x);
	segs[1].y2 = (int) cxscaled2y;

	segs[2].x1 = (int) (kp->width - kp->pen[pn].ox);
	segs[2].y1 = (int) (kp->height - kp->pen[pn].oy);
	segs[2].x2 = (int) (kp->width - kp->pen[pn].cx);
	segs[2].y2 = (int) (kp->height - kp->pen[pn].cy);

	segs[3].x1 = (int) oyscaled2x;
	segs[3].y1 = (int) (kp->height - oxscaled2y);
	segs[3].x2 = (int) cyscaled2x;
	segs[3].y2 = (int) (kp->height - cxscaled2y);

}

/*-
 *
 */
static void
GeneralPolarMoveAndBounce(
				 kaleidstruct * kp,
				 int pn
)
{
	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	if (kp->pen[pn].cx < 0.0) {
	    kp->pen[pn].cx = -kp->pen[pn].cx;
		kp->pen[pn].xv = -kp->pen[pn].xv;
	} else if (kp->pen[pn].cx >= kp->radius) {
	    kp->pen[pn].cx = (kp->radius - 1.0)
		    - (kp->pen[pn].cx - kp->radius);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	}
	switch (kp->bouncetype) {
		case 0:
			{
				kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
				break;
			}
		case 1:
			{
				kp->pen[pn].cy = kp->pen[pn].cy
					+ ((kp->pen[pn].yv * kp->pen[pn].cx) / kp->radius);
				break;
			}
		case 2:
			{
				kp->pen[pn].cy = kp->pen[pn].cy
					+ ((kp->pen[pn].yv * (kp->radius - kp->pen[pn].cx)) / kp->radius);
				break;
			}
		default:
			{
				kp->pen[pn].cy = kp->slice / 2.0;
				kp->pen[pn].cx = kp->radius / 2.0;
				break;
			}
	}

	if (kp->pen[pn].cy < 0) {
	    if (kp->spiral) {
		    kp->pen[pn].RadiusOut = False;
		    kp->pen[pn].cy = kp->pen[pn].cy + 360.0;
		} else {
		    kp->pen[pn].RadiusOut = True;
			if (kp->pen[pn].oy >= 0) {
			    kp->pen[pn].yv = -kp->pen[pn].yv;
			}
		}
	} else if (kp->spiral) {
	    if (kp->pen[pn].cy > 360.0) {
		    kp->pen[pn].RadiusOut = False;
			kp->pen[pn].cy = kp->pen[pn].cy - 360.0;
		}
	} else if (kp->pen[pn].cy >= kp->slice) {
	    kp->pen[pn].RadiusOut = True;
		if (kp->pen[pn].oy < kp->slice) {
		    kp->pen[pn].yv = -kp->pen[pn].yv;
		}
	} else {
		kp->pen[pn].RadiusOut = False;
	}

}


/*-
 *
 */
static void
GeneralRotated(
		      kaleidstruct * kp,
		      int pn,
		      XSegment * segs
)
{
	double      Angle;
	int         segnum;


	GeneralPolarMoveAndBounce(kp, pn);

	Angle = 0.0;
	for (segnum = 0; segnum < kp->modetype; segnum += 1) {
	    if (! kp->spots) {
		    segs[segnum].x1 = (int) (PolarToCartX(kp->pen[pn].ox,
								  kp->pen[pn].oy + Angle
					     ) + kp->widthby2
		        );

			segs[segnum].y1 = (int) (PolarToCartY(kp->pen[pn].ox,
								  kp->pen[pn].oy + Angle
						 ) + kp->heightby2
				);
		}
		segs[segnum].x2 = (int) (PolarToCartX(kp->pen[pn].cx,
						      kp->pen[pn].cy + Angle
					 ) + kp->widthby2
			);
		segs[segnum].y2 = (int) (PolarToCartY(kp->pen[pn].cx,
						      kp->pen[pn].cy + Angle
					 ) + kp->heightby2
			);

		if (kp->spots) {
		    segs[segnum].x1 = segs[segnum].x2;
		    segs[segnum].y1 = segs[segnum].y2;
		}

		Angle += kp->slice;
	}
}

/*-
 *
 */
static void
GeneralMirrored(
		       kaleidstruct * kp,
		       int pn,
		       XSegment * segs
)
{
	double      Angle;
	int         segnum;

	GeneralPolarMoveAndBounce(kp, pn);

	Angle = 0.0;
	for (segnum = 0; segnum < kp->modetype; segnum += 2) {
	    if (! kp->spots) {
		    segs[segnum].x1 = (int) (PolarToCartX(kp->pen[pn].ox,
								  kp->pen[pn].oy + Angle
						 ) + kp->widthby2
				);

			segs[segnum].y1 = (int) (PolarToCartY(kp->pen[pn].ox,
								  kp->pen[pn].oy + Angle
						 ) + kp->heightby2
				);
        }

		segs[segnum].x2 = (int) (PolarToCartX(kp->pen[pn].cx,
						      kp->pen[pn].cy + Angle
					 ) + kp->widthby2
			);

		segs[segnum].y2 = (int) (PolarToCartY(kp->pen[pn].cx,
						      kp->pen[pn].cy + Angle
					 ) + kp->heightby2
			);

		if (kp->spots) {
		    segs[segnum].x1 = segs[segnum].x2;
		    segs[segnum].y1 = segs[segnum].y2;
		}

		Angle += (2.0 * kp->slice);
	}

	Angle = 360.0 - kp->slice;
	for (segnum = 1; segnum < kp->modetype; segnum += 2) {
	    if (! kp->spots) {
		    segs[segnum].x1 =
			    (int) (PolarToCartX(kp->pen[pn].ox,
						 (kp->slice - kp->pen[pn].oy) + Angle
					   ) + kp->widthby2
			    );

			segs[segnum].y1 =
			    (int) (PolarToCartY(kp->pen[pn].ox,
					     (kp->slice - kp->pen[pn].oy) + Angle
					   ) + kp->heightby2
				);
		}

		segs[segnum].x2 =
			(int) (PolarToCartX(kp->pen[pn].cx,
					 (kp->slice - kp->pen[pn].cy) + Angle
			       ) + kp->widthby2
			);

		segs[segnum].y2 =
			(int) (PolarToCartY(kp->pen[pn].cx,
					 (kp->slice - kp->pen[pn].cy) + Angle
			       ) + kp->heightby2
			);

		if (kp->spots) {
		    segs[segnum].x1 = segs[segnum].x2;
		    segs[segnum].y1 = segs[segnum].y2;
		}
		Angle -= (2.0 * kp->slice);
	}
}

/*-
 *
 */
static void
OctMirrored(
		   kaleidstruct * kp,
		   int pn,
		   XSegment * segs
)
{
	double      xdiag;
	double      ydiag;
	double      oxscaled2y, cxscaled2y;
	double      oyscaled2x, cyscaled2x;

	/*
	 *  I know that the "bounce" is not really accurate, but I like the way
	 * it looks.
	 */

	xdiag = (kp->widthby2 * kp->pen[pn].oy) / kp->heightby2;

	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	if (kp->pen[pn].cx < xdiag) {
		kp->pen[pn].cx = xdiag + (xdiag - kp->pen[pn].cx);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	} else if (kp->pen[pn].cx >= kp->widthby2) {
		kp->pen[pn].cx = (kp->widthby2 - 1.0)
			- (kp->pen[pn].cx - kp->widthby2);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	}
	ydiag = (kp->heightby2 * kp->pen[pn].cx) / kp->widthby2;

	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
	if (kp->pen[pn].cy < 0.0) {
		kp->pen[pn].cy = -kp->pen[pn].cy;
		kp->pen[pn].yv = -kp->pen[pn].yv;
	} else if (kp->pen[pn].cy > ydiag) {
		kp->pen[pn].cy = ydiag - (kp->pen[pn].cy - ydiag);
		kp->pen[pn].yv = -kp->pen[pn].yv;
	}
	segs[0].x1 = (int) kp->pen[pn].ox;
	segs[0].y1 = (int) kp->pen[pn].oy;
	segs[0].x2 = (int) kp->pen[pn].cx;
	segs[0].y2 = (int) kp->pen[pn].cy;

	segs[1].x1 = (int) (kp->width - kp->pen[pn].ox);
	segs[1].y1 = (int) kp->pen[pn].oy;
	segs[1].x2 = (int) (kp->width - kp->pen[pn].cx);
	segs[1].y2 = (int) kp->pen[pn].cy;

	segs[4].x1 = segs[1].x1;
	segs[4].y1 = (int) kp->height - segs[0].y1;
	segs[4].x2 = segs[1].x2;
	segs[4].y2 = (int) kp->height - segs[0].y2;

	segs[5].x1 = segs[0].x1;
	segs[5].y1 = (int) kp->height - segs[0].y1;
	segs[5].x2 = segs[0].x2;
	segs[5].y2 = (int) kp->height - segs[0].y2;


	oxscaled2y = ((kp->pen[pn].ox * kp->heightby2) / kp->widthby2);
	oyscaled2x = ((kp->pen[pn].oy * kp->widthby2) / kp->heightby2);
	cxscaled2y = ((kp->pen[pn].cx * kp->heightby2) / kp->widthby2);
	cyscaled2x = ((kp->pen[pn].cy * kp->widthby2) / kp->heightby2);


	segs[7].x1 = (int) oyscaled2x;
	segs[7].y1 = (int) oxscaled2y;
	segs[7].x2 = (int) cyscaled2x;
	segs[7].y2 = (int) cxscaled2y;


	segs[2].x1 = (int) kp->width - segs[7].x1;
	segs[2].y1 = segs[7].y1;
	segs[2].x2 = (int) kp->width - segs[7].x2;
	segs[2].y2 = segs[7].y2;

	segs[6].x1 = segs[7].x1;
	segs[6].y1 = (int) kp->height - segs[7].y1;
	segs[6].x2 = segs[7].x2;
	segs[6].y2 = (int) kp->height - segs[7].y2;

	segs[3].x1 = (int) kp->width - segs[7].x1;
	segs[3].y1 = segs[6].y1;
	segs[3].x2 = (int) kp->width - segs[7].x2;
	segs[3].y2 = segs[6].y2;

}



/*-
 *
 */
#if 0
static void
OldOctRotated(
		     kaleidstruct * kp,
		     int pn,
		     XSegment * segs
)
{
	double      angle, radius;
	double      oangle, oradius;
	double      rv, av;
	double      perp, para;
	int         i;

	/*
	 *  I know that the "bounce" is not really accurate, but I like the way
	 * it looks.
	 */

	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
	angle = CartToAngle(kp->pen[pn].cx, kp->pen[pn].cy);
	radius = CartToRadius(kp->pen[pn].cx, kp->pen[pn].cy);

	oangle = CartToAngle(kp->pen[pn].ox, kp->pen[pn].oy);
	oradius = CartToRadius(kp->pen[pn].ox, kp->pen[pn].oy);

	if (radius < 0.0) {
		if (kp->pen[pn].xv < 0.0) {
			kp->pen[pn].xv = -kp->pen[pn].xv;
		}
	} else if (radius > kp->radius) {
		if (kp->pen[pn].xv > 0.0) {
			kp->pen[pn].xv = -kp->pen[pn].xv;
		}
	}
	if (angle < 0.0) {
		if (oangle > 0.0) {
			kp->pen[pn].yv = -kp->pen[pn].yv;
		}
	} else if (angle > 45.0) {
		if (oangle < 45.0) {
			rv = CartToRadius(kp->pen[pn].xv, kp->pen[pn].yv);
			av = CartToAngle(kp->pen[pn].xv, kp->pen[pn].yv);

			para = PolarToCartX(rv, av - 45.0);
			perp = PolarToCartY(rv, av - 45.0);

			rv = CartToRadius(para, -perp);
			av = CartToAngle(para, -perp);

			kp->pen[pn].xv = PolarToCartX(rv, av + 45.0);
			kp->pen[pn].yv = PolarToCartY(rv, av + 45.0);
		}
	}
	for (i = 0; i < 8; i += 1) {
		segs[i].x1 = (int) (kp->widthby2 + PolarToCartX(oradius, oangle));
		segs[i].y1 = (int) (kp->heightby2 - PolarToCartY(oradius, oangle));
		segs[i].x2 = (int) (kp->widthby2 + PolarToCartX(radius, angle));
		segs[i].y2 = (int) (kp->heightby2 - PolarToCartY(radius, angle));

		oangle += 45.0;
		angle += 45.0;
	}
}

#endif

/*-
 *
 */
static void
OctRotated(
		  kaleidstruct * kp,
		  int pn,
		  XSegment * segs
)
{
	double      xdiag;
	double      ydiag;
	double      radius, angle;
	double      oradius, oangle;
	double      ox, oy, cx, cy;
	double      offset;
	int         i;

	/*
	 *  I know that the "bounce" is not really accurate, but I like the way
	 * it looks.
	 */

	xdiag = (kp->widthby2 * kp->pen[pn].oy) / kp->heightby2;

	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	if (kp->pen[pn].cx < xdiag) {
		kp->pen[pn].cx = xdiag + (xdiag - kp->pen[pn].cx);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	} else if (kp->pen[pn].cx >= kp->widthby2) {
		kp->pen[pn].cx = (kp->widthby2 - 1.0)
			- (kp->pen[pn].cx - kp->widthby2);
		kp->pen[pn].xv = -kp->pen[pn].xv;
	}
	ydiag = (kp->heightby2 * kp->pen[pn].cx) / kp->widthby2;

	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;
	if (kp->pen[pn].cy < 0.0) {
		kp->pen[pn].cy = -kp->pen[pn].cy;
		kp->pen[pn].yv = -kp->pen[pn].yv;
	} else if (kp->pen[pn].cy > ydiag) {
		kp->pen[pn].cy = ydiag - (kp->pen[pn].cy - ydiag);
		kp->pen[pn].yv = -kp->pen[pn].yv;
	}
	offset = CartToRadius(kp->heightby2, kp->widthby2);
	ox = (kp->pen[pn].ox * offset) / kp->widthby2;
	oy = (kp->pen[pn].oy * offset) / kp->heightby2;
	cx = (kp->pen[pn].cx * offset) / kp->widthby2;
	cy = (kp->pen[pn].cy * offset) / kp->heightby2;

	angle = CartToAngle(cx - offset,
			    offset - cy
		) - 90.0;
	radius = CartToRadius(cx - offset,
			      offset - cy
		);

	oangle = CartToAngle(ox - offset,
			     offset - oy
		) - 90.0;
	oradius = CartToRadius(ox - offset,
			       offset - oy
		);


	for (i = 0; i < 8; i += 1) {
		segs[i].x1 = (int) (kp->widthby2 + PolarToCartX(oradius, oangle));
		segs[i].y1 = (int) (kp->heightby2 - PolarToCartY(oradius, oangle));
		segs[i].x2 = (int) (kp->widthby2 + PolarToCartX(radius, angle));
		segs[i].y2 = (int) (kp->heightby2 - PolarToCartY(radius, angle));

		oangle += 45.0;
		angle += 45.0;
	}

}

/*-
 *
 */
static void
GeneralLinearMoveAndBounce(
				  kaleidstruct * kp,
				  int pn,
				  double *angle,
				  double *radius,
				  double *oangle,
				  double *oradius
)
{
	double      rv, av;
	double      perp, para;

	kp->pen[pn].cx = kp->pen[pn].cx + kp->pen[pn].xv;
	kp->pen[pn].cy = kp->pen[pn].cy + kp->pen[pn].yv;

	*angle = CartToAngle(kp->pen[pn].cx, kp->pen[pn].cy);
	*radius = CartToRadius(kp->pen[pn].cx, kp->pen[pn].cy);

	*oangle = CartToAngle(kp->pen[pn].ox, kp->pen[pn].oy);
	*oradius = CartToRadius(kp->pen[pn].ox, kp->pen[pn].oy);

	if (*radius < 0.0) {
		kp->pen[pn].RadiusOut = True;
		if (*oradius > 0.0) {
			rv = CartToRadius(kp->pen[pn].xv, kp->pen[pn].yv);
			av = CartToAngle(kp->pen[pn].xv, kp->pen[pn].yv);

			para = PolarToCartX(rv, av - (*angle + 90.0));
			perp = PolarToCartY(rv, av - (*angle + 90.0));

			rv = CartToRadius(para, -perp);
			av = CartToAngle(para, -perp);

			kp->pen[pn].xv = PolarToCartX(rv, av + (*angle + 90.0));
			kp->pen[pn].yv = PolarToCartY(rv, av + (*angle + 90.0));
		}
	} else if (*radius > kp->radius) {
		kp->pen[pn].RadiusOut = True;
		if (*oradius < kp->radius) {
			rv = CartToRadius(kp->pen[pn].xv, kp->pen[pn].yv);
			av = CartToAngle(kp->pen[pn].xv, kp->pen[pn].yv);

			para = PolarToCartX(rv, av - (*angle + 90.0));
			perp = PolarToCartY(rv, av - (*angle + 90.0));

			rv = CartToRadius(para, -perp);
			av = CartToAngle(para, -perp);

			kp->pen[pn].xv = PolarToCartX(rv, av + (*angle + 90.0));
			kp->pen[pn].yv = PolarToCartY(rv, av + (*angle + 90.0));
		}
	} else {
		kp->pen[pn].RadiusOut = False;
	}


	if (*angle < 0.0) {
	    if (kp->spiral) {
		    kp->pen[pn].AngleOut = False;
		    *angle = *angle + 360.0;
		} else {
		    kp->pen[pn].AngleOut = True;
			  if (*oangle > 0.0) {
				  rv = CartToRadius(kp->pen[pn].xv, kp->pen[pn].yv);
				  av = CartToAngle(kp->pen[pn].xv, kp->pen[pn].yv);

				  para = PolarToCartX(rv, av);
				  perp = PolarToCartY(rv, av);

				  rv = CartToRadius(para, -perp);
				  av = CartToAngle(para, -perp);

				  kp->pen[pn].xv = PolarToCartX(rv, av);
				  kp->pen[pn].yv = PolarToCartY(rv, av);
			  }
		}
	} else if (kp->spiral) {
	    if (*angle > 360.0) {
	         kp->pen[pn].AngleOut = False;
			 *angle = *angle - 360.0;
	    }
	} else if (*angle > kp->slice) {
		kp->pen[pn].AngleOut = True;
		if (*oangle < kp->slice) {
			rv = CartToRadius(kp->pen[pn].xv, kp->pen[pn].yv);
			av = CartToAngle(kp->pen[pn].xv, kp->pen[pn].yv);

			para = PolarToCartX(rv, av - kp->slice);
			perp = PolarToCartY(rv, av - kp->slice);

			rv = CartToRadius(para, -perp);
			av = CartToAngle(para, -perp);

			kp->pen[pn].xv = PolarToCartX(rv, av + kp->slice);
			kp->pen[pn].yv = PolarToCartY(rv, av + kp->slice);
		}
	} else {
		kp->pen[pn].AngleOut = False;
	}
}

/*-
 *
 */
static void
GeneralLinearRotated(
			    kaleidstruct * kp,
			    int pn,
			    XSegment * segs
)
{
	double      angle, radius;
	double      oangle, oradius;
	int         i;

	GeneralLinearMoveAndBounce(kp, pn, &angle, &radius, &oangle, &oradius);

	for (i = 0; i < kp->modetype; i += 1) {
	    if (! kp->spots) {
		    segs[i].x1 = (int) (kp->widthby2 + PolarToCartX(oradius, oangle));
			segs[i].y1 = (int) (kp->heightby2 - PolarToCartY(oradius, oangle));
		}

		segs[i].x2 = (int) (kp->widthby2 + PolarToCartX(radius, angle));
		segs[i].y2 = (int) (kp->heightby2 - PolarToCartY(radius, angle));

		if (kp->spots) {
		    segs[i].x1 = segs[i].x2;
			segs[i].y1 = segs[i].y2;
		}

		oangle += kp->slice;
		angle += kp->slice;
	}
}



/*-
 *
 */
static void
GeneralLinearMirrored(
			     kaleidstruct * kp,
			     int pn,
			     XSegment * segs
)
{
	double      hangle, angle, radius;
	double      hoangle, oangle, oradius;
	int         i;


	GeneralLinearMoveAndBounce(kp, pn, &angle, &radius, &oangle, &oradius);

	hoangle = oangle;
	hangle = angle;

	for (i = 0; i < kp->modetype; i += 2) {
	    if (! kp->spots) {
		    segs[i].x1 = (int) (kp->widthby2 + PolarToCartX(oradius, oangle));
			segs[i].y1 = (int) (kp->heightby2 - PolarToCartY(oradius, oangle));
		}

		segs[i].x2 = (int) (kp->widthby2 + PolarToCartX(radius, angle));
		segs[i].y2 = (int) (kp->heightby2 - PolarToCartY(radius, angle));

		if (kp->spots) {
		    segs[i].x1 = segs[i].x2;
			segs[i].y1 = segs[i].y2;
		}

		oangle += 2.0 * kp->slice;
		angle += 2.0 * kp->slice;
	}

	oangle = kp->slice * 2.0;
	angle = kp->slice * 2.0;
	for (i = 1; i < kp->modetype; i += 2) {
	    if (! kp->spots) {
		    segs[i].x1 = (int) (kp->widthby2 + PolarToCartX(oradius, oangle - hoangle));
			segs[i].y1 = (int) (kp->heightby2 - PolarToCartY(oradius, oangle - hoangle));
		}

		segs[i].x2 = (int) (kp->widthby2 + PolarToCartX(radius, angle - hangle));
		segs[i].y2 = (int) (kp->heightby2 - PolarToCartY(radius, angle - hangle));

		if (kp->spots) {
		    segs[i].x1 = segs[i].x2;
			segs[i].y1 = segs[i].y2;
		}

		oangle += 2.0 * kp->slice;
		angle += 2.0 * kp->slice;
	}
}

/*-
 *
 */
static void
random_velocity(kaleidstruct * kp, int i)
{
	int tMxRV, tMnRV;
	int tMxAV, tMnAV;
	int tMxV,  tMnV;

	if (kp->spots) {
	    tMxRV = MaxRadialVelocity * SpotsMult;
	    tMnRV = MinRadialVelocity * SpotsMult;
	    tMxAV = MaxAngularVelocity * SpotsMult;
	    tMnAV = MinAngularVelocity * SpotsMult;
        tMxV  = MaxVelocity * SpotsMult;
        tMnV  = MinVelocity * SpotsMult;
	} else {
	    tMxRV = MaxRadialVelocity;
	    tMnRV = MinRadialVelocity;
	    tMxAV = MaxAngularVelocity;
	    tMnAV = MinAngularVelocity;
        tMxV  = MaxVelocity;
        tMnV  = MinVelocity;
	}


	if (kp->modetype > 0) {
		kp->pen[i].xv = INTRAND(-tMxRV, tMxRV);
		if (kp->pen[i].xv > 0.0) {
			kp->pen[i].xv += tMnRV;
		} else if (kp->pen[i].xv < 0.0) {
			kp->pen[i].xv -= tMnRV;
		}
		kp->pen[i].yv = INTRAND(-tMxAV, tMxAV);
		if (kp->pen[i].yv > 0.0) {
			kp->pen[i].yv += tMnAV;
		} else if (kp->pen[i].yv < 0.0) {
			kp->pen[i].yv -= tMnAV;
		}
	} else {
		kp->pen[i].xv = INTRAND(-tMxV, tMxV);
		if (kp->pen[i].xv > 0.0) {
			kp->pen[i].xv += tMnV;
		} else if (kp->pen[i].xv < 0.0) {
			kp->pen[i].xv -= tMnV;
		}
		kp->pen[i].yv = INTRAND(-tMxV, tMxV);
		if (kp->pen[i].yv > 0.0) {
			kp->pen[i].yv += tMnV;
		} else if (kp->pen[i].yv < 0.0) {
			kp->pen[i].yv -= tMnV;
		}
	}
}


static void
random_position(kaleidstruct * kp, int i)
{

	if (kp->modetype >= 0) {
		if (kp->linear) {
			double      radius, angle;

			radius = (double) INTRAND(0, (int) (kp->radius - 1.0));
			angle = (double) INTRAND(0, (int) (kp->slice - 1.0));

			kp->pen[i].cx = PolarToCartX(radius, angle);
			kp->pen[i].cy = PolarToCartY(radius, angle);
		} else {
			kp->pen[i].cx = (double) INTRAND(0, (int) (kp->radius - 1.0));
			kp->pen[i].cy = (double) INTRAND(0, (int) (kp->slice - 1.0));
		}
	} else if (kp->modetype == OCT) {
		double      radius, angle;

		radius = (double) INTRAND(0, (int) (kp->radius - 1.0));
		angle = (double) INTRAND(0, 44);

		kp->pen[i].cx = PolarToCartX(radius, angle);
		kp->pen[i].cy = PolarToCartY(radius, angle);
	} else {
		kp->pen[i].cx = (double) INTRAND(0, (int) (kp->widthby2 - 1.0));
		kp->pen[i].cy = (double) INTRAND(0,
		     (int) ((kp->heightby2 * kp->pen[i].cx) / kp->widthby2));
	}
}

static void
free_kaleid_screen(kaleidstruct *kp)
{
	if (kp == NULL) {
		return;
	}
	if (kp->pen != NULL)
		free(kp->pen);
	kp = NULL;
}

/*-
 *
 */
ENTRYPOINT void
init_kaleid(ModeInfo * mi)
{
	int         i;
	kaleidstruct *kp;

	MI_INIT(mi, kaleids);
	kp = &kaleids[MI_SCREEN(mi)];

	kp->PenCount = MI_COUNT(mi);

	if (MI_IS_FULLRANDOM(mi)) {
		kp->alternate = (Bool) (LRAND() & 1);
		kp->disconnected = (Bool) (LRAND() & 1);
		kp->serial = (Bool) (LRAND() & 1);
		kp->linear = (Bool) (LRAND() & 1);
		kp->spiral = (Bool) (LRAND() & 1);
		kp->spots = (Bool) (LRAND() & 1);
	} else {
		kp->alternate = Alternate;
		kp->disconnected = Disconnected;
		kp->serial = Serial;
		kp->linear = Linear;
		kp->spiral = Spiral;
		kp->spots = Spots;
	}

	if (kp->PenCount < -MINPENS) {
		/* if kp->PenCount is random ... the size can change */
		if (kp->pen != NULL) {
			free(kp->pen);
			kp->pen = (penstruct *) NULL;
		}
		kp->PenCount = NRAND(-kp->PenCount - MINPENS + 1) + MINPENS;
	} else if (kp->PenCount < MINPENS)
		kp->PenCount = MINPENS;

	if (kp->pen == NULL) {
		if ((kp->pen = (penstruct *) malloc(kp->PenCount *
				sizeof (penstruct))) == NULL)
			return;
	}

	if ((MI_SIZE(mi)) > MINSIZE) {
		kp->modetype = (!kp->alternate + 1) * MI_SIZE(mi);
	} else if ((MI_SIZE(mi)) < -MINSIZE) {
		kp->modetype = (!kp->alternate + 1) * (NRAND(-MI_SIZE(mi) + 1) + MINSIZE);
	} else {
		kp->modetype = (!kp->alternate + 1) * MINSIZE;
	}
	if (MI_IS_FULLRANDOM(mi)) {
		int         tmp;

		tmp = NRAND(ABS(MI_SIZE(mi)) + 2);
		if (tmp == 0)
			kp->modetype = OCT;
		else if (tmp == 1)
			kp->modetype = QUAD;
	} else {
		if (Oct)
			kp->modetype = OCT;
		else if (Quad)
			kp->modetype = QUAD;
	}

	kp->PercentBlack = (int) MAX(0, MIN(MI_CYCLES(mi), 95));


	/* set various size parameters */

	kp->width = (double) MI_WIDTH(mi);
	kp->height = (double) MI_HEIGHT(mi);

	if (kp->width < 2.0)
		kp->width = 2.0;
	if (kp->height < 2.0)
		kp->height = 2.0;

	kp->radius = sqrt(((kp->width * kp->width) +
			   (kp->height * kp->height)
			  ) / 4.0
		);


	if (kp->modetype >= 0) {
		kp->bouncetype = INTRAND(0, 2);

		kp->slice = 360.0 / (double) kp->modetype;

		kp->widthby2 = kp->width / 2.0;
		kp->heightby2 = kp->height / 2.0;
	} else {
		kp->widthby2 = kp->width / 2.0;
		kp->heightby2 = kp->height / 2.0;
	}

	/* set the maximum pen width */
	if (kp->modetype >= 0) {
		if ((kp->slice == 360.0) || (kp->slice == 180.0)) {
			kp->maxlwidth = (int) ((((double) MIN(kp->widthby2, kp->heightby2)) *
					     (double) WidthPercent) / 100.0);
		} else {
			kp->maxlwidth = (int) (((sin(kp->slice * ToRadians) *
					  MIN(kp->widthby2, kp->heightby2)) *
					     (double) WidthPercent) / 100.0);
		}
	} else {
		kp->maxlwidth = (int) ((MIN(kp->widthby2,
					    kp->heightby2
					) * (double) WidthPercent
				       ) / 100.0
			);
	}

	if (kp->spots) {
	    kp->maxlwidth = 2 * kp->maxlwidth;
	}

	if (kp->maxlwidth <= 0) {
		kp->maxlwidth = 1;
	}

	for (i = 0; i < kp->PenCount; i += 1) {
		if (MI_NPIXELS(mi) > 2) {
			kp->pen[i].pix = NRAND(MI_NPIXELS(mi));
			kp->pen[i].White = 1;
		} else {
			kp->pen[i].White = 1;
		}

		kp->pen[i].curlwidth = INTRAND(1, kp->maxlwidth);

		kp->pen[i].RadiusOut = False;
		kp->pen[i].AngleOut = False;

		random_position(kp, i);

		kp->pen[i].ox = kp->pen[i].cx;
		kp->pen[i].oy = kp->pen[i].cy;
		kp->pen[i].DeferredChange = False;

		random_velocity(kp, i);
	}

	MI_CLEARWINDOW(mi);

}

/*-
 *
 */
static void
set_pen_attributes(ModeInfo * mi, kaleidstruct * kp, int i)
{
	Display    *display = MI_DISPLAY(mi);
	GC          gc = MI_GC(mi);

	if (kp->pen[i].White) {
		if (MI_NPIXELS(mi) > 2)
			XSetForeground(display, gc, MI_PIXEL(mi, kp->pen[i].pix));
		else
			XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
	} else {
		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
	}
	XSetLineAttributes(display, gc,
		       kp->pen[i].curlwidth, LineSolid, CapRound, JoinRound);
}

/*-
 *
 */
static void
change_pen(ModeInfo * mi, kaleidstruct * kp, int i)
{
	if (INTRAND(0, 100) < kp->PercentBlack) {
		kp->pen[i].White = 0;
	} else {
		kp->pen[i].White = 1;
		if (kp->serial) {
			if (MI_NPIXELS(mi) > 2) {
				if (++kp->pen[i].pix >= MI_NPIXELS(mi))
					kp->pen[i].pix = 0;
			}
		} else {
			if (MI_NPIXELS(mi) > 2) {
				kp->pen[i].pix = NRAND(MI_NPIXELS(mi));
			}
		}
	}

	random_velocity(kp, i);

	kp->pen[i].curlwidth = INTRAND(1, kp->maxlwidth);

	if (kp->modetype >= 0) {
		kp->bouncetype = INTRAND(0, 2);
	}
	if (kp->disconnected) {
		random_position(kp, i);
		kp->pen[i].ox = kp->pen[i].cx;
		kp->pen[i].oy = kp->pen[i].cy;
	}
}

/*-
 *
 */
ENTRYPOINT void
draw_kaleid(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	GC          gc = MI_GC(mi);
	XSegment   *segs;
	int         NumberOfSegments;
	int         i;
	kaleidstruct *kp;

	if (kaleids == NULL)
			return;
	kp = &kaleids[MI_SCREEN(mi)];
	if (kp->pen == NULL)
			return;

	MI_IS_DRAWN(mi) = True;
	if (kp->modetype == QUAD) {
		NumberOfSegments = 4;
	} else if (kp->modetype == OCT) {
		NumberOfSegments = 8;
	} else {		/* if (kp->modetype > 0) */
		NumberOfSegments = kp->modetype;
	}
	if ((segs = (XSegment *) malloc(NumberOfSegments *
			sizeof (XSegment))) == NULL) {
		free(kp->pen);
		kp->pen = (penstruct *) NULL;
		return;
	}

	for (i = 0; i < kp->PenCount; i++) {
		set_pen_attributes(mi, kp, i);

		if (kp->modetype == QUAD) {
			if (kp->alternate) {
				QuadRotated(kp, i, segs);
			} else {
				QuadMirrored(kp, i, segs);
			}
		} else if (kp->modetype == OCT) {
			if (kp->alternate) {
				OctRotated(kp, i, segs);
			} else {
				OctMirrored(kp, i, segs);
			}
		} else {
			if (kp->alternate) {

				if (kp->linear) {
					GeneralLinearRotated(kp, i, segs);
				} else {
					GeneralRotated(kp, i, segs);
				}
			} else {
				if (kp->linear) {
					GeneralLinearMirrored(kp, i, segs);
				} else {
					GeneralMirrored(kp, i, segs);
				}
			}
		}
		XDrawSegments(
				     display,
				     MI_WINDOW(mi),
				     gc,
				     segs,
				     NumberOfSegments
			);

		kp->pen[i].ox = kp->pen[i].cx;
		kp->pen[i].oy = kp->pen[i].cy;


		if ((INTRAND(0, 100) < ChangeChance) || kp->pen[i].DeferredChange) {
			if (!kp->pen[i].AngleOut && !kp->pen[i].RadiusOut) {
				kp->pen[i].DeferredChange = False;
				change_pen(mi, kp, i);
			} else {
				kp->pen[i].DeferredChange = True;
			}
		}
	}


	XSetLineAttributes(display, gc, 1, LineSolid, CapRound, JoinRound);
	free(segs);
}

/*-
 *
 */
ENTRYPOINT void
release_kaleid(ModeInfo * mi)
{
	if (kaleids != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
			free_kaleid_screen(&kaleids[screen]);
		}
		free(kaleids);
		kaleids = (kaleidstruct *) NULL;
	}
}

#ifndef STANDALONE
/*-
 *
 */
ENTRYPOINT void
refresh_kaleid(ModeInfo * mi)
{
	MI_CLEARWINDOW(mi);
}
#endif

XSCREENSAVER_MODULE ("Kaleid", kaleid)

#endif /* MODE_kaleid */
