CCL Home Page
Up Directory CCL ScianSpaces
/*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"
#include "ScianDepend.h"
#include "ScianDatasets.h"
#include "ScianFilters.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

/*Stereo and blanking values for CrystalEyes*/
#define YSTEREO		491
#define YBLANK		41

#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*/
    real eyeOffset;
    ObjPtr var;
    Matrix proj, model;
    float aspect;		/*Aspect ratio*/

    aspect = ASPECT;

    curWhichView = whichView;
    if (whichView == VIEW_XLEFT)
    {
	left = (left + right) / 2 + 1;
	aspect = ASPECT;
    }
    else if (whichView == VIEW_XRIGHT)
    {
	right = (left + right) / 2;
	aspect = ASPECT;
    }
    else if (whichView == VIEW_CLEFT)
    {
	bottom = YSTEREO + YBLANK;
	top = bottom + YSTEREO;
    }
    else if (whichView == VIEW_CRIGHT)
    {
	bottom = 0;
	top = bottom + YSTEREO;
    }

    /*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:
	case VIEW_CLEFT:
	    var = GetVar(observer, BINOCULARITY);
	    eyeOffset = -GetReal(var) * 0.5;
	    break;
	case VIEW_XRIGHT:
	case VIEW_RCRIGHT:
	case VIEW_CRIGHT:
	    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 ||
	whichView == VIEW_CLEFT || whichView == VIEW_CRIGHT ||
	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*/
    MakeVar(space, TIME);
    var = GetVar(space, TIME);
    if (var)
    {
	spaceTime = GetReal(var);
    }
    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);
		}
	    }
	}
    }

    {
	if (GetPredicate(observer, AUTOADJUST))
	{
	    /*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;
	    SetVar(observer, SPACESCALE, NewReal(1.0 / scaleFactor));

	    var = NewRealArray(1, 3L);
	    CArray2Array(var, center);
	    SetVar(observer, SPACEORIGIN, var);
	}
	else
	{
	    var = GetRealVar("BeginSpace", observer, SPACESCALE);
	    if (var)
	    {
		scaleFactor = 1.0 / GetReal(var);
	    }
	    else
	    {
		scaleFactor = 1.0;
	    }

	    var = GetFixedArrayVar("BeginSpace", observer, SPACEORIGIN, 1, 3L);
	    if (var)
	    {
		Array2CArray(center, var);
	    }
	    else
	    {
		center[0] = 0.0;
		center[1] = 0.0;
		center[2] = 0.0;
	    }
	}

	scale(scaleFactor, scaleFactor, scaleFactor);
	translate(-center[0], -center[1], -center[2]);
    }
#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];

	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*/
	phi = roll * ROLLYAWFACTOR * dTime;
	GetUpVector(upVector, observer);
	RotateObserver(observer, upVector, phi, false);
	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)
    {
	MakeObjectName(tempStr, observer);
	Log("begin snapshot ");
	Log(tempStr);
	Log("\n");
	LogVariable(observer, LOCATION);
	LogVariable(observer, ROTQUAT);
	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*/

	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*/
		real newRot[3][3];	/*New rotation matrix*/
		Quaternion rotQuat;	/*New quaternion 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*/

		SaveForUndo(observer);
		/*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);

		CROSS(forward, up, side);

		/*Make a matrix*/
		newRot[0][0] = side[0];
		newRot[1][0] = side[1];
		newRot[2][0] = side[2];

		newRot[0][1] = up[0];
		newRot[1][1] = up[1];
		newRot[2][1] = up[2];

		newRot[0][2] = -forward[0];
		newRot[1][2] = -forward[1];
		newRot[2][2] = -forward[2];

		MatrixToQuaternion(newRot, rotQuat);
		var = NewRealArray(1, 4L);
		CArray2Array(var, rotQuat);
		SetVar(observer, ROTQUAT, var);

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

	    if (!lights)
	    {
		SaveForUndo(observer);
	    }

	    /*Get the view type*/
	    var = GetVar(observer, VIEWTYPE);
	    if (var)
	    {
		viewType = GetInt(var);
	    }
	    else
	    {
		viewType == VT_PERSPECTIVE;
	    }

	    SetVar(object, ROTATING, ObjTrue);

	    /*Calculate origin and radius of trackball*/
	    if (viewType == VT_CROSSEYED || viewType == VT_WALLEYED)
	    {
		if (x < (left + right) / 2)
		{
		    Ox = (left + right) / 4;
		}
		else
		{
		    Ox = 3 * (left + right) / 4;
		}
		Oy = (top + bottom) / 2;
	    }
	    else if (viewType == VT_LEFTONTOP)
	    {
		Ox = (left + right) / 2;
		if (y < YSTEREO)
		{
		    Oy = YSTEREO / 2;
		}
		else
		{
		    Oy = YSTEREO * 3 / 2 + YBLANK;
		}
	    }
	    else
	    {
		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*/
		    real dr;			/*Click axis distance*/
		    real sw, cw;		/*Sin and cosine of omega*/
		    real st, ct;		/*Sin and cosine of theta*/
		    real sp, cp;		/*Sin and cosine of phi*/
		    real a1x, a1y, a1z;		/*Temporary values for axis of rotation*/
		    real a2x, a2y, a2z;		/*Temporary values for axis of rotation*/
		    real t;			/*1-cos phi*/

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

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

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

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

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

		    /*Calculate the axis of rotation*/

		    /*First the motion from origin component*/
		    a1x = -rsin(tao);
		    a1y = rcos(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 && maxDrawingQuality >= DQ_FULL)
    {
	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;
		DrawVisObject(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 forwardVector[3];
    real focusDist;
    Quaternion rotQuat;
    Quaternion deltaRotQuat;
    Quaternion newRot;

    /*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;
    }

    /*Get current focus point*/
    GetFocusPoint(focusPoint, observer);

    /*Now make the change rotation quaternion*/
    RotToQuaternion(axis, phi, deltaRotQuat);

    /*Apply it to the rotation quaternion*/
    MakeVar(observer, ROTQUAT);
    var = GetVar(observer, ROTQUAT);
    if (!var)
    {
	ReportError("RotateObserver", "No rotation quaternion");
	return;
    }
    Array2CArray(rotQuat, var);

    QMult(rotQuat, deltaRotQuat, newRot);

    var = NewRealArray(1, 4L);
    CArray2Array(var, newRot);
    SetVar(observer, ROTQUAT, var);

    /*Get new forward vector*/
    GetForwardVector(forwardVector, observer);

    var = GetVar(observer, FOCUSDIST);
    if (var)
    {
	focusDist = GetReal(var);
    }
    else
    {
	focusDist = INITEYEDIST;
    }

    /*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 vector*/
    var = NewRealArray(1, 3L);
    CArray2Array(var, location);
    SetVar(observer, LOCATION, 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]);
}

#ifdef PROTO
void RotToQuaternion(real axis[3], real amount, Quaternion r)
#else
void RotToQuaternion(axis, amount, r)
real axis[3];
real amount;
Quaternion r;
#endif
/*Converts a rotation by amount around axis to a quaternion*/
{
    real absVal, multiplier1, multiplier2;

    absVal = sqrt(SQUARE(axis[0]) + SQUARE(axis[1]) + SQUARE(axis[2]));
    if (absVal > 0.0)
    {
	multiplier1 = rsin(amount * 0.5) / absVal;
	multiplier2 = rcos(amount * 0.5);

	r[0] = multiplier2;
	r[1] = axis[0] * multiplier1;
	r[2] = axis[1] * multiplier1;
	r[3] = axis[2] * multiplier1;
    }
    else
    {
	r[0] = 1.0;
	r[1] = 0.0;
	r[2] = 0.0;
	r[3] = 0.0;
    }
}

#ifdef PROTO
void MatrixToQuaternion(real m[3][3], Quaternion q)
#else
void MatrixToQuaternion(m, q)
real m[3][3];
Quaternion q;
#endif
/*Converts a matrix (rotation only) to a quaternion*/
{
    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 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];
}

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];
}

void QDouble(Quaternion q1, Quaternion q2, Quaternion r)
/*Does the Double operation, but not like [Shoemake, 1985]*/
{
    Quaternion qtemp1, qtemp2;

#if 0
    /*This is from [Shoemake, 1985].  I don't understand the logic, and
	also, it doesn't work.*/

    QMult(q1, q2, qtemp1);
    QMult(qtemp1, q2, qtemp2);

    r[0] = qtemp2[0] * 2.0 - q1[0];
    r[1] = qtemp2[1] * 2.0 - q1[1];
    r[2] = qtemp2[2] * 2.0 - q1[2];
    r[3] = qtemp2[3] * 2.0 - q1[3];
#else
    /*This, however, works fine*/
    QInvert(q1, qtemp1);
    QMult(q2, qtemp1, qtemp2);
    QMult(qtemp2, q2, r);
#endif
}

void Slerp(Quaternion q1, Quaternion q2, real u, Quaternion r)
/*Does the slerp operation in [Shoemake 1985]*/
{
    real theta, costheta, w1, w2, sintheta;

    costheta = q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3];
    theta = racos(costheta);
    sintheta = rsin(theta);
    if (sintheta > 0.0)
    {
	w1 = rsin((1.0 - u) * theta) / sintheta;
	w2 = rsin(u * theta) / sintheta;
    }
    else
    {
	/*They're the same quaternion, so who cares?*/
	w1 = 1.0;
	w2 = 0.0;
    }

    r[0] = w1 * q1[0] + w2 * q2[0];
    r[1] = w1 * q1[1] + w2 * q2[1];
    r[2] = w1 * q1[2] + w2 * q2[2];
    r[3] = w1 * q1[3] + w2 * q2[3];
}

#ifdef PROTO
void QBisect(Quaternion q1, Quaternion q2, Quaternion r)
#else
void QBisect(q1, q2, r)
Quaternion q1, q2, r;
#endif
/*Does the Bisect operation in [Shoemake, 1985]*/
{
    real absVal;

#if 1
    r[0] = q1[0] + q2[0];
    r[1] = q1[1] + q2[1];
    r[2] = q1[2] + q2[2];
    r[3] = q1[3] + q2[3];

    absVal = sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]);
    r[0] /= absVal;
    r[1] /= absVal;
    r[2] /= absVal;
    r[3] /= absVal;
#else
    Slerp(q1, q2, 0.5, r);
#endif
}

#ifdef PROTO
void QRot(real v[3], Quaternion q, real r[3])
#else
void QRot(v, q, r)
real v[3];
Quaternion q;
real r[3];
#endif
/*Rotates v by q into r*/
{
    Quaternion qInv, qTemp;
    Quaternion qVec, qRes;

    QInvert(q, qInv);
    qVec[0] = 0.0;
    qVec[1] = v[0];
    qVec[2] = v[1];
    qVec[3] = v[2];

    QMult(qInv, qVec, qTemp);
    QMult(qTemp, q, qRes);

    r[0] = qRes[1];
    r[1] = qRes[2];
    r[2] = qRes[3];
}

void QDelta(Quaternion q1, Quaternion q2, Quaternion r)
/*Put in r the quaternion such that q1 r = q2*/
{
    Quaternion q1inv;

    QInvert(q1, q1inv);
    QMult(q1inv, q2, r);
}

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
*/
{
    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;
    ObjPtr time;

    MakeVar(object, TIME);
    MakeVar(object, APPEARANCE);
    MakeVar(object, BOUNDS);

    myBounds = GetFixedArrayVar("SpaceBoundsInvalid", object, BOUNDS, 1, 4L);
    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;
	}
    }

    time = GetVar(object, TIME);
    if (time)
    {
	spaceTime = GetReal(time);
    }
    SetAllDatasetTimes(spaceTime);

    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);

    MakeVar(observer, VIEWTYPE);
    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_LEFTONTOP:
		DrawSpaceContents(object, left, right, bottom, top, VIEW_CLEFT);
		DrawSpaceContents(object, left, right, bottom, top, VIEW_CRIGHT);
		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 *) Alloc(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;
		}
		EraseAll();
		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 *) Alloc(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 *) Alloc(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;
}

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*/
	WarnUser(CW_CANNOTVISFILE);
	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*/

	/*Always apply some filters*/
	repObj = MainFilters(object);

	object = NewVis(repObj, visType);
	if (!object)
	{
	    WarnUser(CW_CANNOTVISERROR);
	    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 = floor(t / 3600.0);
			i = n / 12.0;
			n = n - i * 12;
			if (n < 1.00) n += 12.00;
			break;
		    case 'H':
			/*Unrestricted hours*/
			n = t / 3600.0;
			break;
		    case 'i':
			/*Hours, [0,24)*/
			n = floor(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 = floor(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;
			n = floor(n);
			break;
		    case 'S':
			/*Unrestricted seconds*/
		    case 't':
		    case 'T':
			/*Just a timestep*/
			n = t;
			break;
		    case 'e':
		    case 'E':
		    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;
}

void ChangeClockForDataset(window)
WinInfoPtr window;
/*Changes a clock in window for globalDataset*/
{
    ObjPtr space, clock;
    ObjPtr timeBounds;
    real oldtb[2], tb[2];
    ObjPtr contents;
    ObjPtr repObj;
    ThingListPtr runner;

    space = FindSpace(window);
    if (!space)
    {
	ReportError("ChangeClockForDataset", "No space in window");
	return;
    }

    clock = GetObjectVar("ChangeClockForDataset", space, CLOCK);
    if (!clock)
    {
	return;
    }

    timeBounds = GetVar(clock, TIMEBOUNDS);
    if (timeBounds)
    {
	Array2CArray(oldtb, timeBounds);
    }
    else
    {
	oldtb[0] = oldtb[1] = missingData;
    }

    contents = GetListVar("ChangeClockForDataset", space, CONTENTS);
    if (!contents)
    {
	return;
    }

    tb[0] = tb[1] = missingData;
    runner = LISTOF(contents);
    while (runner)
    {
	repObj = GetVar(runner -> thing, MAINDATASET);
	if (!repObj) repObj = GetObjectVar("ChangeClockForDataset", runner -> thing, REPOBJ);
	if (repObj)
	{
	    ObjPtr objTime;
	    real otb[2];
	    MakeVar(repObj, TIMEBOUNDS);
	    objTime = GetVar(repObj, TIMEBOUNDS);
	    if (objTime)
	    {
		Array2CArray(otb, objTime);
		if (tb[0] == missingData)
		{
		    tb[0] = otb[0];
		}
		else
		{
		    tb[0] = MIN(tb[0], otb[0]);
		}

		if (tb[1] == missingData)
		{
		    tb[1] = otb[1];
		}
		else
		{
		    tb[1] = MAX(tb[1], otb[1]);
		}
	    }
	}
	runner = runner -> next;
    }

    if (tb[1] != oldtb[1] || tb[0] != oldtb[0])
    {
	if (tb[1] != missingData && tb[0] != missingData)
	{
	    timeBounds = NewRealArray(1, 2L);
	    CArray2Array(timeBounds, tb);
	    SetVar(clock, TIMEBOUNDS, timeBounds);
	    ImInvalid(clock);
	}
	else
	{
	    SetVar(clock, TIMEBOUNDS, NULLOBJ);
	}
    }
}

ObjPtr MakeClockTimeBounds(clock)
ObjPtr clock;
/*Makes a clock's time bounds for the first time*/
{
    ForAllVisWindows(ChangeClockForDataset);
    SetVar(clock, TIMEBOUNDS, GetVar(clock, TIMEBOUNDS));
    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;
/*Makes a space's RENDERTYPE.  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;
}

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;
    }

    MakeVar(clock, TIME);
    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;
}

#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 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 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);
		}
	    }
	}
    }
    totalTimeSteps = Uniq(totalTimeSteps);
    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);

	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;

	    MakeVar(clock, TIME);
	    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);
	    AssocDirectControlWithVar(control, clock, TIME);

	    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
	    {
		SetVar(clock, DTIME, NewReal(0.0));
		printf("Shouldn't have to set clock's dtime\n");
		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);
	    AssocTextRealControlWithVar(textBox, clock, DTIME, 0.0, plusInf, 0);
	    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);
 
	    var = GetVar(clock, DTIMEUNITS);
	    if (!var) SetVar(clock, DTIMEUNITS, NewInt(0));
	    AssocDirectControlWithVar(radioGroup, clock, DTIMEUNITS);
	    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);
	    if (!var)
	    {
		SetVar(clock, DTIMEPER, NewReal(1.0));
		printf("Shouldn't have to set dtimeper\n");
	    }
	    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);
	    AssocTextRealControlWithVar(textBox, clock, DTIMEPER, 0.0, plusInf, TR_NE_BOTTOM);
	    SetVar(textBox, HELPSTRING, genericHelp);

	    /*Create the seconds and frames box*/
	    radioGroup = NewRadioButtonGroup("Delta Time Per");
	    PrefixList(contents, radioGroup);
	    SetVar(radioGroup, PARENT, panel);
	    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);
 
	    var = GetVar(clock, DTIMEPERUNITS);
	    if (!var) SetVar(clock, DTIMEPERUNITS, NewInt(0));
	    AssocDirectControlWithVar(radioGroup, clock, DTIMEPERUNITS);

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

	    /*Create the cycle time radio group*/
	    radioGroup = NewRadioButtonGroup("Cycle the clock");
	    SetVar(radioGroup, PARENT, panel);
	    PrefixList(contents, radioGroup);
	    AssocDirectControlWithVar(radioGroup, clock, CYCLECLOCK);
	    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"));

	    /*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);

	    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 MakePerspecControlAppearance(control)
ObjPtr control;
/*Makes a perspec control's appearance*/
{
    ObjPtr observer;
    FuncTyp method;
    ObjPtr var;
    int viewType;
    real perspecValue[4];

    /*Get the observer*/
    observer = GetObjectVar("MakePerspecControlAppearance", control, REPOBJ);
    if (!observer)
    {
	return ObjFalse;
    }

    var = GetVar(observer, FOCUSDIST);
    if (var)
    {
	perspecValue[0] = GetReal(var);
    }
    else
    {
	perspecValue[0] = INITEYEDIST;
    }
    var = GetVar(observer, VIEWFIELD);
    if (var)
    {
	perspecValue[1] = GetReal(var);
    }
    else
    {
	perspecValue[1] = INITAOV;
    }
    var = GetVar(observer, VIEWCLIP);
    if (var)
    {
	Array2CArray(&(perspecValue[2]), var);
    }
    else
    {
	perspecValue[2] = INITNEARCLIP;
	perspecValue[3] = INITFARCLIP;
    }
    var = NewRealArray(1, 4L);
    CArray2Array(var, perspecValue);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    InhibitLogging(true);
    SetValue(control, var);
    InhibitLogging(false);
    SetMethod(control, CHANGEDVALUE, method);

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

    if (viewType == VT_ORTHOGRAPHIC)
    {
	if (MakePerspecOrtho(control, true))
	{
	    InhibitLogging(true);
	    ChangedValue(control);
	    InhibitLogging(false);
	}
    }
    else
    {
	if (MakePerspecOrtho(control, false))
	{
	    InhibitLogging(true);
	    ChangedValue(control);
	    InhibitLogging(false);
	}
    }

    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

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);
   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 eyePosn[3];
    ObjPtr var;
    real focusDist;
    Quaternion rotQuat;

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

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

    GetFocusPoint(focusPoint, observer);

    rotQuat[0] = 1.0;
    rotQuat[1] = 0.0;
    rotQuat[2] = 0.0;
    rotQuat[3] = 0.0;
    var = NewRealArray(1, 4L);
    CArray2Array(var, rotQuat);
    SetVar(observer, ROTQUAT, var);

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

    ImInvalid(observer);

    return ObjTrue;
}

static ObjPtr ChangeObserverShown(control)
ObjPtr control;
/*Changes whether a big portion of observer is shown depending on control
  value*/
{
    ObjPtr val;
    ObjPtr repObj;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    val = GetValue(control);
    if (!val)
    {
	return ObjFalse;
    }

    if (IsValidWindow((WinInfoPtr) repObj) && IsWindowOpened(repObj))
    {
	int x, y;
	int l, r, b, t;
	int dummy, side;

	SelWindow((WinInfoPtr) repObj);
	GetWindowOrigin(&x, &y);
	GetWindowBounds(&l, &r, &b, &t);
	if (GetInt(val))
	{
	    /*It's now open*/
	    GetTemplateBounds(ObserverTemplate, "ZPosn", &dummy, &side, &dummy, &dummy);
	    side += MINORBORDER;
	}
	else
	{
	    /*It's now closed*/
	    GetTemplateBounds(ObserverTemplate, "Panel", &dummy, &side, &dummy, &dummy);
	}
	l = x;
	r = x + side;
	t = y + (t - b);
	b = y;

	SetWindowPosition(l, r, b, t);
    }
    return ObjTrue;
}

static ObjPtr ChangeObserverLookat(control)
ObjPtr control;
/*Changes an observers's lookat point*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    long whichIndex;
    long nIndices;
    long k;
    real *el1, *el2;
    real minMax[2];
    real value;
    char *s;
    real forward[3], location[3], up[3], side[3], lookat[3], focusDist;
    real newRot[3][3];
    Quaternion rotQuat;

    repObj = GetVarSurely("ChangeObserverLookat", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    var = GetIntVar("ChangeObserverLookat", control, WHICHINDEX);
    if (!var)
    {
	return ObjFalse;
    }
    whichIndex = GetInt(var);

    var = GetRealVar("ChangeObserverLookat", repObj, FOCUSDIST);
    if (!var)
    {
	return ObjFalse;
    }
    focusDist = GetReal(var);

    GetForwardVector(forward, repObj);
    GetUpVector(up, repObj);
    GetObserverLocation(location, repObj);

    minMax[0] = minusInf;
    minMax[1] = plusInf;

    var = GetValue(control);
    if ((!var) || (!IsString(var)))
    {
	return ObjFalse;
    }

    s = GetString(var);

    if (ParseReal(&value, s))
    {
	if (value == missingData)
	{
	    {
		WarnUser(CW_MISSINGERROR);
		return ObjFalse;
	    }
	}
	else
	{
	    {
		if (value <= minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }

	    {
		if (value >= minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	}

	/*Dropped through, it must be OK*/
	if (GetPredicate(repObj, USESPACECOORDS))
	{
	    real *elements;
	    ObjPtr var;

	    /*Have to convert into observer coords*/

	    var = GetFixedArrayVar("MakeObserverLocationAppearance", repObj, SPACEORIGIN, 1, 3L);
	    if (var)
	    {
		elements = ELEMENTS(var);
		value -= elements[whichIndex];
	    }

	    var = GetRealVar("MakeObserverLocationAppearance", repObj, SPACESCALE);
	    if (var)
	    {
		value /= GetReal(var);
	    }
	}

    }
    else
    {
	WarnUser(CW_NUMBERERROR);
	return ObjFalse;
    }

    /*Now use the new value to make a new lookat*/
    lookat[0] = location[0] + forward[0] * focusDist;
    lookat[1] = location[1] + forward[1] * focusDist;
    lookat[2] = location[2] + forward[2] * focusDist;
    lookat[whichIndex] = value;

    forward[0] = lookat[0] - location[0];
    forward[1] = lookat[1] - location[1];
    forward[2] = lookat[2] - location[2];

    focusDist = sqrt(SQUARE(forward[0]) + SQUARE(forward[1]) + SQUARE(forward[2]));
    if (focusDist < 0.0)
    {
	WarnUser(CW_LOOKATERROR);
	return ObjFalse;
    }

    /*Make a new forward, up, and distance*/
    forward[0] /= focusDist;
    forward[1] /= focusDist;
    forward[2] /= focusDist;

    CROSS(forward, up, side);
    if (side[0] == 0.0 && side[1] == 0.0 && side[2] == 0.0)
    {
	side[0] = forward[1];
	side[1] = forward[2];
	side[2] = forward[0];
    }
    NORMALIZE(side);
    CROSS(side, forward, up);
    NORMALIZE(up);

    /*Make a matrix*/
    newRot[0][0] = side[0];
    newRot[1][0] = side[1];
    newRot[2][0] = side[2];

    newRot[0][1] = up[0];
    newRot[1][1] = up[1];
    newRot[2][1] = up[2];

    newRot[0][2] = -forward[0];
    newRot[1][2] = -forward[1];
    newRot[2][2] = -forward[2];

    MatrixToQuaternion(newRot, rotQuat);
    var = NewRealArray(1, 4L);
    CArray2Array(var, rotQuat);
    SetVar(repObj, ROTQUAT, var);

    SetVar(repObj, FOCUSDIST, NewReal(focusDist));

    ImInvalid(repObj);
    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeObserverLookatAppearance(control)
ObjPtr control;
/*Gets an observer's location and makes appearance*/
{
    real *location, forward[3], focusDist;
    ObjPtr repObj;
    ObjPtr var;
    real value;
    int whichAxis;
    FuncTyp method;
    real observerCoord;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, LOCATION);
    var = GetFixedArrayVar("MakeObserverLookatAppearance", repObj, LOCATION, 1, 3L);
    if (!var)
    {
	return ObjFalse;
    }
    location = ELEMENTS(var);

    GetForwardVector(forward, repObj);

    MakeVar(repObj, FOCUSDIST);
    var = GetRealVar("MakeObserverLookatAppearance", repObj, FOCUSDIST);
    if (!var)
    {
	return ObjFalse;
    }
    focusDist = GetReal(var);

    var = GetIntVar("MakeObserverLookatAppearance", control, WHICHINDEX);
    if (var)
    {
	whichAxis = GetInt(var);
    }
    else
    {
	whichAxis = 0;
    }

    observerCoord = location[whichAxis] + forward[whichAxis] * focusDist;

    if (GetPredicate(repObj, USESPACECOORDS))
    {
	real *elements;

	/*Have to convert into space coords*/
	var = GetRealVar("MakeObserverLookatAppearance", repObj, SPACESCALE);
	if (var)
	{
	    observerCoord *= GetReal(var);
	}

	var = GetFixedArrayVar("MakeObserverLookatAppearance", repObj, SPACEORIGIN, 1, 3L);
	if (var)
	{
	    elements = ELEMENTS(var);
	    observerCoord += elements[whichAxis];
	}
    }

    PrintNumber(tempStr, observerCoord);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    InhibitLogging(true);
    SetValue(control, NewString(tempStr));
    InhibitLogging(false);
    SetMethod(control, CHANGEDVALUE, method);

    SetVar(control, APPEARANCE, ObjTrue);
    ImInvalid(control);
    return ObjTrue;
}

static ObjPtr ChangeObserverUp(control)
ObjPtr control;
/*Changes an observers's up vector */
{
}

static ObjPtr MakeObserverUpAppearance(control)
ObjPtr control;
/*Gets an observer's up and makes appearance*/
{
    real *elements;
    ObjPtr repObj;
    ObjPtr var;
    real value;
    int whichAxis;
    FuncTyp method;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, UPVECTOR);
    var = GetFixedArrayVar("MakeObserverUpAppearance", repObj, UPVECTOR, 1, 3L);
    if (!var)
    {
	return ObjFalse;
    }

    elements = ELEMENTS(var);

    var = GetIntVar("MakeObserverUpAppearance", control, WHICHINDEX);
    if (var)
    {
	whichAxis = GetInt(var);
    }
    else
    {
	whichAxis = 0;
    }

    PrintNumber(tempStr, elements[whichAxis]);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    InhibitLogging(true);
    SetValue(control, NewString(tempStr));
    InhibitLogging(false);
    SetMethod(control, CHANGEDVALUE, method);

    SetVar(control, APPEARANCE, ObjTrue);
    ImInvalid(control);
    return ObjTrue;
}

static ObjPtr ChangeObserverQuaternion(control)
ObjPtr control;
/*Changes an observers's quaternion */
{
    ObjPtr repObj;
    ObjPtr var;
    Quaternion oldQuat, newQuat;
    real value;
    int whichAxis;
    int prevAxis, nextAxis;
    real sign;
    real magnitude, desMag, mult;
    char *s;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, ROTQUAT);
    var = GetFixedArrayVar("MakeObserverQuaternionAppearance", repObj, ROTQUAT, 1, 4L);
    if (!var)
    {
	return ObjFalse;
    }
    Array2CArray(oldQuat, var);

    var = GetIntVar("MakeObserverQuaternionAppearance", control, WHICHINDEX);
    if (var)
    {
	whichAxis = GetInt(var);
    }
    else
    {
	whichAxis = 0;
    }

    var = GetValue(control);
    if (!var)
    {
	return ObjFalse;
    }
    s = GetString(var);
    if (!ParseReal(&value, s))
    {
	return ObjFalse;
    }
    if (value <= minusInf || value >= plusInf)
    {
	WarnUser(CW_INFINITYERROR);
	return ObjFalse;
    }
    if (value == missingData)
    {
	WarnUser(CW_MISSINGERROR);
	return ObjFalse;
    }
    if (value > 1.0) value = 1.0;
    if (value < -1.0) value = -1.0;

    if (whichAxis == 0)
    {
	/*Just change amount of rotation, keep axis the same*/
	newQuat[0] = value;
	magnitude = sqrt(SQUARE(oldQuat[1]) + SQUARE(oldQuat[2]) + SQUARE(oldQuat[3]));
	if (magnitude > 0.0)
	{
	    /*Have to adjust magnitude*/
	    desMag = sqrt(1.0 - SQUARE(newQuat[0]));
	    mult = desMag / magnitude;

	    newQuat[1] = oldQuat[1] * mult;
	    newQuat[2] = oldQuat[2] * mult;
	    newQuat[3] = oldQuat[3] * mult;
	}
	else
	{
	    /*Pick an arbitrary axis*/
	    newQuat[1] = sqrt(1.0 - SQUARE(newQuat[0]));
	    newQuat[2] = 0.0;
	    newQuat[3] = 0.0;
	}
    }
    else
    {
	/*One of the vector components*/
	newQuat[0] = oldQuat[0];
	newQuat[1] = oldQuat[1];
	newQuat[2] = oldQuat[2];
	newQuat[3] = oldQuat[3];

	desMag = sqrt(1.0 - SQUARE(newQuat[0]));
	if (value > desMag) value = desMag;
	if (value < -desMag) value = -desMag;
	newQuat[whichAxis] = value;

	/*Try to fiddle just the next axis*/
	prevAxis = whichAxis - 1;
	if (prevAxis == 0) prevAxis = 3;
	nextAxis = whichAxis + 1;
	if (nextAxis == 4) nextAxis = 1;

	magnitude = sqrt(SQUARE(newQuat[whichAxis]) + SQUARE(newQuat[prevAxis]));
	if (magnitude <= desMag)
	{
	    /*It can be done!*/
	    sign = newQuat[nextAxis] >= 0.0 ? 1.0 : -1.0;

	    newQuat[nextAxis] = sqrt(SQUARE(desMag) - SQUARE(magnitude)) * sign;
	}
	else
	{
	    /*It can't be done.  Flatten to zero and make minimal adjustment
	      to prevAxis*/
	    newQuat[nextAxis] = 0.0;
	    sign = newQuat[prevAxis] >= 0.0 ? 1.0 : -1.0;
	    newQuat[prevAxis] = sqrt(SQUARE(desMag) - SQUARE(newQuat[whichAxis])) * sign;
	}
    }

    var = NewRealArray(1, 4L);
    CArray2Array(var, newQuat);
    SetVar(repObj, ROTQUAT, var);
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr MakeObserverQuaternionAppearance(control)
ObjPtr control;
/*Gets an observer's up and makes appearance*/
{
    real *elements;
    ObjPtr repObj;
    ObjPtr var;
    real value;
    int whichAxis;
    FuncTyp method;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    MakeVar(repObj, ROTQUAT);
    var = GetFixedArrayVar("MakeObserverQuaternionAppearance", repObj, ROTQUAT, 1, 4L);
    if (!var)
    {
	return ObjFalse;
    }

    elements = ELEMENTS(var);

    var = GetIntVar("MakeObserverQuaternionAppearance", control, WHICHINDEX);
    if (var)
    {
	whichAxis = GetInt(var);
    }
    else
    {
	whichAxis = 0;
    }

    PrintNumber(tempStr, elements[whichAxis]);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    InhibitLogging(true);
    SetValue(control, NewString(tempStr));
    InhibitLogging(false);
    SetMethod(control, CHANGEDVALUE, method);

    SetVar(control, APPEARANCE, ObjTrue);
    ImInvalid(control);
    return ObjTrue;
}

static ObjPtr HideOnAutoAdjust(control)
ObjPtr control;
/*Hides a control based on the AUTOADJUST of its repobj*/
{
    ObjPtr repObj;

    repObj = GetVar(control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }
    ActivateTextBox(control, GetPredicate(repObj, AUTOADJUST) ? false : true);

    return ObjTrue;
}

static ObjPtr ChangeObserverLocation(control)
ObjPtr control;
/*Changes an observer's location value*/
{
    ObjPtr repObj, var;
    NameTyp whichVar;
    long whichIndex;
    long nIndices;
    long k;
    real *el1, *el2;
    real minMax[2];
    real value;
    char *s;

    repObj = GetVarSurely("ChangeObserverLocation", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    whichVar = LOCATION;

    var = GetIntVar("ChangeObserverLocation", control, WHICHINDEX);
    if (!var)
    {
	return ObjFalse;
    }
    whichIndex = GetInt(var);

    minMax[0] = minusInf;
    minMax[1] = plusInf;

    var = GetValue(control);
    if ((!var) || (!IsString(var)))
    {
	return ObjFalse;
    }

    s = GetString(var);

    if (ParseReal(&value, s))
    {
	if (value == missingData)
	{
	    {
		WarnUser(CW_MISSINGERROR);
		return ObjFalse;
	    }
	}
	else
	{
	    {
		if (value <= minMax[0])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }

	    {
		if (value >= minMax[1])
		{
		    DeferMessage(control, RANGEALERT);
		    return ObjFalse;
		}
	    }
	}

	/*Dropped through, it must be OK*/
	var = GetArrayVar("ChangeObserverLocation", repObj, whichVar);
	if (!var)
	{
	    return ObjFalse;
	}
	el1 = ELEMENTS(var);
	nIndices = DIMS(var)[0];
	var = NewRealArray(1, nIndices);
	el2 = ELEMENTS(var);
	for (k = 0; k < nIndices; ++k)
	{
	    el2[k] = el1[k];
	}
	if (GetPredicate(repObj, USESPACECOORDS))
	{
	    real *elements;
	    ObjPtr var;

	    /*Have to convert into observer coords*/

	    var = GetFixedArrayVar("MakeObserverLocationAppearance", repObj, SPACEORIGIN, 1, 3L);
	    if (var)
	    {
		elements = ELEMENTS(var);
		value -= elements[whichIndex];
	    }

	    var = GetRealVar("MakeObserverLocationAppearance", repObj, SPACESCALE);
	    if (var)
	    {
		value /= GetReal(var);
	    }
	}

	el2[whichIndex] = value;
	SetVar(repObj, whichVar, var);

	ImInvalid(repObj);
    }
    else
    {
	WarnUser(CW_NUMBERERROR);
	return ObjFalse;
    }

    SetVar(control, APPEARANCE, ObjTrue);

    return ObjTrue;
}

static ObjPtr MakeObserverLocationAppearance(control)
ObjPtr control;
/*Makes a text real control's appearance based on the variable it controls*/
{
    ObjPtr repObj, var, value;
    NameTyp whichVar;
    long whichIndex;
    FuncTyp method;
    real observerCoord;

    repObj = GetVarSurely("MakeObserverLocationAppearance", control, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    whichVar = LOCATION;

    var = GetIntVar("MakeObserverLocationAppearance", control, WHICHINDEX);
    if (!var)
    {
	return ObjFalse;
    }
    whichIndex = GetInt(var);

    var = GetArrayVar("MakeObserverLocationAppearance", repObj, whichVar);
    if (var)
    {
	real *elements;
	elements = ELEMENTS(var);
	observerCoord = elements[whichIndex];
    }
    else
    {
	observerCoord = 0.0;
    }

    if (GetPredicate(repObj, USESPACECOORDS))
    {
	real *elements;

	/*Have to convert into space coords*/
	var = GetRealVar("MakeObserverLocationAppearance", repObj, SPACESCALE);
	if (var)
	{
	    observerCoord *= GetReal(var);
	}

	var = GetFixedArrayVar("MakeObserverLocationAppearance", repObj, SPACEORIGIN, 1, 3L);
	if (var)
	{
	    elements = ELEMENTS(var);
	    observerCoord += elements[whichIndex];
	}
    }

    PrintNumber(tempStr, observerCoord);
    InhibitLogging(true);
    method = GetMethod(control, CHANGEDVALUE);
    SetMethod(control, CHANGEDVALUE, (FuncTyp) 0);
    SetValue(control, NewString(tempStr));
    SetMethod(control, CHANGEDVALUE, method);
    method = GetMethod(control, EXTRAAPPEARANCE);
    if (method)
    {
	(*method)(control);
    }
    ImInvalid(control);
    InhibitLogging(false);
    SetVar(control, APPEARANCE, ObjTrue);

    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, dummy;
    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, location, orientation, and clippint planes."));

	/*Add in a panel*/
	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;
	    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."));

	    button = TemplateRadioButton(ObserverTemplate, "Crystal Eyes");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, stereo \
images will be displayed for use with the Crystal Eyes system.  In order for this \
to work, you must have a set of Crystal Eyes goggles.  Also, the visualization \
window must be set to full screen, and the control panel must be hidden.  Once you \
have set the visualization window to full screen, press the F11 key to toggle \
the Crystal Eyes goggles on and off."));
	    if (!hasStereo)
	    {
		ActivateButton(button, false);
	    }

	    var = GetIntVar("ShowObserverControls", observer, VIEWTYPE);
	    if (!var)
	    {
		SetVar(observer, VIEWTYPE, NewInt(VT_PERSPECTIVE));
	    }
	    AssocDirectControlWithVar(radio, observer, VIEWTYPE);

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

	    var = GetRealVar("ShowObserverControls", observer, BINOCULARITY);
	    if (!var)
	    {
		SetVar(observer, BINOCULARITY, NewReal(0.4));
	    }
	    textBox = TemplateTextBox(ObserverTemplate, "Binocular Spacing", EDITABLE + WITH_PIT + ONE_LINE, readoutText);
	    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);
	    AssocTextRealControlWithVar(textBox, observer, BINOCULARITY, 0.0, plusInf, 0);

	    /*Add in the perspective control*/
    	    control = TemplatePerspecControl(ObserverTemplate, "Perspective Control");
	    PrefixList(contents, control);
	    SetVar(control, PARENT, panel);
	    SetVar(control, REPOBJ, observer);

	    SetMethod(control, CHANGEDVALUE, ChangePerspective);
	    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, VIEWCLIP);
	    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, VIEWFIELD);
	    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, FOCUSDIST);
	    DeclareIndirectDependency(control, APPEARANCE, REPOBJ, VIEWTYPE);
	    SetMethod(control, APPEARANCE, MakePerspecControlAppearance);

	    /*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"));

	    /*Make the expand controls button*/
	    button = TemplateIconButton(ObserverTemplate, "More", ICONRIGHTEXP,
			UIGREEN, BS_PITTED);
	    PrefixList(contents, button);
	    MakeButtonToggled(button, true);
	    SetVar(button, PARENT, panel);
	    SetVar(button, REPOBJ, (ObjPtr) observerWindow);
	    SetVar(button, HELPSTRING, NewString("This button expands the control panel \
to the right to show more controls.  Press it once again to collapse the control panel back.\n"));
	    SetMethod(button, CHANGEDVALUE, ChangeObserverShown);

	    /*Put in the controls on the right*/
	    textBox = TemplateTextBox(ObserverTemplate, "X", PLAIN, "X");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);
	    textBox = TemplateTextBox(ObserverTemplate, "Y", PLAIN, "Y");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);
	    textBox = TemplateTextBox(ObserverTemplate, "Z", PLAIN, "Z");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "Position Text", PLAIN, "Position:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "XPosn", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACESCALE);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACEORIGIN);
	    SetVar(textBox, WHICHINDEX, NewInt(0));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLocation);
	    SetMethod(textBox, APPEARANCE, MakeObserverLocationAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
X coordinate of the observer's position.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "YPosn", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACESCALE);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACEORIGIN);
	    SetVar(textBox, WHICHINDEX, NewInt(1));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLocation);
	    SetMethod(textBox, APPEARANCE, MakeObserverLocationAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Y coordinate of the observer's position.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "ZPosn", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACESCALE);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, SPACEORIGIN);
	    SetVar(textBox, WHICHINDEX, NewInt(2));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLocation);
	    SetMethod(textBox, APPEARANCE, MakeObserverLocationAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Z coordinate of the observer's position.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "Look At Text", PLAIN, "Look At:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "XLookat", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    SetVar(textBox, WHICHINDEX, NewInt(0));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLookat);
	    SetMethod(textBox, APPEARANCE, MakeObserverLookatAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
X coordinate of the point at which the observer is looking.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "YLookat", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    SetVar(textBox, WHICHINDEX, NewInt(1));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLookat);
	    SetMethod(textBox, APPEARANCE, MakeObserverLookatAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Y coordinate of the point at which the observer is looking.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "ZLookat", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, USESPACECOORDS);
	    SetVar(textBox, WHICHINDEX, NewInt(2));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverLookat);
	    SetMethod(textBox, APPEARANCE, MakeObserverLookatAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Z coordinate of the point at which the observer is looking.  Enter a new position and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "Up Text", PLAIN, "Up Vector:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "XUp", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(0));
	    ActivateTextBox(textBox, false);
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverUp);
	    SetMethod(textBox, APPEARANCE, MakeObserverUpAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
X coordinate of the up vector.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "YUp", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(1));
	    ActivateTextBox(textBox, false);
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverUp);
	    SetMethod(textBox, APPEARANCE, MakeObserverUpAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(button, HELPSTRING, NewString("This text box controls the \
Y coordinate of the up vector.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "ZUp", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(2));
	    ActivateTextBox(textBox, false);
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverUp);
	    SetMethod(textBox, APPEARANCE, MakeObserverUpAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Z coordinate of the up vector.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "Quaternion Text", PLAIN, "Rotation:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "QScalar", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(0));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverQuaternion);
	    SetMethod(textBox, APPEARANCE, MakeObserverQuaternionAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
scalar part of the quaternion used for rotation.  This is cos(theta/2), where \
theta is the amount of rotation.  Enter a new value and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "QX", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(1));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverQuaternion);
	    SetMethod(textBox, APPEARANCE, MakeObserverQuaternionAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
X coordinate of the vector part of the quaternion used for rotation.  The \
vector points along the rotation axis with magnitude sin(theta/2), where theta \
is the amount of rotation.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "QY", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(2));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverQuaternion);
	    SetMethod(textBox, APPEARANCE, MakeObserverQuaternionAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Y coordinate of the vector part of the quaternion used for rotation.  The \
vector points along the rotation axis with magnitude sin(theta/2), where theta \
is the amount of rotation.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "QZ", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, observer);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, LOCATION);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FORWARDVECTOR);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, FOCUSDIST);
	    SetVar(textBox, WHICHINDEX, NewInt(3));
	    SetMethod(textBox, CHANGEDVALUE, ChangeObserverQuaternion);
	    SetMethod(textBox, APPEARANCE, MakeObserverQuaternionAppearance);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Z coordinate of the vector part of the quaternion used for rotation.  The \
vector points along the rotation axis with magnitude sin(theta/2), where theta \
is the amount of rotation.  Enter a new coordinate and press the Enter \
key to change it.\n"));

	    textBox = TemplateTextBox(ObserverTemplate, "Origin Text", PLAIN, "Origin:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "XOrigin", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetMethod(textBox, EXTRAAPPEARANCE, HideOnAutoAdjust);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, AUTOADJUST);
	    AssocIndexedTextRealControlWithVar(textBox,
		observer, SPACEORIGIN, 0, minusInf, plusInf, TR_NE_BOTTOM | TR_NE_TOP);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
X coordinate of the observer's origin in space coordinates.  By default, \
this is the center of all visualization objects in the space.  If the Adjust to Space \
check box is on, the origin will automatically be adjusted according to the \
objects within the space.  If the check box is off, you can change the origin \
by entering a number in the text box and pressing the Enter key."));

	    textBox = TemplateTextBox(ObserverTemplate, "YOrigin", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetMethod(textBox, EXTRAAPPEARANCE, HideOnAutoAdjust);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, AUTOADJUST);
	    AssocIndexedTextRealControlWithVar(textBox,
		observer, SPACEORIGIN, 1, minusInf, plusInf, TR_NE_BOTTOM | TR_NE_TOP);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Y coordinate of the observer's origin in space coordinates.  By default, \
this is the center of all visualization objects in the space.  If the Adjust to Space \
check box is on, the origin will automatically be adjusted according to the \
objects within the space.  If the check box is off, you can change the origin \
by entering a number in the text box and pressing the Enter key."));

	    textBox = TemplateTextBox(ObserverTemplate, "ZOrigin", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetMethod(textBox, EXTRAAPPEARANCE, HideOnAutoAdjust);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, AUTOADJUST);
	    AssocIndexedTextRealControlWithVar(textBox,
		observer, SPACEORIGIN, 2, minusInf, plusInf, TR_NE_BOTTOM | TR_NE_TOP);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
Y coordinate of the observer's origin in space coordinates.  By default, \
this is the center of all visualization objects in the space.  If the Adjust to Space \
check box is on, the origin will automatically be adjusted according to the \
objects within the space.  If the check box is off, you can change the origin \
by entering a number in the text box and pressing the Enter key."));

	    /*Scaling*/
	    textBox = TemplateTextBox(ObserverTemplate, "Scaling Text", PLAIN, "Scaling:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, LEFTALIGN);

	    textBox = TemplateTextBox(ObserverTemplate, "Scaling", EDITABLE + WITH_PIT + ONE_LINE, "");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, RIGHTALIGN);
	    SetMethod(textBox, EXTRAAPPEARANCE, HideOnAutoAdjust);
	    DeclareIndirectDependency(textBox, APPEARANCE, REPOBJ, AUTOADJUST);
	    AssocTextRealControlWithVar(textBox,
		observer, SPACESCALE, 0.0, plusInf, TR_NE_BOTTOM | TR_NE_TOP);
	    SetVar(textBox, HELPSTRING, NewString("This text box controls the \
scaling of the observer's coordinate in space coordinates.  This is the amount \
in space coordinates that corresponds to 1 unit in observer coordinates.  By default, \
the scaling is set so that all the objects in the space fit roughly within a \
sphere of radius 1 centered on the origin.  If the Adjust to Space \
check box is on, the scaling will automatically be adjusted according to the \
objects within the space.  If the check box is off, you can change the scaling \
by entering a number in the text box and pressing the Enter key."));

	    /*Adjust to space*/
	    checkBox = TemplateCheckBox(ObserverTemplate, "Adjust to Space", false);
	    PrefixList(contents, checkBox);
	    SetVar(checkBox, PARENT, panel);
	    AssocDirectControlWithVar(checkBox, observer, AUTOADJUST);
	    SetVar(checkBox, HELPSTRING, NewString("This check box controls \
whether the observer's origin and scaling are automatically adjusted to the space being \
drawn.  When this box is off, you can enter the origin and scaling manually.  \
When it is on, the origin and scaling will change depending on which space is \
being drawn.  If the observer controls several spaces with different coordinate \
systems, this may result in the \
numbers changing rapidly as the individual spaces are drawn."));

	    /*Coordinate systems*/
	    radio = NewRadioButtonGroup("Coordinate System");
	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);
	    SetVar(checkBox, HELPSTRING, NewString("This radio button group \
controls whether coordinates are displayed in observer coordinates or space \
coordinates.\n\nObserver coordinates provide a normalized \
coordinate system for viewing, where the field of view is approximately covered \
by a unit sphere centered at (0, 0, 0).  Observer coordinates are dimensionless \
and are always the same, no matter how many spaces are controlled by the observer.\n\n\
Space coordinates are the coordinates \
used by the visualization objects within the space.  These may be kilometers, angstroms, \
or whatever is appropriate.  Space coordinates can be more convenient if you \
want to go to a certain point within the space.  If an observer controls more \
than one space, the space coordinates may be different.  We recommend that you \
only use space coordinates when controlling a single space."));

	    checkBox = TemplateRadioButton(ObserverTemplate, "Observer Coordinates");
	    AddRadioButton(radio, checkBox);

	    checkBox = TemplateRadioButton(ObserverTemplate, "Space Coordinates");
	    AddRadioButton(radio, checkBox);

	    AssocDirectControlWithVar(radio, observer, USESPACECOORDS);
	}
    }

    return (ObjPtr) observerWindow;
}

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);

	    var = GetVar(renderer, RENDERTYPE);
	    if (!var)
	    {
		SetVar(renderer, RENDERTYPE, NewInt(hasRGB ? RT_RGB_HARDWARE : RT_CMAP_HARDWARE));
	    }
	    AssocDirectControlWithVar(radio, renderer, RENDERTYPE);

	    /*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 = GetIntVar("ShowRendererControls", renderer, FILTERTYPE);
	    if (!var)
	    {
		SetVar(renderer, FILTERTYPE, NewInt(FT_NONE));
	    }
	    AssocDirectControlWithVar(radio, renderer, FILTERTYPE);
	}
    }

    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))
	    {
		SetClock(clock, minValue);
	    }
	}
	else
	{
	    SetVar(clock, TIME, NULLOBJ);
	    ImInvalid(clock);
	}
    }
    else
    {
	SetVar(clock, TIME, NULLOBJ);
	ImInvalid(clock);
    }

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

    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;

    MakeVar(observer, UPVECTOR);
    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;

    MakeVar(observer, FORWARDVECTOR);
    var = GetVar(observer, FORWARDVECTOR);
    if (var)
    {
	Array2CArray(forwardVector, var);
    }
    else
    {
	forwardVector[0] = 0.0;
	forwardVector[1] = 0.0;
	forwardVector[2] = -1.0;
    }
}

static ObjPtr MakeObserverForwardVector(observer)
ObjPtr observer;
/*Makes the observer's forward vector*/
{
    real forwardVector[3];
    Quaternion rotQuat;
    ObjPtr var;

    MakeVar(observer, ROTQUAT);
    if (var = GetVar(observer, ROTQUAT))
    {
	/*There's a rotation quaternion, derive forward from that*/
	real temp[3];

	temp[0] = 0.0;
	temp[1] = 0.0;
	temp[2] = -1.0;

	Array2CArray(rotQuat, var);
	QRot(temp, rotQuat, forwardVector);
    }
    else
    {
	forwardVector[0] = 0.0;
	forwardVector[1] = 0.0;
	forwardVector[2] = -1.0;
    }

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

static ObjPtr MakeObserverUpVector(observer)
ObjPtr observer;
/*Makes the observer's up vector*/
{
    real upVector[3];
    Quaternion rotQuat;
    ObjPtr var;

    MakeVar(observer, ROTQUAT);
    if (var = GetVar(observer, ROTQUAT))
    {
	/*There's a rotation quaternion, derive forward from that*/
	real temp[3];

	temp[0] = 0.0;
	temp[1] = 1.0;
	temp[2] = 0.0;

	Array2CArray(rotQuat, var);
	QRot(temp, rotQuat, upVector);
    }
    else
    {
	upVector[0] = 0.0;
	upVector[1] = 1.0;
	upVector[2] = 0.0;
    }
    var = NewRealArray(1, 3L);
    CArray2Array(var, upVector);
    SetVar(observer, UPVECTOR, var);
    return ObjTrue;
}

#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 GetObserverLocation(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;
}

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);
	if (!GetVar(space, SHOWROTGUIDES)) SetVar(space, SHOWROTGUIDES, ObjFalse);
	AssocDirectControlWithVar(checkBox, space, SHOWROTGUIDES);
	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);
	if (!GetVar(space, SHOWMOTGUIDES)) SetVar(space, SHOWMOTGUIDES, ObjFalse);
	AssocDirectControlWithVar(checkBox, space, SHOWMOTGUIDES);
	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;
    MakeVar(space, CLOCK);
    clock = GetVar(space, CLOCK);
    if (clock)
    {
	MakeVar(clock, TIME);
	var = GetVar(clock, TIME);
	if (var)
	{
	    SetVar(space, TIME, 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, 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, TIMEBOUNDS, MakeClockTimeBounds);
    SetMethod(clockClass, NEWCTLWINDOW, ShowClockControls);
    SetVar(clockClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetMethod(clockClass, REINIT, MakeClockDatasets);
    SetVar(clockClass, CLASSID, NewInt(CLASS_CLOCK));
    DeclareDependency(clockClass, TIMESTEPS, DATASETS);
    SetVar(clockClass, DTIMEPER, NewReal(1));
    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, 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));
    DeclareDependency(observerClass, FORWARDVECTOR, ROTQUAT);
    SetMethod(observerClass, FORWARDVECTOR, MakeObserverForwardVector);
    DeclareDependency(observerClass, UPVECTOR, ROTQUAT);
    SetMethod(observerClass, UPVECTOR, MakeObserverUpVector);
    {
	/*Give it an initial identity rotation quaternion*/
	ObjPtr var;
	Quaternion rotQuat;

	rotQuat[0] = 1.0;
	rotQuat[1] = 0.0;
	rotQuat[2] = 0.0;
	rotQuat[3] = 0.0;
	var = NewRealArray(1, 4L);
	CArray2Array(var, rotQuat);
	SetVar(observerClass, ROTQUAT, var);
    }
    {
	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);
	posn[0] = 0.0;
	posn[1] = 0.0;
	posn[2] = 0.0;
	anArray = NewRealArray(1, 3L);
	CArray2Array(anArray, posn);
	SetVar(observerClass, SPACEORIGIN, anArray);
	SetVar(observerClass, SPACESCALE, NewReal(1.0));
    }
    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"));
    SetVar(observerClass, AUTOADJUST, NewInt(1));
    SetVar(observerClass, USESPACECOORDS, NewInt(0));
    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);
}
Modified: Sun Nov 17 17:00:00 1996 GMT
Page accessed 2745 times since Sat Apr 17 21:54:44 1999 GMT