/* -*- Mode: C; tab-width: 4 -*- */
/* scooter -- a journey through space tunnel and stars */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)scooter.c	5.01 2001/03/02 xlockmore";

#endif

/*
 *  scooter.c
 *
 *  Copyright (c) 2001 Sven Thoennissen <posse@gmx.net>
 *
 *  This program is based on the original "scooter", a blanker module from the
 *  Nightshift screensaver which is part of EGS (Enhanced Graphics System) on
 *  the Amiga computer. EGS has been developed by VIONA Development.
 *
 *
 *  (now the obligatory stuff)
 *
 *  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.
 *
 */

#ifdef STANDALONE
#define MODE_scooter
#define PROGCLASS "Scooter"
#define HACK_INIT init_scooter
#define HACK_DRAW draw_scooter
#define scooter_opts xlockmore_opts
#define DEFAULTS "*delay: 20000 \n" \
 "*count: 24 \n" \
 "*cycles: 5 \n" \
 "*size: 100 \n" \
 "*ncolors: 200 \n" \
 "*fullrandom: True \n" \
 "*verbose: False \n"
#include "xlockmore.h"          /* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"              /* in xlockmore distribution */
#endif /* STANDALONE */

#ifdef MODE_scooter

ModeSpecOpt scooter_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct scooter_description =
{"scooter", "init_scooter", "draw_scooter", "release_scooter",
 "refresh_scooter", "change_scooter", (char *) NULL, &scooter_opts,
 20000, 24, 5, 100, 64, 1.0, "",
 "Shows a journey through space tunnel and stars", 0, NULL};
/*
 *  count = number of doors
 *  cycles = speed (see MIN/MAX_SPEED below)
 *  size = number of stars
 *
 */
#endif

typedef struct {
	int x, y, z;
} Vec3D;

typedef struct {
	int x, y, z;
} Angle3D;

typedef struct {
	int r, g, b;
} ColorRGB;

typedef struct {
	XPoint lefttop, rightbottom;
} Rect;

typedef struct {
	Vec3D coords[4]; /* lefttop, righttop, rightbottom, leftbottom */
	int zelement;
	unsigned long color;
	char freecolor;
	char pad;
} Door;

typedef struct {
	int xpos, ypos;
	int width, height;
	int zelement;
	short draw;
} Star;

/* define this to see a pixel for each zelement */
/*
#define _DRAW_ZELEMENTS
*/

typedef struct {
	Vec3D pos;
	Angle3D angle;
} ZElement;

typedef struct {
	Star *stars;
	Door *doors;
	ZElement *zelements;
	int doorcount, ztotal, speed;
	int zelements_per_door, zelement_distance, spectator_zelement;
	int projnorm_z, rotationDuration, rotationStep, starcount;
	Angle3D currentRotation, rotationDelta;

	/* doors color cycling stuff */
	ColorRGB begincolor, endcolor;
	int colorcount, colorsteps;

	/* scale all stars and doors to window dimensions */
	float aspect_scale;

	Bool halt_scooter;
} scooterstruct;

static scooterstruct *scooters = (scooterstruct *) NULL;

#define MIN_DOORS 4

#define MIN_SPEED 1
#define MAX_SPEED 10

#define SPACE_XY_FACTOR 10

#define DOOR_WIDTH  (600*SPACE_XY_FACTOR)
#define DOOR_HEIGHT (400*SPACE_XY_FACTOR)

/* stars distance from doors center */
#define STAR_MIN_X  (1000*SPACE_XY_FACTOR)
#define STAR_MIN_Y  (750*SPACE_XY_FACTOR)
#define STAR_MAX_X  (10000*SPACE_XY_FACTOR)
#define STAR_MAX_Y  (7500*SPACE_XY_FACTOR)

/* star size (random) */
#define STAR_SIZE_MIN (2*SPACE_XY_FACTOR)
#define STAR_SIZE_MAX (64*SPACE_XY_FACTOR)

/* greater values make scooter run harder curves, smaller values produce calm curves */
#define DOOR_CURVEDNESS 14

/* 3d->2d projection (greater values create more fish-eye effect) */
#define PROJECTION_DEGREE 2.4

/*  this is my resolution at which scooter is in its original size, producing a 4:3 aspect ratio.
 *  all variables in this module are adjusted for this screen size; if scooter is run
 *  in windows with different size, it knows how to rescale itself.
 */
#define ASPECT_SCREENWIDTH 1152
#define ASPECT_SCREENHEIGHT 864

/* we define our own sin/cos macros to be faaast ;-) (good old Amiga times) */
#define SINUSTABLE_SIZE 0x8000
#define SINUSTABLE_MASK 0x7fff
#define SIN(a) sintable[a & SINUSTABLE_MASK]
#define COS(a) sintable[(a+(SINUSTABLE_SIZE/4)) & SINUSTABLE_MASK]

/* signum function */
#define SGN(a) (a < 0 ? -1 : 1)

static float *sintable = (float *) NULL;

static void
randomcolor(ColorRGB *col)
{
	unsigned long n;

/*	col->r = NRAND(65536);
	col->g = NRAND(65536);
	col->b = NRAND(65536);
*/
/*	col->r = LRAND() & 0xffff;
	col->g = LRAND() & 0xffff;
	col->b = LRAND() & 0xffff;
*/
	/* this seems best */
	n = NRAND(0x1000000);
	col->r = (n>>16)<<8;
	col->g = ((n>>8) & 0xff)<<8;
	col->b = (n & 0xff)<<8;
}

static void
initdoorcolors(scooterstruct *sp)
{
	/* prepare initial values for nextdoorcolor() */

	randomcolor(&sp->endcolor);

	sp->colorcount = 0;
	sp->colorsteps = 0;
}

static void nextdoorcolor(ModeInfo *mi, Door *door)
{
	scooterstruct *sp = &scooters[MI_SCREEN(mi)];
	Display *display = MI_DISPLAY(mi);
	XColor xcol;

/* comment this to color the doors from xlock's palette (created with saturation value) */
#if 1
	if (door->freecolor) {
		XFreeColors(display, MI_COLORMAP(mi), &(door->color), 1, 0);
		door->freecolor = 0;
	}
	if (MI_NPIXELS(mi) <= 2) {
		door->color = MI_WHITE_PIXEL(mi);
		return;
	}

	if (sp->colorcount >= sp->colorsteps) {

		/* init next color ramp */

		sp->colorcount = 0;
		sp->colorsteps = 8 + NRAND(32);
		sp->begincolor = sp->endcolor;
		randomcolor(&sp->endcolor);
	}

	/* compute next color values */

	xcol.red   = sp->begincolor.r + ((sp-> endcolor.r - sp->begincolor.r) *
		sp->colorcount / sp->colorsteps);
	xcol.green = sp->begincolor.g + ((sp-> endcolor.g - sp->begincolor.g) *
		sp->colorcount / sp->colorsteps);
	xcol.blue  = sp->begincolor.b + ((sp-> endcolor.b - sp->begincolor.b) *
		sp->colorcount / sp->colorsteps);
	xcol.pixel = 0;
	xcol.flags = DoRed | DoGreen | DoBlue;

	sp->colorcount++;

	if (!XAllocColor(display, MI_COLORMAP(mi), &xcol)) {
		 /* fail safe */
		door->color = MI_WHITE_PIXEL(mi);
		door->freecolor = 0;
	} else {
		door->color = xcol.pixel;
		door->freecolor = 1;
	}
#else
	if (MI_NPIXELS(mi) > 2) {
		if (++colorcount >= MI_NPIXELS(mi))
			colorcount = 0;
		door->color = MI_PIXEL(mi,colorcount);
	} else
		door->color = MI_WHITE_PIXEL(mi);
	return;
#endif
}

static void
free_scooter(scooterstruct *sp)
{
	if (sp->doors != NULL) {
		free(sp->doors);
		sp->doors = (Door *) NULL;
	}
	if (sp->stars != NULL) {
		free(sp->stars);
		sp->stars = (Star *) NULL;
	}
	if (sp->zelements != NULL) {
		free(sp->zelements);
		sp->zelements = (ZElement *) NULL;
	}
}

void release_scooter(ModeInfo *mi)
{
        if (scooters != NULL) {
                int         screen;

                for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
                        free_scooter(&scooters[screen]);
                free(scooters);
                scooters = (scooterstruct *) NULL;
        }
	if (sintable != NULL) {
		free(sintable);
		sintable = (float *) NULL;
	}
}

void init_scooter(ModeInfo *mi)
{
	int i;
	scooterstruct *sp;

	if (scooters == NULL) {
		if ((scooters = (scooterstruct *) calloc(MI_NUM_SCREENS(mi),
				sizeof (scooterstruct))) == NULL)
			return;
	}
	sp = &scooters[MI_SCREEN(mi)];

	sp->doorcount = MAX(MI_COUNT(mi),MIN_DOORS);
	sp->speed = MI_CYCLES(mi);
	sp->starcount = MI_SIZE(mi);
	if (sp->starcount < 1)
		sp->starcount = 1;
	if (sp->speed < MIN_SPEED)
		sp->speed = MIN_SPEED;
	if (sp->speed > MAX_SPEED)
		sp->speed = MAX_SPEED;
	sp->zelements_per_door = 60;
	sp->zelement_distance = 300;
	sp->ztotal = sp->doorcount * sp->zelements_per_door;
	/* sp->z_maxdepth = sp->ztotal * sp->zelement_distance; */
	if (sp->starcount > sp->ztotal)
		sp->starcount = sp->ztotal;

	sp->halt_scooter = False;

	initdoorcolors(sp);

	free_scooter(sp);
	if ((sintable = (float *) malloc(sizeof(float) *
			SINUSTABLE_SIZE)) == NULL) {
		release_scooter(mi);
		return;
	}
	for (i = 0; i < SINUSTABLE_SIZE; i++) {
		sintable[i] = SINF(M_PI*2/SINUSTABLE_SIZE*i);
	}

	if ((sp->doors = (Door *) malloc(sizeof(Door) *
			sp->doorcount)) == NULL) {
		return;
	}

	if ((sp->zelements = (ZElement *) malloc(sizeof(ZElement) *
		 sp->ztotal)) == NULL) {
		free_scooter(sp);
		return;
	}
	for (i = 0; i <  sp->doorcount; i++) {
		sp->doors[i].zelement =
			(sp->zelements_per_door * (i + 1)) - 1;
		sp->doors[i].freecolor = 0;
		nextdoorcolor(mi, &sp->doors[i]);
	}

	for (i = 0; i < sp->ztotal; i++) {
		sp->zelements[i].angle.x = 0;
		sp->zelements[i].angle.y = 0;
		sp->zelements[i].angle.z = 0;
	}

	if ((sp->stars = (Star *) malloc(sizeof(Star) *
			sp->starcount)) == NULL) {
		free_scooter(sp);
		return;
	}
	for (i = 0; i < sp->starcount; i++) {
		sp->stars[i].zelement = sp->ztotal * i / sp->starcount;
		sp->stars[i].draw = 0;
	}

	sp->projnorm_z = 50 * 240;
	sp->spectator_zelement = sp->zelements_per_door;

	sp->currentRotation.x = 0;
	sp->currentRotation.y = 0;
	sp->currentRotation.z = 0;

	sp->rotationDelta.x = 0;
	sp->rotationDelta.y = 0;
	sp->rotationDelta.z = 0;

	sp->rotationDuration = 1;
	sp->rotationStep = 0;
}

static void cleardoors(ModeInfo *mi)
{
	MI_CLEARWINDOW(mi);
}

/* Should be taken care of already... but just in case */
#if !defined( __GNUC__ ) && !defined(__cplusplus) && !defined(c_plusplus)
#undef inline
#define inline                  /* */
#endif
static inline float
projection (scooterstruct *sp, int zval)
{
	return (sp->projnorm_z / (PROJECTION_DEGREE * zval));
/* this is another formula. it is not limited to z>0 but it pulls too strong towards the screen center */
/*      return (sp->projnorm_z * pow(1.22,-(zval/200*PROJ_CURVEDNESS)))*/
}

/*

 y

 ^
 |      z
 |    .
 |   /
 |  /
 | /
 |/
-+------------> x
/|

  rotation angles:
	  a = alpha (x-rotation), b = beta (y-rotation), c = gamma (z-rotation)

  x-axis rotation:
	(  z  )'  =  ( cos(a) -sin(a) ) (  z  )
	(  y  )      ( sin(a)  cos(a) ) (  y  )

  y-axis rotation:
	(  z  )'  =  ( cos(b) -sin(b) ) (  z  )
	(  x  )      ( sin(b)  cos(b) ) (  x  )

  z-axis rotation:
	(  x  )'  =  ( cos(c) -sin(c) ) (  x  )
	(  y  )      ( sin(c)  cos(c) ) (  y  )
*/

static void
rotate_3d(Vec3D *src, Vec3D *dest, Angle3D *angle)
{
	Vec3D tmp;
	float	cosa = COS(angle->x),
		cosb = COS(angle->y),
		cosc = COS(angle->z),
		sina = SIN(angle->x),
		sinb = SIN(angle->y),
		sinc = SIN(angle->z);

	/* rotate around X, Y and Z axis (see formulae above) */

	/* X axis */
	tmp.z = src->z;
	tmp.y = src->y;
	dest->z = (int) (tmp.z * cosa - tmp.y * sina);
	dest->y = (int) (tmp.z * sina + tmp.y * cosa);

	/* Y axis */
	tmp.z = dest->z;
	tmp.x = src->x;
	dest->z = (int) (tmp.z * cosb - tmp.x * sinb);
	dest->x = (int) (tmp.z * sinb + tmp.x * cosb);

	/* Z axis */
	tmp.x = dest->x;
	tmp.y = dest->y;
	dest->x = (int) (tmp.x * cosc - tmp.y * sinc);
	dest->y = (int) (tmp.x * sinc + tmp.y * cosc);
}

static void calc_new_element(ModeInfo *mi)
{
	scooterstruct *sp = &scooters[MI_SCREEN(mi)];
	float rot = SIN((SINUSTABLE_SIZE/2)*
		sp->rotationStep/sp->rotationDuration);

	/* change current rotation 3D angle */

	if (sp->rotationStep++ >= sp->rotationDuration) {

		int fps = 1000000/MI_DELAY(mi); /* frames per second as timebase */

 		/*  one rotation interval takes 10-30 seconds at speed 1.
		 */
 		sp->rotationDuration = 10*fps + NRAND(20*fps);

		/* -DOOR_CURVEDNESS <= delta <= +DOOR_CURVEDNESS */
		sp->rotationDelta.x =
			NRAND(DOOR_CURVEDNESS*2+1) - DOOR_CURVEDNESS;
		sp->rotationDelta.y =
			NRAND(DOOR_CURVEDNESS*2+1) - DOOR_CURVEDNESS;
		sp->rotationDelta.z =
			NRAND(DOOR_CURVEDNESS*2+1) - DOOR_CURVEDNESS;

		sp->rotationStep = 0;
	}

	sp->currentRotation.x += (int) (rot * sp->rotationDelta.x);
	sp->currentRotation.y += (int) (rot * sp->rotationDelta.y);
	sp->currentRotation.z += (int) (rot * sp->rotationDelta.z);

	sp->currentRotation.x &= SINUSTABLE_MASK;
	sp->currentRotation.y &= SINUSTABLE_MASK;
	sp->currentRotation.z &= SINUSTABLE_MASK;
}

static void shift_elements(ModeInfo *mi)
{
	scooterstruct *sp = &scooters[MI_SCREEN(mi)];
	int i, iprev;
	Vec3D tmpvec;
	Angle3D tmpangle;

	/* shift angles from zelements */

	for (i = sp->speed; i < sp->ztotal; i++) {
		sp->zelements[i - sp->speed].angle = sp->zelements[i].angle;
	}
	for (i = sp->ztotal - sp->speed; i < sp->ztotal; i++) {
		calc_new_element(mi);
		sp->zelements[i].angle = sp->currentRotation;
	}

	/* calculate new 3D-coords from ALL zelements */

	sp->zelements[sp->spectator_zelement].pos.x = 0;
	sp->zelements[sp->spectator_zelement].pos.y = 0;
	sp->zelements[sp->spectator_zelement].pos.z =
		sp->zelement_distance * sp->spectator_zelement;

	for (i = sp->spectator_zelement - 1; i >= 0; --i) {
		iprev = i + 1;

		tmpvec.x = 0;
		tmpvec.y = 0;
		tmpvec.z = - sp->zelement_distance;
		tmpangle.x = sp->zelements[i].angle.x -
			sp->zelements[sp->spectator_zelement].angle.x;
		tmpangle.y = sp->zelements[i].angle.y -
			sp->zelements[sp->spectator_zelement].angle.y;
		tmpangle.z = sp->zelements[i].angle.z -
			sp->zelements[sp->spectator_zelement].angle.z;
		rotate_3d(&tmpvec, &(sp->zelements[i].pos), &tmpangle);
		sp->zelements[i].pos.x += sp->zelements[iprev].pos.x;
		sp->zelements[i].pos.y += sp->zelements[iprev].pos.y;
		sp->zelements[i].pos.z += sp->zelements[iprev].pos.z;
	}

	for (i = sp->spectator_zelement + 1; i < sp->ztotal; i++) {
		iprev = i - 1;

		tmpvec.x = 0;
		tmpvec.y = 0;
		tmpvec.z = sp->zelement_distance;
		tmpangle.x = sp->zelements[i].angle.x -
			sp->zelements[sp->spectator_zelement].angle.x;
		tmpangle.y = sp->zelements[i].angle.y -
			sp->zelements[sp->spectator_zelement].angle.y;
		tmpangle.z = sp->zelements[i].angle.z -
			sp->zelements[sp->spectator_zelement].angle.z;
		rotate_3d(&tmpvec, &(sp->zelements[i].pos), &tmpangle);
		sp->zelements[i].pos.x += sp->zelements[iprev].pos.x;
		sp->zelements[i].pos.y += sp->zelements[iprev].pos.y;
		sp->zelements[i].pos.z += sp->zelements[iprev].pos.z;
	}


	/* shift doors and wrap around */

	for (i = 0; i < sp->doorcount; i++) {
		if ((sp->doors[i].zelement -= sp->speed) < 0) {
			sp->doors[i].zelement += sp->ztotal;
			nextdoorcolor(mi,&sp->doors[i]);
		}
	}

	/* shift stars */

	for (i = 0; i < sp->starcount; i++) {
		if ((sp->stars[i].zelement -= sp->speed) < 0) {
			int rnd;

			sp->stars[i].zelement += sp->ztotal;
			sp->stars[i].draw = 1;

			/* make sure new stars are outside doors */

			rnd = NRAND(2*(STAR_MAX_X - STAR_MIN_X)) -
				(STAR_MAX_X - STAR_MIN_X);
			sp->stars[i].xpos = rnd + (STAR_MIN_X * SGN(rnd));

			rnd = NRAND(2*(STAR_MAX_Y - STAR_MIN_Y)) -
				(STAR_MAX_Y - STAR_MIN_Y);
			sp->stars[i].ypos = rnd + (STAR_MIN_Y * SGN(rnd));

			rnd = NRAND(STAR_SIZE_MAX - STAR_SIZE_MIN) +
				STAR_SIZE_MIN;
			sp->stars[i].width  = rnd;
			sp->stars[i].height = rnd * 3 / 4;
		}
	}
}

static void door_3d(scooterstruct *sp, Door *door)
{
	ZElement *ze = &sp->zelements[door->zelement];
	Vec3D src;
	Angle3D tmpangle;

	tmpangle.x = ze->angle.x -
		sp->zelements[sp->spectator_zelement].angle.x;
	tmpangle.y = ze->angle.y -
		sp->zelements[sp->spectator_zelement].angle.y;
	tmpangle.z = ze->angle.z -
		sp->zelements[sp->spectator_zelement].angle.z;

	/* calculate 3d coords of all 4 edges */

	src.x = -DOOR_WIDTH/2;
	src.y = DOOR_HEIGHT/2;
	src.z = 0;
	rotate_3d(&src, &(door->coords[0]), &tmpangle);
	door->coords[0].x += ze->pos.x;
	door->coords[0].y += ze->pos.y;
	door->coords[0].z += ze->pos.z;

	src.x = DOOR_WIDTH/2;
	rotate_3d(&src, &(door->coords[1]), &tmpangle);
	door->coords[1].x += ze->pos.x;
	door->coords[1].y += ze->pos.y;
	door->coords[1].z += ze->pos.z;

	src.y = -DOOR_HEIGHT/2;
	rotate_3d(&src, &(door->coords[2]), &tmpangle);
	door->coords[2].x += ze->pos.x;
	door->coords[2].y += ze->pos.y;
	door->coords[2].z += ze->pos.z;

	src.x = -DOOR_WIDTH/2;
	rotate_3d(&src, &(door->coords[3]), &tmpangle);
	door->coords[3].x += ze->pos.x;
	door->coords[3].y += ze->pos.y;
	door->coords[3].z += ze->pos.z;
}

/*
 *  clip the line p1-p2 at the given rectangle
 *
 */
static int clipline(XPoint *p1, XPoint *p2, Rect *rect)
{
	XPoint new1, new2, tmp;
	float m;

	new1 = *p1;
	new2 = *p2;

	/* entire line may not need clipping */

	if (((new1.x >= rect->lefttop.x) && (new1.x <= rect->rightbottom.x))
	 || ((new1.y >= rect->lefttop.y) && (new1.y <= rect->rightbottom.y))
	 || ((new2.x >= rect->lefttop.x) && (new2.x <= rect->rightbottom.x))
	 || ((new2.y >= rect->lefttop.y) && (new2.y <= rect->rightbottom.y)))
		return 1;


	/* first: clip y dimension */

	/* p1 is above p2 */
	if (new1.y > new2.y) {
		tmp = new1;
		new1 = new2;
		new2 = tmp;
	}

	/* line could be totally out of view */
	if ((new2.y < rect->lefttop.y) || (new1.y > rect->rightbottom.y))
		return 0;

	m = (new2.x == new1.x) ? 0 :
		((float)(new2.y - new1.y) / (new2.x - new1.x));

	if (new1.y < rect->lefttop.y) {
		if (m)
			new1.x += (int) ((rect->lefttop.y - new1.y)/m);
		new1.y = rect->lefttop.y;
	}
	if (new2.y > rect->rightbottom.y) {
		if (m)
			new2.x -= (int) ((new2.y - rect->rightbottom.y)/m);
		new2.y = rect->rightbottom.y;
	}


	/* clip x dimension */

	/* p1 is left to p2 */
	if (new1.x > new2.x) {
		tmp = new1;
		new1 = new2;
		new2 = tmp;
	}

	if ((new2.x < rect->lefttop.x) || (new1.x > rect->rightbottom.x))
		return 0;

	m = (new2.x == new1.x) ? 0 :
		((float)(new2.y - new1.y) / (new2.x - new1.x));

	if (new1.x < rect->lefttop.x) {
		new1.y += (int) ((rect->lefttop.y - new1.y)*m);
		new1.x = rect->lefttop.x;
	}
	if (new2.y > rect->rightbottom.y) {
		new2.y -= (int) ((new2.y - rect->rightbottom.y)*m);
		new2.x = rect->rightbottom.x;
	}


	/* push values */

	*p1 = new1;
	*p2 = new2;

	return 1;
}

static void drawdoors(ModeInfo *mi)
{
	scooterstruct *sp = &scooters[MI_SCREEN(mi)];
	Display *display = MI_DISPLAY(mi);
	Window window = MI_WINDOW(mi);
	GC gc = MI_GC(mi);
	int width = MI_WIDTH(mi), height = MI_HEIGHT(mi),
		midx = width/2, midy = height/2;
	int i, j;
	Rect rect = { {0,0}, {0,0} };

        rect.rightbottom.x = width - 1;
        rect.rightbottom.y = height - 1;
	XSetLineAttributes(display, gc, 2, LineSolid, CapNotLast, JoinRound);

#ifdef _DRAW_ZELEMENTS
        XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
        for (i= sp->spectator_zelement; i<ztotal; i++) {
                register float proj;
                XPoint p;

                proj = projection(sp, zelements[i].pos.z) * sp->aspect_scale;
                p.x = midx + (sp->zelements[i].pos.x * proj / SPACE_XY_FACTOR);
                p.y = midy - (sp->zelements[i].pos.y * proj / SPACE_XY_FACTOR);
                XDrawPoint(display, window, gc, p.x, p.y);
        }
#endif

	for (i = 0; i < sp->doorcount; i++) {

		register float proj;
		XPoint lines[4], clip1, clip2;

		door_3d(sp, &sp->doors[i]);

		for (j=0; j<4; j++) {
			if (sp->doors[i].coords[j].z <= 0) break;

			proj = projection(sp, sp->doors[i].coords[j].z) * sp->aspect_scale;
			lines[j].x = midx + (int) (sp->doors[i].coords[j].x *
				proj / SPACE_XY_FACTOR);
			lines[j].y = midy - (int) (sp->doors[i].coords[j].y *
				proj / SPACE_XY_FACTOR);
		}
		if (j<4) continue;

		XSetForeground(display, gc, sp->doors[i].color);

		for (j=0; j<4; j++) {
			clip1 = lines[j];
			clip2 = lines[(j+1)%4];
			if (clipline(&clip1, &clip2, &rect))
				XDrawLine(display, window, gc, clip1.x, clip1.y, clip2.x, clip2.y);
		}
	}
}

static void drawstars(ModeInfo *mi)
{
	scooterstruct *sp = &scooters[MI_SCREEN(mi)];
	Display *display = MI_DISPLAY(mi);
	Window window = MI_WINDOW(mi);
	GC gc = MI_GC(mi);
	int width = MI_WIDTH(mi), height = MI_HEIGHT(mi),
		midx = width/2, midy = height/2;
	int i;

	for (i = 0; i < sp->starcount; i++) {

		float proj;
		ZElement *ze = &sp->zelements[sp->stars[i].zelement];
		Vec3D tmpvec, coords;
		Angle3D tmpangle;
		XPoint lefttop, rightbottom;

		if (!sp->stars[i].draw) continue;


		/* rotate star around its z-element, then add its position */

		tmpangle.x = ze->angle.x -
			sp->zelements[sp->spectator_zelement].angle.x;
		tmpangle.y = ze->angle.y -
			sp->zelements[sp->spectator_zelement].angle.y;
		tmpangle.z = ze->angle.z -
			sp->zelements[sp->spectator_zelement].angle.z;

		tmpvec.x = sp->stars[i].xpos;
		tmpvec.y = sp->stars[i].ypos;
		tmpvec.z = 0;
		rotate_3d(&tmpvec, &coords, &tmpangle);
		coords.x += ze->pos.x;
		coords.y += ze->pos.y;
		coords.z += ze->pos.z;

		if (coords.z <= 0) continue;


		/* projection and clipping (trivial for a rectangle) */

		proj = projection(sp, coords.z) * sp->aspect_scale;

		lefttop.x = midx + (int) ((coords.x - sp->stars[i].width/2) *
			proj / SPACE_XY_FACTOR);
		lefttop.y = midy - (int) ((coords.y + sp->stars[i].height/2) *
			proj / SPACE_XY_FACTOR);
		if (lefttop.x < 0)
			lefttop.x = 0;
		else if (lefttop.x >= width)
			continue;
		if (lefttop.y < 0)
			lefttop.y = 0;
		else if (lefttop.y >= height)
			continue;

		rightbottom.x = midx + (int) ((coords.x + sp->stars[i].width/2) *
			 proj / SPACE_XY_FACTOR);
		rightbottom.y = midy - (int) ((coords.y - sp->stars[i].height/2) *
			 proj / SPACE_XY_FACTOR);
		if (rightbottom.x < 0)
			continue;
		else if (rightbottom.x >= width)
			rightbottom.x = width - 1;
		if (rightbottom.y < 0)
			continue;
		else if (rightbottom.y >= height)
			rightbottom.y = height - 1;


		/* in white color, small stars look darker than big stars */

		XSetForeground(display, gc, MI_WHITE_PIXEL(mi));

		if ((lefttop.x == rightbottom.x) &&
				(lefttop.y == rightbottom.y)) {
			/* star is exactly 1 pixel */
			XDrawPoint(display, window, gc, lefttop.x, lefttop.y);
		} else if ((rightbottom.x - lefttop.x) +
				(rightbottom.y - lefttop.y) == 1) {
			/* star is 2 pixels wide or high */
			XDrawPoint(display, window, gc,
				lefttop.x,     lefttop.y);
			XDrawPoint(display, window, gc,
				rightbottom.x, rightbottom.y);
		} else if ((rightbottom.x - lefttop.x == 1) &&
				(rightbottom.y - lefttop.y == 1)) {

			/*  star is exactly 2x2 pixels.
			 *  a 2x2 rectangle should be drawn faster by plotting all 4 pixels
			 *  than by filling a rectangle (is this really so under X ?)
			 */

			XDrawPoint(display, window, gc,
				lefttop.x,     lefttop.y);
			XDrawPoint(display, window, gc,
				rightbottom.x, lefttop.y);
			XDrawPoint(display, window, gc,
				lefttop.x,     rightbottom.y);
			XDrawPoint(display, window, gc,
				rightbottom.x, rightbottom.y);
		} else {
			XFillRectangle(display, window, gc,
				lefttop.x, lefttop.y,
				rightbottom.x - lefttop.x,
				rightbottom.y - lefttop.y);
		}
	}
}

void draw_scooter(ModeInfo *mi)
{
	scooterstruct *sp;

	if (scooters == NULL)
		return;
	sp = &scooters[MI_SCREEN(mi)];
	if (sp->doors == NULL)
		return;

	cleardoors(mi);

	shift_elements(mi);

	/* With these scale factors, all doors are sized correctly for any window dimension.
	 * If aspect ratio is not 4:3, the smaller part of the window is used, e.g.:
	 *      window = 1000x600
	 *      => door scale factor is like in a 800x600 window (not 1000x750)
	 */

	if ((float)MI_WIDTH(mi)/MI_HEIGHT(mi) >=
			(float)ASPECT_SCREENWIDTH/ASPECT_SCREENHEIGHT) {
		/* window is wider than or equal 4:3 */
		sp->aspect_scale = (float)MI_HEIGHT(mi) / ASPECT_SCREENHEIGHT;
	} else {
		/* window is higher than 4:3 */
		sp->aspect_scale = (float)MI_WIDTH(mi) / ASPECT_SCREENWIDTH;
	}

	drawstars(mi);

	drawdoors(mi);
}

void refresh_scooter(ModeInfo *mi)
{
	MI_CLEARWINDOW(mi);
}

void change_scooter(ModeInfo *mi)
{
	scooterstruct *sp;

	if (scooters == NULL)
		return;
	sp = &scooters[MI_SCREEN(mi)];

	sp->halt_scooter = !sp->halt_scooter;
}

#endif /* MODE_scooter */
