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 "ScianDraw.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianVisWindows.h"
#include "ScianVisObjects.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 "ScianDialogs.h"
#include "ScianSliders.h"
#include "ScianTextBoxes.h"
#include "ScianStyle.h"
#include "ScianComplexControls.h"
#include "ScianMethods.h"
#include "ScianPick.h"
#include "ScianPreferences.h"
#include "ScianFiles.h"

#define NEWCLOCK

Bool phsco = false;			/*True iff phscologram is on*/
Bool drawingTransparent = false;	/*True iff drawing only transparent*/

static void StartSpace(ObjPtr, int, int, int, int, int, int);

typedef struct
    {
	unsigned char alpha;
	unsigned char blue;
	unsigned char green;
	unsigned char red;
    } Pixel;

Pixel imageBuffer[SCRWIDTH * SCRHEIGHT];

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

#define ROTLIGHTS			/*Rotate lights with object*/

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

#define ASPECT		(((float) (right - left)) / ((float) (top - bottom))) * (phsco ? 0.61 : 1.0)
#define MINROT		3		/*Minimum number of pixels to move to rotate*/
#define SQUARE(x)	((x) * (x))
#define AIRSPEEDFACTOR	0.02		/*Airspeed of eye per throttle stop*/
#define MAXROLL		(M_PI / 10.0)	/*Pi/10 of roll max*/
#define MAXDPITCH	0.1		/*Maximum delta percentage pitch*/
#define ROLLYAWFACTOR	1.0		/*Roll into yaw per unit time*/
#define ROLLDSPFACTOR	(900.0 / M_PI)
#define STARSIZE	0.05		/*Size of a star*/
#define MOVEFACTOR	0.35		/*Move per D unit * distance*/

Matrix tempMatrix;			/*Temporary matrix*/

ObjPtr curSpace = 0;			/*Current space*/
real curSpacePerspec[4];		/*Current perspective stuff*/
ObjPtr curSpaceLights;			/*Lights in the current space*/
Matrix viewerCoordsMatrix;		/*Transformation matrix for viewer coords*/
Matrix viewerUnrotMatrix;		/*Transformation matrix for unrotated viewer coords*/

ObjPtr spaceClass = 0;			/*Class of a 3-D space*/
extern ObjPtr visClass;			/*Class of all visualization objects*/
extern ObjPtr iconClass;		/*Class of icons*/
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*/
Bool oneObserver = false;
Bool oneRenderer = false;
Bool oneClock = false;
Bool oneLights = false;			/*True iff to make only one controller*/

Coord starPoints[][3] =			/*Points of a star*/
	{
	    {1.0, STARSIZE, 0.0},
	    {1.0, 0.0, STARSIZE},
	    {1.0, -STARSIZE, 0.0},
	    {1.0, 0.0, -STARSIZE}
	};


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

static float f(x)
float x;
/*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] = 1e12;
    bounds[1] = bounds[3] = bounds[5] = -1e12;

    runner = LISTOF(list);
    if (!runner)
    {
	return false;
    }
    while (runner)
    {
	ObjPtr repObj;
	repObj = runner -> thing;
	if (IsObject(repObj))
	{
	    FuncTyp boundsMethod;

	    boundsMethod = GetMethodSurely("GetListExtent", repObj, GETBOUNDS);
	    if (boundsMethod)
	    {
		real localBounds[6];
		(*boundsMethod)(repObj, localBounds);
		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;
    }
    return true;
}

#define NUDGESIZE	400		/*Size of one nudge*/
#define MAXNNUDGES	3		/*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*/
{
    curZMax -= NUDGESIZE;
    curZMin -= NUDGESIZE;
    lsetdepth(curZMin, curZMax);
}

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

static void StartSpace(space, left, right, bottom, top, action, whichView)
ObjPtr space;
int left, right, bottom, top;
int action;
int whichView;
/*Start a drawing, rotation, or press in a space.  Action is the action
  that is being performed*/
{
    real eyePosn[3];		/*Coords of the eye*/
    real focusPoint[3];		/*Coords of the focus point*/
    real roll;
    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*/
    ObjPtr xformMatrix;		/*The rotation matrix*/
    ObjPtr shearMatrix;		/*The shear matrix*/
    real maxSize;		/*Maximum size of the objects this draw*/
    ObjPtr observer;		/*Observer of the space*/
    ObjPtr clock;		/*The clock*/
    real eyeOffset;
    real eyeDir[3];

    MyPushMatrix();

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

    switch (whichView)
    {
	case VIEW_XLEFT:
	case VIEW_RCLEFT:
	    eyeOffset = -0.2;
	    break;
	case VIEW_XRIGHT:
	case VIEW_RCRIGHT:
	    eyeOffset = 0.2;
	    break;
	default:
	    eyeOffset = 0.0;
    }

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

#if 0
    observers = GetListVar("StartSpace", space, OBSERVERS);
    if (!observers || !LISTOF(observers)) return;
    observer = LISTOF(observers) -> thing;
#else
    observer = GetObjectVar("StartSpace", space, OBSERVER);
#endif
    if (!observer) return;

    curSpacePerspec[0] = INITEYEDIST;
    psStuff = GetFixedArrayVar("StartSpace", observer, PERSPECSTUFF, 1, 4L);
    if (psStuff)
    {
	Array2CArray(curSpacePerspec, psStuff);
    }

    /*Get information about the eye*/
    object = GetFixedArrayVar("StartSpace", observer, LOCATION, 1, 3L);
    if (object)
    {
	Array2CArray(eyePosn, object);
    }
    else
    {
	eyePosn[0] = 0.0;
	eyePosn[1] = 0.0;
	eyePosn[2] = curSpacePerspec[0];
    }
    object = GetFixedArrayVar("StartSpace", observer, FOCUSPOINT, 1, 3L);
    if (object)
    {
	Array2CArray(focusPoint, object);
    }
    else
    {
	focusPoint[0] = 0.0;
	focusPoint[1] = 0.0;
	focusPoint[2] = 0.0;
    }

    object = GetRealVar("StartSpace", observer, ROLL);
    if (object)
    {
	roll = GetReal(object);
    }
    else
    {
	roll = 0.0;
    }

    if (whichView == VIEW_XLEFT || whichView == VIEW_XRIGHT ||
	rgbp && (whichView == VIEW_RCLEFT || whichView == VIEW_RCRIGHT))
    {
	real temp;
	/*Have to slide eyePosn and focusPoint over somewhat*/
	eyeDir[0] = focusPoint[0] - eyePosn[0];
	eyeDir[1] = focusPoint[1] - eyePosn[1];
	eyeDir[2] = focusPoint[2] - eyePosn[2];
	NORMALIZE(eyeDir);

	/*Make 90 degree rotation in plane*/
	temp = eyeDir[0];
	eyeDir[0] = eyeDir[2];
	eyeDir[2] = -temp;
	eyeDir[1] = 0.0;
	NORMALIZE(eyeDir);

	/*Twist by roll*/
	temp = rcos(roll);
	eyeDir[0] *= temp;
	eyeDir[2] *= temp;
	eyeDir[1] *= rsin(roll);

	/*Offset*/
	eyeDir[0] *= eyeOffset;
	eyeDir[1] *= eyeOffset;
	eyeDir[2] *= eyeOffset;

	eyePosn[0] -= eyeDir[0];
	eyePosn[1] -= eyeDir[1];
	eyePosn[2] -= eyeDir[2];
/*
	focusPoint[0] -= eyeDir[0];
	focusPoint[1] -= eyeDir[1];
	focusPoint[2] -= eyeDir[2];
*/
    }

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

    /*Set the current space*/
    curSpace = space;
    RegisterInSpace(true);

    shearMatrix = (ObjPtr) GetVar(space, SHEAR);
    if (observer)
    {
	xformMatrix = (ObjPtr) GetMatrixVar("StartSpace", observer, XFORM);
    }
    curSpaceLights = GetVar(space, LIGHTS);
    /*Set up the lights*/
    if (action == DRAWSPACE)
    {
	shademodel(GOURAUD);
#ifdef GL4D
	mmode(MPROJECTION);
	loadmatrix(Identity);
#endif
	ortho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
	lookat(0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0);

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

	/*Shear the light sources*/
	if (shearMatrix && IsMatrix(shearMatrix))
	{
	    multmatrix(MATRIXOF(shearMatrix));
	}

#ifdef ROTLIGHTS
	/*Rotate the light sources*/
	if (xformMatrix && IsMatrix(xformMatrix))
	{
	    multmatrix(MATRIXOF(xformMatrix));
	}
#endif

#ifdef GL4D
	if (curSpaceLights)
	{
	    StartLights(space, curSpaceLights);
	}
#endif
    }

    if (action == PICKSPACE)
    {
	StartPick(space);
    }
#ifdef GL4D
    mmode(MPROJECTION);
#endif
    if (whichView == VIEW_ORTHO)
    {
	/*Plain old orthographic projection.*/
	ortho(-ASPECT * curSpacePerspec[1], ASPECT * curSpacePerspec[1],
	      -curSpacePerspec[1], curSpacePerspec[1], curSpacePerspec[2], curSpacePerspec[3]);
    }
    else
    {
	/*Exciting perspective projection!!!*/
	perspective((long) (curSpacePerspec[1] * 10.0), ASPECT, curSpacePerspec[2], curSpacePerspec[3]);
    }
    lookat(eyePosn[0], eyePosn[1], eyePosn[2],
	focusPoint[0], focusPoint[1], focusPoint[2], 
	-(Angle) (roll * ROLLDSPFACTOR));

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

    if (action == DRAWSPACE || action == PICKSPACE)
    {
	/*Draw the background of the space*/
	if (action == DRAWSPACE)
	{
	    zbuffer(TRUE);
	    zclear();
	    curZMin = zMin + NUDGESIZE * MAXNNUDGES;
	    curZMax = zMax - NUDGESIZE * MAXNNUDGES;
	    lsetdepth(curZMin, curZMax);
	}

	if (rgbp && whichView == VIEW_RCLEFT)
	{
	    RGBwritemask(0, 0xFF, 0xFF);
	}
	else if (rgbp && whichView == VIEW_RCRIGHT)
	{
	    RGBwritemask(0xFF, 0, 0);
	}

	/*Shear the objects*/
	if (shearMatrix && IsMatrix(shearMatrix))
	{
	    multmatrix(MATRIXOF(shearMatrix));
	}

	/*Draw stuff in focus centered space coordinates*/
	if (action == DRAWSPACE)
	{
	    Matrix justRot;
	    int i;
	    real radius;

	    radius = SPACESPHERESIZE * curSpacePerspec[0] * rsin(curSpacePerspec[1] * M_PI / 180.0);

	    MATCOPY(justRot, MATRIXOF(xformMatrix));
	    for (i = 0; i < 3; ++i)
	    {
		    justRot[3][i] = justRot[i][3] = 0.0;
	    }

	    pushmatrix();
	    translate(focusPoint[0], focusPoint[1], focusPoint[2]);
	    getmatrix(viewerUnrotMatrix);
	    multmatrix(justRot);

	    getmatrix(viewerCoordsMatrix);
	    if (GetPredicate(curSpace, ROTATING) && GetPrefTruth(PREF_ROTGUIDES))
	    {
		DrawWFSphere(0.0, 0.0, 0.0,
			radius, UIGRAY50);
		DrawSpaceLine(0.0 - radius, 0.0, 0.0, 0.0 + radius, 0.0, 0.0, UIGRAY50);
		DrawSpaceLine(0.0, 0.0 - radius, 0.0, 0.0, 0.0 + radius, 0.0, UIGRAY50);
		DrawSpaceLine(0.0, 0.0, 0.0 - radius, 0.0, 0.0, 0.0 + radius, UIGRAY50);
	    }
	    else if (GetPredicate(curSpace, MOVING) && GetPrefTruth(PREF_MOVEGUIDES))
	    {
		real x, y;
		for (x = -10.0; x <= 10.0; x += 2.0)
		{
		    for (y = -10.0; y <= 10.0; y += 2.0)
		    {
			DrawSpaceLine(x, y, -10.0, x, y, 10.0, UIGRAY50);
			DrawSpaceLine(x, -10.0, y, x, 10.0, y, UIGRAY50);
			DrawSpaceLine(-10.0, x, y, 10.0, x, y, UIGRAY50);
		    }
		}
	    }
	    popmatrix();
	}

	/*Rotate the objects*/
	multmatrix(MATRIXOF(xformMatrix));

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

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

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

	/*Translate to be centered around object's center*/
	translate(-center[0], -center[1], -center[2]);
    }
}

static void StopSpace(action)
int action;
/*Stops drawing, rotating, or pressing in a space*/
{
    if (action == PICKSPACE)
    {
	StopPick(curSpace);
    }

    if (action == DRAWSPACE)
    {
	real radius;

	radius = SPACESPHERESIZE * curSpacePerspec[0] * rsin(curSpacePerspec[1] * M_PI / 180.0);

	/*Draw the lights*/
	loadmatrix(viewerCoordsMatrix);
	if (curSpaceLights)
	{
	    DrawLights(curSpace, curSpaceLights, action, radius);
	    StopLights(curSpace);
	}
	if (rgbp)
	{
	   RGBwritemask(0xFF, 0xFF, 0xFF);
	}
    }
    ResetClipRect();
    popviewport();
#ifdef GL4D
    mmode(MSINGLE);
    loadmatrix(Identity);
#endif

    EndRegister();
    MyPopMatrix();

    zbuffer(FALSE);
    curSpace = NULLOBJ;
}

Bool ChangeFocus(observer)
ObjPtr observer;
/*Changes the focus of observer according to roll, pitch, yaw*/
{
    ObjPtr tempObj;
    real eyePosn[3];
    real pitch, yaw, roll;
    real perspecStuff[4];

    tempObj = GetFixedArrayVar("ChangeFocus", observer, LOCATION, 1, 3L);
    if (!tempObj)
    {
	return false;
    }
    Array2CArray(eyePosn, tempObj);

    tempObj = GetRealVar("ChangeFocus", observer, PITCH);
    if (!tempObj)
    {
	return false;
    }
    pitch = GetReal(tempObj);

    tempObj = GetRealVar("ChangeFocus", observer, YAW);
    if (!tempObj)
    {
	return false;
    }
    yaw = GetReal(tempObj);

    tempObj = GetRealVar("ChangeFocus", observer, ROLL);
    if (!tempObj)
    {
	return false;
    }
    roll = GetReal(tempObj);

    /*Get perspective stuff for scaling movement and eyeball distance*/
    tempObj = GetFixedArrayVar("ChangeFocus", observer, PERSPECSTUFF, 1, 4L);
    if (tempObj)
    {
	Array2CArray(perspecStuff, tempObj);
    }
    else
    {
	perspecStuff[0] = INITEYEDIST;
    }

    /*Change focus point to be from new eye position*/
    eyePosn[0] += rsin(yaw) * perspecStuff[0];
    eyePosn[1] += rsin(pitch) * perspecStuff[0];
    eyePosn[2] += rcos(yaw) * perspecStuff[0];
    tempObj = NewRealArray(1, 3L);
    CArray2Array(tempObj, eyePosn);
    SetVar(observer, FOCUSPOINT, tempObj);

    ResolveController(observer);
    return true;
}

ObjPtr WakeObserver(observer, lateness)
ObjPtr observer;
double lateness;
/*Wakes an observer and makes it continue rotation*/
{
    ObjPtr var;
    real axis[3];
    Matrix rotDelta;		/*Delta rotation matrix*/
    real phi;
    Bool wakeAgain = false;	/*True iff wake again*/

    DoNotDisturb(observer, MARKTIME);

    var = GetVar(observer, ROTSPEED);
    if (var && 0.0 != (phi = GetReal(var)))
    {
	/*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;
    }
    else if (GetPredicate(observer, FLYING))
    {
	real focusPoint[3];
	ObjPtr var;
	real eyePosn[3];
	real roll, yaw, dPitch, pitch;
	float dTime;
	real perspecStuff[4];
	real airspeed;

	dTime = lateness;

	var = GetFixedArrayVar("DrawSpace", observer, FOCUSPOINT, 1, 3L);
	if (var)
	{
	    Array2CArray(focusPoint, var);
	}
	else
	{
	    focusPoint[0] = 0.0;
	    focusPoint[1] = 0.0;
	    focusPoint[2] = 0.0;
	}

	/*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 && IsArray(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);
	}
	
	/*Update eye position based on airspeed*/
	    var = GetVar(observer, ROLL);
	    if (var && IsReal(var))
	    {
		roll = GetReal(var);
	    }
	    else
	    {
		roll = 0.0;
		var = NewReal(roll);
		SetVar(observer, ROLL, var);
	    }

	    /*Get dPitch*/
	    var = GetVar(observer, DPITCH);
	    if (var && IsReal(var))
	    {
		dPitch = GetReal(var);
	    }
	    else
	    {
		dPitch = 0.0;
		var = NewReal(dPitch);
		SetVar(observer, DPITCH, var);
	    }

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

	    /*Get yaw*/
	    var = GetRealVar("DrawSpace", observer, YAW);
	    if (var && IsReal(var))
	    {
		yaw = GetReal(var);
	    }
	    else
	    {
		yaw = M_PI;
		var = NewReal(yaw);
		SetVar(observer, YAW, var);
	    }
	
	    /*Change yaw based on roll*/
	    yaw -= roll * ROLLYAWFACTOR * dTime;
	    SetVar(observer, YAW, NewReal(yaw));

	    /*Move eye*/
	    eyePosn[2] += rcos(yaw) * airspeed * dTime;
	    eyePosn[1] += rsin(pitch) * airspeed * dTime;
	    eyePosn[0] += rsin(yaw) * airspeed * dTime; 

	    var = NewRealArray(1, 3L);
	    CArray2Array(var, eyePosn);
	    SetVar(observer, LOCATION, var);
	
	    /*Recalculate focuspoint*/
	    ChangeFocus(observer);
	    ResolveController(observer);
	wakeAgain = true;
    }
    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());
    }
}

static ObjPtr RotateSpace(object, x, y, flags)
ObjPtr object;
int x, y;
int flags;
/*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;

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

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	ObjPtr xformMatrix = 0;
	ObjPtr observer/*, observers*/;
	ObjPtr lights;
	ThingListPtr runner;

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(object);
	    return ObjTrue;
	}

	/*Hey!  It really was a click in the space!*/
	StartSpace(object, left, right, bottom, top, ROTSPACE, VIEW_CENTER);

	/*See if there are any lights to rotate*/
	lights = GetVar(object, LIGHTS);
	if (lights)
	{
	    runner = LISTOF(lights);
	    while (runner)
	    {
		if (IsLightSelected(runner -> thing, GetVar(object, CORRAL)))
		{
		    break;
		}
		runner = runner -> next;
	    }
	}
	/*If none selected, say there are none*/
	if (!runner)
	{
	    lights = NULLOBJ;
	}

	/*Get the rotation matrix*/
#if 0
	observers = GetListVar("RotateSpace", object, OBSERVERS);
	if (!observers || !LISTOF(observers)) return ObjFalse;
	observer = LISTOF(observers) -> thing;
#else
	observer = GetObjectVar("RotateSpace", object, OBSERVER);
#endif

	if (observer)
	{
	    xformMatrix = (ObjPtr) GetMatrixVar("RotateSpace", observer, XFORM);
	}
	if (xformMatrix && IsMatrix(xformMatrix))
	{
	    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];
	    if (flags & F_DOUBLECLICK)
	    {
		if (lights)
		{
		    /*Double-click.  Snap lights to their closest values*/

		    runner = LISTOF(lights);
		    while (runner)
		    {
			if (IsLightSelected(runner -> thing, GetVar(object, CORRAL)))
			{
			    /*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);
				ResolveController(runner -> thing);
			    }
			}
			runner = runner -> next;
		    }
		}
		else
		{
		/*It was a double click.  Align the rotation matrix to the
		  nearest orthogonal unit vector*/
		Matrix newRot;		/*New rotation matrix*/
		Bool jSet[3];		/*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*/

		MATCOPY(newRot, MATRIXOF(xformMatrix));
		for (j = 0; j < 3; ++j)
		{
		    jSet[j] = false;
		}
		for (i = 0; i < 3; ++i)
		{
		    biggest = -1.0;
		    jbiggest = -1;

		    for (j = 0; j < 3; ++j)
		    {
			if ((ABS(newRot[i][j]) > biggest) && (!jSet[j]))
			{
			    biggest = ABS(newRot[i][j]);
			    jbiggest = j;
			}
		    }
		    if (jbiggest == -1)
		    {
			StopSpace(ROTSPACE);
			return ObjTrue;
		    }
		    else
		    {
			jSet[jbiggest] = 1;
			for (j = 0; j < 3; ++j)
			{
			    if (j == jbiggest)
			    {
				if (newRot[i][j] < 0.0) newRot[i][j] = -1.0;
				else newRot[i][j] = 1.0;
			    }
			    else newRot[i][j] = 0.0;
			}
		    }
		}
		MATCOPY(MATRIXOF(xformMatrix), newRot);
		ResolveController(observer);
		}
		ImInvalid(object);
	    }
	    else
	    {
		/*It's a click and drag.  Do the trackball stuff.*/
		float Ox, Oy;		/*Center of virtual trackball*/
		float r;		/*Radius of virtual trackball*/
		float omega;		/*Ratio of the click radius to the trackball radius*/
		float theta;		/*Angle to the click radius*/
		float tao;		/*Angle from the click radius to the direction*/
		float phi;		/*Amount the trackball is pushed*/
		int newX, newY;		/*New X and Y values*/
		Bool firstRun = true;	/*The first run*/
		float lastTime = 0.0;   /*The last time*/
		float cumPhi = 0.0;	/*Cumulative phi*/
		float curTime = 0.0;	/*The current time*/

		SetVar(object, ROTATING, ObjTrue);
		DrawMe(object);

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

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

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

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

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

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

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

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

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

			/*Now multiply in the "on x-axis" component*/
			a2x = a1x * cw + a1z * sw;
			a2y = a1y;
			a2z = a1x * -sw + a1z * cw;
			/*Now multiply in the from the 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;
			    }
			}

			axis[0] = ax;
			axis[1] = ay;
			axis[2] = az;

			/*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;
			ResolveController(observer);
			DrawMe(object);
		    }
		    else
		    {
			firstRun = true;
			motorOn = false;
		    }
		}
		SetVar(object, ROTATING, false);
	    }
	    StopSpace(ROTSPACE);

	    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 (IsLightSelected(runner -> thing, GetVar(object, CORRAL)))
			{
			    /*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*/
		    xformMatrix = (ObjPtr) GetMatrixVar("RotateSpace", observer, XFORM);
		    MatrixToText(tempStr, MATRIXOF(xformMatrix));
	 	    sprintf(cmd, "rotate to %s\n", tempStr);
		    Log(cmd);
		}
	    }

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

	    return ObjTrue;
	}
	else
	{
	    return ObjFalse;
	}
    }
    else
    {
	return ObjFalse;
    }
}

static ObjPtr PressSpace(object, x, y, flags)
ObjPtr object;
int x, y;
int flags;
/*Does a press in a space beginning at x and y.  Returns
  true iff the press really was in the space.*/
{
    int left, right, bottom, top;
    ObjPtr lights, corral;
    ThingListPtr runner;

    if (TOOL(flags) == T_ROTATE)
    {
	return RotateSpace(object, x, y, flags);
    }

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

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	/*It's a click in the space*/
	int d;
	int newX, newY;
	ObjPtr observer/*, observers*/;
	ObjPtr pickedObjects;
	ObjPtr lights, objects;
	ThingListPtr runner;
	ObjPtr xformMatrix;
	ObjPtr tempObj;
	real eyePosn[3];
	real moveVector[3];
	real pitch, yaw, roll;
	real uMove, vMove;
	real uMoveP, vMoveP, wMoveP;
	real sr, cr;
	real sp, cp;
	real sy, cy;
	real perspecStuff[4];
	real sf;

	d = top - bottom;

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(object);
	    return ObjTrue;
	}

	corral = GetVar(object, CORRAL);
	lights = GetVar(object, LIGHTS);
	if (lights && corral)
	{
	    runner = LISTOF(lights);
	    while (runner)
	    {
		if (IsLightSelected(runner -> thing, corral))
		{
		    return ObjTrue;
		}
		runner = runner -> next;
	    }
	}
#if 0
	observers = GetListVar("PressSpace", object, OBSERVERS);
	if (!observers || !LISTOF(observers)) return ObjFalse;
	observer = LISTOF(observers) -> thing;
#else
	observer = GetObjectVar("PressSpace", object, OBSERVER);
#endif
	if (!observer) return ObjFalse;

	SetRotationMotor(0.0, 0.0, 0.0, 1.0);
	MakeMeCurrent(NULLOBJ);

	xformMatrix = (ObjPtr) GetMatrixVar("PressSpace", observer, XFORM);
	if (!xformMatrix)
	{
	    return ObjFalse;
	}
#if 0
Don't pick for now
	/*See if this press picks anything*/
	StartSpace(object, left, right, bottom, top, PICKSPACE, VIEW_CENTER);

	StopSpace(PICKSPACE);

	/*Deselect all objects if shift not down*/
	if (0 == (flags & F_SHIFTDOWN))
	{
	    /*First lights*/
	    objects = GetVar(object, LIGHTS);
	    if (objects)
	    {
		runner = LISTOF(objects);
		while (runner)
		{
		    FuncTyp method;
		    method = GetMethod(runner -> thing, SELECT);
		    if (method)
		    {
			(*method)(runner -> thing, false);
		    }
		    runner = runner -> next;
		}
	    }
	}

	if (pickedObjects = PickedObjects())
	{
	    /*Some objects were picked*/
	    /*Now select all the picked objects*/
	    runner = LISTOF(pickedObjects);
	    while (runner)
	    {
		/*Select or deselect the object*/
		if (GetPredicate(runner -> thing, SELECTED))
	        {
		    /*Already selected.  Only deselect if shift down*/
		    if (flags & F_SHIFTDOWN)
		    {
			FuncTyp method;
			method = GetMethod(runner -> thing, SELECT);
			if (method)
			{
			    (*method)(runner -> thing, false);
			}
		    }
		}
		else
		{
		    FuncTyp method;
		    method = GetMethod(runner -> thing, SELECT);
		    if (method)
		    {
			(*method)(runner -> thing, true);
		    }
		}
		runner = runner -> next;
	    }
	}
#endif
	StartSpace(object, left, right, bottom, top, MOVESPACE, VIEW_CENTER);

	SetVar(object, MOVING, ObjTrue);
	DrawMe(object);

	/*Get info about the space*/
	tempObj = GetFixedArrayVar("PressSpace", observer, LOCATION, 1, 3L);
	if (!tempObj)
	{
	    return ObjFalse;
	}
	Array2CArray(eyePosn, tempObj);

	tempObj = GetRealVar("PressSpace", observer, PITCH);
	if (!tempObj)
	{
	    return ObjFalse;
	}
	pitch = GetReal(tempObj);

	tempObj = GetRealVar("PressSpace", observer, YAW);
	if (!tempObj)
	{
	    return ObjFalse;
	}
	yaw = GetReal(tempObj);

	tempObj = GetRealVar("PressSpace", observer, ROLL);
	if (!tempObj)
	{
	    return ObjFalse;
	}
	roll = GetReal(tempObj);

	/*Get perspective stuff for scaling movement and eyeball distance*/
	tempObj = GetFixedArrayVar("PressSpace", observer, PERSPECSTUFF, 1, 4L);
	if (tempObj)
	{
	    Array2CArray(perspecStuff, tempObj);
	}
	else
	{
	    perspecStuff[0] = INITEYEDIST;
	}

	if (flags & F_DOUBLECLICK)
	{
	
	}
	/*Press the contents of the space*/
	else while (Mouse(&newX, &newY))
	{
	    if (newX != x && newY != y)
	    {
		real focusPoint[3];

		/*Get raw uMove and vMove*/
		sf = perspecStuff[0] * MOVEFACTOR;
		uMove = sf * ((float) (x - newX)) / (float) d;
		vMove = sf * ((float) (y - newY)) / (float) d;

		/*Transform by roll*/
		sr = sin((double) roll);
		cr = cos((double) roll);
		uMoveP = uMove * cr + vMove * sr;
		vMoveP = uMove * -sr + vMove * cr;

		sp = sin((double) pitch);
		cp = cos((double) pitch);
		sy = sin((double) yaw);
		cy = cos((double) yaw);

		/*Produce a motion vector*/
		moveVector[0] = uMoveP * -cy + vMoveP * (sy * -sp);
		moveVector[1] = vMoveP * cp;;
		moveVector[2] = uMoveP * sy + uMoveP * (sy * sp);

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

		    /*Constrain the axis to the nearest ortho axis*/
		    for (k = 0; k < 3; ++k)
		    {
			curDot =
			    moveVector[0] * MATRIXOF(xformMatrix)[k][0] +
			    moveVector[1] * MATRIXOF(xformMatrix)[k][1] +
			    moveVector[2] * MATRIXOF(xformMatrix)[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 * MATRIXOF(xformMatrix)[best][0];
		    moveVector[1] = maxDot * MATRIXOF(xformMatrix)[best][1];
		    moveVector[2] = maxDot * MATRIXOF(xformMatrix)[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);

		/*Change focus point*/
		focusPoint[0] = eyePosn[0] + rsin(yaw) * perspecStuff[0];
		focusPoint[1] = eyePosn[1] + rsin(pitch) * perspecStuff[0];
		focusPoint[2] = eyePosn[2] + rcos(yaw) * perspecStuff[0];
		tempObj = NewRealArray(1, 3L);
		CArray2Array(tempObj, focusPoint);
		SetVar(observer, FOCUSPOINT, tempObj);

		x = newX;
		y = newY;

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

	StopSpace(MOVESPACE);
	if (logging)
	{
	    ObjPtr eyeStuff;
	    real eyePosn[3];

	    eyeStuff = GetFixedArrayVar("PressSpace", observer, LOCATION, 1, 3L);
	    if (eyeStuff)
	    {
		Array2CArray(eyePosn, eyeStuff);
		sprintf(tempStr, "eyeposn [%g %g %g]\n",
			eyePosn[0], eyePosn[1], eyePosn[2]);
		Log(tempStr);
	    }
	}
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

int dsn = 0;

void DrawSpaceContents(space, left, right, bottom, top, viewType)
ObjPtr space;
int left, right, bottom, top;
int viewType;
{
    ObjPtr contents;
    Bool anyTransparent;		/*True iff any objects are transparent*/
    ThingListPtr drawList;		/*List of objects to draw*/
    ObjPtr frontPanel;

    frontPanel = GetVar(space, FRONTPANEL);
    if (frontPanel && GetVar(frontPanel, BACKGROUND))
    {
	/*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);

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

    /*First draw opaque objects*/
    anyTransparent = false;
    drawList = LISTOF(contents);
    while (drawList)
    {
	if (IsObject(drawList -> thing))
	{
	    if (GetPredicate(drawList -> thing, ISTRANSPARENT))
	    {
		anyTransparent = true;
	    }
	    DrawObject(drawList -> thing);
	}
	else if (IsList(drawList -> thing))
	{
	    DrawList(drawList -> thing);
	}
	drawList = drawList -> next;
    }

    if (anyTransparent && rgbp)
    {
	zwritemask(0);
	blendfunction(BF_SA, BF_MSC);
/***DEBUG
	blendfunction(BF_SA, BF_MSA);
*/
	drawingTransparent = true;
	drawList = LISTOF(contents);
	while (drawList)
	{
	    if (IsObject(drawList -> thing))
	    {
		DrawObject(drawList -> thing);
	    }
	    else if (IsList(drawList -> thing))
	    {
		DrawList(drawList -> thing);
	    }
	    drawList = drawList -> next;
	}
	blendfunction(BF_ONE, BF_ZERO);
	zwritemask(0xffffffff);
	drawingTransparent = false;
    }

    StopSpace(DRAWSPACE);
}

#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 rotDelta*/
{
    ObjPtr xformMatrix;
    ObjPtr var;
    real focusPoint[3];
    Matrix rotDelta;
    float t;
    float sp, cp;		/*Sin and cosine of phi*/

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

    /*Now that there's a new delta, rotate and redraw*/
    xformMatrix = GetMatrixVar("RotateObserver", observer, XFORM);
    if (!xformMatrix)
    {
	return false;
    }

    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] * MATRIXOF(xformMatrix)[k][0] +
		axis[1] * MATRIXOF(xformMatrix)[k][1] +
		axis[2] * MATRIXOF(xformMatrix)[k][2];

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

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

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

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

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

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

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

    var = GetFixedArrayVar("RotateObserver", observer, FOCUSPOINT, 1, 3L);
    if (var)
    {
	Array2CArray(focusPoint, var);
    }
    else
    {
	focusPoint[0] = 0.0;
	focusPoint[1] = 0.0;
	focusPoint[2] = 0.0;
    }

    pushmatrix();
		    
    loadmatrix(Identity);
    translate(focusPoint[0], focusPoint[1], focusPoint[2]);
    multmatrix(rotDelta);
    translate(-focusPoint[0], -focusPoint[1], -focusPoint[2]);

    multmatrix(MATRIXOF(xformMatrix));
    xformMatrix = NewMatrix();
    getmatrix(MATRIXOF(xformMatrix));
    popmatrix();
    SetVar(observer, XFORM, xformMatrix);
    ResolveController(observer);
    return true;
}

#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*/
{
    ObjPtr xformMatrix;
    Matrix rotFixed;
    ThingListPtr runner;
    Matrix rotDelta;
    real axis[3];
    float t;
    float sp, cp;		/*Sin and cosine of phi*/

    xformMatrix = GetMatrixVar("RotateObserver", observer, XFORM);
    if (!xformMatrix)
    {
	return false;
    }

    MATCOPY(rotFixed, MATRIXOF(xformMatrix));

    runner = LISTOF(lights);
    while (runner)
    {
	if (IsLightSelected(runner -> thing, GetVar(space, CORRAL)))
	{
	    /*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;


	    /*Prerotate by rotFixed*/
	    newLoc[0] = rotFixed[0][0] * location[0] +
			rotFixed[1][0] * location[1] +
			rotFixed[2][0] * location[2];
	    newLoc[1] = rotFixed[0][1] * location[0] +
			rotFixed[1][1] * location[1] +
			rotFixed[2][1] * location[2];
	    newLoc[2] = rotFixed[0][2] * location[0] +
			rotFixed[1][2] * location[1] +
			rotFixed[2][2] * location[2];
	    location[0] = newLoc[0];
	    location[1] = newLoc[1];
	    location[2] = newLoc[2];
    
	    if (constrain)
	    {
		float dot1, dot2;
		float rr;

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

		/*Test azimuth*/
		dot1 = axis[0] * rotFixed[2][0] +
		       axis[1] * rotFixed[2][1] +
		       axis[2] * rotFixed[2][2];

		/*Test elevation*/
	    newLoc[0] = rotFixed[0][0] * tv[0] +
			rotFixed[1][0] * tv[1] +
			rotFixed[2][0] * tv[2];
	    newLoc[1] = rotFixed[0][1] * tv[0] +
			rotFixed[1][1] * tv[1] +
			rotFixed[2][1] * tv[2];
	    newLoc[2] = rotFixed[0][2] * tv[0] +
			rotFixed[1][2] * tv[1] +
			rotFixed[2][2] * tv[2];
		tv[0] = newLoc[0];
		tv[1] = newLoc[1];
		tv[2] = newLoc[2];
		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] = rotFixed[2][0];
		    axis[1] = rotFixed[2][1];
		    axis[2] = rotFixed[2][2];
		    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];
    
		/*Unrotate by rotFixed*/
		newLoc[0] = rotFixed[0][0] * location[0] + 
			    rotFixed[0][1] * location[1] +
			    rotFixed[0][2] * location[2];
		newLoc[1] = rotFixed[1][0] * location[0] +
			    rotFixed[1][1] * location[1] +
			    rotFixed[1][2] * location[2];
		newLoc[2] = rotFixed[2][0] * location[0] +
			    rotFixed[2][1] * location[1] +
			    rotFixed[2][2] * location[2];

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

ObjPtr DrawSpace(object)
ObjPtr object;
/*Draws a space and everything it contains*/
{
    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 renderer;			/*Current renderer*/
    ObjPtr var;				/*A random variable*/
    int renderType, filterType;		/*Types of renderers and filters*/
    int viewType;			/*Type of view*/

    renderer = GetObjectVar("DrawSpace", object, RENDERER);
    if (!renderer) return ObjFalse;

    /*Get render type*/
    var = GetIntVar("DrawSpace", renderer, RENDERTYPE);
    if (var)
    {
	renderType = GetInt(var);
    }
    else
    {
	renderType = RT_HARDWARE;
    }
    if (renderType == RT_NONE)
    {
	/*Don't render*/
	return ObjTrue;
    }

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

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

    /*Update the rotation matrix and fly through*/
#if 0
    observers = GetListVar("DrawSpace", object, OBSERVERS);
    if (!observers || !LISTOF(observers)) return ObjFalse;
    observer = LISTOF(observers) -> thing;
#else
    observer = GetObjectVar("DrawSpace", object, OBSERVER);
#endif

    var = GetVar(observer, VIEWTYPE);
    if (var)
    {
	viewType = GetInt(var);
    }
    else
    {
	viewType = VT_PERSPECTIVE;
    }
    switch(viewType)
    {
	case VT_PERSPECTIVE:
	    DrawSpaceContents(object, left, right, bottom, top, VIEW_CENTER);
	    break;
	case VT_ORTHOGRAPHIC:
	    DrawSpaceContents(object, left, right, bottom, top, VIEW_ORTHO);
	    break;
	case VT_CROSSEYED:
	    DrawSpaceContents(object, left, right, bottom, top, VIEW_XLEFT);
	    DrawSpaceContents(object, left, right, bottom, top, VIEW_XRIGHT);
	    break;
	case VT_REDCYAN:
	    if (rgbp)
	    {
		DrawSpaceContents(object, left, right, bottom, top, VIEW_RCLEFT);
		DrawSpaceContents(object, left, right, bottom, top, VIEW_RCRIGHT);
	    }
	    else
	    {
		DrawSpaceContents(object, left, right, bottom, top, VIEW_CENTER);
	    }
	    break;
    }

/*Filter space if need be*/
    if (rgbp && drawingQuality == DQ_FULL && filterType == FT_SHRINK)
    {
	/*Shrink the pixels in the window*/
	int s, d;
	register int y, x;
	int xdd, ydd;
	int xSize, ySize;

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

	lrectread(left, bottom, right, top, (unsigned int *) imageBuffer);

	s = 0;
	d = 0;
	for (y = 0; y <= ydd; ++y)
	{
	    for (x = 0; x <= xdd; ++x)
	    {
		imageBuffer[d] . red =
			(imageBuffer[s] . red +
			 imageBuffer[s + 1] . red +
			 imageBuffer[s + xSize] . red +
			 imageBuffer[s + xSize + 1] . red) / 4;
		imageBuffer[d] . green =
			(imageBuffer[s] . green +
			 imageBuffer[s + 1] . green +
			 imageBuffer[s + xSize] . green +
			 imageBuffer[s + xSize + 1] . green) / 4;
		imageBuffer[d] . blue =
			(imageBuffer[s] . blue +
			 imageBuffer[s + 1] . blue +
			 imageBuffer[s + xSize] . blue +
			 imageBuffer[s + xSize + 1] . blue) / 4;
		imageBuffer[d] . alpha =
			(imageBuffer[s] . alpha +
			 imageBuffer[s + 1] . alpha +
			 imageBuffer[s + xSize] . alpha +
			 imageBuffer[s + xSize + 1] . alpha) / 4;
		s += 2;
		++d;
	    }
	    s += xSize;
	}
	cpack(0);
	clear();
	lrectwrite(left, bottom, left + xdd, bottom + ydd, (unsigned int *) imageBuffer);
    }
    if (rgbp && drawingQuality == DQ_FULL && filterType == FT_4AVERAGE)
    {
	/*Average the pixels in the window*/
	int s;
	register int y, x;
	int xSize, ySize;

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

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

	lrectread(left, bottom, right, top, (unsigned int *) 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(left, bottom, right, top, (unsigned int *) imageBuffer);
    }

    return ObjTrue;
}

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

    return ObjFalse;
}

ObjPtr ShowIconControls(object)
ObjPtr object;
/*Shows the controls for an icon*/
{
    DoTask(DoShowControls);
    return ObjTrue;
}

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

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

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

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

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

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

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

    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 (globalEventFlags & F_OPTIONDOWN)
	{
	    SetVar(icon, ICONGREYED, ObjTrue);
	}
	DropIconInCorral(corral, icon);
    }

    /*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*/
{
#if 0
    ObjPtr observersList;

    observersList = GetVar(space, OBSERVERS);
    if (!observersList)
    {
	observersList = NewList();
	SetVar(space, OBSERVERS, observersList);
    }
    PostfixList(observersList, observer);
#else
    SetVar(space, OBSERVER, observer);
#endif
    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;

    controllerClass = GetVar(controller, CONTROLLERCLASS);
    if (controllerClass)
    {
	/*First see if there is already a controller there*/
	contents = GetListVar("AddControllerToSpace", corral, CONTENTS);
	if (!contents) return;
	list = LISTOF(contents);
	while (list)
	{
	    ObjPtr foundController;
	    foundController = GetVar(list -> thing, REPOBJ);
	    if (InClass(foundController, controllerClass))
	    {
		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."));
    }
    SetMethod(icon, DOUBLECLICK, GetMethod(controller, DOUBLECLICK));
    SetVar(icon, SPACE, space);
    SetVar(icon, REPOBJ, controller);
    method = GetMethod(controller, BINDTOSPACE);
    if (method)
    {
	(*method)(controller, space);
    }
    SetVar(icon, ICONLOC, loc);
    DropIconInCorral(corral, icon);

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

    ResolveController(controller);

    return true;
}

ObjPtr NewSpace(left, right, bottom, top)
int left, right, bottom, top;
/*Makes a new Space with bounds left, right, bottom, top*/
{
    ObjPtr xformMatrix;	/*The rotation matrix of the space, I to start*/
    ObjPtr retVal;

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

    return retVal;
}

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

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

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

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

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

		/*Now see what the format is*/
		switch (*f)
		{
		    case 'h':
			/*Hours, [1,12]*/
			n = t / 3600.0;
			i = n / 12.0;
			n = n - i * 12;
			if (n < 1.00) n += 12.00;
			break;
		    case 'H':
			/*Hours, [0,24)*/
			n = t / 3600.0;
			i = n / 24.0;
			i = n - i * 24;
			n = i;
			break;
		    case 'M':
			/*Unrestricted minutes*/
			n = t / 60.0;
			break;
		    case 'm':
			/*Minutes, 0 to 59*/
			n = t / 60.0;
			i = n / 60.0;
			i = n - i * 60;
			n = i;
			break;
		    case 's':
			/*Seconds, 0 to 59*/
			i = t / 60.0;
			i = t - i * 60;
			n = i;
			break;
		    case 'S':
			/*Unrestricted seconds*/
		    case 't':
		    case 'T':
			/*Just a timestep*/
			n = t;
			break;
		    case 'd':
		    case 'D':
		    case 'i':
		    case 'I':
			/*Integral seconds*/
			i = t;
			n = i;
			break;
		    default:
			strcpy(s, "Bad format: ");
			while (*s) ++s;
			*s++ = *f++;
			*s = 0;
			goto dontsprintf;
		}
		++f;

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

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

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

    format = GetVar(clock, FORMAT);
    if (format)
    {
	PrintClock(s, GetString(format), time);
    }
    else
    {
        sprintf(s, "%#.2f", time);
    }

    /*See if there's a display clock in the space*/
    displayClock = GetVar(space, DISPLAYCLOCK);
    if (displayClock)
    {
	/*Yes, display it*/
	SetTextBox(displayClock, s);
    }

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

    boundsChanged = false;
    runner = LISTOF(contents);
    while (runner)
    {
	ChangeVar(runner -> thing, TIME, timeObj);
	repObj = GetVar(runner -> thing, MAINDATASET);
	if (!repObj) repObj = GetObjectVar("TouchSpaceClock", runner -> thing, REPOBJ);
	if (repObj)
	{
	    data = GetVar(repObj, DATA);
	    if (data)
	    {
		ObjPtr objTime;
		real otb[2];
		MakeVar(data, TIMEBOUNDS);
		objTime = GetVar(data, TIMEBOUNDS);
		if (objTime)
		{
		    ObjPtr lastTimeStep;
		    real lts;

		    lastTimeStep = GetRealVar("TouchSpaceClock", data, LASTTIMESTEP);
		    if (lastTimeStep)
		    {
			lts = GetReal(lastTimeStep);
		    }
		    else
		    {
			lts = 0.0;
		    }
	
		    Array2CArray(otb, objTime);
		    if (clockHasTime)
		    {
			if (boundsChanged)
			{
			    if (otb[0] < tb[0])
			    {
				tb[0] = otb[0];
			    }
			}
			else
			{
			    tb[0] = otb[0];
			    boundsChanged = true;
			}
			if (boundsChanged)
			{
			    if (otb[1] > tb[1] + lts)
			    {
				tb[1] = otb[1] + lts;
			    }
			}
			else
			{
			    tb[1] = otb[1] + lts;
			    boundsChanged = true;
			}
		    }
		    else
		    {
			tb[0] = otb[0];
			tb[1] = otb[1] + lts;
			clockHasTime = true;
			boundsChanged = true;
		    }
		}
	    }
	}
	runner = runner -> next;
    }
    if (boundsChanged)
    {
	timeBounds = NewRealArray(1, 2L);
	CArray2Array(timeBounds, tb);
	SetVar(clock, TIMEBOUNDS, timeBounds);
    }
    return ObjTrue;
}

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

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

	if (dialog)
	{
	    ObjPtr control;

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

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

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

static ObjPtr TouchSpaceObserver(observer, space)
ObjPtr observer, space;
/*Touches a space with an observer.  Returns ObjTrue if it had an effect,
  ObjFalse otherwise.*/
{
#if 0
    ObjPtr observers;
    if ((observers = GetListVar("TouchSpaceObserver", space, OBSERVERS)) &&
	LISTOF(observers) -> thing == observer)
    {
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
#else
    return ObjTrue;
#endif
}

static ObjPtr TouchSpaceRenderer(renderer, space)
ObjPtr renderer, space;
/*Touches a space with an renderer.  Returns ObjTrue if it had an effect,
  ObjFalse otherwise.*/
{
    ObjPtr renderers;
    if (GetVar(space, RENDERER) == renderer)
    {
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}

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

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

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

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

static void DoClockAlert()
{
    WinInfoPtr errWindow;
    errWindow = AlertUser(UIRED, (WinInfoPtr) 0, "A clock cannot be displayed in a space it doesn't control.", 0, 0, "");
    SetVar((ObjPtr) errWindow, HELPSTRING,
	NewString("You have just tried to drag a clock into a space that it does not control.  \
In order for a clock to be displayed in a space, it must control that space.  \
You can make a clock control a space by dragging it into the icon corral of \
the visualization window which contains the space."));
}

static ObjPtr DeleteClockReadout(readout)
ObjPtr readout;
/*Deletes a clock readout from a space*/
{
    ObjPtr space;
    space = GetObjectVar("DeleteClockReadout", readout, SPACE);
    if (!space)
    {
	return ObjFalse;
    }
    SetVar(space, DISPLAYCLOCK, NULLOBJ);
    return ObjTrue;
}

void AddClockToSpacePanel(clock, panel, space, x, y)
ObjPtr clock, panel, space;
int x, y;
/*Adds clock to panel.  If there is already a clock there, just move it and
  update the clock*/
{
    ObjPtr clockThere;

    if (GetVar(space, CLOCK) != clock)
    {
	DoTask(DoClockAlert);
	return;
    }

    clockThere = GetVar(space, DISPLAYCLOCK);
    if (clockThere)
    {
	/*There's already a clock there, just move and update*/
	int w, h;
	int l, r, b, t;
	
	Get2DIntBounds(clockThere, &l, &r, &b, &t);
	w = r - l;
	h = t - b;
	Set2DIntBounds(clockThere, x - w / 2, x + (w + 1) / 2,
			y - (h + 1) / 2, y + h / 2);
	ImInvalid(clockThere);
    }
    else
    {
	clockThere = NewTextBox(x - DSPCLOCKWIDTH / 2, x + DSPCLOCKWIDTH / 2, 
			y - DSPCLOCKHEIGHT / 2, y + DSPCLOCKHEIGHT / 2,
			/*WITH_BG + EDITABLE*/ ADJUSTABLE, "Space Clock", "1:37");
	PrefixList(GetVar(panel, CONTENTS), clockThere);
	SetMethod(clockThere, DELETEICON, DeleteClockReadout);
	SetVar(clockThere, SPACE, space);
	SetVar(clockThere, HELPSTRING, NewString("This text box displays the time \
given by the clock in this space.  The display format is controlled by the Format \
text box in the clock control panel.\n"));
	SetVar(clockThere, PARENT, panel);
	SetTextAlign(clockThere, RIGHTALIGN);
	SetTextFont(clockThere, "Helvetica-Bold");
	SetTextSize(clockThere, 18);
	SetTextColor(clockThere, NewInt(UIGRAY75));
	SetVar(clockThere, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGTOP + FLOATINGBOTTOM));
	MakeMeCurrent(clockThere);
    }
    SetVar(clockThere, REPOBJ, clock);
    SetVar(space, DISPLAYCLOCK, clockThere);

    /*Resolve the clock references*/
    ResolveController(clock);
}

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));
    MakeMeCurrent(paletteDisplay);
    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 (InClass(dropObj, iconClass))
	{
	    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;

	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 (InClass(repObj, clockClass))
	{
	    AddClockToSpacePanel(repObj, panel, space, x, y);
	}
	else if (IsPalette(repObj))
	{
	    AddPaletteToSpacePanel(repObj, panel, space, x, y);
	}
	else
	{
	    AddObjToSpace(repObj, space, GetVar(space, CORRAL), NULLOBJ, NULLOBJ);
	}

	/*Drop remaining icons*/
	runner = restIcons;
	while (runner)
	{
	    repObj = GetVar(runner -> thing, REPOBJ);
	    if (InClass(repObj, clockClass))
	    {
		iconLoc = GetFixedArrayVar("DropInSpacePanel", runner -> thing, ICONLOC, 1, 2L);
		if (!iconLoc) break;
		Array2CArray(loc, iconLoc);
		loc[0] += xDisp;
		loc[1] += yDisp;

		AddClockToSpacePanel(repObj, panel, space, (int) loc[0], (int) loc[1]);
	    }
	    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]);
	    }
	    else
	    {
		AddObjToSpace(repObj, space, GetVar(space, CORRAL), NULLOBJ, NULLOBJ);
	    }
	    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));
    ResolveController(clock);
}

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

    InhibitLogging(true);

    dTime = GetVar(clock, DTIME);
    if (!dTime || !IsReal(dTime))
    {
	return ObjTrue;
    }
    deltaTime = GetReal(dTime);

    wrap = GetPredicate(clock, CYCLECLOCK);

    /*Another wakeup call*/
    DoNotDisturb(clock, MARKTIME);
    WakeMe(clock, MARKTIME, Clock());

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

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

    /*Increment the time by the elapsed time*/
    time += deltaTime * lateness;

    /*Check against time bounds*/
    timeBounds = GetVar(clock, TIMEBOUNDS);
    if (timeBounds && IsArray(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];
	else time = tb[1];
    }
    else if (deltaTime < 0.0 && time < tb[0])
    {
	if (wrap) time = tb[1];
	else time = tb[0];
    }

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

    InhibitLogging(false);

    return ObjTrue;
}

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

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

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

    SetClock(repObj, time);

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

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

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

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

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

static ObjPtr ChangeDeltaTime(object)
ObjPtr object;
/*Changed value for a slider delta time*/
{
    real deltaTime;
    ObjPtr val;
    ObjPtr repObj;
    ObjPtr speedControl;

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

    val = GetVar(object, VALUE);
    if (val)
    {
	deltaTime = GetReal(val);
    }
    else
    {
	deltaTime = 0.0;
    }

    SetVar(repObj, DTIME, NewReal(deltaTime));
    speedControl = GetObjectVar("ChangeDeltaTime", object, SPEEDCONTROL);
    if (speedControl)
    {
	ClockSliderToRadio(speedControl, object);
    }
    DoNotDisturb(repObj, MARKTIME);
    if (deltaTime != 0.0)
    {
	WakeMe(repObj, MARKTIME, Clock());
    }
    return ObjTrue;
}

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

static ObjPtr ChangeClockSpeed(radioGroup)
ObjPtr radioGroup;
/*Changes the speed of a clock according to a newly pressed button*/
{
    ObjPtr slider;
    real loValue, hiValue;
    ObjPtr var;
    int value;
    FuncTyp method;

    var = GetValue(radioGroup);
    if (var) value = GetInt(var); else value = 0;
    
    slider = GetObjectVar("ChangeClockSpeed", radioGroup, REPOBJ);
    if (!slider) return ObjFalse;
    GetSliderRange(slider, &loValue, &hiValue);

    /*Temporarily save my changedvalue method*/
    method = GetMethod(radioGroup, CHANGEDVALUE);
    SetMethod(radioGroup, CHANGEDVALUE, (FuncTyp) 0);

    switch (value)
    {
	case 0:		/*Stop*/
	    SetValue(slider, NewReal(0.0));
	    break;
	case 1:		/*Play*/
	    SetValue(slider, NewReal((hiValue - loValue) * PLAYCLOCK));
	    break;
	case 2:		/*Reverse*/
	    SetValue(slider, NewReal((loValue - hiValue) * PLAYCLOCK));
	    break;
	case 3:		/*Fast Forward*/
	    SetValue(slider, NewReal((hiValue - loValue) * FASTCLOCK));
	    break;
	case 4:		/*Fast Reverse*/
	    SetValue(slider, NewReal((loValue - hiValue) * FASTCLOCK));
	    break;
    }

    SetMethod(radioGroup, CHANGEDVALUE, method);

    return ObjTrue;
}

ObjPtr ChangeClockFormat(textBox)
ObjPtr textBox;
/*Changes a clock's FORMAT according to textBox*/
{
    ObjPtr repObj;
    repObj = GetObjectVar("ChangeClockFormat", textBox, REPOBJ);
    if (!repObj) return ObjFalse;
    SetVar(repObj, FORMAT, GetValue(textBox));
    ResolveController(repObj);
    return ObjTrue;
}

ObjPtr ShowClockControls(clock, ownerWindow, windowName)
ObjPtr clock;
ObjPtr ownerWindow, 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)
    {
	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 slider, button, checkBox, radioGroup, control;
	    ObjPtr timeBounds;
	    ObjPtr time;
	    ObjPtr textBox;
	    real tb[2], tbt[2];
	    real bigStep, littleStep, anchor;
	    real dt, mdt;
	    int left, right, top;

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

	    time = GetVar(clock, TIME);

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

	    SetMethod(control, CHANGEDVALUE, ChangeTime);

	    ReinitController(clock);

	    top -= CLTCHEIGHT + MAJORBORDER;

	    timeBounds = GetVar(clock, TIMEBOUNDS);
	    if (timeBounds && IsArray(timeBounds) && RANK(timeBounds) == 1 &&
	        DIMS(timeBounds)[0] == 2)
	    {
		Array2CArray(tb, timeBounds);
	    }
	    else
	    {
		tb[0] = 0.0;
		tb[1] = 1.0;
	    }

	    tbt[0] = tb[0]; tbt[1] = tb[1];
	    ChooseGoodSliderParams(&(tbt[0]), &(tbt[1]), &bigStep, &littleStep, &anchor);

	    /*Create the format text box*/
	    textBox = NewTextBox(left, left + 70, 
				top - EDITBOXHEIGHT + (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2,
				top - (EDITBOXHEIGHT - TEXTBOXHEIGHT) / 2, 
				PLAIN, "Format:", "Format:");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    
	    /*Create the format editable text box*/
	    format = GetVar(clock, FORMAT);
	    textBox = NewTextBox(left + 70, right - 70, 
				top - EDITBOXHEIGHT,
				top, 
				DEFERCHANGE + EDITABLE + WITH_PIT + ONE_LINE, "Format",
				format ? GetString(format) : "%#.2t");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetVar(textBox, REPOBJ, clock);
	    SetMethod(textBox, CHANGEDVALUE, ChangeClockFormat);

	    top -= EDITBOXHEIGHT + MAJORBORDER;

	    /*Create the time units per second slider*/
	    slider = NewSlider(left, right, 
			    top - MAJORBORDER - SLIDERWIDTH,
			    top - MAJORBORDER,
			    SCALE, "Delta Time per second");
	    PrefixList(contents, slider);
	    SetVar(slider, HELPSTRING,
		NewString("This slider controls the rate at which time flows \
in the spaces controlled by the clock.  The number on the slider specifies how \
many time units in the space will go by for every second of real time.  This rate is \
approximated as well as can be considering the time it takes to draw windows.  \
It compensates correctly when recording frame-by-frame.  This slider is connected \
to the tape recorder style buttons below it."));
	    SetVar(slider, PARENT, panel);
	    dt = (tb[1] - tb[0]) * 0.2;
	    mdt = -dt;
	    ChooseGoodSliderParams(&mdt, &dt, &bigStep, &littleStep, &anchor);
	    SetSliderRange(slider, dt, mdt, 0.0);
	    SetSliderValue(slider, 0.0);
	    SetSliderScale(slider, bigStep, littleStep, 0.0, "%g");
	    SetVar(slider, REPOBJ, clock);
	    SetMethod(slider, CHANGEDVALUE, ChangeDeltaTime);

	    /*Create the text box*/
	    textBox = NewTextBox(left, right, 
				top - MAJORBORDER - SLIDERWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
				top - MAJORBORDER - SLIDERWIDTH - TEXTBOXSEP,
				PLAIN, "Delta time text", "Delta time per second");
	    SetVar(textBox, PARENT, panel);
 	    PrefixList(contents, textBox);
	    SetTextAlign(textBox, CENTERALIGN);

	    top -= 2 * MAJORBORDER + SLIDERWIDTH + TEXTBOXSEP + TEXTBOXHEIGHT;

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

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

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

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

	    top -= MAJORBORDER + CHECKBOXHEIGHT;

	    /*Create the icon buttons*/
	    radioGroup = NewRadioButtonGroup("Speed Control");
	    SetVar(radioGroup, PARENT, panel);
	    SetVar(radioGroup, REPOBJ, slider);
	    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");
	    SetVar(button, REPOBJ, slider);
	    SetVar(button, HELPSTRING, NewString("This button stops the clock."));
	    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");
	    SetVar(button, REPOBJ, slider);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving forward at a moderate pace."));
	    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");
	    SetVar(button, REPOBJ, slider);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving backward at a moderate pace."));
	    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");
	    SetVar(button, REPOBJ, slider);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving forward at a quick pace."));
	    AddRadioButton(radioGroup, button);

	    /*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");
	    SetVar(button, REPOBJ, slider);
	    SetVar(button, HELPSTRING, NewString("This button sets time moving backward at a quick pace."));
	    AddRadioButton(radioGroup, button);

	    /*Link dtime slider to this speed control*/
	    SetVar(slider, SPEEDCONTROL, radioGroup);

	    /*Set the radio group*/
	    ClockSliderToRadio(radioGroup, slider);

	    SetMethod(radioGroup, CHANGEDVALUE, ChangeClockSpeed);
	}
    }

    return (ObjPtr) clockWindow;
}

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

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

    val = GetFixedArrayVar("ChangePerspective", repObj, PERSPECSTUFF, 1, 4L);
    if (!val)
    {
	return ObjFalse;
    }
    Array2CArray(oldPerspec, val);
    val = GetFixedArrayVar("ChangePerspective", object, VALUE, 1, 4L);
    if (!val)
    {
	return ObjFalse;
    }
    Array2CArray(newPerspec, val);
    val = NewRealArray(1, 4L);
    CArray2Array(val, newPerspec);
    SetVar(repObj, PERSPECSTUFF, val);

    /*See if field distance has changed*/
    if (oldPerspec[0] != newPerspec[0])
    {
	/*If so, change LOCATION accordingly*/
	real posn[3];
	ObjPtr posnArray;
	real yaw, pitch;

	val = GetRealVar("ChangePerspective", repObj, YAW);
	yaw = GetReal(val);
	val = GetRealVar("ChangePerspective", repObj, PITCH);
	pitch = GetReal(val);
	posnArray = GetFixedArrayVar("ChangePerspective", repObj, FOCUSPOINT, 1, 3L);
	Array2CArray(posn, posnArray);
	posn[0] -= rsin(yaw) * newPerspec[0];
	posn[1] -= rsin(pitch) * newPerspec[0];
	posn[2] -= rcos(yaw) * newPerspec[0];
	posnArray = NewRealArray(1, 3L);
	CArray2Array(posnArray, posn);
	SetVar(repObj, LOCATION, posnArray);
   }

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

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

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

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

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

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

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

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

    SetVar(controller, VIEWTYPE, NewInt(newValue));
    ResolveController(controller);
}

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);
    ResolveController(controller);
    IdleTimers();

    return ObjTrue;
}

ObjPtr ChangeRollDpitch(control)
ObjPtr control;
/*Changes roll and dpitch based on an XY control*/
{
    ObjPtr controller;
    ObjPtr value;
    real roll, dpitch;

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

    GetXYControlValue(control, &roll, &dpitch);

    SetVar(controller, ROLL, NewReal(roll));
    SetVar(controller, DPITCH, NewReal(-dpitch));
    ResolveController(controller);
    IdleTimers();

    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);
    ResolveController(repObj);
    return ObjTrue;
}

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

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

    perspecStuff[0] = INITEYEDIST;
    var = GetFixedArrayVar("StartSpace", observer, PERSPECSTUFF, 1, 4L);
    if (var)
    {
	Array2CArray(perspecStuff, var);
    }

    /*Change the eye position*/
    eyePosn[0] = 0.0;
    eyePosn[1] = 0.0;
    eyePosn[2] = perspecStuff[0];
    var = NewRealArray(1, 3L);
    CArray2Array(var, eyePosn);
    SetVar(observer, LOCATION, var);
    /*Fix roll, pitch, yaw*/
    SetVar(observer, ROLL, NewReal(0.0));
    SetVar(observer, PITCH, NewReal(0.0));
    SetVar(observer, YAW, NewReal(M_PI));
    ChangeFocus(observer);

    /*Change the transformation matrix*/
    var = GetMatrixVar("ResetPosition", observer, XFORM);
    if (!var)
    {
	return NULLOBJ;
    }

    MATCOPY(justRot, MATRIXOF(var));
    for (i = 0; i < 3; ++i)
    {
	justRot[3][i] = justRot[i][3] = 0.0;
    }
    var = NewMatrix();
    MATCOPY(MATRIXOF(var), justRot);
    SetVar(observer, XFORM, var);

    ResolveController(observer);

    return ObjTrue;
}

ObjPtr ResetRotation(button)
ObjPtr button;
/*Resets an observer's rotation*/
{
    ObjPtr observer, var;
    Matrix rot;
    int i, j;

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

    /*Change the transformation matrix*/
    var = GetMatrixVar("ResetPosition", observer, XFORM);
    if (!var)
    {
	return NULLOBJ;
    }

    MATCOPY(rot, MATRIXOF(var));
    for (i = 0; i < 3; ++i)
    {
	for (j = 0; j < 3; ++j)
	{
	    rot[i][j] = i == j ? 1.0 : 0.0;
	}
    }
    var = NewMatrix();
    MATCOPY(MATRIXOF(var), rot);
    SetVar(observer, XFORM, var);

    ResolveController(observer);

    return ObjTrue;
}

ObjPtr ShowObserverControls(observer, ownerWindow, windowName)
ObjPtr observer;
ObjPtr ownerWindow, 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;
    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");
    }
    observerWindow = GetDialog((WinInfoPtr) observer, whichDialog, tempStr, 
	OBWINWIDTH, OBWINHEIGHT, OBWINWIDTH, OBWINHEIGHT, WINDBUF);

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

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

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, OBWINWIDTH, 0, OBWINHEIGHT);
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) observerWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) observerWindow);

	contents = GetListVar("ShowObserverControls", panel, CONTENTS);
	if (contents)
	{
	    ObjPtr control;
	    ObjPtr textBox;
	    ObjPtr slider, var;
	    ObjPtr radio, button;
	    ObjPtr tempObj;
	    ObjPtr titleBox, checkBox;
	    real roll, dPitch, airspeed;
	    int left, right, bottom, top;
	    int viewType;

	    /*Bottom control group*/
	    left = MAJORBORDER;
	    right = OBWINWIDTH - MAJORBORDER;
	    bottom = MAJORBORDER;
	    top = MAJORBORDER + OBBOTTOMHEIGHT;

	    /*View type chooser*/
	    titleBox = NewTitleBox(left, right, bottom, top, "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 = NewRadioButton(left + MINORBORDER, (left + right) / 2,
				    top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT,
				    top - TITLEBOXTOP - MINORBORDER, "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 = NewRadioButton(left + MINORBORDER, (left + right) / 2,
				    bottom + MINORBORDER,
				    bottom + MINORBORDER + CHECKBOXHEIGHT, "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 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 = NewRadioButton((left + right) / 2 - MINORBORDER, right - MINORBORDER,
				    top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT,
				    top - TITLEBOXTOP - MINORBORDER, "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 = NewRadioButton((left + right) / 2 - MINORBORDER, right - MINORBORDER,
				    bottom + MINORBORDER,
				    bottom + MINORBORDER + CHECKBOXHEIGHT, "Anaglyphic Stereo");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING, NewString("When this button is down, two \
perspective images are shown, one red and one cyan, for a stereo pair.  To view the stereo \
pair, you will need to use a pair of red/cyan 3-D glasses.  Because one eye \
can see only one set of colors, subtle color variations will be washed out.  \
Also, colors which are heavily toward red or heavily toward blue will be confusing."));

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

	    /*Middle control group*/
	    left = MAJORBORDER;
	    right = OBWINWIDTH - MAJORBORDER;
	    bottom = MAJORBORDER + MINORBORDER + OBBOTTOMHEIGHT;
	    top = bottom + OBMIDHEIGHT;

	    /*Add in the perspective control*/
    	    control = NewPerspecControl(
			right - PCWIDTH,
			right,
			bottom + TEXTBOXHEIGHT + TEXTBOXSEP,
			top,
			"Perspective Control");
	    PrefixList(contents, control);
	    SetVar(control, PARENT, panel);
	    SetVar(control, REPOBJ, observer);
	    if (viewType == VT_ORTHOGRAPHIC)
	    {
		MakePerspecOrtho(control, true);
	    }
	    SetValue(control, GetVar(observer, PERSPECSTUFF));
	    SetMethod(control, CHANGEDVALUE, ChangePerspective);

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

	    /*Add in the perspective text box*/
	    textBox = NewTextBox(
			right - PCWIDTH - MINORBORDER,
			right + MINORBORDER,
			bottom, bottom + TEXTBOXHEIGHT,
			PLAIN,
			"Perspective Text",
			"Perspective");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Get observer's roll and dPitch*/
	    tempObj = GetVar(observer, ROLL);
	    if (tempObj && IsReal(tempObj))
	    {
		roll = GetReal(tempObj);
	    }
	    else
	    {
		roll = 0.0;
	    }

	    tempObj = GetVar(observer, DPITCH);
	    if (tempObj && IsReal(tempObj))
	    {
		dPitch = GetReal(tempObj);
	    }
	    else
	    {
		dPitch = 0.0;
	    }

	    /*Put in the flight control*/
	    control = NewXYControl(
			left + SCALESLOP + SLIDERWIDTH + MINORBORDER,
			right - PCWIDTH - MINORBORDER,
			bottom + TEXTBOXHEIGHT + TEXTBOXSEP,
			top,
			"Flight Control");
	    PrefixList(contents, control);
	    SetVar(control, PARENT, panel);
	    SetVar(control, REPOBJ, observer);
	    SetVar(control, ALWAYSCHANGE, ObjTrue);
	    SetXYControlLimits(control, -MAXROLL, MAXROLL, -MAXDPITCH, MAXDPITCH);
	    SetXYControlValue(control, roll, dPitch);
	    SetMethod(control, CHANGEDVALUE, ChangeRollDpitch);
	    SetVar(control, HELPSTRING, NewString("This control is like the control stick of an airplane.  \
Move the indicator down to pull back on the stick and climb.  Move the indicator down to push \
forward on the stick and dive.  Move the indicator from side to side to bank and turn.  \
This will only work properly when you have set your airspeed.  Here's a hint: It's easier to fly \
if the perspective control is set to a wide angle view."));

	    /*Add in the flight control text box*/
	    textBox = NewTextBox(
			left + SCALESLOP + SLIDERWIDTH,
			right - PCWIDTH - MINORBORDER,
			bottom, bottom + TEXTBOXHEIGHT,
			PLAIN,
			"Control Text",
			"Flight Control");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Get the observer's airspeed*/
	    tempObj = GetVar(observer, AIRSPEED);
	    if (tempObj && IsReal(tempObj))
	    {
		airspeed = GetReal(tempObj);
	    }
	    else
	    {
		airspeed = 0.0;
	    }

	    /*Add in the airspeed slider*/
	    slider = NewSlider(
			left + SCALESLOP, left + SCALESLOP + SLIDERWIDTH, 
			bottom + TEXTBOXHEIGHT + TEXTBOXSEP,
			top,
			SCALE,
			"Airspeed");
	    
	    if (!slider)
	    {
		return ObjFalse;
	    }
	    PrefixList(contents, slider);
	    SetVar(slider, HELPSTRING,
		NewString("This slider controls the airspeed of the flight \
simulator.  Use the square flight control to navigate while flying."));
	    SetVar(slider, PARENT, panel);
	    SetVar(slider, REPOBJ, observer);
	    SetSliderRange(slider, 0.2, -0.2, 0.0);
	    SetSliderScale(slider, 0.1, 0.02, 0.0, "%g");
	    SetSliderValue(slider, airspeed);
	    SetMethod(slider, CHANGEDVALUE, ChangeAirspeed);

	    /*Add in the airspeed text box*/
	    textBox = NewTextBox(
			left - MINORBORDER, left + SCALESLOP + SLIDERWIDTH + MINORBORDER, 
			bottom, bottom + TEXTBOXHEIGHT,
			PLAIN,
			"Airspeed Text",
			"Airspeed");
	    PrefixList(contents, textBox);
	    SetVar(textBox, PARENT, panel);
	    SetTextAlign(textBox, CENTERALIGN);

	    /*Add the top controls*/
	    bottom = top + MINORBORDER;
	    top = bottom +  TITLEBOXTOP + 2 * MINORBORDER + CHECKBOXSPACING + 2 * CHECKBOXHEIGHT;
	    left = MAJORBORDER;
	    right = (OBWINWIDTH - MINORBORDER) / 2;
	    titleBox = NewTitleBox(left, right, bottom, top, "Flight Simulator");
	    PrefixList(contents, titleBox);
	    SetVar(titleBox, PARENT, panel);

	    radio = NewRadioButtonGroup("Flight On/Off");
	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);
	    SetVar(radio, HELPSTRING, NewString("This radio button group controls \
whether the flight simulator is on.  If the Anchored button is selected, the \
flight simulator is off.  If the Flying button is selected, the flight simulator \
is on, and the Airspeed slider and the Flight Control are used to fly around.\n"));

	    button = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
		top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT,
		top - TITLEBOXTOP - MINORBORDER, "Anchored");
	    AddRadioButton(radio, button);

	    button = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
		top - TITLEBOXTOP - MINORBORDER - 2 * CHECKBOXHEIGHT - CHECKBOXSPACING,
		top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT - CHECKBOXSPACING, 
		"Flying");
	    AddRadioButton(radio, button);

	    SetValue(radio, NewInt(GetPredicate(observer, FLYING) ? 1 : 0));
	    SetMethod(radio, CHANGEDVALUE, ChangeFlying);
	    SetVar(radio, REPOBJ, observer);

	    /*Add in the reset buttons*/
	    left = right + MINORBORDER;
	    right = OBWINWIDTH - MAJORBORDER;
	    button = NewButton(left, right,
			bottom + BUTTONHEIGHT + MINORBORDER,
			bottom + 2 * BUTTONHEIGHT + MINORBORDER,
			"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 is useful if you get lost.\n"));

	    button = NewButton(left, right,
			bottom, bottom + BUTTONHEIGHT,
			"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 is useful if you get lost.\n"));
	}
    }

    return (ObjPtr) observerWindow;
}

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

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

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

    return ObjTrue;
}

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

    space = FindSpace(visWindow);
    if (space)
    {
	if (objectForRGB == GetVar(space, RENDERER))
	{
	    if (0 == (visWindow -> flags & WINRGB))
	    {
		DeferMessage((ObjPtr) visWindow, SETRGBMESSAGE);
	    }
	}
    }
}

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

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

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

    return ObjTrue;
}

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

    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");
    }
    rendererWindow = GetDialog((WinInfoPtr) renderer, whichDialog, tempStr, 
	RWINWIDTH, RWINHEIGHT, RWINWIDTH, RWINHEIGHT, WINDBUF);

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

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

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, RWINWIDTH, 0, RWINHEIGHT);
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) rendererWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) rendererWindow);

	contents = GetListVar("ShowRendererControls", panel, CONTENTS);
	if (contents)
	{
	    ObjPtr titleBox;
	    ObjPtr button;
	    ObjPtr radio;
	    ObjPtr var;
	    int left, right, top;
	    left = MAJORBORDER;
	    right = RWINWIDTH - MAJORBORDER;
	    top = RWINHEIGHT - MAJORBORDER;

	    /*Make the title box around render type*/
	    titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - 2 * MINORBORDER - 2 * CHECKBOXHEIGHT - CHECKBOXSPACING, top, 
			"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 = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
			top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT,
			top - TITLEBOXTOP - MINORBORDER,
			"None");
	    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 hardware renderer button*/
	    button = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
			top - TITLEBOXTOP - MINORBORDER - 2 * CHECKBOXHEIGHT - CHECKBOXSPACING,
			top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT - CHECKBOXSPACING,
			"Hardware");
	    AddRadioButton(radio, button);
	    SetVar(button, HELPSTRING,
		NewString("This button sets the space to use the hardware renderer.  \
At present, this is the only renderer available."));

	    PrefixList(contents, radio);
	    SetVar(radio, PARENT, panel);
	    SetVar(radio, REPOBJ, renderer);
	    var = GetVar(renderer, RENDERTYPE);
	    if (var)
	    {
		SetValue(radio, var);
	    }
	    else
	    {
		SetValue(radio, NewInt(RT_HARDWARE));
	    }
	    SetMethod(radio, CHANGEDVALUE, ChangeRenderType);

	    top -= TITLEBOXTOP + 2 * MINORBORDER + 2 * CHECKBOXHEIGHT + CHECKBOXSPACING + MAJORBORDER;

	    /*Make the title box around filter type*/
	    titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - 2 * MINORBORDER - 3 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING, top, 
			"Image Filter");
	    PrefixList(contents, titleBox);
	    SetVar(titleBox, PARENT, panel);

	    /*Make the no filter button*/
	    radio = NewRadioButtonGroup("Filter Type");

	    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 = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
			top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT,
			top - TITLEBOXTOP - MINORBORDER,
			"None");
	    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 = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
			top - TITLEBOXTOP - MINORBORDER - 2 * CHECKBOXHEIGHT - CHECKBOXSPACING,
			top - TITLEBOXTOP - MINORBORDER - CHECKBOXHEIGHT - CHECKBOXSPACING,
			"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);

	    /*Make the shrink filter button*/
	    button = NewRadioButton(left + MINORBORDER, right - MINORBORDER,
			top - TITLEBOXTOP - MINORBORDER - 3 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING,
			top - TITLEBOXTOP - MINORBORDER - 2 * CHECKBOXHEIGHT - 2 * CHECKBOXSPACING,
			"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);

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

    return (ObjPtr) rendererWindow;
}

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

    MakeVar(clock, DATASETS);
    curFormat = 0;

    datasets = GetVar(clock, DATASETS);
    if (datasets)
    {
	elements = ELEMENTS(datasets);
	for (k = 0; k < DIMS(datasets)[0]; ++k)
	{
	    format = GetVar(elements[k], 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 *elements;
    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);

	elements = ELEMENTS(array);
	for (k = 0; k < DIMS(array)[0]; ++k)
	{
	    ObjPtr name, timeSteps;
	    MakeVar(elements[k], NAME);
	    MakeVar(elements[k], TIMESTEPS);
	    name = GetVar(elements[k], NAME);
	    timeSteps = GetVar(elements[k], TIMESTEPS);
	    if (timeSteps)
	    {
		if (hasMinValue)
		{
		    minValue = MIN(minValue, *((real *)ELEMENTS(timeSteps)));
		}
		else
		{
		    minValue = *((real *)ELEMENTS(timeSteps));
		    hasMinValue = true;
		}
	    }
	}
	if (hasMinValue)
	{
	    if (!GetVar(clock, TIME))
	    {
		SetVar(clock, TIME, NewReal(minValue));
		ResolveController(clock);
	    }
	}
	else
	{
	    SetVar(clock, TIME, NULLOBJ);
	}
    }
    else
    {
	array = NULLOBJ;
	SetVar(clock, TIME, NULLOBJ);
    }

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

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

	if (dialog)
	{
	    ObjPtr control;

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

    return ObjTrue;
}

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

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

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


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

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

ObjPtr NewObserver()
/*Returns a new observer*/
{
    ObjPtr retVal;
    ObjPtr name;
    real perspecStuff[4];
    ObjPtr psArray, anArray;
    real posn[3];

    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());
    SetVar(retVal, XFORM, NewMatrix());
    perspecStuff[0] = INITEYEDIST;
    perspecStuff[1] = INITAOV;
    perspecStuff[2] = INITNEARCLIP;
    perspecStuff[3] = INITFARCLIP;
    psArray = NewRealArray(1, 4);
    CArray2Array(psArray, perspecStuff);
    SetVar(retVal, PERSPECSTUFF, psArray);
    posn[0] = 0.0;
    posn[1] = 0.0;
    posn[2] = INITEYEDIST;
    anArray = NewRealArray(1, 3L);
    CArray2Array(anArray, posn);
    SetVar(retVal, LOCATION, anArray);
    posn[2] = 0.0;
    anArray = NewRealArray(1, 3L);
    CArray2Array(anArray, posn);
    SetVar(retVal, FOCUSPOINT, anArray);
    SetVar(retVal, FOCUSDIST, NewReal((real) INITEYEDIST));
    SetVar(retVal, ROLL, NewReal((real) 0.0));
    SetVar(retVal, YAW, NewReal((real) M_PI));
    SetVar(retVal, PITCH, NewReal((real) 0.0));

    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());
    SetVar(retVal, RENDERTYPE, NewInt(RT_HARDWARE));
    SetVar(retVal, FILTERTYPE, NewInt(RT_NONE));

    return retVal;
}

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

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

    om = GetVar(observer, XFORM);
    if (om)
    {
	m = NewMatrix();
	MATCOPY(MATRIXOF(m), MATRIXOF(om));
	SetVar(retVal, XFORM, m);
    }

    CopyVar(retVal, observer, ROTAXIS);
    CopyVar(retVal, observer, ROTSPEED);	
    CopyVar(retVal, observer, PERSPECSTUFF);
    CopyVar(retVal, observer, LOCATION);
    CopyVar(retVal, observer, FOCUSPOINT);
    CopyVar(retVal, observer, FOCUSDIST);
    CopyVar(retVal, observer, ROLL);
    CopyVar(retVal, observer, YAW);
    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

void InitSpaces()
/*Initializes the spaces*/
{
    ObjPtr icon;
 
    /*Create a class of spaces*/
    spaceClass = NewObject(NULLOBJ, 0);
    AddToReferenceList(spaceClass);
    SetMethod(spaceClass, DRAW, DrawSpace);
    SetVar(spaceClass, NAME, NewString("Space"));
    SetMethod(spaceClass, PRESS, PressSpace);
    SetMethod(spaceClass, KEYDOWN, KeyDownSpace);
    SetVar(spaceClass, XSTRETCH, NewInt(1));
    SetVar(spaceClass, YSTRETCH, NewInt(1));
    SetVar(spaceClass, TYPESTRING, NewString("space"));
    SetVar(spaceClass, HELPSTRING,
	NewString("A space provides a 3-dimensional world for visualization \
objects.  Objects are automatically scaled and translated to appear near the \
center by default.  Any number of visualization objects can exist within a space. \
Spaces are controlled by controllers, such as observers and lights.\n\
-\n\
Click and drag within the space using the left \
mouse button to move all the objects within the space.  Hold down the Shift \
key while dragging to contrain motion to an orthogonal axis of the space.  \
If Space Motion Guides is selected in the Preferences window, you will see the \
objects as embedded within a gray lattice showing the orthogonal axes.\n\
-\n\
Click and drag within the space ucing the center mouse button to rotate \
the entire space around the focus point of the observer at the center of the \
space.  The space can be rotated around any axis.  Hold down the Shift key \
to constrain to rotation around an orthogonal axis of the space.  Double-click \
to snap the space to the nearest straight-on orientation.  \
If Space Rotation Guides is selected in the Preferences window, you will see the \
virtual trackball that you are rotation as a wire frame sphere.  If Rotation Inertia \
is selected in the Preferences window, the space will continue to rotate if you \
give it a spin and let go while moving the mouse.\n\
-\n\
Both motion and rotation will affect other spaces controlled by the same observer."));
    lmdef(DEFMATERIAL, 1, 0, NULL);

    /*Initialize a space panel*/
    spacePanelClass = NewObject(panelClass, 0);
    AddToReferenceList(spacePanelClass);
    SetMethod(spacePanelClass, DROPOBJECTS, DropInSpacePanel);

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

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

    /*Create class of observers*/
    observerClass = NewObject(controllerClass, 0);
    AddToReferenceList(observerClass);
    SetMethod(observerClass, BINDTOSPACE, BindObserverToSpace);
    SetMethod(observerClass, TOUCHSPACE, TouchSpaceObserver);
    SetMethod(observerClass, NEWCTLWINDOW, ShowObserverControls);
    SetMethod(observerClass, DOUBLECLICK, ShowIconControls);
    SetVar(observerClass, VIEWTYPE, NewInt(VT_PERSPECTIVE));
    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, CONTROLLERCLASS, observerClass);
    SetVar(observerClass, NAME, NewString("Observer"));
    SetMethod(observerClass, CLONE, CloneObserver);

    /*Create class of renderers*/
    rendererClass = NewObject(controllerClass, 0);
    AddToReferenceList(rendererClass);
    SetMethod(rendererClass, BINDTOSPACE, BindRendererToSpace);
    SetMethod(rendererClass, TOUCHSPACE, TouchSpaceRenderer);
    SetMethod(rendererClass, NEWCTLWINDOW, ShowRendererControls);
    SetMethod(rendererClass, DOUBLECLICK, ShowIconControls);
    SetVar(rendererClass, CONTROLLERCLASS, rendererClass);
    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 2705 times since Sat Apr 17 21:54:18 1999 GMT