/*ScianSpaces.c
 Space objects
  Eric Pepke
  April 6, 1990
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianColors.h"
#include "ScianLists.h"
#include "ScianArrays.h"
#include "ScianLights.h"
#include "ScianSpaces.h"
#include "ScianEvents.h"
#include "ScianWindows.h"
#include "ScianDraw.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianVisObjects.h"
#include "ScianDialogs.h"
#include "ScianScripts.h"
#include "ScianIcons.h"
#include "ScianIDs.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianTextBoxes.h"
#include "ScianTitleBoxes.h"
#include "ScianPerspec.h"
#include "ScianErrors.h"
#include "ScianTimers.h"
#include "ScianSliders.h"
#include "ScianStyle.h"
#include "ScianComplexControls.h"
#include "ScianMethods.h"
#include "ScianPick.h"
#include "ScianPreferences.h"
#include "ScianFiles.h"
#include "ScianObjFunctions.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSymbols.h"

Bool phsco = false;			/*True iff phscologram is on*/
Bool drawingTransparent = false;	/*True iff drawing only transparent*/
int clockDisplaySerialNum = 0;		/*Clock display serial number*/
extern real fps;
ObjPtr spacePickedObject = NULLOBJ;

int qualityDrawnSoFar;		/*Max quality drawn so far*/
int minDrawingQuality;		/*Minimum quality to draw*/
int maxDrawingQuality;		/*Maximum quality to draw*/

#define PLAYCLOCK (0.1)			/*Rate for clock play*/
#define FASTCLOCK (0.2)			/*Rate for clock fast*/

#define ROTLIGHTS			/*Rotate lights with object*/

#define DSPCLOCKWIDTH	97
#define DSPCLOCKHEIGHT	48
#define DSPCLOCKLEFT	55
#define DSPCLOCKTOP	70
#define DSPCLOCKTLEFT	10
#define DSPCLOCKTRIGHT	10
#define DSPCLOCKTTOP	23

#define ASPECT		(((float) (right - left)) / ((float) (top - bottom))) * (phsco ? 0.61 : 1.0)
#define MINROT		3		/*Minimum number of pixels to move to rotate*/

ObjPtr curSpace = 0;			/*Current space*/
real curFocusDist = 0.0;		/*Current focus distance*/
real curViewField = 0.0;		/*Current field of view.  Angle if 
					  perspec, width if ortho*/
real curViewClip[2];			/*Current viewing clipping distances*/
ObjPtr curSpaceLights;			/*Lights in the current space*/
int curWhichView;			/*Current which view*/
ObjPtr spaceClass = 0;			/*Class of a 3-D space*/
real spaceTime = 0.0;			/*Time for current space.  Cute name, huh?*/
ObjPtr spacePanelClass;			/*Invisible panel over a space*/
ObjPtr spaceBackPanelClass;		/*Colored panel behind a space*/
ObjPtr controllerClass;			/*Class for space controllers (clocks, lights, etc.)*/
ObjPtr clockClass;			/*Class of clocks*/
ObjPtr observerClass;			/*Class of observers*/
ObjPtr rendererClass;			/*Class of renderers*/
Matrix viewerCoordsMatrix;		/*Matrix in viewer coordinates*/
Bool oneObserver = false;
Bool oneRenderer = false;
Bool oneClock = false;
Bool oneLights = false;			/*True iff to make only one controller*/


void DrawSpaceContents(ObjPtr, int, int, int, int, int);

#ifdef PROTO
static double f(double x)
#else
static double f(x)
double x;
#endif
/*Performs the increasing monotonic function required for the virtual trackball
  of click radius ratio x.  Yeah, right.*/
{
    if (x <= 0.0)
    {
	return 0.0;
    }
    else if (x >= 1.0)
    {
	return M_PI_2;
    }
    else
    {
	return M_PI_2 * x;
    }
}

Bool GetListExtent(list, bounds)
ObjPtr list;
real bounds[];
/*Puts the extent of the objects (or the objects they represent) in list 
  into bounds.  Returns true iff it's nonzero*/
{
    ThingListPtr runner;

    /*Make bounds ludicrous*/
    bounds[0] = bounds[2] = bounds[4] = PLUSINF;
    bounds[1] = bounds[3] = bounds[5] = MINUSINF;

    runner = LISTOF(list);
    if (!runner)
    {
	return false;
    }
    while (runner)
    {
	ObjPtr repObj;
	ObjPtr var;

	repObj = runner -> thing;
	if (IsObject(repObj))
	{
	    MakeVar(repObj, BOUNDS);
	    var = GetVar(repObj, BOUNDS);
	    if (var)
	    {
		real localBounds[6];
		Array2CArray(localBounds, var);
		
		var = GetVar(repObj, XSCALE);
		if (var)
		{
		    real v;
		    v = GetReal(var);
		    localBounds[0] *= v;
		    localBounds[1] *= v;
		}
		var = GetVar(repObj, YSCALE);
		if (var)
		{
		    real v;
		    v = GetReal(var);
		    localBounds[2] *= v;
		    localBounds[3] *= v;
		}
		var = GetVar(repObj, ZSCALE);
		if (var)
		{
		    real v;
		    v = GetReal(var);
		    localBounds[4] *= v;
		    localBounds[5] *= v;
		}
		if (localBounds[0] < bounds[0]) bounds[0] = localBounds[0];
		if (localBounds[1] > bounds[1]) bounds[1] = localBounds[1];
		if (localBounds[2] < bounds[2]) bounds[2] = localBounds[2];
		if (localBounds[3] > bounds[3]) bounds[3] = localBounds[3];
		if (localBounds[4] < bounds[4]) bounds[4] = localBounds[4];
		if (localBounds[5] > bounds[5]) bounds[5] = localBounds[5];
	    }
	}
	runner = runner -> next;
    }

    /*If bounds ludicrous, make them sane*/
    if (bounds[0] == PLUSINF || bounds[2] == PLUSINF || bounds[4] == PLUSINF ||
	bounds[1] == MINUSINF || bounds[3] == MINUSINF || bounds[5] == MINUSINF)
    {
	bounds[0] = bounds[2] = bounds[4] = 0.0;
	bounds[1] = bounds[3] = bounds[5] = 1.0;
    }

    return true;
}

#define NUDGESIZE	2000		/*Size of one nudge*/
#define MAXNNUDGES	4		/*Maximum number of nudges*/
long zMin = 0 + NUDGESIZE * MAXNNUDGES;
long zMax = 0x7fffff - NUDGESIZE * MAXNNUDGES;
long curZMin, curZMax;

void NudgeCloser()
/*Nudges objects to be drawn closer*/
{
#ifdef GRAPHICS
    curZMax -= NUDGESIZE;
    curZMin -= NUDGESIZE;
    lsetdepth(curZMin, curZMax);
#endif
}

void NudgeFarther()
/*Nudges objects to be drawn farther*/
{
#ifdef GRAPHICS
    curZMax += NUDGESIZE;
    curZMin += NUDGESIZE;
    lsetdepth(curZMin, curZMax);
#endif
}

#ifdef PROTO
void upat(float vx, float vy, float vz, 
          float px, float py, float pz, 
          float ux, float uy, float uz)
#else
void upat(vx, vy, vz, px, py, pz, ux, uy, uz)
float vx; float vy; float vz; 
float px; float py; float pz; 
float ux; float uy; float uz;
#endif
/*Replacement for lookat.  Specifies a viewer at (vx, vy, vz) looking at
  a point (px, py, pz) with an up vector of (ux, uy, uz).  Adapted from 
  Thant Tessman's code in 4Dgifts.*/
{
#ifdef GRAPHICS
    int i;
    float forward[3], side[3], up[3];
    float m[4][4];

    forward[0] = px - vx;
    forward[1] = py - vy;
    forward[2] = pz - vz;

    /* temporarily use view-up to hold world-up */
    up[0] = ux;	    
    up[1] = uy;
    up[2] = uz;

    NORMALIZE(forward);

    /* generate the vector side from the
     * 2 vectors view forward and world up 
     */
    CROSS(forward, up, side);
    NORMALIZE(side);

    /* generate the view up vector from 
     * the 2 vectors view forward and view side 
     */
    CROSS(side, forward, up);

    m[0][0] = side[0];
    m[1][0] = side[1];
    m[2][0] = side[2];
    m[3][0] = 0.0;

    m[0][1] = up[0];
    m[1][1] = up[1];
    m[2][1] = up[2];
    m[3][1] = 0.0;

    m[0][2] = -forward[0];
    m[1][2] = -forward[1];
    m[2][2] = -forward[2];
    m[3][2] = 0.0;

    m[0][3] = 0.0;
    m[1][3] = 0.0;
    m[2][3] = 0.0;
    m[3][3] = 1.0;
    multmatrix(m);
    translate(-vx, -vy, -vz);
#endif
}

#ifdef PROTO
void StartSpace(ObjPtr space, int left, int right, int bottom, int top,
	int action, int whichView)
#else
void StartSpace(space, left, right, bottom, top, action, whichView)
ObjPtr space;
int left, right, bottom, top;
int action;
int whichView;
#endif
/*Start a drawing, rotation, or press in a space.  Action is the action
  that is being performed*/
{
#ifdef GRAPHICS
    real eyePosn[3];		/*Coords of the eye*/
    real focusPoint[3];		/*Coords of the focus point*/
    real upVector[3];		/*The up vector of the observer*/
    real forwardVector[3];
    ObjPtr object;
    ObjPtr psStuff;
    ObjPtr boundsArray;
    real bounds[4];
    real bigBounds[6];		/*Bounds of the objects*/
    Coord center[3];		/*The center of the data set*/
    ObjPtr contents;
    float scaleFactor;		/*Factor to scale*/
    real maxSize;		/*Maximum size of the objects this draw*/
    ObjPtr observer;		/*Observer of the space*/
    ObjPtr clock;		/*The clock*/
    real eyeOffset;
    ObjPtr var;
    Matrix proj, model;

    curWhichView = whichView;
    if (whichView == VIEW_XLEFT)
    {
	left = (left + right) / 2 + 1;
    }
    else if (whichView == VIEW_XRIGHT)
    {
	right = (left + right) / 2;
    }

    /*Set a new viewport to the current area*/
    SetSubPort(left, right, bottom, top);

    observer = GetObjectVar("StartSpace", space, OBSERVER);
    if (!observer) return;

    switch (whichView)
    {
	case VIEW_XLEFT:
	case VIEW_RCLEFT:
	    var = GetVar(observer, BINOCULARITY);
	    eyeOffset = -GetReal(var) * 0.5;
	    break;
	case VIEW_XRIGHT:
	case VIEW_RCRIGHT:
	    var = GetVar(observer, BINOCULARITY);
	    eyeOffset = GetReal(var) * 0.5;
	    break;
	default:
	    eyeOffset = 0.0;
    }

    /*Set up current viewing parameters*/
    var = GetVar(observer, FOCUSDIST);
    if (var)
    {
	curFocusDist = GetReal(var);
    }
    else
    {
	curFocusDist = INITEYEDIST;
    }

    var = GetVar(observer, VIEWFIELD);
    if (var)
    {
	curViewField = GetReal(var);
    }
    else
    {
	curViewField = INITAOV;
    }

    var = GetVar(observer, VIEWCLIP);
    if (var)
    {
	Array2CArray(curViewClip, var);
    }
    else
    {
	curViewClip[0] = INITNEARCLIP;
	curViewClip[1] = INITFARCLIP;
    }

    /*Get information about the eye*/
    GetObserverLocation(eyePosn, observer);
    GetFocusPoint(focusPoint, observer);
    GetAdjustedVectors(forwardVector, upVector, observer);

    if (whichView == VIEW_XLEFT || whichView == VIEW_XRIGHT ||
	rgbp && (whichView == VIEW_RCLEFT || whichView == VIEW_RCRIGHT))
    {
	real sideVector[3];
	real temp;

	/*Have to slide eyePosn and focusPoint over somewhat*/
	CROSS(forwardVector, upVector, sideVector);

	/*Move eye over*/
	eyePosn[0] += sideVector[0] * eyeOffset;
	eyePosn[1] += sideVector[1] * eyeOffset;
	eyePosn[2] += sideVector[2] * eyeOffset;
    }

    /*Get information about the clock*/
    clock = GetObjectVar("StartSpace", space, CLOCK);
    if (clock)
    {
	ObjPtr time;
	time = GetVar(clock, TIME);
	if (time)
	{
	    spaceTime = GetReal(time);
	}
	else
	{
	    spaceTime = 0.0;
	}
    }
    else
    {
	spaceTime = 0.0;
    }

    /*Set the current space*/
    curSpace = space;

    if (action == PICKSPACE)
    {
	StartPick();
    }
#ifdef GL4D
    mmode(MPROJECTION);
#endif

    if (whichView == VIEW_ORTHO)
    {
	/*Plain old orthographic projection.*/
	ortho(-ASPECT * curViewField, ASPECT * curViewField,
	      -curViewField, curViewField, curViewClip[0], curViewClip[1]);
    }
    else
    {
	/*Exciting perspective projection!!!*/
	perspective((long) (curViewField * 10.0), ASPECT,
		    curViewClip[0], curViewClip[1]);
    }

#ifdef GL4D
    mmode(MVIEWING);
    loadmatrix(Identity);
#endif

#if 1
    upat(eyePosn[0], eyePosn[1], eyePosn[2],
	focusPoint[0], focusPoint[1], focusPoint[2], 
	upVector[0], upVector[1], upVector[2]);
#else
    lookat(eyePosn[0], eyePosn[1], eyePosn[2],
	focusPoint[0], focusPoint[1], focusPoint[2], 
	0.0);
#endif

    getmatrix(viewerCoordsMatrix);

    /*Set up the lights*/
    curSpaceLights = GetVar(space, LIGHTS);
    if (action == DRAWSPACE)
    {
	if (curSpaceLights)
	{
	    StartLights(space, curSpaceLights, false);
	}
    }

    if (action == DRAWSPACE)
    {
	int i;
	real radius;

	/*Shift filter for 3-D glasses*/
	if (rgbp && whichView == VIEW_RCLEFT)
	{
	    RGBwritemask(0, 0xFF, 0xFF);
	}
	else if (rgbp && whichView == VIEW_RCRIGHT)
	{
	    RGBwritemask(0xFF, 0, 0);
	}

	/*Go to Gouraud shading*/
	shademodel(GOURAUD);

	/*Set up the Z-buffer*/
	if (action == DRAWSPACE)
	{
	    curZMin = zMin + NUDGESIZE * MAXNNUDGES;
	    curZMax = zMax - NUDGESIZE * MAXNNUDGES;

	    lsetdepth(curZMin, curZMax);
	    zbuffer(TRUE);
	    zclear();
	}

	/*Draw rotation and motion guides*/
	if (whichView == VIEW_ORTHO)
	{
	    radius = SPACESPHERESIZE * 1.85 * curViewField;
	}
	else
	{
	    radius = SPACESPHERESIZE * curFocusDist * rsin(curViewField * M_PI / 180.0);
	}

	if (GetPredicate(curSpace, SHOWROTGUIDES))
	{
	    SetUIColor(UIWHITE);

	    DrawWFSphere(focusPoint[0], focusPoint[1], focusPoint[2], radius);
	    DrawSpaceLine(focusPoint[0] - radius, focusPoint[1], focusPoint[2],
			  focusPoint[0] + radius, focusPoint[1], focusPoint[2]);
	    DrawSpaceLine(focusPoint[0], focusPoint[1] - radius, focusPoint[2],
			  focusPoint[0], focusPoint[1] + radius, focusPoint[2]);
	    DrawSpaceLine(focusPoint[0], focusPoint[1], focusPoint[2] - radius,
			  focusPoint[0], focusPoint[1], focusPoint[2] + radius);
	}
	if (GetPredicate(curSpace, SHOWMOTGUIDES))
	{
	    real x, y;
	    SetUIColor(UIGRAY50);
	    for (x = -10.0; x <= 10.0; x += 1.0)
	    {
		for (y = -10.0; y <= 10.0; y += 1.0)
		{
		    DrawSpaceLine(x, y, -10.0, x, y, 10.0);
		    DrawSpaceLine(x, -10.0, y, x, 10.0, y);
		    DrawSpaceLine(-10.0, x, y, 10.0, x, y);
		}
	    }
	}
    }

    {
	/*Determine the size of the objects within*/ 
	contents = GetVar(space, CONTENTS);
	GetListExtent(contents, bigBounds);

	/*Get their center*/
	center[0] = (bigBounds[1] + bigBounds[0]) / 2.0;
	center[1] = (bigBounds[3] + bigBounds[2]) / 2.0;
	center[2] = (bigBounds[5] + bigBounds[4]) / 2.0;

	/*Scale to a reasonable scaling factor for the data*/
	maxSize = ABS(bigBounds[1] - bigBounds[0]);
	if (ABS(bigBounds[3] - bigBounds[2]) > maxSize)
	{
	    maxSize = ABS(bigBounds[3] - bigBounds[2]);
	}
	if (ABS(bigBounds[5] - bigBounds[4]) > maxSize)
	{
	    maxSize = ABS(bigBounds[5] - bigBounds[4]);
	}
	scaleFactor = 1.6 / maxSize;
	scale(scaleFactor, scaleFactor, scaleFactor);
	SetVar(space, SPACESCALE, NewReal(scaleFactor));

	/*Translate to be centered around object's center*/
	translate(-center[0], -center[1], -center[2]);
	var = NewRealArray(1, 3L);
	CArray2Array(var, center);
	SetVar(space, SPACECENTER, var);
    }
#endif
}

#ifdef PROTO
void StopSpace(int action)
#else
void StopSpace(action)
int action;
#endif
/*Stops drawing, rotating, or pressing in a space*/
{
#ifdef GRAPHICS
    if (action == DRAWSPACE)
    {
	real radius;

	if (hasDithering)
	{
#ifdef     DT_OFF
	    dither(DT_OFF);
#endif
	}
	if (curWhichView == VIEW_ORTHO)
	{
	    radius = SPACESPHERESIZE * 1.85 * curViewField;
	}
	else
	{
	    radius = SPACESPHERESIZE * curFocusDist * rsin(curViewField * M_PI / 180.0);
	}

	/*Draw the lights*/
	if (curSpaceLights)
	{
	    loadmatrix(viewerCoordsMatrix);
	    DrawLights(curSpace, curSpaceLights, action, radius);
	    StopLights(curSpace);
	}
	if (rgbp)
	{
	    RGBwritemask(0xFF, 0xFF, 0xFF);
	}
    }

    if (action == PICKSPACE)
    {
	spacePickedObject = StopPick();
    }
    if (action == DRAWSPACE)
    {
	zbuffer(FALSE);
    }

    RestoreSubPort();

#endif
    curSpace = NULLOBJ;
}

static ObjPtr ForAllSpaceObjects(object, routine)
ObjPtr object;
FuncTyp routine;
/*Does routine on a space and its contents*/
{
    ObjPtr contents;
    ObjPtr lights;

    (*routine)(object);

    /*Now check my CONTENTS*/
    contents = GetVar(object, CONTENTS);
    if (contents)
    {
	ForAllObjects(contents, routine);
    }

    /*Now check the lights*/
    lights = GetVar(object, LIGHTS);
    if (lights)
    {
	ForAllObjects(lights, routine);
    }

    /*DIK also observer and renderer and clock*/

    return ObjTrue;
}


ObjPtr WakeObserver(observer, lateness)
ObjPtr observer;
double lateness;
/*Wakes an observer and makes it continue rotation*/
{
    ObjPtr var;
    Bool wakeAgain = false;	/*True iff wake again*/
    int x, y;
    real phi;

    DoNotDisturb(observer, MARKTIME);

    /*See if we need to spin*/
    var = GetVar(observer, ROTSPEED);
    if (var && 0.0 != (phi = GetReal(var)))
    {
	real axis[3];

	/*Turn phi from speed to amount*/
	phi *= lateness;

	var = GetFixedArrayVar("WakeObserver", observer, ROTAXIS, 1, 3L);
	if (var)
	{
	    Array2CArray(axis, var);

	    RotateObserver(observer, axis, phi, false);
	}

	wakeAgain = true;
    }
    /*See if we want to fly*/
    else if (GetPredicate(observer, FLYING))
    {
#ifdef INTERACTIVE
	ObjPtr var;
	real eyePosn[3];
	real roll, dPitch, pitch;
	float dTime;
	real perspecStuff[4];
	real airspeed;
	long mouseX, mouseY;
	real upVector[3];
	real forwardVector[3];
	real sp, cp, t;
	real temp[3];
	Matrix rotDelta;

	dTime = lateness;

	/*Move eye according to airspeed*/
	var = GetVar(observer, AIRSPEED);
	if (var && IsReal(var))
	{
	    airspeed = GetReal(var);
	}
	else
	{
	    airspeed = 0.0;
	}
	
	var = GetVar(observer, LOCATION);
	if (var && IsRealArray(var) && RANK(var) == 1 && 
	    DIMS(var)[0] == 3)
	{
	    Array2CArray(eyePosn, var);
	}
	else
	{
	    eyePosn[0] = 0.0;
	    eyePosn[1] = 0.0;
	    eyePosn[2] = INITEYEDIST;
	    var = NewRealArray(1, (long) 3);
	    SetVar(observer, LOCATION, var);
	}

	/*Get a new roll and dpitch value*/
	mouseX = getvaluator(MOUSEX);
	mouseY = getvaluator(MOUSEY);

	roll = (((real) mouseX) / (real) scrWidth) * 2 * MAXROLL - MAXROLL;
	dPitch = (((real) mouseY) / (real) scrHeight) * 2 * MAXDPITCH - MAXDPITCH;

	/*Update eye position based on airspeed*/

	/*Get pitch*/
	var = GetVar(observer, PITCH);
	if (var && IsReal(var))
	{
	    pitch = GetReal(var);
	}
	else
	{
	    pitch = 0.0;
	    var = NewReal(pitch);
	    SetVar(observer, PITCH, var);
	}
    
	/*Change the pitch based on dpitch, asymptotic to up and down*/
	if (dPitch > 0.0)
	{
	    pitch = pitch + (M_PI_2 - pitch) * dPitch * dTime;
	}
	else
	{
	    pitch = pitch + (pitch + M_PI_2) * dPitch * dTime;
	}
	SetVar(observer, PITCH, NewReal(pitch));

	/*Rotate around the up vector*/
	GetUpVector(upVector, observer);
	GetForwardVector(forwardVector, observer);

	phi = roll * ROLLYAWFACTOR * dTime;

	sp = rsin(phi);
	cp = rcos(phi);
	t = 1.0 - cp;
    
	/*Make the change rotation matrix by rows from top to bottom*/
	rotDelta[0][0] = t * SQUARE(upVector[0]) + cp;
	rotDelta[0][1] = t * upVector[0] * upVector[1] + sp * upVector[2];
	rotDelta[0][2] = t * upVector[0] * upVector[2] - sp * upVector[1];
	rotDelta[0][3] = 0.0;
    
	rotDelta[1][0] = t * upVector[0] * upVector[1] - sp * upVector[2];
	rotDelta[1][1] = t * SQUARE(upVector[1]) + cp;
	rotDelta[1][2] = t * upVector[1] * upVector[2] + sp * upVector[0];
	rotDelta[1][3] = 0.0;
    
	rotDelta[2][0] = t * upVector[0] * upVector[2] + sp * upVector[1];
	rotDelta[2][1] = t * upVector[1] * upVector[2] - sp * upVector[0];
	rotDelta[2][2] = t * SQUARE(upVector[2]) + cp;
	rotDelta[2][3] = 0.0;
    
	rotDelta[3][0] = 0.0;
	rotDelta[3][1] = 0.0;
	rotDelta[3][2] = 0.0;
	rotDelta[3][3] = 1.0;

	MATBYVECTOR(rotDelta, forwardVector, temp);

	var = NewRealArray(1, 3L);
	CArray2Array(var, temp);
	SetVar(observer, FORWARDVECTOR, var);
 
	GetAdjustedVectors(forwardVector, upVector, observer);

	/*Move eye*/
	eyePosn[0] += forwardVector[0] * airspeed * dTime;
	eyePosn[1] += forwardVector[1] * airspeed * dTime;
	eyePosn[2] += forwardVector[2] * airspeed * dTime; 

	var = NewRealArray(1, 3L);
	CArray2Array(var, eyePosn);
	SetVar(observer, LOCATION, var);
    
	ImInvalid(observer);
	wakeAgain = true;
#endif
    }
    if (wakeAgain)
    {
	WakeMe(observer, MARKTIME, Clock() + 0.001);
    }
    return ObjTrue;
}

#ifdef PROTO
void SetRotationMotor(real rotSpeed, real ax, real ay, real az)
#else
void SetRotationMotor(rotSpeed, ax, ay, az)
real rotSpeed, ax, ay, az;
#endif
/*Sets a rotation motor at rotSpeed around axis ax, ay, az*/
{
    ObjPtr var;
    real axis[3];
    char logLine[256];
    ObjPtr space, observer;

    if (!selWinInfo)
    {
	return;
    }

    space = FindSpace(selWinInfo);
    if (!space)
    {
	return;
    }

    observer = GetObjectVar("SetRotationMotor", space, OBSERVER);
    if (!observer)
    {
	return;
    }

    var = NewRealArray(1, 3L);
    axis[0] = ax;
    axis[1] = ay;
    axis[2] = az;
    CArray2Array(var, axis);

    SetVar(observer, ROTAXIS, var);
    SetVar(observer, ROTSPEED, NewReal(rotSpeed));

    if (logging)
    {
	sprintf(logLine, "set rotation %g [%g %g %g]\n",
		rotSpeed * 180.0 / M_PI, ax, ay, az);
	Log(logLine);
    }

    if (rotSpeed > 0.0)
    {
	DoNotDisturb(observer, MARKTIME);
	WakeMe(observer, MARKTIME, Clock() + 0.001);
    }
}

#ifdef PROTO
void LogObserver(ObjPtr observer)
#else
void LogObserver(observer)
ObjPtr observer;
#endif
/*Logs an observer*/
{
    ObjPtr var;
    real temp[3];

    if (logging)
    {
	Log("begin snapshot observer\n");

	LogVariable(observer, LOCATION);
	LogVariable(observer, FORWARDVECTOR);
	LogVariable(observer, UPVECTOR);
	LogVariable(observer, FOCUSDIST);

	Log("end snapshot\n");
    }
}

#ifdef PROTO
ObjPtr MoveSpace(ObjPtr object, int x, int y, long flags)
#else
ObjPtr MoveSpace(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
#endif
/*Does a move in a space beginning at x and y.  Returns
  ObjTrue if it was a good move.*/
{
#ifdef INTERACTIVE
    int d;
    int newX, newY;
    ObjPtr objects, observer, corral;
    ThingListPtr runner;
    ObjPtr tempObj;
    real eyePosn[3];
    real up[3], forward[3], side[3];
    real moveVector[3];
    real uMove, vMove;
    real sr, cr;
    real sp, cp;
    real sy, cy;
    real sf;
    real focusDist;
    int left, right, bottom, top;

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    d = top - bottom;

    corral = GetVar(object, CORRAL);

    SetRotationMotor(0.0, 0.0, 0.0, 1.0);

    SetVar(object, MOVING, ObjTrue);

    /*Get info about the observer*/
    observer = GetObjectVar("MoveSpace", object, OBSERVER);

    SaveForUndo(observer);

    GetObserverLocation(eyePosn, observer);
    GetAdjustedVectors(forward, up, observer);
    CROSS(forward, up, side);

    /*Get perspective stuff for scaling movement and eyeball distance*/
    tempObj = GetRealVar("PressSpace", observer, FOCUSDIST);
    if (tempObj)
    {
	focusDist = GetReal(tempObj);
    }
    else
    {
	focusDist = INITEYEDIST;
    }

    if (flags & F_DOUBLECLICK)
    {
    
    }
    /*Press the contents of the space*/
    else while (Mouse(&newX, &newY))
    {
	if (newX != x && newY != y)
	{
	    /*Get raw uMove and vMove*/
	    sf = focusDist * MOVEFACTOR;
	    uMove = sf * ((float) (x - newX)) / (float) d;
	    vMove = sf * ((float) (y - newY)) / (float) d;

	    moveVector[0] = uMove * side[0] + vMove * up[0];

	    /*Produce a motion vector*/
	    moveVector[0] = uMove * side[0] + vMove * up[0];
	    moveVector[1] = uMove * side[1] + vMove * up[1];
	    moveVector[2] = uMove * side[2] + vMove * up[2];

	    if (flags & F_SHIFTDOWN)
	    {
		int k, best = 0;
		float curDot, maxDot = 0.0;
		float rr;

		/*Constrain the motion axis to the nearest ortho axis*/
		for (k = 0; k < 3; ++k)
		{
		    curDot =
			moveVector[0] * Identity[k][0] +
			moveVector[1] * Identity[k][1] +
			moveVector[2] * Identity[k][2];

		    if (ABS(curDot) > ABS(maxDot))
		    {
			/*It's a better choice*/
			maxDot = curDot;
			best = k;
		    }
		}

		/*Now we have a best match*/
		moveVector[0] = maxDot * Identity[best][0];
		moveVector[1] = maxDot * Identity[best][1];
		moveVector[2] = maxDot * Identity[best][2];
	    }

	    eyePosn[0] += moveVector[0];
	    eyePosn[1] += moveVector[1];
	    eyePosn[2] += moveVector[2];

	    /*Put eyePosn back*/
	    tempObj = NewRealArray(1, 3L);
	    CArray2Array(tempObj, eyePosn);
	    SetVar(observer, LOCATION, tempObj);

	    x = newX;
	    y = newY;

	    ImInvalid(observer);
	    DrawMe(object);
	}
    }
    SetVar(object, MOVING, ObjFalse);

    if (logging)
    {
	LogObserver(observer);
    }
#endif
    return ObjTrue;
}

#ifdef PROTO
ObjPtr RotateSpace(ObjPtr object, int x, int y, long flags)
#else
ObjPtr RotateSpace(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
#endif
/*Does a rotate in a space beginning at x and y.  Returns
  true iff the rotate really was in the panel.*/
{
    int left, right, bottom, top;
    ObjPtr observer/*, observers*/;
    ObjPtr lights;
    ThingListPtr runner;

#ifdef INTERACTIVE
    Get2DIntBounds(object, &left, &right, &bottom, &top);

    /*See if there are any lights to rotate*/
    lights = GetVar(object, LIGHTS);
    if (lights)
    {
	runner = LISTOF(lights);
	while (runner)
	{
	    if (IsSelected(runner -> thing))
	    {
		break;
	    }
	    runner = runner -> next;
	}
    }

    /*If none selected, say there are none*/
    if (!runner)
    {
	lights = NULLOBJ;
    }

    /*Get the observer*/
    observer = GetObjectVar("RotateSpace", object, OBSERVER);

    if (observer)
    {
	Bool motorOn = false;		/*True iff motor on*/
	float rotSpeed = 0.0;		/*Rotation speed*/
	float ax, ay, az;			/*Three components of axis of rotation*/
	real axis[3];			/*Final axis of rotation*/
	ObjPtr var;				/*Temporary var*/
	real eyePosn[3];	/*Observer info*/
	real focusDist;
	real focusPoint[3];
	real forward[3], up[3], side[3];	/*Observer axes*/

	SaveForUndo(observer);

	var = GetRealVar("RotateSpace", observer, FOCUSDIST);
	if (var)
	{
	    focusDist = GetReal(var);
	}
	else
	{
	    focusDist = INITEYEDIST;
	}
	GetFocusPoint(focusPoint, observer);
	GetObserverLocation(eyePosn, observer);
	GetAdjustedVectors(forward, up, observer);

	CROSS(forward, up, side);
	NORMALIZE(side);

	if (flags & F_DOUBLECLICK)
	{
	    if (lights)
	    {
		/*Double click.  Snap lights to their closest values*/

		runner = LISTOF(lights);
		while (runner)
		{
		    if (IsSelected(runner -> thing))
		    {
			/*Rotate the light*/
			ObjPtr var;
			int i;
			float biggest;
			int ibiggest;
			real location[3];
	
			var = GetFixedArrayVar("RotateSpace", runner -> thing, LOCATION, 1, 3L);
			if (var)
			{
			    Array2CArray(location, var);
			    biggest = 0.0;
			    ibiggest = 0;
			    for (i = 0; i < 3; ++i)
			    {
				if (ABS(location[i]) > biggest)
				{
				    ibiggest = i;
				    biggest = ABS(location[i]);
				}
			    }
			    location[ibiggest] =
				location[ibiggest] > 0.0 ? 1.0 : -1.0;
			    for (i = 0; i < 3; ++i)
			    {
				if (i != ibiggest)
				{
				    location[i] = 0.0;
				}
			    }
			    var = NewRealArray(1, 3L);
			    CArray2Array(var, location);
			    SetVar(runner -> thing, LOCATION, var);
			    ImInvalid(runner -> thing);
			}
		    }
		    runner = runner -> next;
		}
	    }
	    else
	    {
		/*It was a double click.  Align forward and up to the
		  nearest orthogonal unit vector*/
		Matrix newRot;	/*New rotation matrix*/
		Bool set;		/*True iff this j has been set nonzero*/
		int i, j;		/*Counters within the matrix*/
		float biggest;	/*The biggest xform coefficient so far*/
		int jbiggest;	/*The j of biggest*/

		/*First do forward*/
		biggest = 0.0;
		jbiggest = -1;
		for (j = 0; j < 3; ++j)
		{
		    if (ABS(forward[j]) > biggest)
		    {
			biggest = ABS(forward[j]);
			jbiggest = j;
		    }
		}
		if (jbiggest == -1)
		{
		    return ObjTrue;
		}
		for (j = 0; j < 3; ++j)
		{
		    if (j == jbiggest)
		    {
			forward[j] = forward[j] > 0 ? 1.0 : -1.0;
		    }
		    else
		    {
			forward[j] = 0.0;
		    }
		}

		/*Now do up*/
		biggest = 0.0;
		jbiggest = -1;
		for (j = 0; j < 3; ++j)
		{
		    if (ABS(up[j]) > biggest)
		    {
			biggest = ABS(up[j]);
			jbiggest = j;
		    }
		}
		if (jbiggest == -1)
		{
		    return ObjTrue;
		}
		for (j = 0; j < 3; ++j)
		{
		    if (j == jbiggest)
		    {
			up[j] = up[j] > 0 ? 1.0 : -1.0;
		    }
		    else
		    {
			up[j] = 0.0;
		    }
		}

		/*Make sure up is not the same as forward*/
		if ((ABS(forward[0]) == ABS(up[0])) &&
		    (ABS(forward[1]) == ABS(up[1])) &&
		    (ABS(forward[2]) == ABS(up[2])))
		{
		    /*Make a different up based on forward*/
		    up[0] = forward[1];
		    up[1] = forward[2];
		    up[2] = forward[0];
		}

		/*Make new variables for observer*/
		eyePosn[0] = focusPoint[0] - focusDist * forward[0];
		eyePosn[1] = focusPoint[1] - focusDist * forward[1];
		eyePosn[2] = focusPoint[2] - focusDist * forward[2];
		var = NewRealArray(1, 3L);
		CArray2Array(var, eyePosn);
		SetVar(observer, LOCATION, var);

		var = NewRealArray(1, 3L);
		CArray2Array(var, up);
		SetVar(observer, UPVECTOR, var);

		var = NewRealArray(1, 3L);
		CArray2Array(var, forward);
		SetVar(observer, FORWARDVECTOR, var);

		ImInvalid(observer);
		if (logging)
		{
		    LogObserver(observer);
		}
	    }
	    ImInvalid(object);
	}
	else
	{
	    /*It's a click and drag.  Do the trackball stuff.*/
	    float Ox, Oy;		/*Center of virtual trackball*/
	    float r;		/*Radius of virtual trackball*/
	    float omega;		/*Ratio of the click radius to the trackball radius*/
	    float theta;		/*Angle to the click radius*/
	    float tao;		/*Angle from the click radius to the direction*/
	    float phi;		/*Amount the trackball is pushed*/
	    int newX, newY;		/*New X and Y values*/
	    Bool firstRun = true;	/*The first run*/
	    float lastTime = 0.0;   /*The last time*/
	    float cumPhi = 0.0;	/*Cumulative phi*/
	    float curTime = 0.0;	/*The current time*/

	    SetVar(object, ROTATING, ObjTrue);

	    /*Calculate origin and radius of trackball*/
	    Ox = (left + right) / 2;
	    Oy = (top + bottom) / 2;
	    r = 0.4 * (float) (top - bottom);

	    x -= Ox;		/*Offset x and y around trackball*/
	    y -= Oy;

	    while (Mouse(&newX, &newY))
	    {
		newX -= Ox;		/*Offset the new x and y*/
		newY -= Oy;
	    
		if (ABS(newX - x) >= MINROT || ABS(newY - y) >= MINROT)
		{
		    /*Now we have a differential*/
		    float dr;		/*Click axis distance*/
		    float sw, cw;		/*Sin and cosine of omega*/
		    float st, ct;		/*Sin and cosine of theta*/
		    float sp, cp;		/*Sin and cosine of phi*/
		    float a1x, a1y, a1z;	/*Temporary values for axis of rotation*/
		    float a2x, a2y, a2z;	/*Temporary values for axis of rotation*/
		    float t;		/*1-cos phi*/

		    dr = fsqrt(SQUARE((float) x) +
			       SQUARE((float) y));
		    omega = f(dr / r);

		    /*Calculate theta*/
		    theta = fatan2((float) (y), (float) (x));

		    /*Calculate tao as offset from theta*/
		    tao = fatan2((float) (newY - y), (float) (newX - x)) - theta;

		    /*Calculate phi simplistically*/
		    phi = fsqrt(SQUARE((float) (newX - x)) +
				SQUARE((float) (newY - y))) / r;

		    /*Calculate sin and cos of the angles for speed*/
		    sw = fsin(omega);
		    cw = fcos(omega);
		    st = fsin(theta);
		    ct = fcos(theta);
		    sp = fsin(phi);
		    cp = fcos(phi);
		    t = 1.0 - cp;

		    /*Calculate the axis of rotation*/

		    /*First the motion from origin component*/
		    a1x = -fsin(tao);
		    a1y = fcos(tao);
		    a1z = 0.0;

		    /*Now multiply in the "on x-axis" component*/
		    a2x = a1x * cw + a1z * sw;
		    a2y = a1y;
		    a2z = a1x * -sw + a1z * cw;

		    /*Now multiply in the "from x-axis" component*/
		    ax = a2x * ct + a2y * -st;
		    ay = a2x * st + a2y * ct;
		    az = a2z;

		    /*Calculate the phi and delta time*/
		    if (firstRun)
		    {
			firstRun = false;
			cumPhi = 0.0;
			lastTime = Clock();
		    }
		    else
		    {
			curTime = Clock();
			cumPhi += phi;
			if (curTime > lastTime + MINROTTIME)
			{
			    motorOn = true;
			    rotSpeed = cumPhi / (curTime - lastTime);
			    lastTime = curTime;
			    cumPhi = 0.0;
			}
		    }

		    /*Calculate axis based on combinations of
		      forward, up, and side vectors*/

		    axis[0] = 0.0;
		    axis[1] = 0.0;
		    axis[2] = 0.0;

		    axis[0] += ax * side[0];
		    axis[1] += ax * side[1];
		    axis[2] += ax * side[2];

		    axis[0] += ay * up[0];
		    axis[1] += ay * up[1];
		    axis[2] += ay * up[2];

		    axis[0] -= az * forward[0];
		    axis[1] -= az * forward[1];
		    axis[2] -= az * forward[2];

		    /*Now that there's a new delta, save it and redraw*/
		    if (lights)
		    {
			RotateLights(lights, axis, phi, flags & F_SHIFTDOWN ? true : false, observer, object);
		    }
		    else
		    {
			RotateObserver(observer, axis, phi, flags & F_SHIFTDOWN ? true : false);
		    }

		    x = newX;
		    y = newY;
		    ImInvalid(observer);
		    DrawMe(object);
		}
		else
		{
		    firstRun = true;
		    motorOn = false;
		}
	    }
	    SetVar(object, ROTATING, false);
	}

	if (logging)
	{
	    char cmd[256];
	    char *s;
	    Bool rotLights;
	    ThingListPtr runner;

	    /*See if it's lights that were rotated*/
	    rotLights = false;
	    if (lights)
	    {
		runner = LISTOF(lights);

		while (runner)
		{
		    if (IsSelected(runner -> thing))
		    {
			/*Rotated light*/
	    
			ObjPtr var;
			real location[3];
			    
			rotLights = true;
			var = GetFixedArrayVar("RotateSpace", runner -> thing, LOCATION, 1, 3L);
			if (var)
			{
			    Array2CArray(location, var);
			    sprintf(cmd, "set location ");
			    s = cmd;
			    while (*s) ++s;
			    MakeObjectName(s, runner -> thing);
			    while (*s) ++s;
			    sprintf(s, " [%g %g %g]\n",
				    location[0],
				    location[1],
				    location[2]);
			    Log(cmd);
			}
		    }
		    runner = runner -> next;
		}
	    }
	    else
	    {
		/*No lights rotated*/
		LogObserver(observer);
	    }
	}

	if (!lights)
	{
	    if (GetPrefTruth(PREF_ROTINERTIA) && motorOn)
	    {
		SetRotationMotor((real) rotSpeed, axis[0], axis[1], axis[2]);
	    }
	    else
	    {
		SetRotationMotor((real) 0.0, axis[0], axis[1], axis[2]);
	    }
	}

    }
#endif
    return ObjTrue;
}

static ObjPtr FingerSpace(space, x, y, flags)
ObjPtr space;
int x, y;
long flags;
/*Does a 3-D finger in a space*/
{
    int left, right, bottom, top;
    ObjPtr contents;
    ThingListPtr runner;
    FuncTyp method;

    Get2DIntBounds(space, &left, &right, &bottom, &top);

    StartSpace(space, left, right, bottom, top, PICKSPACE, 0);

    /*Pick the contents of the space*/
    contents = GetVar(space, CONTENTS);

    runner = LISTOF(contents);
    while (runner)
    {
	if (IsObject(runner -> thing))
	{
	    PickObject(runner -> thing, PR_CENTER);
	    method = GetMethod(runner -> thing, PICKPOINT);
	    if (method)
	    {
		(*method)(runner -> thing);
	    }
	}
	runner = runner -> next;
    }

    StopSpace(PICKSPACE);

    if (0 == (flags & F_EXTEND))
    {
	DeselectAll();
    }

    if (spacePickedObject)
    {
	if ((bestPickVertex >= 0) && (method = GetMethod(spacePickedObject, 
		SELECTSELPOINT)))
	{
	    if (!IsSelected(spacePickedObject))
	    {
		Select(spacePickedObject, true);
	    }
	    (*method)(spacePickedObject, bestPickVertex, flags);
	}
	else
	{
	    /*Select or deselect as a whole*/
	    if ((flags & F_EXTEND) && IsSelected(spacePickedObject))
	    {
		Select(spacePickedObject, false);
	    }
	    else
	    {
		Select(spacePickedObject, true);
	    }
	}
	return ObjTrue;
    }
    return ObjFalse;
}


static ObjPtr PressSpace(object, x, y, flags)
ObjPtr object;
int x, y;
long flags;
/*Does a press in a space beginning at x and y.  Returns
  true iff the press really was in the space.*/
{
#ifdef INTERACTIVE
    int left, right, bottom, top;
    ObjPtr tool;
    int spaceTool;
 
    /*Get the space tool*/
    tool = GetIntVar("PressSpace", object, EDITTOOL);
    if (!tool)
    {
	return ObjFalse;
    }
    spaceTool = GetInt(tool);

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(object);
	    return ObjTrue;
	}

	/*It's a click in the space*/
	switch (spaceTool)
	{
	    case ST_FLYING:
		{
		    /*Toggle flying*/
		    Bool flying;
		    ObjPtr observer;
		    MakeMeCurrent(object);

		    observer = GetObjectVar("PressSpace", object, OBSERVER);
		    if (!observer) return ObjFalse;
	    
		    flying = GetPredicate(observer, FLYING);
		    flying = flying ? false : true;
		    if (flying)
		    {
			WakeMe(observer, MARKTIME, Clock() + 0.001);
		    }
		    else
		    {
			DoNotDisturb(observer, MARKTIME);
		    }
	    
		    SetVar(observer, FLYING, flying ? ObjTrue : ObjFalse);
		}
		return ObjTrue;
		break;
	    case ST_FINGER:
		MakeMeCurrent(object);
		if (TOOL(flags) == T_ROTATE)
		{
		    return RotateSpace(object, x, y, flags);
		}
		else
		{
		    ObjPtr test;
		    test = FingerSpace(object, x, y, flags);
		    if (IsTrue(test))
		    {
		        MoveSpace(object, x, y, flags);
			return ObjTrue;
		    }
		}
	}
	return ObjFalse;
    }
    else
#endif
    {
	return ObjFalse;
    }
}

int dsn = 0;

#ifdef PROTO
void DrawSpaceContents(ObjPtr space, int left, int right, int bottom, int top, int viewType)
#else
void DrawSpaceContents(space, left, right, bottom, top, viewType)
ObjPtr space;
int left, right, bottom, top;
int viewType;
#endif
{
#ifdef GRAPHICS
    ObjPtr contents;
    Bool anyTransparent;		/*True iff any objects are transparent*/
    ThingListPtr drawList;		/*List of objects to draw*/
    ObjPtr frontPanel;
    int prefDrawMoving;			/*Preferred draw moving style*/
    ObjPtr var;

    frontPanel = GetVar(space, FRONTPANEL);
    if (frontPanel &&
	(GetVar(frontPanel, BACKGROUND) || GetPredicate(frontPanel, TEMPOBSCURED)))
    {
	/*Front panel has a background.  Don't need to draw space*/
	return;
    }

    /*Set a new viewport to the current area*/
    StartSpace(space, left, right, bottom, top, DRAWSPACE, viewType);

    /*Figure out the range of drawing qualities to use.*/
    if (interactiveMoving)
    {
	/*It's interactive.  Figure out what quality to use*/
	prefDrawMoving = GetPrefInteger(PREF_DRAWMOVING);

	if (prefDrawMoving == DM_SKELETON)
	{
	    minDrawingQuality = DQ_MIN;
	    maxDrawingQuality = DQ_OUTLINE;
	}
	else if (prefDrawMoving == DM_FULL)
	{
	    minDrawingQuality = DQ_MIN;
	    maxDrawingQuality = DQ_MAX;
	}
	else if (prefDrawMoving == DM_AUTO)
	{
	    var = GetVar(space, DRAWINGTIME);
	    if (var)
	    {
		long drawingTime;
		/*There is a drawing time recorded.  Get it*/
		drawingTime = GetInt(var);

		if (drawingTime < MAXMOVETIME)
		{
		    /*It's fast enough, might as well do it fully*/
		    minDrawingQuality = DQ_MIN;
		    maxDrawingQuality = DQ_MAX;
		}
		else
		{
		    /*Do outline*/
		    minDrawingQuality = DQ_MIN;
		    maxDrawingQuality = DQ_OUTLINE;
		}
	    }
	    else
	    {
		/*Assume we have to do full drawing*/
		minDrawingQuality = DQ_MIN;
		maxDrawingQuality = DQ_MAX;
	    }
	}
    }
    else
    {
	/*Not interactive.  Just use full quality*/
	minDrawingQuality = DQ_MIN;
	maxDrawingQuality = DQ_MAX;
    }

    /*Draw the contents of the space*/
    contents = GetVar(space, CONTENTS);

    /*First draw opaque objects*/
    anyTransparent = false;
    drawList = LISTOF(contents);
    drawingTransparent = false;
    while (drawList)
    {
	if (IsObject(drawList -> thing))
	{
	    if (GetPredicate(drawList -> thing, ISTRANSPARENT))
	    {
		anyTransparent = true;
	    }
	    qualityDrawnSoFar = DQ_MIN;
	    DrawVisObject(drawList -> thing);
	    if (maxDrawingQuality < DQ_FULL)
	    {
		ImInvalid(drawList -> thing);
	    }
	}
	drawList = drawList -> next;
    }

    /*Now draw transparent objects*/
    if (anyTransparent && rgbp)
    {
	zwritemask(0);
	blendfunction(BF_SA, BF_MSC);

	drawingTransparent = true;
	drawList = LISTOF(contents);
	while (drawList)
	{
	    minDrawingQuality = DQ_FULL;	
	    maxDrawingQuality = DQ_FULL;
	    if (IsObject(drawList -> thing))
	    {
		qualityDrawnSoFar = DQ_MIN;
		DrawObject(drawList -> thing);
	    }
	    drawList = drawList -> next;
	}
	blendfunction(BF_ONE, BF_ZERO);
	zwritemask(0xffffffff);
	drawingTransparent = false;
    }

    if (maxDrawingQuality >= DQ_FULL)
    {
	/*End the timing of the full drawing*/
	SetVar(space, DRAWINGTIME, NewInt(GetPictureTime()));
    }

    StopSpace(DRAWSPACE);
#endif
}

#ifdef PROTO
Bool RotateObserver(ObjPtr observer, real axis[3], real phi, Bool constrain)
#else
Bool RotateObserver(observer, axis, phi, constrain)
ObjPtr observer;
real axis[3];
real phi;
Bool constrain;
#endif
/*Rotates an observer by phi around axis.  If constrain, constrains axis as
  a side effect.*/
{
    ObjPtr var;
    real focusPoint[3];
    real location[3];
    real upVector[3];
    real forwardVector[3];
    real tempVector[3];
    real sideVector[3];
    real focusDist;
    Matrix rotDelta;
    float t;
    float sp, cp;		/*Sin and cosine of phi*/

    sp = rsin(phi);
    cp = rcos(phi);
    t = 1.0 - cp;

    /*Now that there's a new delta, rotate and redraw*/
    if (constrain)
    {
	int k, best;
	float curDot, maxDot = 0.0;
	float rr;

	/*Constrain the axis to the nearest ortho axis*/
	for (k = 0; k < 3; ++k)
	{
	    curDot =
		axis[0] * Identity[k][0] +
		axis[1] * Identity[k][1] +
		axis[2] * Identity[k][2];

	    if (ABS(curDot) > ABS(maxDot))
	    {
		/*It's a better choice*/
		maxDot = curDot;
		best = k;
	    }
	}

	/*Now we have a best match*/
	axis[0] = Identity[best][0];
	axis[1] = Identity[best][1];
	axis[2] = Identity[best][2];
	if (maxDot < 0.0)
	{
	    axis[0] = -axis[0];
	    axis[1] = -axis[1];
	    axis[2] = -axis[2];
	}

	/*Normalize the axis*/
	rr = 1.0 / sqrt(axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]);
	axis[0] *= rr;
	axis[1] *= rr;
	axis[2] *= rr;
    }

    /*Now make the change rotation matrix by rows from top to bottom*/
    rotDelta[0][0] = t * SQUARE(axis[0]) + cp;
    rotDelta[0][1] = t * axis[0] * axis[1] + sp * axis[2];
    rotDelta[0][2] = t * axis[0] * axis[2] - sp * axis[1];
    rotDelta[0][3] = 0.0;

    rotDelta[1][0] = t * axis[0] * axis[1] - sp * axis[2];
    rotDelta[1][1] = t * SQUARE(axis[1]) + cp;
    rotDelta[1][2] = t * axis[1] * axis[2] + sp * axis[0];
    rotDelta[1][3] = 0.0;

    rotDelta[2][0] = t * axis[0] * axis[2] + sp * axis[1];
    rotDelta[2][1] = t * axis[1] * axis[2] - sp * axis[0];
    rotDelta[2][2] = t * SQUARE(axis[2]) + cp;
    rotDelta[2][3] = 0.0;

    rotDelta[3][0] = 0.0;
    rotDelta[3][1] = 0.0;
    rotDelta[3][2] = 0.0;
    rotDelta[3][3] = 1.0;

    /*Get information from observer*/
    GetFocusPoint(focusPoint, observer);
    GetForwardVector(forwardVector, observer);
    GetUpVector(upVector, observer);
    GetObserverLocation(location, observer);
    var = GetVar(observer, FOCUSDIST);
    if (var)
    {
	focusDist = GetReal(var);
    }
    else
    {
	focusDist = INITEYEDIST;
    }

    /*Rotate forward vector by the delta rotation*/
    MATBYVECTOR(rotDelta, forwardVector, tempVector);
    VECCOPY(forwardVector, tempVector);

    /*Rotate the up vector by the delta rotation*/
    MATBYVECTOR(rotDelta, upVector, tempVector);

    /*Make a size vector*/
    CROSS(forwardVector, tempVector, sideVector);

    /*And now a new up vector*/
    CROSS(sideVector, forwardVector, upVector);
    NORMALIZE(upVector);

    /*Make a new location*/
    location[0] = focusPoint[0] - forwardVector[0] * focusDist;
    location[1] = focusPoint[1] - forwardVector[1] * focusDist;
    location[2] = focusPoint[2] - forwardVector[2] * focusDist;

    /*Put back the location and up vector*/
    var = NewRealArray(1, 3L);
    CArray2Array(var, location);
    SetVar(observer, LOCATION, var);

    var = NewRealArray(1, 3L);
    CArray2Array(var, upVector);
    SetVar(observer, UPVECTOR, var);

    var = NewRealArray(1, 3L);
    CArray2Array(var, forwardVector);
    SetVar(observer, FORWARDVECTOR, var);

    ImInvalid(observer);
    return true;
}

void PrintMatrix(real m[4][4])
/*Prints a matrix*/
{
    printf("%10g %10g %10g %10g\n", m[0][0], m[0][1], m[0][2], m[0][3]);
    printf("%10g %10g %10g %10g\n", m[1][0], m[1][1], m[1][2], m[1][3]);
    printf("%10g %10g %10g %10g\n", m[2][0], m[2][1], m[2][2], m[2][3]);
    printf("%10g %10g %10g %10g\n", m[3][0], m[3][1], m[3][2], m[3][3]);
}

void QInvert(Quaternion s, Quaternion r)
/*Inverts quaternion s into result r, assuming |s| == 1*/
{
    r[0] = s[0];
    r[1] = -s[1];
    r[2] = -s[2];
    r[3] = -s[3];
}

#ifdef PROTO
void MatrixToQuaternion(Matrix m, Quaternion q)
#else
void MatrixToQuaternion(m, q)
Matrix m;
Quaternion q;
#endif
/*Converts a matrix (rotation part only) to a quaternion.
  BUGGY, use version from seq.c*/
{
    real greatestSquare;
    char whichGreatest;
    real square;
    real dividend;

#undef w
#undef x
#undef y
#undef z
#define w q[0]
#define x q[1]
#define y q[2]
#define z q[3]

    /*Solving along diagonal, see which is greatest*/
    greatestSquare = 0.0;
    whichGreatest = ' ';

    /*Test solving for x*/
    if ((square = 1.0 - m[2][2] + m[0][0] - m[1][1]) > greatestSquare)
    {
	greatestSquare = square;
	whichGreatest = 'x';
    }

    /*Test solving for y*/
    if ((square = 1.0 - m[0][0] + m[1][1] - m[2][2]) > greatestSquare)
    {
	greatestSquare = square;
	whichGreatest = 'y';
    }

    /*Test solving for z*/
    if ((square = 1.0 - m[1][1] + m[2][2] - m[0][0]) > greatestSquare)
    {
	greatestSquare = square;
	whichGreatest = 'z';
    }

    if (whichGreatest == 'x')
    {
	/*Arbitrarily choose +x*/
	x = sqrt(greatestSquare) * 0.5;
	dividend = 1.0 / (4.0 * x);

	/*Calculate y*/
	y = (m[0][1] + m[1][0]) * dividend;

	/*Calculate z*/
	z = (m[0][2] + m[2][0]) * dividend;

	/*Calculate w*/
	w = 2.0 * (2.0 * y * z - m[2][1]) * dividend; 
    }
    else if (whichGreatest == 'y')
    {
	/*Arbitrarily choose +y*/
	y = sqrt(greatestSquare) * 0.5;
	dividend = 1.0 / (4.0 * y);

	/*Calculate x*/
	x = (m[0][1] + m[1][0]) * dividend;

	/*Calculate z*/
	z = (m[2][1] + m[1][2]) * dividend;

	/*Calculate w*/
	w = 2.0 * (2.0 * x * z - m[0][2]) * dividend; 
    }
    else if (whichGreatest == 'z')
    {
	/*Arbitrarily choose +z*/
	z = sqrt(greatestSquare) * 0.5;
	dividend = 1.0 / (4.0 * z);

	/*Calculate x*/
	x = (m[0][2] + m[2][0]) * dividend;

	/*Calculate y*/
	y = (m[2][1] + m[1][2]) * dividend;

	/*Calculate w*/
	w = 2.0 * (2.0 * x * y - m[1][0]) * dividend; 
    }
    else
    {
	/*It has to be [1, [0 0 0]]*/
	q[0] = 1.0;
	q[1] = 0.0;
	q[2] = 0.0;
	q[3] = 0.0;
    }
#undef w
#undef x
#undef y
#undef z
}

void QMult(Quaternion q1, Quaternion q2, Quaternion r)
/*Multiplies two quaternions giving a third*/
{
    real qp[3];

    r[0] = q1[0] * q2[0] - (q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3]);

    CROSS(&(q1[1]), &(q2[1]), qp);
    r[1] = q1[0] * q2[1] + q2[0] * q1[1] + qp[0];
    r[2] = q1[0] * q2[2] + q2[0] * q1[2] + qp[1];
    r[3] = q1[0] * q2[3] + q2[0] * q1[3] + qp[2];
}

TestObserver()
/*Does an observer test in the current window*/
{
    ObjPtr observer;
    real *forward, *up;

    if (selWinInfo)
    {
	observer = FindObserver(selWinInfo);
	if (observer)
	{
	    ObjPtr var;
	    real *elements;
	    real side[3], m[4][4];
	    Quaternion q, qInv, qTest, qVec;

	    var = GetVar(observer, LOCATION);
	    if (var)
	    {
		forward = (real *) ELEMENTS(var);
		printf("Location = [%g %g %g]\n",
			forward[0], forward[1], forward[2]);
	    }

	    var = GetVar(observer, FORWARDVECTOR);
	    if (var)
	    {
		forward = (real *) ELEMENTS(var);
		printf("Forward = [%g %g %g]\n",
			forward[0], forward[1], forward[2]);
	    }

	    var = GetVar(observer, UPVECTOR);
	    if (var)
	    {
		up = (real *) ELEMENTS(var);
		printf("Up = [%g %g %g]\n",
			up[0], up[1], up[2]);
	    }
	    /* generate the vector side from the
	     * 2 vectors view forward and world up 
	     */
	    CROSS(forward, up, side);
	    NORMALIZE(side);

	    m[0][0] = side[0];
	    m[1][0] = side[1];
	    m[2][0] = side[2];
	    m[3][0] = 0.0;

	    m[0][1] = up[0];
	    m[1][1] = up[1];
	    m[2][1] = up[2];
	    m[3][1] = 0.0;

	    m[0][2] = -forward[0];
	    m[1][2] = -forward[1];
	    m[2][2] = -forward[2];
	    m[3][2] = 0.0;

	    m[0][3] = 0.0;
	    m[1][3] = 0.0;
	    m[2][3] = 0.0;
	    m[3][3] = 1.0;

#if 0
	    printf("Lookat matrix = \n");
	    PrintMatrix(m);

	    /*Now try to make quaternion*/
	    MatrixToQuaternion(m, q);
	    printf("Rotation quaternion = %g %g %g %g\n",
			q[0], q[1], q[2], q[3]);
	    printf("Absolute value squared = %g\n",
			q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]);
	    QInvert(q, qInv);
	    printf("Inverted = %g %g %g %g\n",
			qInv[0], qInv[1], qInv[2], qInv[3]);

	    qVec[0] = 0.0;
	    qVec[1] = 0.0;
	    qVec[2] = 0.0;
	    qVec[3] = -1.0;
	    QMult(qInv, qVec, qTest);
	    QMult(qTest, q, qVec);
	    printf("Rotated forward = %g %g %g %g\n",
			qVec[0], qVec[1], qVec[2], qVec[3]);
	    qVec[0] = 0.0;
	    qVec[1] = 0.0;
	    qVec[2] = 1.0;
	    qVec[3] = 0.0;
	    QMult(qInv, qVec, qTest);
	    QMult(qTest, q, qVec);
	    printf("Rotated up = %g %g %g %g\n",
			qVec[0], qVec[1], qVec[2], qVec[3]);
#endif
	}
    }
}

#ifdef PROTO
Bool RotateLights(ObjPtr lights, real startAxis[3], real phi, Bool constrain, ObjPtr observer, ObjPtr space)
#else
Bool RotateLights(lights, axis, phi, constrain, observer, space)
ObjPtr lights;
real startAxis[3];
real phi;
Bool constrain;
ObjPtr observer;
ObjPtr space;
#endif
/*Rotates lights by rotDelta wrt observer within space*/
{
    ThingListPtr runner;
    Matrix rotDelta;
    real axis[3];
    float t;
    float sp, cp;		/*Sin and cosine of phi*/

    runner = LISTOF(lights);
    while (runner)
    {
	if (IsSelected(runner -> thing))
	{
	    /*Rotate the light*/
	    ObjPtr var;
	    real location[3];
	    real newLoc[3];
	    float tv[3];

	    axis[0] = startAxis[0];
	    axis[1] = startAxis[1];
	    axis[2] = startAxis[2];

	    var = GetFixedArrayVar("DrawSpace", runner -> thing, LOCATION, 1, 3L);
	    if (!var)
	    {
		return false;
	    }
	    Array2CArray(location, var);

		/*Test elevation*/
		tv[0] = location[1];
		tv[1] = -location[0];
		tv[2] = 0.0;
   
	    if (constrain)
	    {
		float dot1, dot2;
		float rr;

		/*Constrain the axis to the azimuth or elevation*/

		/*Test azimuth*/
		dot1 = axis[2];

		/*Test elevation*/
		rr = sqrt(tv[0] * tv[0] + tv[1] * tv[1] + tv[2] * tv[2]);
		if (rr > 0.0)
		{
		    rr = 1.0 / rr;
		    tv[0] *= rr;
		    tv[1] *= rr;
		    tv[2] *= rr;
		}
		else
		{
		    tv[0] = 1.0;
		    tv[1] = 0.0;
		    tv[2] = 0.0;
		}

		dot2 = axis[0] * tv[0] +
		       axis[1] * tv[1] +
		       axis[2] * tv[2];

		if (ABS(dot1) > ABS(dot2))
		{
		    /*Azimuth is better*/
		    axis[0] = 0.0;
		    axis[1] = 0.0;
		    axis[2] = 1.0;
		    if (dot1 < 0.0)
		    {
			axis[0] = -axis[0];
			axis[1] = -axis[1];
			axis[2] = -axis[2];
		    }
		}
	        else
		{
		    axis[0] = tv[0];
		    axis[1] = tv[1];
		    axis[2] = tv[2];
		    /*Elevation is better*/
		    if (dot2 < 0.0)
		    {
			axis[0] = -axis[0];
			axis[1] = -axis[1];
			axis[2] = -axis[2];
		    }
		}
	    }

	    sp = rsin(phi);
	    cp = rcos(phi);
	    t = 1.0 - cp;

	    /*Now make the change rotation matrix by rows from top to bottom*/
	    rotDelta[0][0] = t * SQUARE(axis[0]) + cp;
	    rotDelta[0][1] = t * axis[0] * axis[1] + sp * axis[2];
	    rotDelta[0][2] = t * axis[0] * axis[2] - sp * axis[1];
	    rotDelta[0][3] = 0.0;

	    rotDelta[1][0] = t * axis[0] * axis[1] - sp * axis[2];
	    rotDelta[1][1] = t * SQUARE(axis[1]) + cp;
	    rotDelta[1][2] = t * axis[1] * axis[2] + sp * axis[0];
	    rotDelta[1][3] = 0.0;

	    rotDelta[2][0] = t * axis[0] * axis[2] + sp * axis[1];
	    rotDelta[2][1] = t * axis[1] * axis[2] - sp * axis[0];
	    rotDelta[2][2] = t * SQUARE(axis[2]) + cp;
	    rotDelta[2][3] = 0.0;

	    rotDelta[3][0] = 0.0;
	    rotDelta[3][1] = 0.0;
	    rotDelta[3][2] = 0.0;
	    rotDelta[3][3] = 1.0;

	    /*Rotate this location by rotDelta*/
	    newLoc[0] = rotDelta[0][0] * location[0] +
			rotDelta[1][0] * location[1] +
			rotDelta[2][0] * location[2];
	    newLoc[1] = rotDelta[0][1] * location[0] +
			rotDelta[1][1] * location[1] +
			rotDelta[2][1] * location[2];
	    newLoc[2] = rotDelta[0][2] * location[0] +
			rotDelta[1][2] * location[1] +
			rotDelta[2][2] * location[2];
	    location[0] = newLoc[0];
	    location[1] = newLoc[1];
	    location[2] = newLoc[2];

	    NORM3(newLoc);
	    var = NewRealArray(1, 3L);
	    CArray2Array(var, newLoc);
	    SetVar(runner -> thing, LOCATION, var);
	    ImInvalid(runner -> thing);
	}
	runner = runner -> next;
    }
    return true;
}

int spaceDraw = 1;

ObjPtr SpacePanelBoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
/*For a space panel, tests to see if it needs drawing.  Returns
  NULLOBJ	if it does not
  array[4]	giving bounds if it does
  ObjTrue	it it needs to be redrawn but does not know where

  Contents are assumed to be shifted in the bounds of owner
*/
{
    ObjPtr contents;
    ObjPtr myBounds;
    real boundsElements[4];

    MakeVar(object, APPEARANCE);

    MakeVar(object, CHANGEDBOUNDS);
    if (GetVarChangeCount(object, CHANGEDBOUNDS) > changeCount)
    {
	/*Object is not good, so return the bounds*/

	myBounds = GetVar(object, CHANGEDBOUNDS);
	return myBounds;
    }

    myBounds = GetVar(object, BOUNDS);

    MakeVar(object, CONTENTS);
    contents = GetVar(object, CONTENTS);
    if (contents && IsList(contents))
    {
	/*Still, maybe some of the contents need to be drawn*/
	real testElements[4];
	real myBoundsElements[4];
 	ObjPtr test;
 	ThingListPtr runner;

	MakeVar(object, BOUNDS);
	myBounds = GetVar(object, BOUNDS);
	Array2CArray(myBoundsElements, myBounds);

	runner = LISTOF(contents);
	while (runner)
	{
	    test = BoundsInvalid(runner -> thing, changeCount);
	    if (test)
	    {
		/*Hey, the kid needs redrawing*/
		return myBounds;
	    }
	    runner = runner -> next;
	}
    }

    return NULLOBJ;
}

ObjPtr SpaceBoundsInvalid(object, changeCount)
ObjPtr object;
unsigned long changeCount;
/*For a space, tests to see if it needs drawing.  Returns
  NULLOBJ	if it does not
  array[4]	giving bounds if it does
  ObjTrue	it it needs to be redrawn but does not know where

  Contents are assumed to be shifted in their space
*/
{
    ObjPtr contents;
    ObjPtr lights, observer, renderer;
    ObjPtr myBounds;
    real boundsElements[4];
    real testElements[4];
    ObjPtr test;
    real myBoundsElements[4];
    ObjPtr scrollBar;
    Bool firstTime = true;
    Bool doubleNoBounds = false;
    ObjPtr retVal = NULLOBJ;

    MakeVar(object, APPEARANCE);

    MakeVar(object, BOUNDS);
    myBounds = GetVar(object, BOUNDS);
    Array2CArray(myBoundsElements, myBounds);

    if (GetVarChangeCount(object, CHANGEDBOUNDS) > changeCount)
    {
	/*Object is not good, so return the bounds*/
	ObjPtr myBounds;

	MakeVar(object, CHANGEDBOUNDS);
	myBounds = GetVar(object, CHANGEDBOUNDS);

	if (myBounds && IsArray(myBounds) && RANK(myBounds) == 1
		&& DIMS(myBounds)[0] == 4)
	{
	    retVal = myBounds;
	}
	else
	{
	    retVal = ObjTrue;
	}
    }

    MakeVar(object, CONTENTS);
    contents = GetVar(object, CONTENTS);
    if (contents && IsList(contents))
    {
	/*Still, maybe some of the contents need to be drawn*/
 	ThingListPtr runner;

	runner = LISTOF(contents);
	while (runner)
	{
	    test = BoundsInvalid(runner -> thing, changeCount);
	    if (test)
	    {
		/*Hey, the kid needs redrawing*/
		retVal = myBounds;
	    }
	    runner = runner -> next;
	}
    }

    MakeVar(object, LIGHTS);
    lights = GetVar(object, LIGHTS);
    if (lights && IsList(lights))
    {
	/*Still, maybe some of the lights need to be drawn*/
 	ThingListPtr runner;

	runner = LISTOF(lights);
	while (runner)
	{
	    test = BoundsInvalid(runner -> thing, changeCount);
	    if (test)
	    {
		/*Hey, the light needs redrawing*/
		retVal =  myBounds;
	    }
	    runner = runner -> next;
	}
    }

    MakeVar(object, OBSERVER);
    observer = GetVar(object, OBSERVER);
    if (observer)
    {
	test = BoundsInvalid(observer, changeCount);
	if (test)
	{
	    /*Hey, the observer needs redrawing*/
	    retVal = myBounds;
	}
    }

    MakeVar(object, RENDERER);
    renderer = GetVar(object, RENDERER);
    if (renderer)
    {
	test = BoundsInvalid(renderer, changeCount);
	if (test)
	{
	    /*Hey, the renderer needs redrawing*/
	    retVal =  myBounds;
	}
    }

    return retVal;
}

ObjPtr DrawSpace(object)
ObjPtr object;
/*Draws a space and everything it contains*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    ObjPtr eyePosnObj;			/*Eyeposition object*/
    ObjPtr tempObj;			/*Temporary object*/
    ObjPtr dRotMatrix;
    ObjPtr observer, observers;		/*Observer of the space*/
    ObjPtr panel;			/*Front and back panel*/
    ObjPtr var;				/*A random variable*/
    int renderType, filterType;		/*Types of renderers and filters*/
    int viewType;			/*Type of view*/

#if 0
    printf("DrawSpace %d\n", spaceDraw++);
#endif

    /*Get render type*/
    MakeVar(object, RENDERTYPE);
    var = GetIntVar("DrawSpace", object, RENDERTYPE);
    if (var)
    {
	renderType = GetInt(var);
    }
    else
    {
	renderType = hasRGB ? RT_RGB_HARDWARE : RT_CMAP_HARDWARE;
    }
    if (renderType == RT_NONE)
    {
	/*Don't render*/
	return ObjTrue;
    }

    /*Get filter type*/
    MakeVar(object, FILTERTYPE);
    var = GetIntVar("DrawSpace", object, FILTERTYPE);
    if (var)
    {
	filterType = GetInt(var);
    }
    else
    {
	filterType = FT_NONE;
    }

    Get2DIntBounds(object, &left, &right, &bottom, &top);
    if (IsDrawingRestricted(left, right, bottom, top)) return ObjFalse;

    /*Update the rotation matrix and fly through*/
    observer = GetObjectVar("DrawSpace", object, OBSERVER);

    var = GetVar(observer, VIEWTYPE);
    if (var)
    {
	viewType = GetInt(var);
    }
    else
    {
	viewType = VT_PERSPECTIVE;
    }

    if (drawingMode == DRAW_SCREEN)
    {
	switch(viewType)
	{
	    case VT_PERSPECTIVE:
		DrawSpaceContents(object, left, right, bottom, top, VIEW_CENTER);
		break;
	    case VT_ORTHOGRAPHIC:
		DrawSpaceContents(object, left, right, bottom, top, VIEW_ORTHO);
		break;
	    case VT_CROSSEYED:
		DrawSpaceContents(object, left, right, bottom, top, VIEW_XLEFT);
		DrawSpaceContents(object, left, right, bottom, top, VIEW_XRIGHT);
		break;
	    case VT_REDCYAN:
		if (rgbp)
		{
		    DrawSpaceContents(object, left, right, bottom, top, VIEW_RCLEFT);
		    DrawSpaceContents(object, left, right, bottom, top, VIEW_RCRIGHT);
		}
		else
		{
		    DrawSpaceContents(object, left, right, bottom, top, VIEW_CENTER);
		}
		break;
	}
    
	/*Filter space if need be*/
	if (rgbp && drawingQuality == DQ_FULL && filterType == FT_SHRINK)
	{
	    /*Shrink the pixels in the window*/
	    int s, d;
	    register int y, x;
	    int xdd, ydd;
	    int xSize, ySize;
	    Pixel *imageBuffer;
    
	    if (right - left > SCRWIDTH) right = left + SCRWIDTH;
	    if (top - bottom > SCRHEIGHT) top = bottom + SCRHEIGHT;
    
	    if ((right - left) & 1) --right;
	    if ((top - bottom) & 1) --top;
	    xdd = (right - left) / 2;
	    ydd = (top - bottom) / 2;
	    xSize = (right - left);
	    ySize = (top - bottom);
    
	    /*Create an image buffer*/
	    imageBuffer = (Pixel *) malloc(SCRWIDTH * SCRHEIGHT * sizeof(Pixel));
	    if (imageBuffer)
	    {
		lrectread((Screencoord) left, (Screencoord) bottom, 
			(Screencoord) right - 1, (Screencoord) top - 1, (unsigned long *) imageBuffer);
	
		s = 0;
		d = 0;
		for (y = 0; y <= ydd; ++y)
		{
		    for (x = 0; x <= xdd; ++x)
		    {
			imageBuffer[d] . red =
				(imageBuffer[s] . red +
				 imageBuffer[s + 1] . red +
				 imageBuffer[s + xSize] . red +
				 imageBuffer[s + xSize + 1] . red) / 4;
			imageBuffer[d] . green =
				(imageBuffer[s] . green +
				 imageBuffer[s + 1] . green +
				 imageBuffer[s + xSize] . green +
				 imageBuffer[s + xSize + 1] . green) / 4;
			imageBuffer[d] . blue =
				(imageBuffer[s] . blue +
				 imageBuffer[s + 1] . blue +
				 imageBuffer[s + xSize] . blue +
				 imageBuffer[s + xSize + 1] . blue) / 4;
			imageBuffer[d] . alpha =
				(imageBuffer[s] . alpha +
				 imageBuffer[s + 1] . alpha +
				 imageBuffer[s + xSize] . alpha +
				 imageBuffer[s + xSize + 1] . alpha) / 4;
			s += 2;
			++d;
		    }
		    s += xSize;
		}
		cpack(0);
		clear();
		lrectwrite((Screencoord) left, (Screencoord) bottom, 
			(Screencoord) left + xdd - 1, (Screencoord) bottom + ydd - 1, 
			(unsigned long *) imageBuffer);
		SAFEFREE(imageBuffer);
	    }
	}
	if (rgbp && drawingQuality == DQ_FULL && filterType == FT_4AVERAGE)
	{
	    /*Average the pixels in the window*/
	    int s;
	    register int y, x;
	    int xSize, ySize;
	    Pixel *imageBuffer;
    
	    if (right - left > SCRWIDTH) right = left + SCRWIDTH;
	    if (top - bottom > SCRHEIGHT) top = bottom + SCRHEIGHT;
    
	    xSize = (right - left);
	    ySize = (top - bottom);
    
	    /*Create an image buffer*/
	    imageBuffer = (Pixel *) malloc(SCRWIDTH * SCRHEIGHT * sizeof(Pixel));
	    if (imageBuffer)
	    {
		lrectread((Screencoord) left, (Screencoord) bottom, 
			(Screencoord) right - 1, (Screencoord) top - 1, (unsigned long *) imageBuffer);
	
		s = 0;
		for (y = 0; y <= ySize - 1; ++y)
		{
		    for (x = 0; x <= xSize - 1; ++x)
		    {
			imageBuffer[s] . red =
				(imageBuffer[s] . red +
				 imageBuffer[s + 1] . red +
				 imageBuffer[s + xSize] . red +
				 imageBuffer[s + xSize + 1] . red) / 4;
			imageBuffer[s] . green =
				(imageBuffer[s] . green +
				 imageBuffer[s + 1] . green +
				 imageBuffer[s + xSize] . green +
				 imageBuffer[s + xSize + 1] . green) / 4;
			imageBuffer[s] . blue =
				(imageBuffer[s] . blue +
				 imageBuffer[s + 1] . blue +
				 imageBuffer[s + xSize] . blue +
				 imageBuffer[s + xSize + 1] . blue) / 4;
			imageBuffer[s] . alpha = 255;
			++s;
		    }
		    ++s;
		}
		lrectwrite((Screencoord) left, (Screencoord) bottom, 
			(Screencoord) right - 1, (Screencoord) top - 1, (unsigned long *) imageBuffer);
		SAFEFREE(imageBuffer);
	    }
	}
    }
    else if (drawingMode == DRAW_POSTSCRIPT && psFile)
    {
	/*Just save the image to a PostScript file*/
	register int y, x;
	int xSize, ySize;
	Pixel *imageBuffer;

	if (right - left > SCRWIDTH) right = left + SCRWIDTH;
	if (top - bottom > SCRHEIGHT) top = bottom + SCRHEIGHT;

	xSize = (right - left);
	ySize = (top - bottom);

	/*Create an image buffer*/
	imageBuffer = (Pixel *) malloc(xSize * ySize * sizeof(Pixel));
	if (imageBuffer)
	{
	    lrectread((Screencoord) left, (Screencoord) bottom, 
		    (Screencoord) right - 1, (Screencoord) top - 1, (unsigned long *) imageBuffer);
	    SavePSImage(psFile, imageBuffer, left, right, bottom, top);
	    SAFEFREE(imageBuffer);
	}
    }
#endif
    return ObjTrue;
}

static ObjPtr KeyDownSpace(object, key, flags)
ObjPtr object;
int key;
long flags;
/*Does a keydown in a space.  Returns
  true iff the press really was in the space.*/
{
    ObjPtr retVal, tool, var;

    /*Get the space tool*/
    tool = GetIntVar("KeyDownSpace", object, EDITTOOL);

    if (tool && GetInt(tool) == ST_FLYING)
    {
	ObjPtr observer;

	observer = GetObjectVar("KeyDownSpace", object, OBSERVER);
	if (!observer)
	{
	    return ObjFalse;
	}

	switch(key)
	{
	    case '0':
		SetVar(observer, AIRSPEED, NewReal(0.0));
		return ObjTrue;
	    case FK_UP_ARROW:
		var = GetVar(observer, AIRSPEED);
		if (var)
		{
		    SetVar(observer, AIRSPEED, NewReal(AIRSPEEDFACTOR + GetReal(var)));
		}
		else
		{
		    SetVar(observer, AIRSPEED, NewReal(AIRSPEEDFACTOR));
		}
		return ObjTrue;
	    case FK_DOWN_ARROW:
		var = GetVar(observer, AIRSPEED);
		if (var)
		{
		    SetVar(observer, AIRSPEED, NewReal(GetReal(var) - AIRSPEEDFACTOR));
		}
		else
		{
		    SetVar(observer, AIRSPEED, NewReal(-AIRSPEEDFACTOR));
		}
		return ObjTrue;
	}
    }
    return ObjFalse;
}

static void DoFileInVisAlert()
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, (WinInfoPtr) 0, "Files cannot be visualized directly.  First open the files and then visualize the datasets they contain.", 0, 0, "");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("SciAn must first read data files into datasets before the data can \
be visualized.  First open the file, then select the \
datasets you want to visualize within the Datasets window and visualize them."));
}

static void DoCannotVisError()
/*Whines at the user that it cannot visualize an object*/
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIERRORALERT, (WinInfoPtr) 0, "Some objects could not be visualized.", 0, 0, "");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("Some of the objects which you have tried to visualize could not \
be visualized, either because they are of the wrong type for visualization or \
because they need to be modified before they can be visualized."));
}

Bool AddObjToSpace(object, space, corral, loc, visType)
ObjPtr object;
ObjPtr space;
ObjPtr corral;
ObjPtr loc;
ObjPtr visType;
/*Adds object or a representation of it to space.
  If the class of object is
    iconClass    Finds a preferred visualization for REPOBJ and adds it
    visClass     Adds it directly
    fileClass	 Whines
    otherwise    Finds a preferred visualization and adds it

  loc is a location to put the new icon, or NULLOBJ
  visType is the preferred visualization, or NULLOBJ to get the preferred
*/
{
    ObjPtr repObj, icon, defaultIcon;
    ObjPtr name;
    ObjPtr contents;
    ObjPtr clock;
    FuncTyp AddControls;
    FuncTyp method;
    ObjPtr parents;

    /*If object is a template, make a new copy*/
    if (GetPredicate(object, TEMPLATEP))
    {
	FuncTyp method;

	method = GetMethodSurely("AddObjToSpace", object, CLONE);
	if (method)
	{
	    object = (*method)(object);
	}
	SetVar(object, TEMPLATEP, ObjFalse);
	SetVar(object, SELECTED, ObjFalse);
    }

    if (IsIcon(object))
    {
	/*It's an icon; got to find a REPOBJ*/
	object = GetVar(object, REPOBJ);
	if (!object)
	{
	    return false;
	}
    }
    if (IsFile(object))
    {
	/*It's a file.  Whine*/
	DoUniqueTask(DoFileInVisAlert);
	return false;
    }
    if (IsVisObj(object) && visType)
    {
	/*Go down to dataset level if visObject*/
	repObj = GetVar(object, MAINDATASET);
	if (!repObj)
	{
	    repObj = GetVar(object, REPOBJ);
	}
	object = repObj;
    }
    if (!IsVisObj(object))
    {
	/*It's not a visualization yet.  Gotta find one*/

	repObj = object;
	object = NewVis(repObj, visType);
	if (!object)
	{
	    DoUniqueTask(DoCannotVisError);
	    return false;
	}
	if (OptionDown())
	{
	    SetVar(object, HIDDEN, ObjTrue);
	}
    }
    else
    {
	repObj = GetVar(object, MAINDATASET);
	if (!repObj)
	{
	    repObj = GetVar(object, REPOBJ);
	}
    }

    contents = GetVar(space, CONTENTS);
    PrefixList(contents, object);
    parents = GetListVar("AddObjToSpace", object, PARENTS);
    if (parents)
    {
	PrefixList(parents, space);
    }
    else
    {
	SetVar(object, PARENT, space);
    }
    ImInvalid(object);
    SetVar(object, SPACE, space);

    /*Make an icon that represents the field and put it in the corral*/
    icon = NewVisIcon(object);
    if (icon)
    {
	SetVar(icon, ICONLOC, loc);	
	SetVar(icon, SPACE, space);
	if (OptionDown())
	{
	    SetVar(object, HIDDEN, ObjTrue);
	}
	DropIconInCorral(corral, icon);
    }

    method = GetMethod(object, DROPPEDINSPACE);
    if (method)
    {
	(*method)(object, space);
    }

    /*Reinitialize the clock in the space*/
    clock = GetVar(space, CLOCK);
    if (clock)
    {
	ReinitController(clock);
    }

    return true;
}

Bool DeleteControllerFromSpace(controller, space, corral)
ObjPtr controller, space, corral;
/*Deletes a controller from a space and its icon from the corral*/
{
    ObjPtr spaces;
    ObjPtr contents;
    ThingListPtr list;

    spaces = GetListVar("DeleteControllerFromSpace", controller, SPACES);
    if (!spaces) return false;

    /*Remove the space reference from the controller*/
    if (0 == DeleteFromList(spaces, space)) return false;
    
    /*Remove the controller's icon from the corral*/
    contents = GetListVar("DeleteControllerFromSpace", corral, CONTENTS);
    list = LISTOF(contents);
    while (list)
    {
	ObjPtr foundController;
	foundController = GetVar(list -> thing, REPOBJ);
	if (foundController == controller)
	{
	    DeleteFromList(contents, list -> thing);
	    ImInvalid(corral);
	    break;
	}
	list = list -> next;
    }

    return true;
}

static ObjPtr BindClockToSpace(clock, space)
ObjPtr clock, space;
/*Makes space know about clock*/
{
    SetVar(space, CLOCK, clock);
    return ObjTrue;
}

static ObjPtr BindObserverToSpace(observer, space)
ObjPtr observer, space;
/*Makes space know about observer*/
{
    SetVar(space, OBSERVER, observer);
    return ObjTrue;
}

static ObjPtr BindRendererToSpace(renderer, space)
ObjPtr renderer, space;
/*Makes space know about renderer*/
{
    SetVar(space, RENDERER, renderer);
    return ObjTrue;
}

Bool AddControllerToSpace(controller, space, corral, loc)
ObjPtr controller;
ObjPtr space;
ObjPtr corral;
ObjPtr loc;
/*Adds a space controller to space at loc and puts its icon in corral.
*/
{
    ObjPtr repObj, icon, iconY;
    ObjPtr contents, spaces;
    ThingListPtr list;
    ObjPtr controllerClass;
    ObjPtr defaultIcon;
    ObjPtr name;
    FuncTyp method;

    if (GetPredicate(controller, ONEONLY) &&
	(controllerClass = GetVar(controller, CLASSID)) &&
	IsInt(controllerClass))
    {
	int cc;

	/*First see if there is already a controller there*/
	cc = GetInt(controllerClass);
	contents = GetListVar("AddControllerToSpace", corral, CONTENTS);
	if (!contents) return false;
	list = LISTOF(contents);
	while (list)
	{
	    ObjPtr foundController;
	    foundController = GetVar(list -> thing, REPOBJ);
	    if (IsController(foundController) && IntVarEql(foundController, CLASSID, cc))
	    {
		if (foundController == controller)
		{
		    /*This is really the same controller.  Just move the icon*/
		    if (loc)
		    {
			SetVar(list -> thing, ICONLOC, loc);
			ImInvalid(list -> thing);
		    }
		    return true;
		}
		else
		{
		    /*It's a new controller.  Delete the old one and fall through*/
		    DeleteControllerFromSpace(foundController, space, corral);
		    break;
		}
	    }
	    list = list -> next;
	}
    }
    /*Make an icon that represents the new controller and put it in the corral*/
    name = GetStringVar("AddControllerToSpace", controller, NAME);
    defaultIcon = GetObjectVar("AddControllerToSpace", controller, DEFAULTICON);
    if (defaultIcon)
    {
	icon = NewObject(defaultIcon, 0);
	SetVar(icon, NAME, name ? name : NewString("Controller"));
	SetVar(icon, ICONGREYED, GetVar(controller, HIDDEN));
    }
    else
    {
	icon = NewIcon(0, 0, ICONQUESTION,
		   name ? GetString(name) : "Controller");
	SetVar(icon, HELPSTRING, 
	    NewString("This icon represents a controller.  For some reason, \
the default icon could not be found, so the icon appears as a question mark.  \
Please report this as a bug in SciAn."));
    }
    SetVar(icon, SPACE, space);
    SetVar(icon, REPOBJ, controller);
    method = GetMethod(controller, BINDTOSPACE);
    if (method)
    {
	(*method)(controller, space);
    }
    SetVar(icon, ICONLOC, loc);

    /*Make the controller know about this space*/
    spaces = GetListVar("AddControllerToSpace", controller, SPACES);
    if (!spaces) return false;
    PrefixList(spaces, space);

    ImInvalid(controller);
    DropIconInCorral(corral, icon);

    return true;
}

ObjPtr NewSpace(left, right, bottom, top)
int left, right, bottom, top;
/*Makes a new Space with bounds left, right, bottom, top*/
{
    ObjPtr retVal;

    retVal = NewObject(spaceClass, 0);
	
    Set2DIntBounds(retVal, left, right, bottom, top);
    SetVar(retVal, CONTENTS, NewList());

    return retVal;
}

#ifdef PROTO
void PrintClock(char *s, char *f, real t)
#else
void PrintClock(s, f, t)
char *s;
char *f;
real t;
#endif
/*Prints t in seconds to a string s using format f*/
{
    while (*f)
    {
	if (*f == '%')
	{
	    /*Format string*/
	    ++f;
	    if (*f == '%')
	    {
		/*Just print out a %*/
		*s++ = *f++;
	    }
	    else
	    {
		char *temp;	/*Pointer into tempStr to assemble*/
		real n;		/*Number to print*/
		long i;		/*Temporary integer*/
		/*It's a real format.  Start assembling*/

		temp = tempStr;
		*temp++ = '%';

		/*Skip over flag(s)*/
		while (*f == '-' || *f == '+' || *f == ' ' || *f == '#')
		{
		    *temp++ = *f++;
		}

		/*Skip over first number*/
		while (*f >= '0' && *f <= '9')
		{
		    *temp++ = *f++;
		}

		/*Skip over second number*/
		if (*f == '.')
		{
		    *temp++ = *f++;
		    while (*f >= '0' && *f <= '9')
		    {
			*temp++ = *f++;
		    }
		}

		/*Now see what the format is*/
		switch (*f)
		{
		    case 'h':
			/*Hours, [1,12]*/
			n = t / 3600.0;
			i = n / 12.0;
			n = n - i * 12;
			if (n < 1.00) n += 12.00;
			break;
		    case 'H':
			/*Hours, [0,24)*/
			n = t / 3600.0;
			i = n / 24.0;
			n = n - i * 24;
			break;
		    case 'a':
			/*am or pm*/
			n = t / 3600.0;
			i = n;
			i = i % 24;
			sprintf(s, "%s", i >= 12 ? "pm" : "am");
			++f;
			goto dontsprintf;
			break;
		    case 'A':
			/*AM or PM*/
			n = t / 3600.0;
			i = n / 24.0;
			i = i % 24;
			sprintf(s, "%s", i >= 12 ? "PM" : "AM");
			++f;
			goto dontsprintf;
			break;
		    case 'M':
			/*Unrestricted minutes*/
			n = t / 60.0;
			break;
		    case 'm':
			/*Minutes, 0 to 59*/
			n = t / 60.0;
			i = n / 60.0;
			n = n - i * 60;
			break;
		    case 's':
			/*Seconds, 0 to 59*/
			i = t / 60.0;
			n = t - i * 60;
			break;
		    case 'S':
			/*Unrestricted seconds*/
		    case 't':
		    case 'T':
			/*Just a timestep*/
			n = t;
			break;
		    case 'f':
		    case 'F':
		    case 'g':
		    case 'G':
			*temp++ = *f++;
			*temp = 0;
			sprintf(s, tempStr, t);
			goto dontsprintf;
			break;
		    case 'x':
		    case 'X':
		    case 'd':
		    case 'D':
			*temp++ = *f++;
			*temp = 0;
			sprintf(s, tempStr, (int) t);
			goto dontsprintf;
			break;
		    default:
			strcpy(s, "Bad format: ");
			while (*s) ++s;
			*s++ = *f++;
			*s = 0;
			goto dontsprintf;
		}
		++f;

		/*Ready to print*/
		*temp++ = 'f';
		*temp = 0;
		sprintf(s, tempStr, n);
dontsprintf:
		while (*s)
		{
		    ++s;
		}
	    }
	}
	else
	{
	    *s++ = *f++;
	}
    }
    *s = 0;
}

static ObjPtr TouchSpaceClock(clock, space)
ObjPtr clock, space;
/*Touches a space with a clock.  Returns ObjTrue if it had an effect,
  ObjFalse otherwise.*/
{
    real time;
    ObjPtr timeObj;
    ObjPtr timeBounds;
    Bool clockHasTime;
    Bool boundsChanged;
    ObjPtr objTime;
    real tb[2];
    ObjPtr displayClock;
    ObjPtr format;
    ObjPtr contents;
    ObjPtr repObj;		/*The repobj of a vis object, ie, main dataset*/
    ObjPtr data;		/*The DATA ARRAY of the main dataset*/
    char s[256];
    ThingListPtr runner;

    timeObj = GetVar(clock, TIME);
    if (timeObj)
    {
	time = GetReal(timeObj);
    }
    else
    {
	time = 0.0;
    }


    /*Go through all the objects in the space expanding the time bounds*/
    timeBounds = GetVar(clock, TIMEBOUNDS);
    clockHasTime = timeBounds ? true : false;
    contents = GetListVar("TouchSpaceClock", space, CONTENTS);
    if (!contents)
    {
	return ObjTrue;
    }

    boundsChanged = false;
    runner = LISTOF(contents);
    SetVar(space, TIME, timeObj);
    while (runner)
    {
	ChangeVar(runner -> thing, TIME, timeObj);
	repObj = GetVar(runner -> thing, MAINDATASET);
	if (!repObj) repObj = GetObjectVar("TouchSpaceClock", runner -> thing, REPOBJ);
	if (repObj)
	{
	    data = GetVar(repObj, DATA);
	    if (data)
	    {
		ObjPtr objTime;
		real otb[2];
		MakeVar(data, TIMEBOUNDS);
		objTime = GetVar(data, TIMEBOUNDS);
		if (objTime)
		{
		    Array2CArray(otb, objTime);
		    if (clockHasTime)
		    {
			if (boundsChanged)
			{
			    if (otb[0] < tb[0])
			    {
				tb[0] = otb[0];
			    }
			}
			else
			{
			    tb[0] = otb[0];
			    boundsChanged = true;
			}
			if (boundsChanged)
			{
			    if (otb[1] > tb[1])
			    {
				tb[1] = otb[1];
			    }
			}
			else
			{
			    tb[1] = otb[1];
			    boundsChanged = true;
			}
		    }
		    else
		    {
			tb[0] = otb[0];
			tb[1] = otb[1];
			clockHasTime = true;
			boundsChanged = true;
		    }
		}
	    }
	}
	runner = runner -> next;
    }
    if (boundsChanged)
    {
	timeBounds = NewRealArray(1, 2L);
	CArray2Array(timeBounds, tb);
	SetVar(clock, TIMEBOUNDS, timeBounds);
    }
    return ObjTrue;
}

ObjPtr RegisterTime(clock)
ObjPtr clock;
/*Registers time or timebounds changes in clock*/
{
    ObjPtr time;
    WinInfoPtr dialog;

    time = GetVar(clock, TIME);
	/*Now change the slider*/
	dialog = DialogExists((WinInfoPtr) clock, NewString("Clock"));

	if (dialog)
	{
	    ObjPtr control;

	    control = GetVar((ObjPtr) dialog, TIMECONTROL);
	    if (control)
	    {
		ObjPtr value;
		value = GetVar(control, VALUE);
		if (time && !Equal(value, time))
		{
		    FuncTyp method;
		    method = GetMethod(control, CHANGEDVALUE);
		    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
		    SetValue(control, time);
		    AutoScroll(control);
		    SetMethod(control, CHANGEDVALUE, method);
		}
	    }
	}

    SetVar(clock, TIMEREGISTERED, ObjTrue);
    return ObjTrue;
}

ObjPtr ResolveClock(clock)
ObjPtr clock;
/*Resolves a clock after all the spaces have ben touched*/
{
    MakeVar(clock, TIMEREGISTERED);
    return ObjTrue;
}

static ObjPtr MakeSpaceFilterType(space)
ObjPtr space;
/*Makes a space's filter type*/
{
    ObjPtr renderer;

    renderer = GetObjectVar("MakeSpaceFilterType", space, RENDERER);
    if (!renderer)
    {
	return ObjFalse;
    }
    SetVar(space, FILTERTYPE, GetVar(renderer, FILTERTYPE));
    return ObjTrue;
}

static ObjPtr MakeSpaceRenderType(space)
ObjPtr space;
/*Touches a space with an renderer.  Returns ObjTrue if it had an effect,
  ObjFalse otherwise.*/
{
    ObjPtr renderer;
    ObjPtr spaceRenderType, renderType;

    renderer = GetObjectVar("MakeSpaceRenderType", space, RENDERER);
    if (!renderer)
    {
	return ObjFalse;
    }

    spaceRenderType = GetVar(space, RENDERTYPE);
    renderType = GetVar(renderer, RENDERTYPE);

    if (!Equal(spaceRenderType, renderType))
    {
 	WinInfoPtr window;
	window = (WinInfoPtr) GetWindowVar("MakeSpaceRenderType", space, OWNERWINDOW);
	if (!window) return ObjTrue;

	    switch(GetInt(renderType))
	    {
		case RT_RGB_HARDWARE:
		    DeferMessage((ObjPtr) window, SETTORGBMODE);
		    break;
		case RT_CMAP_HARDWARE:
		    DeferMessage((ObjPtr) window, SETTOCMAPMODE);
		    break;
	    }
    }
    SetVar(space, RENDERTYPE, renderType);
    return ObjTrue;
}

ObjPtr ClockInvalid(controller)
ObjPtr controller;
/*Resolves all the space references from controller*/
{
    ObjPtr spaces;
    ThingListPtr list;
    FuncTyp method;	

    spaces = GetListVar("ControllerInvalid", controller, SPACES);
    if (!spaces) return;
    list = LISTOF(spaces);
    while (list)
    {
	method = GetMethod(controller, TOUCHSPACE);
	if (method)
	{
	    if (IsTrue((*method)(controller, list -> thing)))
	    {
		ImInvalid(list -> thing);
	    }
	}
        list = list -> next;
    }

    /*Let the controller know if it's been resolved*/
    method = GetMethod(controller, RESOLVEDONE);
    if (method)
    {
	(*method)(controller);
    }
}

void ReinitController(controller)
ObjPtr controller;
/*Reinitializes a controller by sending it a REINIT message and then resolving
  it*/
{
    FuncTyp method;
    method = GetMethod(controller, REINIT);
    if (method)
    {
	(*method)(controller);
    }
}

int pdspSerialNum = 0;

static ObjPtr DeletePaletteDisplay(display)
ObjPtr display;
/*Deletes display*/
{
    return ObjTrue;
}

void AddPaletteToSpacePanel(palette, panel, space, x, y)
ObjPtr palette, panel, space;
int x, y;
/*Adds palette to panel.*/
{
    ObjPtr paletteDisplay;

    sprintf(tempStr, "Palette Display %d", ++pdspSerialNum); 
    paletteDisplay = NewPaletteDisplay(x - DSPPALETTEWIDTH / 2, x + DSPPALETTEWIDTH / 2, 
			y - DSPPALETTEHEIGHT / 2, y + DSPPALETTEHEIGHT / 2,
			tempStr, palette);
    PrefixList(GetVar(panel, CONTENTS), paletteDisplay);
    SetVar(paletteDisplay, PARENT, panel);
    SetMethod(paletteDisplay, DELETEICON, DeletePaletteDisplay);
    SetVar(paletteDisplay, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGTOP + FLOATINGBOTTOM));
    SetTextFont(paletteDisplay, ANNOTFONT);
    SetTextSize(paletteDisplay, ANNOTFONTSIZE);
}
static ObjPtr DropInSpacePanel(panel, dropObj, x, y)
ObjPtr panel, dropObj;
int x, y;
/*Drops object in a panel beginning at x and y.  Returns
  true iff the drop really was in the panel.*/
{
    int left, right, bottom, top;

    Get2DIntBounds(panel, &left, &right, &bottom, &top);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*Hey!  It really was a drop in the panel*/
	ObjPtr contents;
	ObjPtr firstIcon;
	ThingListPtr restIcons;
	ThingListPtr runner;
	ObjPtr repObj;
	ObjPtr space;
	ObjPtr iconLoc;
	real loc[2];
	int xDisp, yDisp;

	if (IsList(dropObj))
	{
	    restIcons = LISTOF(dropObj);
	    firstIcon = restIcons -> thing;
	    restIcons = restIcons -> next;
	}
	else if (IsIcon(dropObj))
	{
	    firstIcon = dropObj;
	    restIcons = 0;
	}
	else
	{
	    ReportError("DropInSpacePanel", "An object other than an icon was dropped");
	    return ObjFalse;
	}

	space = GetObjectVar("DropInSpacePanel", panel, SPACE);
	if (!space) return ObjFalse;

	iconLoc = GetFixedArrayVar("DropInSpacePanel", firstIcon, ICONLOC, 1, 2L);
	if (!iconLoc)
	{
	    return ObjFalse;
	}
	Array2CArray(loc, iconLoc);

    	/*Setup the new viewport*/
	StartPanel(left, right, bottom, top);


        x -= left;
        y -= bottom;

	xDisp = x - loc[0];
	yDisp = y - loc[1];

	/*Drop first icon*/
	repObj = GetVar(firstIcon, REPOBJ);
	if (IsClock(repObj))
	{
	}
	else if (IsPalette(repObj))
	{
	    AddPaletteToSpacePanel(repObj, panel, space, x, y);
	}

	/*Drop remaining icons*/
	runner = restIcons;
	while (runner)
	{
	    repObj = GetVar(runner -> thing, REPOBJ);
	    if (IsClock(repObj))
	    {
	    }
	    else if (IsPalette(repObj))
	    {
		iconLoc = GetFixedArrayVar("DropInSpacePanel", runner -> thing, ICONLOC, 1, 2L);
		if (!iconLoc) break;
		Array2CArray(loc, iconLoc);
		loc[0] += xDisp;
		loc[1] += yDisp;
		AddPaletteToSpacePanel(repObj, panel, space, (int) loc[0], (int) loc[1]);
	    }
	    runner = runner -> next;
	}

	StopPanel();
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

#ifdef PROTO
void SetClock(ObjPtr clock, real time)
#else
void SetClock(clock, time)
ObjPtr clock;
real time;
#endif
/*Sets clock to time*/
{
    SetVar(clock, TIME, NewReal(time));

    ImInvalid(clock);
}

static ObjPtr MarkClockTime(clock, lateness)
ObjPtr clock;
double lateness;
/*Marks time in a clock after a delay of lateness*/
{
    ObjPtr curTime;
    ObjPtr var;
    ObjPtr timeBounds;
    real deltaTime, deltaTimePer;
    int deltaTimeUnits, deltaTimePerUnits;
    real time;
    real tb[2];
    ObjPtr name;
    ObjPtr whichDialog;
    WinInfoPtr dialog;
    ObjPtr spaces;
    ThingListPtr list;
    Bool wrap;
    ObjPtr runSpeed, runControl;

    DoNotDisturb(clock, MARKTIME);

    runControl = GetVar(clock, RUNCONTROL);

    runSpeed = GetVar(clock, RUNSPEED);
    if (!runSpeed)
    {
	return ObjTrue;
    }

    /*Get the delta time and delta time per units*/
    var = GetVar(clock, DTIMEUNITS);
    if (var)
    {
	deltaTimeUnits = GetInt(var);
    }
    else
    {
	deltaTimeUnits = 0;
    }

    var = GetVar(clock, DTIMEPERUNITS);
    if (var)
    {
	deltaTimePerUnits = GetInt(var);
    }
    else
    {
	deltaTimePerUnits = 0;
    }

    MakeVar(clock, DTIME);
    var = GetVar(clock, DTIME);
    if (!var || !IsReal(var))
    {
	/*Obviously don't want to do anything*/
	if (runControl)
	{
	    InhibitLogging(true);
	    SetValue(runControl, NewInt(RC_STOP));
	    InhibitLogging(false);
	}
	DoNotDisturb(clock, MARKTIME);
	return ObjTrue;
    }
    deltaTime = GetReal(var);

    var = GetVar(clock, DTIMEPER);
    if (var)
    {
	deltaTime /= GetReal(var);
    }

    if (deltaTimePerUnits)
    {
	deltaTime = deltaTime * fps;
    }

    wrap = GetPredicate(clock, CYCLECLOCK);

    if (deltaTime == 0.0)
    {
	return ObjTrue;
    }

    switch (GetInt(runSpeed))
    {
	case RC_STOP:
	    return ObjTrue;
	case RC_FORWARD:
	    break;
	case RC_REVERSE:
	    deltaTime = -deltaTime;
	    break;
	case RC_FAST_FORWARD:
	    deltaTime = 3.0 * deltaTime;
	    break;
	case RC_FAST_REVERSE:
	    deltaTime = -3.0 * deltaTime;
	    break;
    }


    if (lateness <= 0.0)
    {
	return ObjTrue;
    }

    curTime = GetVar(clock, TIME);
    if (curTime)
    {
	time = GetReal(curTime);
    }
    else
    {
	time = 0.0;
    }

    /*Increment the time by the elapsed time*/
    if (deltaTimeUnits)
    {
	ObjPtr timeSteps;
	MakeVar(clock, TIMESTEPS);
	timeSteps = GetVar(clock, TIMESTEPS);
	if (timeSteps)
	{
	    real index, newTime;
	    index = SearchFuzzyReal(timeSteps, time);

	    index += deltaTime * lateness;

	    newTime = FuzzyRealIndex(timeSteps, index);
	    if (newTime != time)
	    {
		time = newTime;
	    }
	    else
	    {
		if (runControl)
		{
		    InhibitLogging(true);
		    SetValue(runControl, NewInt(RC_STOP));
		    InhibitLogging(false);
		}
	    }
	}
    }
    else
    {
	time += deltaTime * lateness;
    }

    /*Check against time bounds*/
    MakeVar(clock, TIMEBOUNDS);
    timeBounds = GetVar(clock, TIMEBOUNDS);
    if (timeBounds && IsRealArray(timeBounds) && RANK(timeBounds) == 1 &&
    DIMS(timeBounds)[0] == 2)
    {
	Array2CArray(tb, timeBounds);
    }
    else
    {
	tb[0] = 0.0;
	tb[1] = 1.0;
    }
    if (deltaTime > 0.0 && time > tb[1])
    {
	if (wrap)
	{
	    time = tb[0];
	    /*Another wakeup call*/
	    WakeMe(clock, MARKTIME, Clock() + 0.001);
	}
	else
	{
	    time = tb[1];
	    if (runControl)
	    {
		InhibitLogging(true);
		SetValue(runControl, NewInt(RC_STOP));
		InhibitLogging(false);
	    }
	}
    }
    else if (deltaTime < 0.0 && time < tb[0])
    {
	if (wrap)
	{
	    time = tb[1];
	    /*Another wakeup call*/
	    WakeMe(clock, MARKTIME, Clock() + 0.001);
	}
	else
	{
	    time = tb[0];
	    if (runControl)
	    {
		InhibitLogging(true);
		SetValue(runControl, NewInt(RC_STOP));
		InhibitLogging(false);
	    }
	}
    }
    else
    {
	/*Another wakeup call*/
	WakeMe(clock, MARKTIME, Clock() + 0.001);
    }

    /*Now change the clock*/
    InhibitLogging(true);
    SetClock(clock, time);
    InhibitLogging(false);

    return ObjTrue;
}

static ObjPtr ChangeTime(object)
ObjPtr object;
/*Changed value for a control time*/
{
    real time;
    ObjPtr val;
    ObjPtr repObj;
    ObjPtr spaces;
    ThingListPtr list;

    repObj = GetObjectVar("ChangeTime", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetValue(object);
    if (val)
    {
	time = GetReal(val);
    }
    else
    {
	time = 0.0;
    }

    SetClock(repObj, time);

    spaces = GetListVar("ChangeTime", repObj, SPACES);
    if (!spaces) return ObjFalse;
    list = LISTOF(spaces);
    while (list)
    {
	DrawMe(list -> thing);
	list = list -> next;
    }
    return ObjTrue;
}

#ifdef PROTO
void GetSliderRange(ObjPtr slider, real *loValue, real *hiValue)
#else
void GetSliderRange(slider, loValue, hiValue)
ObjPtr slider;
real *loValue, *hiValue;
#endif
/*Returns the range of slider into loValue and hiValue*/
{
    ObjPtr var;
    var = GetRealVar("GetSliderRange", slider, LOVALUE);
    if (var) *loValue = GetReal(var);
    var = GetRealVar("GetSliderRange", slider, HIVALUE);
    if (var) *hiValue = GetReal(var);
}

static void ClockSliderToRadio(radioGroup, slider)
ObjPtr radioGroup, slider;
/*Sets the value of the radio group based on the value of the dtime slider*/
{
    real loValue, hiValue;
    FuncTyp method;
    ObjPtr var;
    real value;

    /*Temporarily save changedvalue method*/
    method = GetMethod(radioGroup, CHANGEDVALUE);
    SetMethod(radioGroup, CHANGEDVALUE, (FuncTyp) 0);
    
    GetSliderRange(slider, &loValue, &hiValue);
    var = GetValue(slider);
    if (var) value = GetReal(var); else value = 0.0;
    if (value == 0.0)
    {
	/*Stop*/
	SetValue(radioGroup, NewInt(0));
    }
    else if (value >= (hiValue - loValue) * (FASTCLOCK + PLAYCLOCK) * 0.5)
    {
	/*Fast Forward*/
	SetValue(radioGroup, NewInt(3));
    }
    else if (value <= (loValue - hiValue) * (FASTCLOCK + PLAYCLOCK) * 0.5)
    {
	/*Fast Reverse*/
	SetValue(radioGroup, NewInt(4));
    }
    else if (value > 0.0)
    {
	/*Play*/
	SetValue(radioGroup, NewInt(1));
    }
    else
    {
	/*Reverse*/
	SetValue(radioGroup, NewInt(2));
    }

    /*Put changedvalue method back*/
    SetMethod(radioGroup, CHANGEDVALUE, method);
}

static ObjPtr ChangeDeltaTimeBox(box)
ObjPtr box;
/*Changed value for a box delta time*/
{
    real deltaTime;
    ObjPtr value;
    ObjPtr repObj;
    char *s;

    repObj = GetObjectVar("ChangeDeltaTimeBox", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    s = GetString(value);
    if (!ParseReal(&deltaTime, s))
    {
	return ObjFalse;
    }

    if (deltaTime < 0.0)
    {
	DoUniqueTask(DoLT0Error);
	return ObjFalse;
    }

    SetVar(repObj, DTIME, NewReal(deltaTime));
    return ObjTrue;
}

static ObjPtr ChangeDeltaTimePerBox(box)
ObjPtr box;
/*Changed value for a box delta time per*/
{
    real deltaTime;
    ObjPtr value;
    ObjPtr repObj;
    char *s;

    repObj = GetObjectVar("ChangeDeltaTimePerBox", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    s = GetString(value);
    if (!ParseReal(&deltaTime, s))
    {
	return ObjFalse;
    }

    if (deltaTime <= 0.0)
    {
	DoUniqueTask(DoLE0Error);
	return ObjFalse;
    }

    SetVar(repObj, DTIMEPER, NewReal(deltaTime));
    return ObjTrue;
}

static ObjPtr ChangeCycleClock(checkBox)
ObjPtr checkBox;
/*Sets the CYCLECLOCK in the checkBoxes repobj to the value of checkBox*/
{
    ObjPtr repObj, value;
    repObj = GetObjectVar("ChangeCycleClock", checkBox, REPOBJ);
    if (!repObj) return ObjFalse;
    value = GetValue(checkBox);
    if (!IsInt(value) && !IsReal(value)) return ObjFalse;
    SetVar(repObj, CYCLECLOCK, GetInt(value) ? ObjTrue : ObjFalse);
    return ObjTrue;
} 

static ObjPtr ChangeRunControl(radioGroup)
ObjPtr radioGroup;
/*Changes the run control of a clock according to a newly pressed button*/
{
    ObjPtr clock;
    real loValue, hiValue;
    ObjPtr var;
    int value;

    var = GetValue(radioGroup);
    if (var) value = GetInt(var); else value = RC_STOP;
    
    clock = GetObjectVar("ChangeClockSpeed", radioGroup, REPOBJ);
    if (!clock) return ObjFalse;

    DoNotDisturb(clock, MARKTIME);

    SetVar(clock, RUNSPEED, NewInt(value));

    WakeMe(clock, MARKTIME, Clock() + 0.001);

    return ObjTrue;
}

static ObjPtr ChangeDeltaTimeUnits(box)
ObjPtr box;
/*Changes a delta time units from box*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeDeltaTimeUnits", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, DTIMEUNITS, GetValue(box));
    return ObjTrue;
}

static ObjPtr ChangeDeltaTimePerUnits(box)
ObjPtr box;
/*Changes a delta time units from box*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeDeltaTimePerUnits", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, DTIMEPERUNITS, GetValue(box));
    return ObjTrue;
}

static ObjPtr MakeClockTimeSteps(clock)
ObjPtr clock;
/*Makes a clock's TIMESTEPS variable*/
{
    long k;
    ObjPtr datasets;
    ObjPtr totalTimeSteps = NULLOBJ;

    MakeVar(clock, DATASETS);
    datasets = GetVar(clock, DATASETS);
    if (datasets)
    {
	for (k = 0; k < DIMS(datasets)[0]; ++k)
	{
	    ObjPtr timeSteps;
	    ObjPtr element;

	    element = GetObjectElement(datasets, &k);
	    MakeVar(element, TIMESTEPS);
	    timeSteps = GetVar(element, TIMESTEPS);
	    if (timeSteps)
	    {
		if (!totalTimeSteps)
		{
		    totalTimeSteps = timeSteps;
		}
		else
		{
		    totalTimeSteps = MergeRealArrays(totalTimeSteps, timeSteps);
		}
	    }
	}
    }
    SetVar(clock, TIMESTEPS, totalTimeSteps);
    return ObjTrue;
}

static ObjPtr MakeClockDTime(clock)
ObjPtr clock;
/*Method to make a clock's DTIME*/
{
    real dt;
    ObjPtr datasets;
    Bool stepSet = false;
    real minTimeStep;
    long nTimeSteps = 0;
    long k;

		/*Calculate minimum time step*/
		MakeVar(clock, DATASETS);
		datasets = GetVar(clock, DATASETS);
		if (datasets)
		{
		    ObjPtr element;

		    for (k = 0; k < DIMS(datasets)[0]; ++k)
		    {
		        ObjPtr timeSteps;
			element = GetObjectElement(datasets, &k);

		        MakeVar(element, TIMESTEPS);
		        timeSteps = GetVar(element, TIMESTEPS);
		        if (timeSteps)
			{
			    ObjPtr deltas;
			    deltas = SortArray(Uniq(RealArrayDeltas(timeSteps)));

			    /*Set the step only if there is one*/
			    nTimeSteps = DIMS(timeSteps)[0];
			    if (DIMS(timeSteps)[0] > 1)
			    {
				if (stepSet)
				{
				    minTimeStep = MIN(minTimeStep,
					  *((real *) ELEMENTS(deltas)));
				}
				else
				{
				    minTimeStep = *((real *) ELEMENTS(deltas));
				    stepSet = true;
				}
			    }
			}
		    }
		}
		/*Estimate dt*/
		if (nTimeSteps > 1)
		{
		    k = nTimeSteps / 20.0;
		    if (k < 1) k = 1;
		    else if (k > 10) k = 10;
		    dt = minTimeStep * (real) k;
		}
		else
		{
		    dt = 1.0;
		}
		SetVar(clock, DTIME, NewReal(dt));

}

static ObjPtr ShowClockControls(clock,  windowName)
ObjPtr clock;
ObjPtr windowName;
/*Makes a new clock window to control clock.  Ignores ownerWindow and windowName*/
{
    WinInfoPtr clockWindow;
    ObjPtr name;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr contents;
    ObjPtr button, checkBox;
    int left;
    ObjPtr format;
    WinInfoPtr dialogExists;
    ObjPtr whichDialog;

    if (!clock) return NULLOBJ;

    name = GetVar(clock, NAME);

    whichDialog = NewString("Clock");
    dialogExists = DialogExists((WinInfoPtr) clock, whichDialog);
    if (name)
    {
	strncpy(tempStr, GetString(name), TEMPSTRSIZE);
	tempStr[TEMPSTRSIZE] = 0;
    }
    else
    {
	strcpy(tempStr, "Clock");
    }
    clockWindow = GetDialog((WinInfoPtr) clock, whichDialog, tempStr, 
	CLWINWIDTH, CLWINHEIGHT, CLWINWIDTH, CLWINHEIGHT, WINDBUF + WINFIXEDSIZE);

    if (!dialogExists)
    {
	int left, right, bottom, top;

	SetVar((ObjPtr) clockWindow, REPOBJ, clock);

	/*Put in a help string*/
	SetVar((ObjPtr) clockWindow, HELPSTRING,
	    NewString("This window shows controls for a clock.  Using these \
controls, you can change the time displayed in all the spaces a clock controls \
or set time to advance forward or backward at a certain rate."));

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, CLWINWIDTH, 0, CLWINHEIGHT);
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) clockWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) clockWindow);
	ContentsExpectWindowSize(clockWindow, CLWINWIDTH, CLWINHEIGHT);

	contents = GetListVar("ShowClockControls", panel, CONTENTS);
	if (contents)
	{
	    ObjPtr button, checkBox, radioGroup, control, titleBox;
	    ObjPtr timeBounds;
	    ObjPtr time;
	    ObjPtr textBox;
	    char readoutText[40];
	    ObjPtr genericHelp;
		
	    real dt, mdt;
	    int left, right, bottom, top;

	    left = MAJORBORDER;
	    right = CLWINWIDTH - MAJORBORDER;
	    top = CLWINHEIGHT - MAJORBORDER;

	    time = GetVar(clock, TIME);

	    genericHelp = NewString("This controls the rate at which the clock \
advances through time.  When the forward play button is pushed, the clock will \
begin to advance forward the number of seconds or time steps given by the first \
text box for every number of seconds or frames given by the second time box.\n\
\n\
The easiest way to understand this is to read the text boxes and check boxes as \
a complete sentence.  Examples:\n\n\
\"Advance 0.25 second(s) every 1 second(s).\"\n\
This causes the clock to step forward at a rate of one-quarter second for every \
second that passes in the real world.  In other words, the clock will step at one-quarter \
real time.  When recording on videodisc, this will result in a one-quarter real time \
display when the videodisc is played back.\n\
\n\
\"Advance 5 time step(s) every 1 second(s).\"\n\
This causes the clock to step forward through five timesteps of the data for every \
second.  If the timesteps are unevenly spaced, the clock will speed up and slow down \
as neccessary to ensure that 5 timesteps are displayed every second.\n\
\n\
\"Advance 0.3 second(s) every 1 frame(s).\"\n\
This causes the clock to step forward three seconds for every frames.  A frame is \
the smallest unit of time that can be displayed given the limitations of the graphics \
device.  Videodiscs have a fixed frame rate of 30 frames per second (or 25 for PAL systems).  \
The interactive display has no fixed frame rate; it varies with how much time it takes to produce \
a picture.  \n\
\n\
To see one time step after another as fast as they can be displayed, set the controls to \
\"Advance 1 time step(s) every 1 frame(s).\"");

	    /*Add a time control*/
	    control = NewTimeControl(left, right, top - CLTCHEIGHT, top, "Time Control");
	    PrefixList(contents, control);
	    SetVar(control, PARENT, panel);
	    SetVar(control, REPOBJ, clock);
	    SetVar((ObjPtr) clockWindow, TIMECONTROL, control);
	    if (time)
	    {
		SetValue(control, time);
	    }

	    SetMethod(control, CHANGEDVALUE, ChangeTime);

	    ReinitController(clock);

	    top -= CLTCHEIGHT + MINORBORDER;

	    /*Add a title box*/
	    left = MINORBORDER;
	    bottom = MINORBORDER;
	    right = CLWINWIDTH - MINORBORDER;
	    titleBox = NewTitleBox(left, right, bottom, top, "Animation");
	    PrefixList(contents, titleBox);
	    SetVar(titleBox, PARENT, panel);

	    top -= TITLEBOXTOP + MINORBORDER;
	    left += MINORBORDER;
	    right -= MINORBORDER;

	    MakeVar(clock, DTIME);
	    var = GetVar(clock, DTIME);
	    if (var)
	    {
		dt = GetReal(var);
	    }
	    else
	    {
		dt = 0.0;
	    }

	    bottom = top - EDITBOXHEIGHT;
	    /*Create the Advance text box*/
	    textBox = NewTextBox(left, left + (right - left) / 4,
				 bottom + (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2 + EDITBOXDOWN,
				 top - (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2 + EDITBOXDOWN,
				 PLAIN, "Advance legend", "Advance");
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Create the dTime editable text box*/
	    PrintNumber(readoutText, dt);
	    textBox = NewTextBox(left + (right - left) / 4, left + 2 * (right - left) / 4,
				 bottom, top,
				 EDITABLE + WITH_PIT + ONE_LINE,
				 "Delta Time Box", readoutText);
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);
	    SetVar(textBox, REPOBJ, clock);
	    SetMethod(textBox, CHANGEDVALUE, ChangeDeltaTimeBox);
	    SetVar(textBox, HELPSTRING, genericHelp);

	    /*Create the seconds and timesteps box*/
	    radioGroup = NewRadioButtonGroup("Delta Time Units");
	    PrefixList(contents, radioGroup);
	    SetVar(radioGroup, PARENT, panel);

	    button = NewRadioButton(left + (right - left) / 4, left + 2 * (right - left) / 4,
				bottom - CHECKBOXSPACING - CHECKBOXHEIGHT, bottom - CHECKBOXSPACING, "second(s)");
	    AddRadioButton(radioGroup, button);
	    button = NewRadioButton(left + (right - left) / 4, left + 2 * (right - left) / 4,
				bottom - 2 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING, bottom - CHECKBOXHEIGHT - 2 * CHECKBOXSPACING, "time step(s)");
	    AddRadioButton(radioGroup, button);
 
	    SetVar(radioGroup, REPOBJ, clock);
	    var = GetVar(clock, DTIMEUNITS);
	    SetValue(radioGroup, var ? var : NewInt(0));
	    SetMethod(radioGroup, CHANGEDVALUE, ChangeDeltaTimeUnits);
	    SetVar(radioGroup, HELPSTRING, genericHelp);

	    /*Create the Every text box*/
	    textBox = NewTextBox(left + 2 * (right - left) / 4, left + 3 * (right - left) / 4,
				 bottom + (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2 + EDITBOXDOWN,
				 top - (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2 + EDITBOXDOWN,
				 PLAIN, "Every legend", "every");
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Create the dTimePer editable text box*/
	    var = GetVar(clock, DTIMEPER);
	    PrintNumber(readoutText, var ? GetReal(var) : 1.0);
	    textBox = NewTextBox(left + 3 * (right - left) / 4, right,
				 bottom, top,
				 EDITABLE + WITH_PIT + ONE_LINE,
				 "Delta Time Per Box", readoutText);
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);
	    SetVar(textBox, HELPSTRING, genericHelp);

	    /*Create the seconds and frames box*/
	    radioGroup = NewRadioButtonGroup("Delta Time Per");
	    PrefixList(contents, radioGroup);
	    SetVar(radioGroup, PARENT, panel);
	    SetVar(radioGroup, REPOBJ, clock);
	    SetMethod(radioGroup, CHANGEDVALUE, ChangeDeltaTimePerUnits);
	    SetVar(radioGroup, HELPSTRING, genericHelp);

	    button = NewRadioButton(left + 3 * (right - left) / 4, right,
				bottom - CHECKBOXSPACING - CHECKBOXHEIGHT, bottom - CHECKBOXSPACING, "second(s)");
	    AddRadioButton(radioGroup, button);
	    button = NewRadioButton(left + 3 * (right - left) / 4, right,
				bottom - 2 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING, bottom - CHECKBOXHEIGHT - 2 * CHECKBOXSPACING, "frame (s)");
	    AddRadioButton(radioGroup, button);
 
	    SetVar(textBox, REPOBJ, clock);
	    var = GetVar(clock, DTIMEPERUNITS);
	    SetValue(radioGroup, var ? var : NewInt(0));
	    SetMethod(textBox, CHANGEDVALUE, ChangeDeltaTimePerBox);

	    top = bottom - MAJORBORDER - 2 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING;

	    /*Create the cycle time radio group*/
	    radioGroup = NewRadioButtonGroup("Cycle the clock");
	    SetVar(radioGroup, PARENT, panel);
	    SetVar(radioGroup, REPOBJ, clock);
	    SetMethod(radioGroup, CHANGEDVALUE, ChangeCycleClock);
	    SetVar(radioGroup, HELPSTRING,
		NewString("These radio buttons control whether the clock, when it is running forward \
or backward, stops at the endpoints in time or cycles around to the other end.\n"));
	    PrefixList(contents, radioGroup);

	    /*And buttons*/
	    button = NewRadioButton(left, (right + left) / 2,
				top - CHECKBOXHEIGHT,
				top,
				"Stop at endpoints");
	    SetVar(button, HELPSTRING,
		NewString("Click on this button to have the clock automatically stop when it reaches an endpoint."));
	    AddRadioButton(radioGroup, button);

	    button = NewRadioButton((right + left) / 2, right,
				top - CHECKBOXHEIGHT,
				top,
				"Cycle at endpoints");
	    SetVar(button, HELPSTRING,
		NewString("Click on this button to have the clock automatically cycle around it reaches an endpoint."));
	    AddRadioButton(radioGroup, button);

	    SetValue(radioGroup, NewInt(GetPredicate(clock, CYCLECLOCK)));

	    top -= MAJORBORDER + CHECKBOXHEIGHT;

	    /*Create the icon buttons*/
	    radioGroup = NewRadioButtonGroup("Speed Control");
	    SetVar(radioGroup, PARENT, panel);
	    SetVar(radioGroup, REPOBJ, clock);
	    SetVar(radioGroup, HELPSTRING,
		NewString("These radio buttons control the speed of the clock.  \
They are tied to the Delta Time Per Second slider and provide easy access to some convenient values.  \
The most useful is Stop, the button in the center with the square icon."));
	    PrefixList(contents, radioGroup); 

	    /*Stop*/
	    button = NewIconButton((left + right) / 2 - ICONBUTTONSIZE / 2,
				   (left + right) / 2 + ICONBUTTONSIZE / 2,
				   top - ICONBUTTONSIZE, top,
				   ICONSTOP, UIGREEN, "Stop", BS_PLAIN);
	    SetVar(button, REPOBJ, clock);
	    SetVar(button, HELPSTRING, NewString("This button stops the animation."));
	    AddRadioButton(radioGroup, button);

	    /*Forward*/
	    button = NewIconButton((left + right) / 2 - ICONBUTTONSIZE / 2 + ICONBUTTONSIZE + MINORBORDER,
				   (left + right) / 2 + ICONBUTTONSIZE / 2 + ICONBUTTONSIZE + MINORBORDER,
				   top - ICONBUTTONSIZE, top,
				   ICONPLAY, UIGREEN, "Play", BS_PLAIN);
	    SetVar(button, REPOBJ, clock);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving forward at the rate specified above."));
	    AddRadioButton(radioGroup, button);

	    /*Reverse*/
	    button = NewIconButton((left + right) / 2 - ICONBUTTONSIZE / 2 - ICONBUTTONSIZE - MINORBORDER,
				   (left + right) / 2 + ICONBUTTONSIZE / 2 - ICONBUTTONSIZE - MINORBORDER,
				   top - ICONBUTTONSIZE, top,
				   ICONREV, UIGREEN, "Reverse", BS_PLAIN);
	    SetVar(button, REPOBJ, clock);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving backward at the rate specified above."));
	    AddRadioButton(radioGroup, button);

	    /*Fast Forward*/
	    button = NewIconButton((left + right) / 2 - ICONBUTTONSIZE / 2 + 2 * (ICONBUTTONSIZE + MINORBORDER),
				   (left + right) / 2 + ICONBUTTONSIZE / 2 + 2 * (ICONBUTTONSIZE + MINORBORDER),
				   top - ICONBUTTONSIZE, top,
				   ICONFF, UIGREEN, "Fast Forward", BS_PLAIN);
	    SetVar(button, REPOBJ, clock);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving forward at three times the rate specified above."));
	    AddRadioButton(radioGroup, button);

	    /*Fast Reverse*/
	    button = NewIconButton((left + right) / 2 - ICONBUTTONSIZE / 2 - 2 * (ICONBUTTONSIZE + MINORBORDER),
				   (left + right) / 2 + ICONBUTTONSIZE / 2 - 2 * (ICONBUTTONSIZE + MINORBORDER),
				   top - ICONBUTTONSIZE, top,
				   ICONFR, UIGREEN, "Fast Reverse", BS_PLAIN);
	    SetVar(button, REPOBJ, clock);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving backward at three times the rate specified above."));
	    AddRadioButton(radioGroup, button);

	    var = GetVar(clock, RUNSPEED);
	    if (var)
	    {
		SetValue(radioGroup, var);
	    }
	    else
	    {
		SetValue(radioGroup, NewInt(RC_STOP));
	    }

	    SetMethod(radioGroup, CHANGEDVALUE, ChangeRunControl);
	    SetVar(clock, RUNCONTROL, radioGroup);
	}
    }

    return (ObjPtr) clockWindow;
}

static ObjPtr ChangePerspective(object)
ObjPtr object;
/*Change value for a perspective control*/
{
    ObjPtr val;
    ObjPtr repObj;
    real oldFocusDist;
    real newPerspec[4];

    repObj = GetObjectVar("ChangePerspective", object, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetRealVar("ChangePerspective", repObj, FOCUSDIST);
    if (!val)
    {
	return ObjFalse;
    }
    oldFocusDist = GetReal(val);

    val = GetFixedArrayVar("ChangePerspective", object, VALUE, 1, 4L);
    if (!val)
    {
	return ObjFalse;
    }
    Array2CArray(newPerspec, val);

    /*Change the observer's bits*/
    SetVar(repObj, VIEWFIELD, NewReal(newPerspec[1]));
    val = NewRealArray(1, 2L);
    CArray2Array(val, &(newPerspec[2]));
    SetVar(repObj, VIEWCLIP, val);

    /*See if field distance has changed*/
    if (oldFocusDist != newPerspec[0])
    {
	/*If so, change LOCATION accordingly*/
	real posn[3];
	real focus[3];
	real forward[3];
	ObjPtr var;

	GetFocusPoint(focus, repObj);
	GetObserverLocation(forward, repObj);
	forward[0] = focus[0] - forward[0];
	forward[1] = focus[1] - forward[1];
	forward[2] = focus[2] - forward[2];
	NORMALIZE(forward);

	posn[0] = focus[0] - forward[0] * newPerspec[0];
	posn[1] = focus[1] - forward[1] * newPerspec[0];
	posn[2] = focus[2] - forward[2] * newPerspec[0];

	SetVar(repObj, FOCUSDIST, NewReal(newPerspec[0]));
	var = NewRealArray(1, 3L);
	CArray2Array(var, posn);
	SetVar(repObj, LOCATION, var);
   }

   ImInvalid(repObj);
   DrawMe(object);
   return ObjTrue;
}

ObjPtr ChangeViewType(radio)
ObjPtr radio;
/*Changes the view type according to radio*/
{
    ObjPtr controller, control;
    ObjPtr var;
    int oldValue, newValue;

    controller = GetObjectVar("ChangeViewType", radio, REPOBJ);
    if (!controller)
    {
	return ObjFalse;
    }

    var = GetIntVar("ChangeViewType", controller, VIEWTYPE);
    if (!var)
    {
	return ObjFalse;
    }
    oldValue = GetInt(var);

    var = GetValue(radio);
    if (!var)
    {
	return ObjFalse;
    }
    newValue = GetInt(var);

    if (newValue == oldValue)
    {
	return ObjTrue;
    }

    control = GetObjectVar("ChangeViewType", radio, PERSPECCONTROL);
    if (!control)
    {
	return ObjFalse;
    }

    if (newValue == VT_ORTHOGRAPHIC)
    {
	MakePerspecOrtho(control, true);
	ChangedValue(control);
    }
    else if (oldValue == VT_ORTHOGRAPHIC)
    {
	MakePerspecOrtho(control, false);
	ChangedValue(control);
    }

    SetVar(controller, VIEWTYPE, NewInt(newValue));
    ImInvalid(controller);
    return ObjTrue;
}

ObjPtr ChangeAirspeed(slider)
ObjPtr slider;
/*Changes the airspeed of an observer according to a slider*/
{
    ObjPtr controller;
    ObjPtr value;

    controller = GetObjectVar("ChangeAirspeed", slider, REPOBJ);
    if (!controller)
    {
	return ObjFalse;
    }

    value = GetRealVar("ChangeAirspeed", slider, VALUE);
    if (!value)
    {
	return ObjFalse;
    }

    SetVar(controller, AIRSPEED, value);
    ImInvalid(controller);

    return ObjTrue;
}

ObjPtr ChangeFlying(radio)
ObjPtr radio;
/*Changes whether an observer is flying based on radio*/
{
    ObjPtr repObj, var;
    Bool flying;
    repObj = GetObjectVar("ChangeFlying", radio, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }
    var = GetValue(radio);
    if (!var)
    {
	return ObjFalse;
    }
    flying = GetInt(var) ? true : false;
    if (flying)
    {
    	WakeMe(repObj, MARKTIME, Clock() + 0.001);
    }

    SetVar(repObj, FLYING, flying ? ObjTrue : ObjFalse);
    ImInvalid(repObj);
    return ObjTrue;
}

ObjPtr ResetPosition(button)
ObjPtr button;
/*Resets an observer's position*/
{
    ObjPtr observer, var;
    real focusDist;
    real forward[3];
    real eyePosn[3];
    int i;

    observer = GetObjectVar("ResetPosition", button, REPOBJ);
    if (!observer)
    {
	return ObjFalse;
    }

    focusDist = INITEYEDIST;
    var = GetRealVar("ResetPosition", observer, FOCUSDIST);
    if (var)
    {
	focusDist = GetReal(var);
    }

    GetForwardVector(forward, observer);

    /*Derive the eye position from 0,0,0 through eye distance*/
    
    eyePosn[0] = -forward[0] * focusDist;
    eyePosn[1] = -forward[1] * focusDist;
    eyePosn[2] = -forward[2] * focusDist;
    var = NewRealArray(1, 3L);
    CArray2Array(var, eyePosn);
    SetVar(observer, LOCATION, var);

    ImInvalid(observer);

    return ObjTrue;
}

ObjPtr ResetRotation(button)
ObjPtr button;
/*Resets an observer's rotation*/
{
    ObjPtr observer;
    real focusPoint[3];
    real location[3];
    real forward[3];
    real eyePosn[3];
    real up[3];
    ObjPtr var;
    real focusDist;

    observer = GetObjectVar("ResetPosition", button, REPOBJ);
    if (!observer)
    {
	return ObjFalse;
    }

    focusDist = INITEYEDIST;
    var = GetRealVar("ResetPosition", observer, FOCUSDIST);
    if (var)
    {
	focusDist = GetReal(var);
    }

    GetFocusPoint(focusPoint, observer);
    forward[0] = 0.0;
    forward[1] = 0.0;
    forward[2] = -1.0;
    var = NewRealArray(1, 3L);
    CArray2Array(var, forward);
    SetVar(observer, FORWARDVECTOR, var);

    up[0] = 0.0;
    up[1] = 1.0;
    up[2] = 0.0;
    var = NewRealArray(1, 3L);
    CArray2Array(var, up);
    SetVar(observer, UPVECTOR, var);

    eyePosn[0] = focusPoint[0] - forward[0] * focusDist;
    eyePosn[1] = focusPoint[1] - forward[1] * focusDist;
    eyePosn[2] = focusPoint[2] - forward[2] * focusDist;
    var = NewRealArray(1, 3L);
    CArray2Array(var, eyePosn);
    SetVar(observer, LOCATION, var);

    ImInvalid(observer);

    return ObjTrue;
}

static ObjPtr EnterBinocularity(box)
ObjPtr box;
/*Enters the binocularity from box*/
{
    ObjPtr value, repObj;
    float val;
    char *s;

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    s = GetString(value);
    if (!ParseReal(&val, s))
    {
	return ObjFalse;
    }
    if (val == missingData)
    {
	DoUniqueTask(DoMissingError);
	return ObjFalse;
    }
    if (val >= plusInf || val <= minusInf)
    {
	DoUniqueTask(DoInfinityError);
	return ObjFalse;
    }

    repObj = GetObjectVar("EnterBinocularity", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, BINOCULARITY, NewReal(val));
    ImInvalid(repObj);

    return ObjTrue;
}

ObjPtr ShowObserverControls(observer, windowName)
ObjPtr observer;
ObjPtr windowName;
/*Makes a new observer window to control observer.  Ignores ownerWindow and windowName*/
{
    WinInfoPtr observerWindow;
    ObjPtr name;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr contents;
    int left, right, bottom, top;
    WinInfoPtr dialogExists;
    ObjPtr whichDialog;

    if (!observer) return NULLOBJ;

    name = GetVar(observer, NAME);

    whichDialog = NewString("Observer");
    dialogExists = DialogExists((WinInfoPtr) observer, whichDialog);
    if (name)
    {
	strncpy(tempStr, GetString(name), TEMPSTRSIZE);
	tempStr[TEMPSTRSIZE] = 0;
    }
    else
    {
	strcpy(tempStr, "Observer");
    }
    GetTemplateBounds(ObserverTemplate, "Panel", &left, &right, &bottom, &top);
    observerWindow = GetDialog((WinInfoPtr) observer, whichDialog, tempStr, 
	right - left, top - bottom, right - left, top - bottom, WINFIXEDSIZE + WINDBUF);

    if (!dialogExists)
    {
	SetVar((ObjPtr) observerWindow, REPOBJ, observer);

	/*Add in a help string*/
	SetVar((ObjPtr) observerWindow, HELPSTRING,
	    NewString("This window shows controls for an observer.  The observer \
represents you, looking into the space containing visualization objects.  The controls \
allow you to change your viewing angle, focus point, and clipping planes \
and also let you fly through the visualization with a simple flight simulator."));

	/*Add in a panel*/
	ContentsExpectWindowSize(observerWindow, right - left, top - bottom);
	panel = TemplatePanel(ObserverTemplate, "Panel");
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) observerWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) observerWindow);

	contents = GetListVar("ShowObserverControls", panel, CONTENTS);
	if (contents)
	{
	    ObjPtr control;
	    ObjPtr textBox;
	    ObjPtr slider, var;
	    ObjPtr radio, button;
	    ObjPtr tempObj;
	    ObjPtr titleBox, checkBox;
	    real perspecValue[4];
	    int viewType;
	    char readoutText[40];

	    /*View type chooser*/
	    titleBox = TemplateTitleBox(ObserverTemplate, "View Type");
	    SetVar(titleBox, PARENT, panel);
	    PrefixList(contents, titleBox);

	    radio = NewRadioButtonGroup("View Type Radio");
	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);
	    SetVar(radio, REPOBJ, observer);
	    SetVar(radio, HELPSTRING, NewString("This radio group controls \
the type of view given in all spaces controlled by this observer.  For more information \
about a given view type, use Help In Context on the button naming the view type.\n"));

	    button = TemplateRadioButton(ObserverTemplate, "Perspective");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, the standard \
perspective view is used.  Objects closer to the observer appear larger, giving a \
realistic image.\n"));

	    button = TemplateRadioButton(ObserverTemplate, "Orthographic");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, the \
orthograpic view is enabled.  Objects appear the same size no matter what the \
distance to the observer.  This view does not appear as realistic as the perspective \
view, but it does have the advantage that objects of the same size at different depths line up.  \
It is useful for viewing 2-D data or for comparing values in 3-D data at different \
depths.\n"));

	    button = TemplateRadioButton(ObserverTemplate, "Crosseyed Stereo");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, two \
perspective images are shown side by side for a stereo pair.  To view the stereo \
pair, cross your eyes until the images coincide.  For some people, this is very \
easy to do.  Some people have difficulty doing it.  Only try this if you personally \
find it easy, and if it becomes a strain, stop at once."));

	    button = TemplateRadioButton(ObserverTemplate, "Anaglyphic Stereo");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, two \
perspective images are shown, one red and one cyan, for a stereo pair.  To view the stereo \
pair, you will need to use a pair of red/cyan 3-D glasses.  Because one eye \
can see only one set of colors, subtle color variations will be washed out.  \
Also, colors which are heavily toward red or heavily toward blue will be confusing."));

	    var = GetVar(observer, VIEWTYPE);
	    if (var)
	    {
		SetValue(radio, var);
		viewType = GetInt(var);
	    }
	    else
	    {
		SetValue(radio, NewInt(viewType = VT_PERSPECTIVE));
	    }
	    SetMethod(radio, CHANGEDVALUE, ChangeViewType);

	    /*Binocular spacing*/
	    textBox = TemplateTextBox(ObserverTemplate, "Binocular Legend", PLAIN, "Binocular Spacing:");
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);

	    var = GetRealVar("ShowObserverControls", observer, BINOCULARITY);
	    PrintNumber(readoutText, var ? GetReal(var) : 0.0);
	    textBox = TemplateTextBox(ObserverTemplate, "Binocular Spacing", EDITABLE + WITH_PIT + ONE_LINE, readoutText);
	    SetVar(textBox, REPOBJ, observer);
	    SetVar(textBox, PARENT, panel);
	    PrefixList(contents, textBox);
	    SetVar(textBox, HELPSTRING, NewString("This editable text box controls \
the spacing between the eyes while viewing a visualization in one of the stereo \
modes.  The larger the number, the stronger the stereo effect will be.  The \
maximum comfortable stereo effect may vary depending on the particular image, \
so you may have to experiment to get the best effect."));
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetMethod(textBox, CHANGEDVALUE, EnterBinocularity);

	    /*Add in the perspective control*/
    	    control = TemplatePerspecControl(ObserverTemplate, "Perspective Control");
	    PrefixList(contents, control);
	    SetVar(control, PARENT, panel);
	    SetVar(control, REPOBJ, observer);
	    if (viewType == VT_ORTHOGRAPHIC)
	    {
		MakePerspecOrtho(control, true);
	    }

	    /*Set its initial value*/
	    tempObj = GetVar(observer, FOCUSDIST);
	    if (tempObj)
	    {
		perspecValue[0] = GetReal(tempObj);
	    }
	    else
	    {
		perspecValue[0] = INITEYEDIST;
	    }
	    tempObj = GetVar(observer, VIEWFIELD);
	    if (tempObj)
	    {
		perspecValue[1] = GetReal(tempObj);
	    }
	    else
	    {
		perspecValue[1] = INITAOV;
	    }
	    tempObj = GetVar(observer, VIEWCLIP);
	    if (tempObj)
	    {
		Array2CArray(&(perspecValue[2]), tempObj);
	    }
	    else
	    {
		perspecValue[2] = INITNEARCLIP;
		perspecValue[3] = INITFARCLIP;
	    }
	    tempObj = NewRealArray(1, 4L);
	    CArray2Array(tempObj, perspecValue);
	    SetValue(control, tempObj);
	    SetMethod(control, CHANGEDVALUE, ChangePerspective);

	    /*Link it to the radio*/
	    SetVar(radio, PERSPECCONTROL, control);

	    /*Add in the perspective text box*/
	    textBox = TemplateTextBox(ObserverTemplate, "Perspective Text",
			PLAIN, "Perspective");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Reset controls*/
	    button = TemplateButton(ObserverTemplate, "Reset Position");
	    PrefixList(contents, button);
	    SetVar(button, PARENT, panel);
	    SetVar(button, REPOBJ, observer);
	    SetMethod(button, CHANGEDVALUE, ResetPosition);
	    SetVar(radio, HELPSTRING, NewString("This button resets the position \
of the observer to the default.  It can be useful if you get lost.\n"));

	    button = TemplateButton(ObserverTemplate, "Reset Rotation");
	    PrefixList(contents, button);
	    SetVar(button, PARENT, panel);
	    SetVar(button, REPOBJ, observer);
	    SetMethod(button, CHANGEDVALUE, ResetRotation);
	    SetVar(radio, HELPSTRING, NewString("This button resets the rotation \
of the observer to the default.  It can be useful if you get lost.\n"));
	}
    }

    return (ObjPtr) observerWindow;
}

ObjPtr ChangeRenderType(control)
ObjPtr control;
/*Changes the render type based on a radio button*/
{
    ObjPtr controller;
    ObjPtr value;
    real roll, dpitch;

    controller = GetObjectVar("ChangeRenderType", control, REPOBJ);
    if (!controller)
    {
	return ObjFalse;
    }

    SetVar(controller, RENDERTYPE, GetValue(control));
    ImInvalid(controller);

    return ObjTrue;
}

void RendererRGB(visWindow)
WinInfoPtr visWindow;
/*Sends a deferred message to visWindow if it has objectForRGB*/
{
    ObjPtr space;

    space = FindSpace(visWindow);
    if (space)
    {
	if (objectForRGB == GetVar(space, RENDERER))
	{
	    DeferMessage((ObjPtr) visWindow, SETRGBMESSAGE);
	}
    }
}

ObjPtr ChangeFilterType(control)
ObjPtr control;
/*Changes the filter type based on a radio button*/
{
    ObjPtr controller;
    ObjPtr value;

    controller = GetObjectVar("ChangeFilterType", control, REPOBJ);
    if (!controller)
    {
	return ObjFalse;
    }

    value = GetValue(control);
    SetVar(controller, FILTERTYPE, GetValue(control));
    if (GetInt(value) != FT_NONE)
    {
	ObjPtr renderType;
	renderType = GetVar(controller, RENDERTYPE);
#ifdef GRAPHICS
	if (renderType && GetInt(renderType) == 1 && hasRGB)
	{
	    objectForRGB = controller;
	    ForAllVisWindows(RendererRGB);
	}
#endif
    }
    ImInvalid(controller);

    return ObjTrue;
}

ObjPtr ShowRendererControls(renderer, windowName)
ObjPtr renderer;
ObjPtr windowName;
/*Makes a new renderer window to control renderer.*/
{
    WinInfoPtr rendererWindow;
    ObjPtr name;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr contents;
    WinInfoPtr dialogExists;
    ObjPtr whichDialog;
    int left, right, bottom, top;

    if (!renderer) return NULLOBJ;

    name = GetVar(renderer, NAME);

    whichDialog = NewString("Renderer");
    dialogExists = DialogExists((WinInfoPtr) renderer, whichDialog);
    if (name)
    {
	strncpy(tempStr, GetString(name), TEMPSTRSIZE);
	tempStr[TEMPSTRSIZE] = 0;
    }
    else
    {
	strcpy(tempStr, "Renderer");
    }
    GetTemplateBounds(RendererTemplate, "Panel", &left, &right, &bottom, &top);

    rendererWindow = GetDialog((WinInfoPtr) renderer, whichDialog, tempStr, 
	right - left, top - bottom, right - left, top - bottom, WINDBUF);

    if (!dialogExists)
    {
	SetVar((ObjPtr) rendererWindow, REPOBJ, renderer);

	/*Add controls*/
	SetVar((ObjPtr) rendererWindow, HELPSTRING,
	    NewString("This window shows controls that affect the rendering of \
the objects within the space."));
	contents = GetVar((ObjPtr) rendererWindow, CONTENTS);

	/*Add in a panel*/
	panel = TemplatePanel(RendererTemplate, "Panel");
	if (!panel)
	{
	    return ObjFalse;
	}
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) rendererWindow);

	contents = GetListVar("ShowRendererControls", panel, CONTENTS);
	if (contents)
	{
	    ObjPtr titleBox;
	    ObjPtr button;
	    ObjPtr radio;
	    ObjPtr var;

	    /*Make the title box around render type*/
	    titleBox = TemplateTitleBox(RendererTemplate, "Renderer Type");
	    PrefixList(contents, titleBox);
	    SetVar(titleBox, PARENT, panel);

	    /*Make the no renderer button*/
	    radio = NewRadioButtonGroup("Renderer");
	    SetVar(radio, HELPSTRING, NewString("These radio buttons control what kind of renderer is \
used to render the objects within the space.  At present, there is only one renderer available: a hardware renderer."));

	    button = TemplateRadioButton(RendererTemplate, "No Renderer");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING,
		NewString("This button causes rendering not to be done on the space.  \
This is sometimes useful as to hide all the visualization objects."));

	    /*Make the full color hardware renderer button*/
	    button = TemplateRadioButton(RendererTemplate,  "Full Color Hardware");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING,
		NewString("This button sets the space to use the hardware renderer \
in full color mode.  In this mode, all the lighting and filtering is available.  Full color \
mode only works if the hardware has enough bitplanes to support it."));
	    if (!hasRGB)
	    {
		ActivateButton(button, false);
	    }	

	    /*Make the color map hardware renderer button*/
	    button = TemplateRadioButton(RendererTemplate,  "Color Map Hardware");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING,
		NewString("This button sets the space to use the hardware renderer \
in color map mode.  In this mode, lighting and image filtering is not available, but it can be useful \
on simple color shaded visualizations.  Color map mode only works on hardware with \
color maps."));
	    if (!hasCmap)
	    {
		ActivateButton(button, false);
	    }	

	    /*Set the radio button group*/
	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);
	    SetVar(radio, REPOBJ, renderer);

	    var = GetVar(renderer, RENDERTYPE);
	    if (var)
	    {
		SetValue(radio, var);
	    }
	    else
	    {
		SetValue(radio, NewInt(hasRGB ? RT_RGB_HARDWARE : RT_CMAP_HARDWARE));
	    }
	    SetMethod(radio, CHANGEDVALUE, ChangeRenderType);

	    /*Make the title box around filter type*/
	    titleBox = TemplateTitleBox(RendererTemplate, "Image Filter");
	    PrefixList(contents, titleBox);
	    SetVar(titleBox, PARENT, panel);

	    /*Make the no filter button*/
	    radio = NewRadioButtonGroup("Filter Type");
	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);

	    SetVar(radio, HELPSTRING,
		NewString("These radio buttons select the kind of filtration that is done \
to the image of the space after it has been rendererd.  Filtration only works when the \
space is set to full color mode."));
	    button = TemplateRadioButton(RendererTemplate, "No Filter");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING,
		NewString("This button causes the image of the space to be shown \
without filtration.  This is the fastest and is recommended for interactive work."));

	    /*Make the shrink filter button*/
	    button = TemplateRadioButton(RendererTemplate, "2 to 1 shrink");
	    SetVar(button, HELPSTRING,
		NewString("This button causes the image of the space to be shrunk \
2 to 1 and averaged before being displayed.  This only works well when the window is \
in full color mode and the entire window is shown on the screen and is not covered by \
any other window.  It produces pretty good results with video.  Use the Double Video Screen \
item in the Window menu."));
	    AddRadioButton(radio, button);
	    if (!hasRGB)
	    {
		ActivateButton(button, false);
	    }	

	    /*Make the shrink filter button*/
	    button = TemplateRadioButton(RendererTemplate, "Four neighbor average");
	    SetVar(button, HELPSTRING,
		NewString("This button causes each pixel of image of the space to be \
averaged with its four neighbors in the horizontal and vertical directions before being displayed.  \
This only works well when the window is in full color mode and is not covered by \
other windows.  It produces pretty good results with video, \
though not as good as the shrinking filter."));
	    AddRadioButton(radio, button);
	    if (!hasRGB)
	    {
		ActivateButton(button, false);
	    }	

	    SetVar(radio, REPOBJ, renderer);
	    var = GetVar(renderer, FILTERTYPE);
	    if (var)
	    {
		SetValue(radio, var);
	    }
	    else
	    {
		SetValue(radio, NewInt(FT_NONE));
	    }
	    SetMethod(radio, CHANGEDVALUE, ChangeFilterType);
	}
    }

    return (ObjPtr) rendererWindow;
}

ObjPtr MakeClockTimeFormat(clock)
ObjPtr clock;
/*Makes a clock's TIMEFORMAT variable*/
{
    ObjPtr datasets;
    int curFormat;
    ObjPtr format;
    ObjPtr element;
    long k;

    MakeVar(clock, DATASETS);
    curFormat = 0;

    datasets = GetVar(clock, DATASETS);
    if (datasets)
    {
	for (k = 0; k < DIMS(datasets)[0]; ++k)
	{
	    element = GetObjectElement(datasets, &k);
	    MakeVar(element, TIMEFORMAT);
	    format = GetVar(element, TIMEFORMAT);
	    if (format)
	    {
		int newFormat;
		newFormat = GetInt(format);
		curFormat |= newFormat;
	    }
	}
    }
    if (!curFormat)
    {
	curFormat = TF_SECONDS + TF_SUBSECONDS;
    }
    SetVar(clock, TIMEFORMAT, NewInt(curFormat));
    return ObjTrue;
}

ObjPtr MakeClockDatasets(clock)
ObjPtr clock;
/*Makes a clock's DATASETS variable, along with time slices*/
{
    ObjPtr list, spaces, array;
    ObjPtr element;
    long k;
    WinInfoPtr dialog;

    list = NewList();
    spaces = GetListVar("MakeClockDatasets", clock, SPACES);
    if (spaces)
    {
	ThingListPtr spaceRunner;
	spaceRunner = LISTOF(spaces);
	while (spaceRunner)
	{
	    ObjPtr contents;
	    contents = GetListVar("MakeClockDatasets", spaceRunner -> thing, CONTENTS);
	    if (contents)
	    {
		ThingListPtr contentsRunner;
		contentsRunner = LISTOF(contents);
		while (contentsRunner)
		{
		    PrefixDatasets(list, contentsRunner -> thing);
		    contentsRunner = contentsRunner -> next;
		}
	    }
	    spaceRunner = spaceRunner -> next;
	}
    }

    if (LISTOF(list))
    {
	/*There's something in the list*/
	Bool hasMinValue = false;
	real minValue;

	/*Convert to an array*/
	array = ListToArray(list);

	/*Remove duplicate elements*/
	array = Uniq(array);

	/*Sort the array*/
	array = SortArrayByStringVar(array, NAME);

	for (k = 0; k < DIMS(array)[0]; ++k)
	{
	    ObjPtr name, timeSteps;
	    element = GetObjectElement(array, &k);

	    MakeVar(element, NAME);
	    MakeVar(element, TIMESTEPS);
	    name = GetVar(element, NAME);
	    timeSteps = GetVar(element, TIMESTEPS);
	    if (timeSteps)
	    {
		if (hasMinValue)
		{
		    minValue = MIN(minValue, *((real *)ELEMENTS(timeSteps)));
		}
		else
		{
		    minValue = *((real *)ELEMENTS(timeSteps));
		    hasMinValue = true;
		}
	    }
	}
	if (hasMinValue)
	{
	    if (!GetVar(clock, TIME))
	    {
		SetVar(clock, TIME, NewReal(minValue));
		ImInvalid(clock);
	    }
	}
	else
	{
	    SetVar(clock, TIME, NULLOBJ);
	}
    }
    else
    {
	array = NULLOBJ;
	SetVar(clock, TIME, NULLOBJ);
    }

    /*Set the clock's datasets to datasets*/
    SetVar(clock, DATASETS, array);

	/*Now inval the control*/
	dialog = DialogExists((WinInfoPtr) clock, NewString("Clock"));

	if (dialog)
	{
	    ObjPtr control;

	    control = GetVar((ObjPtr) dialog, TIMECONTROL);
	    if (control)
	    {
		ImInvalid(control);
		MakeVar(control, DATASETS);
	    }
	}

    return ObjTrue;
}

ObjPtr commonClock = NULLOBJ;
ObjPtr commonObserver = NULLOBJ;
ObjPtr commonRenderer = NULLOBJ;

int clockNum = 0;
int observerNum = 0;
int rendererNum = 0;

ObjPtr NewClock()
/*Returns a new clock*/
{
    ObjPtr retVal;
    ObjPtr name;


    if (oneClock)
    {
	if (commonClock) return commonClock;
    }

    retVal = NewObject(clockClass, 0);
    if (oneClock)
    {
	commonClock = retVal;
    }
    name = GetVar(clockClass, NAME);
    if (name)
    {
	sprintf(tempStr, "%s %d", GetString(name), ++clockNum);
    }
    else
    {
	sprintf(tempStr, "Clock %d", ++clockNum);
    }
    name = NewString(tempStr);
    SetVar(retVal, NAME, name);
    SetVar(retVal, SPACES, NewList());
    SetVar(retVal, CYCLECLOCK, ObjTrue);
    return retVal;
}

#ifdef PROTO
void GetUpVector(real upVector[3], ObjPtr observer)
#else
void GetUpVector(upVector, observer)
real upVector[3];
ObjPtr observer;
#endif
/*Gets the up vector of an observer*/
{
    ObjPtr var;

    var = GetVar(observer, UPVECTOR);
    if (var)
    {
	Array2CArray(upVector, var);
    }
    else
    {
	upVector[0] = 0.0;
	upVector[1] = 1.0;
	upVector[2] = 0.0;
    }
}

#ifdef PROTO
void GetForwardVector(real forwardVector[3], ObjPtr observer)
#else
void GetForwardVector(forwardVector, observer)
real forwardVector[3];
ObjPtr observer;
#endif
/*Gets the forward vector of an observer*/
{
    ObjPtr var;
    var = GetVar(observer, FORWARDVECTOR);
    if (var)
    {
	Array2CArray(forwardVector, var);
    }
    else
    {
	forwardVector[0] = 0.0;
	forwardVector[1] = 0.0;
	forwardVector[2] = -1.0;
    }
}

#ifdef PROTO
void GetAdjustedVectors(real forwardVector[3], real upVector[3], ObjPtr observer)
#else
void GetAdjustedVectors(forwardVector, upVector, observer)
real forwardVector[3];
real upVector[3];
ObjPtr observer;
#endif
/*Gets the vectors of an observer, adjusted for roll and pitch*/
{
    ObjPtr var;
    real temp[3];
    real sp, cp, t;
    Matrix rotDelta;
    real phi;

    GetUpVector(upVector, observer);
    GetForwardVector(forwardVector, observer);

   var = GetVar(observer, PITCH);
    if (var)
    {
	phi = GetReal(var);
	if (phi != 0.0)
	{
	    /*Rotate down around pitch*/
	    real side[3];

	    sp = rsin(phi);
	    cp = rcos(phi);
	    t = 1.0 - cp;
	
	    /*Get a side vector*/
	    CROSS(forwardVector, upVector, side);

	    /*Make the change rotation matrix by rows from top to bottom*/
	    rotDelta[0][0] = t * SQUARE(side[0]) + cp;
	    rotDelta[0][1] = t * side[0] * side[1] + sp * side[2];
	    rotDelta[0][2] = t * side[0] * side[2] - sp * side[1];
	    rotDelta[0][3] = 0.0;
	
	    rotDelta[1][0] = t * side[0] * side[1] - sp * side[2];
	    rotDelta[1][1] = t * SQUARE(side[1]) + cp;
	    rotDelta[1][2] = t * side[1] * side[2] + sp * side[0];
	    rotDelta[1][3] = 0.0;
	
	    rotDelta[2][0] = t * side[0] * side[2] + sp * side[1];
	    rotDelta[2][1] = t * side[1] * side[2] - sp * side[0];
	    rotDelta[2][2] = t * SQUARE(side[2]) + cp;
	    rotDelta[2][3] = 0.0;
	
	    rotDelta[3][0] = 0.0;
	    rotDelta[3][1] = 0.0;
	    rotDelta[3][2] = 0.0;
	    rotDelta[3][3] = 1.0;
	
	    /*Rotate forward and up down through pitch*/

	    MATBYVECTOR(rotDelta, forwardVector, temp);
	    VECCOPY(forwardVector, temp);

	    MATBYVECTOR(rotDelta, upVector, temp);
	    VECCOPY(upVector, temp);
	}
    }

    var = GetVar(observer, ROLL);
    if (var)
    {
	phi = -GetReal(var);
	if (phi != 0.0)
	{
	    /*Rotate around -roll*/
	    sp = rsin(phi);
	    cp = rcos(phi);
	    t = 1.0 - cp;
	
	    /*Make the change rotation matrix by rows from top to bottom*/
	    rotDelta[0][0] = t * SQUARE(forwardVector[0]) + cp;
	    rotDelta[0][1] = t * forwardVector[0] * forwardVector[1] + sp * forwardVector[2];
	    rotDelta[0][2] = t * forwardVector[0] * forwardVector[2] - sp * forwardVector[1];
	    rotDelta[0][3] = 0.0;
	
	    rotDelta[1][0] = t * forwardVector[0] * forwardVector[1] - sp * forwardVector[2];
	    rotDelta[1][1] = t * SQUARE(forwardVector[1]) + cp;
	    rotDelta[1][2] = t * forwardVector[1] * forwardVector[2] + sp * forwardVector[0];
	    rotDelta[1][3] = 0.0;
	
	    rotDelta[2][0] = t * forwardVector[0] * forwardVector[2] + sp * forwardVector[1];
	    rotDelta[2][1] = t * forwardVector[1] * forwardVector[2] - sp * forwardVector[0];
	    rotDelta[2][2] = t * SQUARE(forwardVector[2]) + cp;
	    rotDelta[2][3] = 0.0;
	
	    rotDelta[3][0] = 0.0;
	    rotDelta[3][1] = 0.0;
	    rotDelta[3][2] = 0.0;
	    rotDelta[3][3] = 1.0;
	
	    /*Rotate up around through roll*/
	    MATBYVECTOR(rotDelta, upVector, temp);
	    VECCOPY(upVector, temp);
	}
    }
}

#ifdef PROTO
void GetObserverLocation(real *location, ObjPtr observer)
#else
void GetFocusPoint(location, observer)
real *location;
ObjPtr observer;
#endif
/*Gets an observer's location*/
{
    ObjPtr var;
    var = GetVar(observer, LOCATION);
    if (var)
    {
	Array2CArray(location, var);
    }
    else
    {
	location[0] = 0.0;
	location[1] = 0.0;
	location[2] = INITEYEDIST;
    }
}

#ifdef PROTO
void GetFocusPoint(real *focusPoint, ObjPtr observer)
#else
void GetFocusPoint(focusPoint, observer)
real *focusPoint;
ObjPtr observer;
#endif
/*Gets the focus point of an observer, adjusted for roll and pitch*/
{
    real forward[3], up[3];
    ObjPtr var;
    real focusDist;

    GetAdjustedVectors(forward, up, observer);
    GetObserverLocation(focusPoint, observer);
    var = GetVar(observer, FOCUSDIST);
    if (var)
    {
	focusDist = GetReal(var);
    }
    else
    {
	focusDist = INITEYEDIST;
    }
    focusPoint[0] += focusDist * forward[0];
    focusPoint[1] += focusDist * forward[1];
    focusPoint[2] += focusDist * forward[2];
}

ObjPtr NewObserver()
/*Returns a new observer*/
{
    ObjPtr retVal;
    ObjPtr name;

    if (oneObserver)
    {
	if (commonObserver) return commonObserver;
    }
    retVal = NewObject(observerClass, 0);
    if (oneObserver)
    {
	commonObserver = retVal;
    }
    name = GetVar(observerClass, NAME);
    if (name)
    {
	sprintf(tempStr, "%s %d", GetString(name), ++observerNum);
    }
    else
    {
	sprintf(tempStr, "Observer %d", ++observerNum);
    }
    name = NewString(tempStr);
    SetVar(retVal, NAME, name);
    SetVar(retVal, SPACES, NewList());

    return retVal;
}

ObjPtr NewRenderer()
/*Returns a new renderer*/
{
    ObjPtr retVal;
    ObjPtr name;

    if (oneRenderer)
    {
	if (commonRenderer) return commonRenderer;
    }
    retVal = NewObject(rendererClass, 0);
    if (oneRenderer)
    {
	commonRenderer = retVal;
    }
    name = GetVar(rendererClass, NAME);
    if (name)
    {
	sprintf(tempStr, "%s %d", GetString(name), ++rendererNum);
    }
    else
    {
	sprintf(tempStr, "Renderer %d", ++rendererNum);
    }
    name = NewString(tempStr);
    SetVar(retVal, NAME, name);
    SetVar(retVal, SPACES, NewList());

    return retVal;
}

static ObjPtr CloneObserver(observer)
ObjPtr observer;
/*Clones observer*/
{
    Bool oldOneObserver;
    ObjPtr retVal;

    oldOneObserver = oneObserver;
    oneObserver = false;
    retVal = NewObserver();
    oneObserver = oldOneObserver;

    CopyVar(retVal, observer, ROTAXIS);
    CopyVar(retVal, observer, ROTSPEED);	
    CopyVar(retVal, observer, VIEWFIELD);
    CopyVar(retVal, observer, VIEWCLIP);
    CopyVar(retVal, observer, LOCATION);
    CopyVar(retVal, observer, FORWARDVECTOR);
    CopyVar(retVal, observer, FOCUSDIST);
    CopyVar(retVal, observer, ROLL);
    CopyVar(retVal, observer, PITCH);
    CopyVar(retVal, observer, AIRSPEED);

    return retVal;
}

static ObjPtr CloneClock(clock)
ObjPtr clock;
{
    ObjPtr retVal;
    Bool oldOneClock;

    oldOneClock = oneClock;
    oneClock = false;
    retVal = NewClock();
    oneClock = oldOneClock;

    CopyVar(retVal, clock, CYCLECLOCK);
    CopyVar(retVal, clock, TIMESTEPS);
    CopyVar(retVal, clock, DATASETS);
    CopyVar(retVal, clock, TIME);

    return retVal;
}

static ObjPtr CloneRenderer(renderer)
ObjPtr renderer;
/*Clones renderer*/
{
    Bool oldOneRenderer;
    ObjPtr retVal;

    oldOneRenderer = oneRenderer;
    oneRenderer = false;
    retVal = NewRenderer();
    oneRenderer = oldOneRenderer;

    CopyVar(retVal, renderer, RENDERTYPE);
    CopyVar(retVal, renderer, FILTERTYPE);

    return retVal;
}

#if 0
static ObjPtr DrawExtraObserverIcon(icon, x, y)
ObjPtr icon;
int x, y;
/*Draws the extra stuff for an observer icon*/
{
    ObjPtr space, observers;
    space = GetObjectVar("DrawExtraObserverIcon", icon, SPACE);
    if (!space)
    {
	return ObjFalse;
    }
    observers = GetListVar("DrawExtraObserverIcon", space, OBSERVERS);
    if (!observers)
    {
	return ObjFalse;
    }
    if (GetVar(icon, REPOBJ) == LISTOF(observers) -> thing)
    {
	FrameUIRect(x - ICONSIZE / 2 - 4, x + ICONSIZE / 2 + 4, y - ICONSIZE / 2 - ICONSHADOW - 4, y + ICONSIZE / 2 + 4, UIREC);
	SetUIColor(UIBLACK);
    }
    return ObjTrue;
}
#endif

ObjPtr ReshapeSpacePanel(object, ol, or, ob, ot, left, right, bottom, top)
ObjPtr object;
int ol, or, ob, ot;
int left, right, bottom, top;
/*Reshapes a space panel, which used to exist within owner with edges ol, or, ob, ot
  to one which exists within owner with edges left, right, bottom, top.*/
{
    ObjPtr boundsArray;
    ObjPtr stickyInt;
    real bounds[4];
    real oldWidth, oldHeight;	/*Old width and height*/
    Bool sideLocked[4];		/*True iff side is locked*/
    Bool xStretch, yStretch;	/*Stretchiness in x and y*/
    int stickiness;		/*Side stickiness of the object*/
    real oldBounds[4];		/*Old bounds of the object*/
    ObjPtr contents;		/*Contents of the object, if any*/
    real wr, hr;		/*Width and height ratios*/

    /*Reshape the panel itself*/
    wr = ((real) (right - left)) / ((real) (or - ol));
    hr = ((real) (top - bottom)) / ((real) (ot - ob));

    MakeVar(object, BOUNDS);
    boundsArray = GetVar(object, BOUNDS);
    if (!boundsArray || !IsRealArray(boundsArray) || RANK(boundsArray) != 1 ||
	DIMS(boundsArray)[0] != 4)
    {
	return ObjFalse;
    }
    Array2CArray(bounds, boundsArray);
    Array2CArray(oldBounds, boundsArray);
    oldWidth = bounds[1] - bounds[0];
    oldHeight = bounds[3] - bounds[2];

    /*Get the object's stickiness*/
    stickyInt = GetVar(object, STICKINESS);
    if (stickyInt && IsInt(stickyInt))
    {
	stickiness = GetInt(stickyInt);
    }
    else
    {
	stickiness = 0;
    }

    if ((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT))
    {
	if (stickiness & FLOATINGLEFT)
	{
	    bounds[0] = (bounds[0] - ol) * wr + left;
	}
	else
	{
	    bounds[0] += left - ol;
	}
	if (!((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT)))
	{
	    bounds[1] = bounds[0] + oldWidth;
	}
    }
    if ((stickiness & STICKYRIGHT) || (stickiness & FLOATINGRIGHT))
    {
	if (stickiness & FLOATINGRIGHT)
	{
	    bounds[1] = (bounds[1] - ol) * wr + left;
	}
	else
	{
	    bounds[1] += right - or;
	}
	if (!((stickiness & STICKYLEFT) || (stickiness & FLOATINGLEFT)))
	{
	    bounds[0] = bounds[1] - oldWidth;
	}
    }

    if ((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM))
    {
	if (stickiness & FLOATINGBOTTOM)
	{
	    bounds[2] = (bounds[2] - ob) * hr + bottom;
	}
	else
	{
	    bounds[2] += bottom - ob;
	}
	if (!((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP)))
	{
	    bounds[3] = bounds[2] + oldHeight;
	}
    }
    if ((stickiness & STICKYTOP) || (stickiness & FLOATINGTOP))
    {
	if (stickiness & FLOATINGTOP)
	{
	    bounds[3] = (bounds[3] - ob) * hr + bottom;
	}
	else
	{
	    bounds[3] += top - ot;
	}
	if (!((stickiness & STICKYBOTTOM) || (stickiness & FLOATINGBOTTOM)))
	{
	    bounds[2] = bounds[3] - oldHeight;
	}
    }

    /*We've got a new bounds, put it back*/
    boundsArray = NewRealArray(1, 4L);
    CArray2Array(boundsArray, bounds);
    SetVar(object, BOUNDS, boundsArray);
 
    /*If there are some contents to this, reshape them*/
    contents = GetVar(object, CONTENTS);
    if (contents && IsList(contents))
    {
	ThingListPtr runner;
	real oldCenterX, oldCenterY, centerX, centerY;
	real enlargement;
	
	oldCenterX = ((real) (oldBounds[1] - oldBounds[0])) * 0.5;
	oldCenterY = ((real) (oldBounds[3] - oldBounds[2])) * 0.5;
	centerX = ((real) (bounds[1] - bounds[0])) * 0.5;
	centerY = ((real) (bounds[3] - bounds[2])) * 0.5;
	enlargement = ((real) (bounds[3] - bounds[2])) /
		      ((real) (oldBounds[3] - oldBounds[2]));

	runner = LISTOF(contents);
	while (runner)
	{
	    ObjPtr var;
	    real point[2];
	    object = runner -> thing;
	    MakeVar(object, BOUNDS);
	    boundsArray = GetVar(object, BOUNDS);
	    if (boundsArray && IsRealArray(boundsArray) && RANK(boundsArray) == 1 &&
		DIMS(boundsArray)[0] == 4)
	    {
		/*Valid bounds.  Change them.*/
		Array2CArray(bounds, boundsArray);

		bounds[0] = centerX + (bounds[0] + HANDLESIZE / 2 - oldCenterX) * enlargement - HANDLESIZE / 2;
		bounds[1] = centerX + (bounds[1] - HANDLESIZE / 2 - oldCenterX) * enlargement + HANDLESIZE / 2;
		bounds[2] = centerY + (bounds[2] + HANDLESIZE / 2 - oldCenterY) * enlargement - HANDLESIZE / 2;
		bounds[3] = centerY + (bounds[3] - HANDLESIZE / 2 - oldCenterY) * enlargement + HANDLESIZE / 2;

		/*We've got a new bounds, put it back*/
		boundsArray = NewRealArray(1, 4L);
		CArray2Array(boundsArray, bounds);
		SetVar(object, BOUNDS, boundsArray);
	    }

	    var = GetVar(object, STARTPOINT);
	    if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 2)
	    {
		Array2CArray(point, var);
		point[0] = centerX + (point[0] - oldCenterX) * enlargement;
		point[1] = centerY + (point[1] - oldCenterY) * enlargement;
		var = NewRealArray(1, 2L);
		CArray2Array(var, point);
		SetVar(object, STARTPOINT, var);
	    }
	    
	    var = GetVar(object, ENDPOINT);
	    if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 2)
	    {
		Array2CArray(point, var);
		point[0] = centerX + (point[0] - oldCenterX) * enlargement;
		point[1] = centerY + (point[1] - oldCenterY) * enlargement;
		var = NewRealArray(1, 2L);
		CArray2Array(var, point);
		SetVar(object, ENDPOINT, var);
	    }
	    
	    runner = runner -> next;
	}
    }
    return ObjTrue;
}

static ObjPtr ChangeRotGuides(box)
ObjPtr box;
/*Changes the rot guides according to box*/
{
    ObjPtr repObj, value;

    repObj = GetObjectVar("ChangeRotGuides", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(box);

    SetVar(repObj, SHOWROTGUIDES, IsTrue(value) ? ObjTrue : ObjFalse);
    ImInvalid(repObj);
    return ObjTrue;
}

static ObjPtr ChangeMotionGuides(box)
ObjPtr box;
/*Changes the motion guides according to box*/
{
    ObjPtr repObj, value;

    repObj = GetObjectVar("ChangeRotGuides", box, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(box);

    SetVar(repObj, SHOWMOTGUIDES, IsTrue(value) ? ObjTrue : ObjFalse);
    ImInvalid(repObj);
    return ObjTrue;
}

PrintCurMatrix(s)
char *s;
	{
	    Matrix haha;
	    int i, j;
	    printf("%s\n", s);
	    getmatrix(haha);
	    for (j = 0; j < 4; ++j)
	    {
		for (i = 0; i < 4; ++i)
		{
		    printf("%15g ", haha[j][i]);
		}
		printf("\n");
	    }
	    printf("\n");
	}

static ObjPtr DrawSpacePanel(object)
ObjPtr object;
/*Draws a space panel*/
{
#ifdef GRAPHICS
    int l, r, b, t;
    ObjPtr backColor, borderType;
    ObjPtr frontPanel, space;

    /*Only really draw it if we're drawing to the screen, so that
      PostScript image stuff works properly*/
    if (drawingMode != DRAW_SCREEN)
    {
	return ObjTrue;
    }

    space = GetVar(object, SPACE);
    if (space)
    {
	frontPanel = GetVar(space, FRONTPANEL);
	if (frontPanel && (frontPanel != object))
	{
	    /*I'm not the front panel*/
	    if (GetVar(frontPanel, BACKGROUND) || GetPredicate(frontPanel, TEMPOBSCURED))
	    {
		/*Front panel has a background.  I don't need to draw.*/
		return ObjTrue;
	    }
	}
    }

    Get2DIntBounds(object, &l, &r, &b, &t);
    if (IsDrawingRestricted(l, r, b, t)) return ObjFalse;

    /*Setup the new viewport*/
    StartPanel(l, r, b, t);

    /*Get the color, if any, and draw it*/
    backColor = GetVar(object, BACKGROUND);
    if (backColor)
    {
	if (GetPredicate(object, BACKNOTNEEDED))
	{
	    SetVar(object, BACKNOTNEEDED, ObjFalse);
	}
	else
	{
	    SetObjectColor(backColor);
	    FillRect(0, r - l, 0, t - b);
#if MACHINE == RS6000
	    /*KLUDGE for IBM*/
	    FrameRect(0, r - l, 0, t - b);
#endif
	}
    }
    else if (GetPredicate(object, TEMPOBSCURED))
    {
	FillUIRect(0, r - l, 0, t - b, UIBLACK);
#if MACHINE == RS6000
	    /*KLUDGE for IBM*/
	    FrameRect(0, r - l, 0, t - b);
#endif
    }
    SetVar(object, BACKNOTNEEDED, ObjFalse);

    borderType = GetVar(object, BORDERTYPE);
    if (borderType)
    {
	FrameUIRect(0, r - l - 1, 0, t - b - 1, UIBLACK);
    }

    /*Draw the grid if need be*/
    if (GetPredicate(object, SHOWGRID))
    {
	real x, y;
	SetScreenGrid(object);
	SetUIColor(UIWHITE);

	for (x = GRIDX(-xGrid); x < (r - l) + xGrid; x = GRIDX(x + xGrid))
	{
	    for (y = GRIDY(-yGrid); y < (t - b) + yGrid; y = GRIDY(y + yGrid))
	    {
		FillUIRect(x - 1, x + 1, y - 1, y + 1, UIBLACK);
		FrameUIRect(x - 1, x + 1, y - 1, y + 1, UIWHITE);
	    }
	}
    }

    /*Draw the contents of the panel*/
    DrawList(GetVar(object, CONTENTS));
    StopPanel();
#endif

    return NULLOBJ;
}

ObjPtr ShowSpaceControls(space, windowName)
ObjPtr space;
char *windowName;
/*Makes a new control window to control space*/
{
    WinInfoPtr dialogExists, controlWindow;
    ObjPtr whichDialog;
    ObjPtr name;
    int left, right, bottom, top;

    name = GetVar(space, NAME);

    whichDialog = NewString("Controls");
    dialogExists = DialogExists((WinInfoPtr) space, whichDialog);
    if (name)
    {
	strncpy(tempStr, GetString(name), TEMPSTRSIZE);
	tempStr[TEMPSTRSIZE] = 0;
    }
    else
    {
	strcpy(tempStr, "Space");
    }
    GetTemplateBounds(SpaceTemplate, "Panel", &left, &right, &bottom, &top);
    controlWindow = GetDialog((WinInfoPtr) space, whichDialog, tempStr, 
	right - left, top - bottom, right - left, top - bottom, WINDBUF);

    if (!dialogExists)
    {
	ObjPtr panel, contents, checkBox;

	SetVar((ObjPtr) controlWindow, REPOBJ, space);

	/*Add controls*/
	SetVar((ObjPtr) controlWindow, HELPSTRING,
	    NewString("This window shows controls that affect attributes \
of a 3-dimensional space."));

	/*Add in a panel*/
	panel = TemplatePanel(SpaceTemplate, "Panel");
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar((ObjPtr) panel, CONTENTS);

	checkBox = TemplateCheckBox(SpaceTemplate, "Show Rotation Guides", GetPredicate(space, SHOWROTGUIDES));
	SetVar(checkBox, PARENT, panel);
	PrefixList(contents, checkBox);
	SetVar(checkBox, REPOBJ, space);
	SetMethod(checkBox, CHANGEDVALUE, ChangeRotGuides);
	SetVar(checkBox, HELPSTRING, NewString(
		"This controls whether rotation guides are shown in the space.  If the check box is \
on, a wire frame sphere will appear in the space.  The sphere represents the virtual \
trackball you can press and move using the middle mouse button"));


	checkBox = TemplateCheckBox(SpaceTemplate, "Show Motion Guides", GetPredicate(space, SHOWMOTGUIDES));
	SetVar(checkBox, PARENT, panel);
	PrefixList(contents, checkBox);
	SetVar(checkBox, REPOBJ, space);
	SetMethod(checkBox, CHANGEDVALUE, ChangeMotionGuides);
	SetVar(checkBox, HELPSTRING, NewString(
		"This controls whether motion guides are shown in the space.  If it is \
on, a wire frame lattice will appear within the space, aligned to space coordinates."));

    }

    return (ObjPtr) dialogExists;
}

static ObjPtr MakeSpaceAppearance(space)
ObjPtr space;
{
    SetVar(space, APPEARANCE, ObjTrue);
    ImInvalid(space);
    return ObjTrue;
}

static ObjPtr MakeSpaceTime(space)
ObjPtr space;
{
    ObjPtr clock, var;
    clock = GetVar(space, CLOCK);
    if (clock)
    {
	var = GetVar(clock, TIME);
	if (var)
	{
	    SetVar(space, TIME, NewReal(GetReal(var)));
	    return ObjTrue;
	}
    }
    SetVar(space, TIME, NewReal(0.0));
    return ObjTrue;
}

void InitSpaces()
/*Initializes the spaces*/
{
    ObjPtr icon;
    ObjPtr list;
 
    /*Create a class of spaces*/
    spaceClass = NewObject(NULLOBJ, 0);
    AddToReferenceList(spaceClass);
    SetVar(spaceClass, CLASSID, NewInt(CLASS_SPACE));
    SetMethod(spaceClass, DRAW, DrawSpace);
    SetVar(spaceClass, NAME, NewString("Space"));
    SetMethod(spaceClass, PRESS, PressSpace);
    SetMethod(spaceClass, KEYDOWN, KeyDownSpace);
    SetMethod(spaceClass, BOUNDSINVALID, SpaceBoundsInvalid);
    SetVar(spaceClass, XSTRETCH, NewInt(1));
    SetVar(spaceClass, YSTRETCH, NewInt(1));
    SetVar(spaceClass, TYPESTRING, NewString("space"));
    SetVar(spaceClass, HELPSTRING,
	NewString("A space provides a 3-dimensional world for visualization \
objects.  Objects are automatically scaled and translated to appear near the \
center by default.  Any number of visualization objects can exist within a space. \
Spaces are controlled by controllers, such as observers and lights.\n\
\n\
The tools control panel on the left controls how the mouse affects the space.  \
For more information on a particular tool, use help in context on the tool button."));

#ifdef GRAPHICS
    lmdef(DEFMATERIAL, 1, 0, NULL);
#endif

    SetMethod(spaceClass, FORALLOBJECTS, ForAllSpaceObjects); 
    SetMethod(spaceClass, NEWCTLWINDOW, ShowSpaceControls);
    SetMethod(spaceClass, SHOWCONTROLS, NewControlWindow);
    DeclareDependency(spaceClass, TIME, CLOCK);
    DeclareIndirectDependency(spaceClass, TIME, CLOCK, TIME);
    SetMethod(spaceClass, TIME, MakeSpaceTime);
    DeclareDependency(spaceClass, APPEARANCE, TIME);
    SetMethod(spaceClass, APPEARANCE, MakeSpaceAppearance);

    DeclareDependency(spaceClass, APPEARANCE, RENDERER);

    DeclareDependency(spaceClass, APPEARANCE, RENDERTYPE);
    DeclareDependency(spaceClass, RENDERTYPE, RENDERER);
    DeclareIndirectDependency(spaceClass, RENDERTYPE, RENDERER, RENDERTYPE);
    SetMethod(spaceClass, RENDERTYPE, MakeSpaceRenderType);

    DeclareDependency(spaceClass, APPEARANCE, FILTERTYPE);
    DeclareDependency(spaceClass, FILTERTYPE, RENDERER);
    DeclareIndirectDependency(spaceClass, FILTERTYPE, RENDERER, FILTERTYPE);
    SetMethod(spaceClass, FILTERTYPE, MakeSpaceFilterType);

    /*Initialize a space panel*/
    spacePanelClass = NewObject(panelClass, 0);
    AddToReferenceList(spacePanelClass);
    SetMethod(spacePanelClass, DROPOBJECTS, DropInSpacePanel);
    SetMethod(spacePanelClass, RESHAPE, ReshapeSpacePanel);
    SetMethod(spacePanelClass, DRAW, DrawSpacePanel);
    SetMethod(spacePanelClass, BOUNDSINVALID, SpacePanelBoundsInvalid);

    /*Initialize a space back panel*/
    spaceBackPanelClass = NewObject(spacePanelClass, 0);
    AddToReferenceList(spaceBackPanelClass);
    SetVar(spaceBackPanelClass, BACKGROUND, NewInt(UIBLACK));

    /*Create class of space controllers*/
    controllerClass = NewObject(NULLOBJ, 0);
    AddToReferenceList(controllerClass);
    SetVar(controllerClass, CONTROLLERP, ObjTrue);
    SetMethod(controllerClass, SHOWCONTROLS, NewControlWindow);

    /*Create class of clocks*/
    clockClass = NewObject(controllerClass, 0);
    AddToReferenceList(clockClass);
    SetMethod(clockClass, MARKTIME, MarkClockTime);
    SetMethod(clockClass, CLONE, CloneClock);
    SetMethod(clockClass, BINDTOSPACE, BindClockToSpace);
    SetMethod(clockClass, TOUCHSPACE, TouchSpaceClock);
    SetMethod(clockClass, NEWCTLWINDOW, ShowClockControls);
    SetMethod(clockClass, IMINVALID, ClockInvalid);
    SetVar(clockClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetMethod(clockClass, RESOLVEDONE, ResolveClock);
    SetMethod(clockClass, REINIT, MakeClockDatasets);
    SetVar(clockClass, CLASSID, NewInt(CLASS_CLOCK));
    DeclareDependency(clockClass, TIMESTEPS, DATASETS);
    SetMethod(clockClass, TIMESTEPS, MakeClockTimeSteps);
    SetMethod(clockClass, DTIME, MakeClockDTime);
    SetVar(clockClass, ONEONLY, ObjTrue);
    SetVar(clockClass, DEFAULTICON, icon = NewIcon(0, 0, ICONCLOCK, "Clock"));
    SetVar(icon, HELPSTRING,
	NewString("This icon represents a clock.  The clock controls the current \
time displayed within a space and the rate at which time goes forward or backward.  \
You can see controls for this clock by selecting it and choosing the Show Controls \
item in the Object menu.\n\
\n\
You can drag this icon into the icon corral of another visualization window to have \
it control the other space as well.  \
A space can only be controlled by one clock at a time.  If you drag another \
clock icon into this space, this clock will be replaced.\n\
\n\
You can place a time display in the image of the space itself by dragging this \
icon into the space itself."));
    SetVar(clockClass, NAME, NewString("Clock"));
    DeclareDependency(clockClass, TIMEREGISTERED, TIMEBOUNDS);
    SetMethod(clockClass, TIMEREGISTERED, RegisterTime);
    DeclareDependency(clockClass, TIMEFORMAT, DATASETS);
    SetMethod(clockClass, TIMEFORMAT, MakeClockTimeFormat);

    /*Create class of observers*/
    observerClass = NewObject(controllerClass, 0);
    AddToReferenceList(observerClass);
    list = NewList();
    PrefixList(list, NewSymbol(LOCATION));
    PrefixList(list, NewSymbol(FORWARDVECTOR));
    PrefixList(list, NewSymbol(UPVECTOR));
    PrefixList(list, NewSymbol(FOCUSDIST));
    SetVar(observerClass, SNAPVARS, list);
    SetMethod(observerClass, BINDTOSPACE, BindObserverToSpace);
    SetMethod(observerClass, NEWCTLWINDOW, ShowObserverControls);
    SetVar(observerClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetMethod(observerClass, LOCALCOPY, MakeLocalCopy);
    SetVar(observerClass, VIEWTYPE, NewInt(VT_PERSPECTIVE));
    SetVar(observerClass, FOCUSDIST, NewReal(INITEYEDIST));
    SetVar(observerClass, VIEWFIELD, NewReal(INITAOV));
    {
	ObjPtr clipArray;
	real clip[2];
	clip[0] = INITNEARCLIP;
	clip[1] = INITFARCLIP;
	clipArray = NewRealArray(1, 2L);
	CArray2Array(clipArray, clip);
    }
    {
	ObjPtr anArray;
	real posn[3];
	posn[0] = 0.0;
	posn[1] = 0.0;
	posn[2] = INITEYEDIST;
	anArray = NewRealArray(1, 3L);
	CArray2Array(anArray, posn);
	SetVar(observerClass, LOCATION, anArray);
	posn[0] = 0.0;
	posn[1] = 0.0;
	posn[2] = -1.0;
	anArray = NewRealArray(1, 3L);
	CArray2Array(anArray, posn);
	SetVar(observerClass, FORWARDVECTOR, anArray);
	posn[0] = 0.0;
	posn[1] = 1.0;
	posn[2] = 0.0;
	anArray = NewRealArray(1, 3L);
	CArray2Array(anArray, posn);
	SetVar(observerClass, UPVECTOR, anArray);
    }
    SetVar(observerClass, FOCUSDIST, NewReal((real) INITEYEDIST));


    SetMethod(observerClass, MARKTIME, WakeObserver);
    icon = NewIcon(0, 0, ICONOBSERVER, "Observer");
    SetVar(icon, HELPSTRING, 
	NewString("This icon represents an observer.  The observer represents \
you looking into the 3-dimensional space.  You can change attributes such as the \
viewing angle and near and far clipping planes in the control panel, which you \
can show by selecting the icon and choosing the Show Controls \
item in the Object menu.\n\
\n\
You can drag this icon into the icon corral of another visualization window to have \
it control the other space as well.  When an observer controls more than one \
space, the view of the objects in the spaces are tied together.  This is very \
useful for viewing several similar datasets from the same viewpoint at once.  \
A space can only be controlled by one observer at a time.  If you drag another \
observer icon into this space, this observer will be replaced."));
#if 0
    SetMethod(icon, ICONEXTRADRAW, DrawExtraObserverIcon);
#endif
    SetVar(observerClass, DEFAULTICON, icon);
    SetVar(observerClass, CLASSID, NewInt(CLASS_OBSERVER));
    SetVar(observerClass, ONEONLY, ObjTrue);
    SetVar(observerClass, NAME, NewString("Observer"));
    SetMethod(observerClass, CLONE, CloneObserver);
    SetVar(observerClass, BINOCULARITY, NewReal(0.4));

    /*Create class of renderers*/
    rendererClass = NewObject(controllerClass, 0);
    AddToReferenceList(rendererClass);
    SetMethod(rendererClass, BINDTOSPACE, BindRendererToSpace);
    SetMethod(rendererClass, NEWCTLWINDOW, ShowRendererControls);
    SetVar(rendererClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetVar(rendererClass, CLASSID, NewInt(CLASS_RENDERER));
    SetVar(rendererClass, ONEONLY, ObjTrue);
    SetVar(rendererClass, RENDERTYPE, NewInt(hasRGB ? RT_RGB_HARDWARE : RT_CMAP_HARDWARE));
    SetVar(rendererClass, FILTERTYPE, NewInt(RT_NONE));
    icon = NewIcon(0, 0, ICONRENDERER, "Renderer");
    SetVar(icon, HELPSTRING,
	NewString("This icon represents a renderer.  The controls the process \
of rendering, or producing an image from the visualization objects in the space.  \
You can show controls for the renderer by selecting the icon and choosing the Show Controls \
item in the Object menu.\n\
\n\
You can drag this icon into the icon corral of another visualization window to have \
it control the other space as well.  \
A space can only be controlled by one renderer at a time.  If you drag another \
renderer icon into this space, this renderer will be replaced."));
    SetVar(rendererClass, DEFAULTICON, icon);
    SetVar(rendererClass, NAME, NewString("Renderer"));
    SetMethod(rendererClass, CLONE, CloneRenderer);

    InitLights();
}

void KillSpaces()
/*Kills the spaces*/
{
    KillLights();
    DeleteThing(rendererClass);
    DeleteThing(observerClass);
    DeleteThing(clockClass);
    DeleteThing(controllerClass);
    DeleteThing(spaceBackPanelClass);
    DeleteThing(spacePanelClass);
    DeleteThing(spaceClass);
}
