/* ScianPerspec.c:	John R. Murray, 3-30-90
		creates and deals with perspective Controls
*/
#include "Scian.h"
#include "ScianTypes.h"
#include "ScianColors.h"
#include "ScianIDs.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianErrors.h"
#include "ScianEvents.h"
#include "ScianScripts.h"
#include "ScianButtons.h"
#include "ScianPerspec.h"
#include "ScianArrays.h"
#include "ScianControls.h"
#include "ScianIcons.h"
#include "ScianDraw.h"
#include "ScianObjFunctions.h"
#include "ScianSnap.h"

/* globals */
ObjPtr	perspecControlClass;			/* class of perspecControl */

/* temporary defs to get something going */

/* none */

/* local constants */
#define MY_PI		3.14159
#define BUTTONHEIGHT	4	/* button "height"=width (pixels) of border) */

/* internal drawing things and fudges */
#define PERSPECBORDER	8.0
#define RECWIDTH	15.0
#define RECDEPTH	7.0
#define RECSIZE		(RECDEPTH+RECWIDTH)
#define TOPFUDGE	RECSIZE
#define SIDEFUDGE	RECSIZE
#define BOTTOMFUDGE	RECSIZE + 20.0
#define CLIPLINELENGTH	50.0
#define SLOP		30.0
#define ANGLESLOP	6.0
#define ORTHOSLOP	((MAXORTHOFOV - MINORTHOFOV) / 20.0)
#define PERSPECSCALE .1
#define MINCLIPDELTA	0.01

ObjPtr	DrawPerspecControl(theControl)
ObjPtr	theControl;
{
#ifdef GRAPHICS
    int	left, right, bottom, top;
    ObjPtr	theLabel;
    char	*label;
    ObjPtr	valuesArray, hilitesArray, locArray;
    real	values[4], hilites[4], loc[2];
    real	eyeDist, fov, nearClip, farClip;
    Coord	v[4][2];
    real	xPos, yPos;
    int		iX, iY;
    real	xRel, yRel;
    real	fudgeScale;
    int		color;

    /* get bounds, and draw control background */
    if (!Get2DIntBounds(theControl, &left, &right, &bottom, &top))
    {
        return NULLOBJ;
    }

    if (IsDrawingRestricted(left, right, bottom, top))
    {
	return ObjFalse;
    }

    valuesArray = GetFixedArrayVar("DrawPerspecControl", theControl,
				    VALUE, 1, 4L);
    hilitesArray = GetFixedArrayVar("DrawPerspecControl", theControl,
				    HIGHLIGHTED, 1, 4L);
    locArray = GetFixedArrayVar("DrawPerspecControl", theControl,
				    ICONLOC, 1, 2L);
    if (!valuesArray || !hilitesArray || !locArray)
    {
	return NULLOBJ;
    }

    Array2CArray(values, valuesArray);
    eyeDist = values[0];
    fov = values[1];
    nearClip = values[2];
    farClip = values[3];
    Array2CArray(hilites, hilitesArray);
    Array2CArray(loc, locArray);

    DrawRaisedRect(left, right, bottom, top, UIBACKGROUND);

    /* draw everything else */
    yPos = (int) (bottom + BOTTOMFUDGE
           + eyeDist * ((top - TOPFUDGE) - (bottom + BOTTOMFUDGE))
             / (MAXEYEDIST - MINEYEDIST));
    xPos = (int) (left + (right - left) / 2);
    iY = yPos;
    iX = xPos;

    if ((int) hilites[0])
    {
	SetUIColor(UIGRAY25);
    }
    else
    {
	SetUIColor(UITEXT);
    }

    /* Draw icon-like central figure */
#if 0
    move2(xPos,yPos);  /* center of figure */
    rmv2(4.0, 4.0);		/* +4, +4 */
    rdr2(-RECWIDTH, 0.0);	/* +4-RW, +4 */
    rdr2(0.0, -RECWIDTH);	/* +4-RW, +4-RW */
    rdr2(RECWIDTH, 0.0);	/* +4, +4-RW */
    rdr2(0.0, RECWIDTH);	/* +4, +4 */
    rdr2(RECDEPTH, RECDEPTH);	/* +4+RD, +4+RD */
    rdr2(-RECWIDTH, 0.0);	/* +4+RD-RW, +4+RD */
    rdr2(0.0, -RECWIDTH);	/* +4+RD-RW, +4+RD-RW */
    rdr2(RECWIDTH, 0.0);	/* +4+RD, +4+RD-RW */
    rdr2(0.0, RECWIDTH);	/* +4+RD, +4+RD */
    rmv2(-RECWIDTH, 0.0);	/* mmm +4+RD-RW, +4+RD */
    rdr2(-RECDEPTH, -RECDEPTH);	/* +4-RW, +4 */
    rmv2(0.0, -RECWIDTH);	/* mmm +4-RW, +4-RW */
    rdr2(RECDEPTH, RECDEPTH);	/* +4+RD-RW, +4+RD-RW */
    rmv2(RECWIDTH, 0.0);	/* mmm +4+RD, +4+RD-RW */
    rdr2(-RECDEPTH, -RECDEPTH);	/* +4, +4-RW */
#else
    iX += 4;
    iY += 4;

    DrawLine(iX, iY, iX, iY - RECWIDTH);
    DrawLine(iX - RECWIDTH, iY, iX - RECWIDTH, iY - RECWIDTH);
    DrawLine(iX, iY, iX - RECWIDTH, iY);
    DrawLine(iX, iY - RECWIDTH, iX - RECWIDTH, iY - RECWIDTH);

    DrawLine(iX + RECDEPTH, iY + RECDEPTH, iX + RECDEPTH, iY + RECDEPTH - RECWIDTH);
    DrawLine(iX + RECDEPTH - RECWIDTH, iY + RECDEPTH, iX + RECDEPTH - RECWIDTH, iY + RECDEPTH - RECWIDTH);
    DrawLine(iX + RECDEPTH, iY + RECDEPTH, iX + RECDEPTH - RECWIDTH, iY + RECDEPTH);
    DrawLine(iX + RECDEPTH, iY + RECDEPTH - RECWIDTH, iX + RECDEPTH - RECWIDTH, iY + RECDEPTH - RECWIDTH);

    DrawLine(iX, iY, iX + RECDEPTH, iY + RECDEPTH);
    DrawLine(iX - RECWIDTH, iY, iX + RECDEPTH - RECWIDTH, iY + RECDEPTH);
    DrawLine(iX - RECWIDTH, iY - RECWIDTH, iX + RECDEPTH - RECWIDTH, iY + RECDEPTH - RECWIDTH);
    DrawLine(iX, iY - RECWIDTH, iX + RECDEPTH, iY + RECDEPTH - RECWIDTH);
#endif

    /* Draw near-clip and far-clip lines */
    yPos = (int) (bottom + BOTTOMFUDGE
           + nearClip * ((top - TOPFUDGE) - (bottom + BOTTOMFUDGE))
                         / (MAXEYEDIST - MINEYEDIST));
    xPos = (int) (left + (right - left) / 2);
    iX = xPos;
    iY = yPos;

    if ((int) hilites[2])
    {
	SetUIColor(UIGRAY12);
    }
    else
    {
	SetUIColor(UITEXT);
    }
#if 0
    move2(xPos, yPos);
    rmv2(-CLIPLINELENGTH/2, 0.0);
    rdr2(CLIPLINELENGTH, 0.0);
    if ((int) hilites[2])
    {
	rmv2(0,1);
	rdr2(-CLIPLINELENGTH, 0);
	rmv2(0,-2);
	rdr2(CLIPLINELENGTH, 0);
    }
#else
    DrawLine(iX - CLIPLINELENGTH/2, iY, iX + CLIPLINELENGTH/2, iY);
    if ((int) hilites[2])
    {
	DrawLine(iX - CLIPLINELENGTH/2, iY+1, iX + CLIPLINELENGTH/2, iY+1);
	DrawLine(iX - CLIPLINELENGTH/2, iY-1, iX + CLIPLINELENGTH/2, iY-1);
    }
#endif
    yPos = (int) (bottom + BOTTOMFUDGE
           + farClip * ((top - TOPFUDGE) - (bottom + BOTTOMFUDGE))
                        / (MAXEYEDIST - MINEYEDIST));
    xPos = (int) (left + (right - left) / 2);
    iX = xPos;
    iY = yPos;

    if ((int) hilites[3])
    {
	SetUIColor(UIGRAY12);
    }
    else
    {
	SetUIColor(UITEXT);
    }
#if 0
    move2(xPos, yPos);
    rmv2(-CLIPLINELENGTH/2, 0.0);
    rdr2(CLIPLINELENGTH, 0.0);
    if ((int) hilites[3])
    {
	rmv2(0,1);
	rdr2(-CLIPLINELENGTH, 0);
	rmv2(0,-2);
	rdr2(CLIPLINELENGTH, 0);
    }
#else
    DrawLine(iX - CLIPLINELENGTH/2, iY, iX + CLIPLINELENGTH/2, iY);
    if ((int) hilites[3])
    {
	DrawLine(iX - CLIPLINELENGTH/2, iY+1, iX + CLIPLINELENGTH/2, iY+1);
	DrawLine(iX - CLIPLINELENGTH/2, iY-1, iX + CLIPLINELENGTH/2, iY-1);
    }
#endif
 
    /* Draw field-of-view lines */
    if ((int) hilites[1])
    {
	SetUIColor(UIGRAY12);
    }
    else
    {
	SetUIColor(UITEXT);
    }

/* (((b[1]-SF)-(b[0]+SF))/2.0)) * fov / MAXO = mX-(b[0]+b[1])/2.0 */

    if (GetPredicate(theControl, ORTHO))
    {
	real xOff;
	/*draw width of ortho view lines */
	xOff = fov / MAXORTHOFOV * ((right - SIDEFUDGE) - (left + SIDEFUDGE)) / 2.0;
#if 0
	move2((left + right) / 2 + xOff, bottom + BOTTOMFUDGE);
	draw2((left + right) / 2 + xOff, top - TOPFUDGE);
#else
	DrawLine((left + right) / 2 + xOff, bottom + BOTTOMFUDGE,
		 (left + right) / 2 + xOff, top - TOPFUDGE);
#endif
	if ((int) hilites[1])
	{
#if 0
	    move2((left + right) / 2 + xOff + 1, bottom + BOTTOMFUDGE);
	    draw2((left + right) / 2 + xOff + 1, top - TOPFUDGE);
#else
	    DrawLine((left + right) / 2 + xOff + 1, bottom + BOTTOMFUDGE,
	    	     (left + right) / 2 + xOff + 1, top - TOPFUDGE);
#endif
	}
#if 0
        move2((left + right) / 2 - xOff, bottom + BOTTOMFUDGE);
        draw2((left + right) / 2 - xOff, top - TOPFUDGE);
#else
        DrawLine((left + right) / 2 - xOff, bottom + BOTTOMFUDGE,
        	 (left + right) / 2 - xOff, top - TOPFUDGE);
#endif
        if ((int) hilites[1])
        {
#if 0
            move2((left + right) / 2 - xOff - 1, bottom + BOTTOMFUDGE);
            draw2((left + right) / 2 - xOff - 1, top - TOPFUDGE);
#else
            DrawLine((left + right) / 2 - xOff - 1, bottom + BOTTOMFUDGE,
            	     (left + right) / 2 - xOff - 1, top - TOPFUDGE);
#endif
        }
    }
    else
    {
	/* draw angle of perspective view lines */
	xPos = (int) (left + (right - left) / 2);
	yPos = (int) (bottom + BOTTOMFUDGE);
#if 0
	move2(xPos, yPos);
#endif
	xRel = sin(MY_PI * (fov/2.0)/180.0);
	yRel = cos(MY_PI * (fov/2.0)/180.0);
	if (xRel / yRel >= ((right - PERSPECBORDER) - xPos)
			     / ((top - PERSPECBORDER) - yPos))
	{
	    fudgeScale = ((right - PERSPECBORDER) - xPos) / xRel;
	    xRel = xRel * fudgeScale; /* should end up being xPos - left */
	    yRel = yRel * fudgeScale;
	}
	else
	{
	    fudgeScale = ((top - PERSPECBORDER) - yPos) / yRel;
	    yRel = yRel * fudgeScale;
	    xRel = xRel * fudgeScale;
	}
#if 0
	rdr2(xRel, yRel);
	if ((int) hilites[1])
	{
	    rmv2(1,0);
	    rdr2(-xRel, -yRel);
	}
	move2(xPos, yPos);
	rdr2(-xRel, yRel);
	if ((int) hilites[1])
	{
	    rmv2(-1,0);
	    rdr2(xRel, -yRel);
	}
#else
	if ((int) hilites[1])
	{
	    SetLineWidth(2);
	}

	DrawLine(xPos, yPos, xPos + xRel, yPos + yRel);
	DrawLine(xPos, yPos, xPos - xRel, yPos + yRel);

	if ((int) hilites[1])
	{
	    SetLineWidth(1);
	}
#endif
    }

    if (GetVar(theControl, COLOR))
    {
	color = GetInt(GetIntVar("DrawIconButtonObj", theControl, COLOR));
    }
    else
    {
	color = UIBACKGROUND;
    }

    if (GetVar(theControl, LABEL))
    {
	label = GetString(GetStringVar("DrawIconButtonObj", theControl, LABEL));
    }
    else
    {
	label = (char *) NIL;
    }

    /* Draw the Eye */
    DrawIcon((left + right) / 2 + loc[0], bottom + BOTTOMFUDGE/2.0 + loc[1], 
	GetInt(GetIntVar("DrawIconButtonObj", theControl, WHICHICON)),
	label, (char *) NIL, color,
	DI_DRAWFORE | DI_DRAWBACK | DI_SMALLTEXT);
    return NULLOBJ;

#endif /* GRAPHICS */
}

void Eyeball(left, right, bottom, top, xPos, yPos)
int left, right, bottom, top;
real xPos, yPos;
{

#ifdef GRAPHICS
#if 0
    /* Draw THE EYEBALL!!! */
    xPos = left + (right - left) / 2;
    yPos = bottom + BOTTOMFUDGE/2.0;
    SetUIColor(UITEXT);
    move2(xPos, yPos);
    rdr2(1,0);
    rdr2(0,1);
    rdr2(-1, 0);
    rdr2(0,-1);
    rmv2(-1,-1);
    rdr2(1,-1);
    rdr2(1, 0);
    rdr2(2, 2);
    rdr2(0, 1);
    rdr2(-2,2);
    rdr2(-1,0);
    rdr2(-2,-2);
    rdr2(0,-1);
    rmv2(0,4);
    rdr2(-2, -2);
    rdr2(0, -3);
    rdr2(3,-3);
    rdr2(3,0);
    rdr2(3,3);
    rdr2(0,3);
    rdr2(-2,2);
    rdr2(0,4);
    rdr2(-5,0);
    rdr2(0,-4);
    rmv2(-3,0);
    rdr2(11,0);
    SetUIColor(UIRED);
    move2(xPos - 1, yPos + 3);
    rdr2(-2,-2);
    rdr2(0,-1);
    rdr2(3,-3);
    rdr2(1,0);
    rdr2(3,3);
    rdr2(0,1);
    rdr2(-2,2);
    rdr2(1,0);
    rdr2(0,-3);
    rdr2(-2,-2);
    rdr2(-3,0);
    rdr2(-2,2);
    rdr2(0,3);
    rdr2(1,1);
#endif
#endif /* GRAPHICS */
}

ObjPtr	PressPerspecControl(theControl, mouseX, mouseY, flags)
ObjPtr	theControl;
int     mouseX, mouseY;
int             flags;
{
#ifdef INTERACTIVE
    int		mX, mY, mXold, mYold;	/* mouse X and Y returned by Mouse()*/
    int		deltaX, deltaY;		/* change from initial X and Y */
    int		bounds[4];              /* meat of boundsArray */
    ObjPtr	valuesArray;		/* array of data for complex control */
    real	values[4];		/* meat of valuesArray */
    real	oldValues[4];		/* old meat of valuesArray */
    real	saveValues[4];		/* old meat of valuesArray */
    int         isChanged;              /* return value of changer */
    ObjPtr	hilitesArray;		/* ptr to hilites values for parts */
    real	hilites[4];		/* meat of hilites values array */
    int		i;			/* loop counter */
    int		which;			/* which control part we're moving */
    real	tempvalue;		/* temp. holder for value swapping */
    real	nearYPos, farYPos;	/* calculation temporaries */
    Bool	ortho;			/* value of GetPredicate(obj, ORTHO) */
    Bool	hiResMode = false;	/* true iff user wants hi mouse res. */
    real	clipOff1, clipOff2;	/* offsets of clip planes from object*/

    if (!Get2DIntBounds(theControl,&bounds[0],&bounds[1],&bounds[2],&bounds[3]))
    {
        return ObjFalse;
    }
    valuesArray = GetFixedArrayVar("PressPerspecControl",theControl,VALUE,1,4L);
    hilitesArray = GetFixedArrayVar("PressPerspecControl",theControl,HIGHLIGHTED,1,4L);
    if (valuesArray && hilitesArray)
    {
	Array2CArray(values, valuesArray);
	Array2CArray(hilites, hilitesArray);
    }
    else
    {
        return ObjFalse;
    }

    /* test if mouse in my rectangle */
    if (mouseX < bounds[0] || mouseX > bounds[1] ||
        mouseY < bounds[2] || mouseY > bounds[3])
    {
        /* mouse out of my rectangle, do nothing, return */
        return ObjFalse;
    }

    /* test for helpclick */
    if (TOOL(flags) == T_HELP)
    {
	ContextHelp(theControl);
	return ObjTrue;
    }

    /* it's really a click, and not out of bounds, and not a helpclick */

    SaveForUndo(theControl);

    if (ShiftDown())
    {
	hiResMode = true;
    }

    for (i=0; i<4; ++i)
	saveValues[i] = oldValues[i] = values[i];

    ortho = GetPredicate(theControl, ORTHO);

    /* determine which part is moving */
    if (fabs(mouseX - (bounds[0] + bounds[1])/2) < RECSIZE/2
	&& fabs(mouseY - (bounds[2] + BOTTOMFUDGE
		+ values[0] * ((bounds[3]-TOPFUDGE) - (bounds[2]+BOTTOMFUDGE))
			     / (MAXEYEDIST - MINEYEDIST))) < RECSIZE/2)
    {
	/* changing the eyeDistance */
	which = 0;
	clipOff1 = values[2] - values[0];
	clipOff2 = values[3] - values[0];
    }
    else if (ortho && ORTHOSLOP >
		fabs(values[1] - fabs(MAXORTHOFOV *
		(mouseX - (bounds[0] + bounds[1])/2.0)
		/ ( ((bounds[1]-SIDEFUDGE)-(bounds[0]+SIDEFUDGE)) / 2.0))))
    {
	/* changing the orthogonal field of view */
	which = 1;
    }
    else if (fabs(values[1] - fabs(2.0 * 180.0 * atan2(mouseX - (bounds[1] + bounds[0]) / 2.0,
		 mouseY - (bounds[2] + BOTTOMFUDGE)) / MY_PI)) < ANGLESLOP)
    {
	/* changing the perspective angle of view */
	which = 1;
    }
    else /* default to moving the nearest clipping plane */
    {
	nearYPos = (int) (bounds[2] + BOTTOMFUDGE
	   + values[2] * ((bounds[3] - TOPFUDGE) - (bounds[2] + BOTTOMFUDGE))
			 / (MAXEYEDIST - MINEYEDIST));
	farYPos = (int) (bounds[2] + BOTTOMFUDGE
	   + values[3] * ((bounds[3] - TOPFUDGE) - (bounds[2] + BOTTOMFUDGE))
			 / (MAXEYEDIST - MINEYEDIST));
	if (fabs(mouseY - nearYPos) < fabs(mouseY - farYPos))
	{
	    which = 2;
	    if (fabs(mouseY - nearYPos) > 3)
	    /* if more than 3 pixels away, snap line there */
	    {
		saveValues[2] = (mouseY - (bounds[2] + BOTTOMFUDGE))
				/ (((bounds[3] - TOPFUDGE)
				   - (bounds[2] + BOTTOMFUDGE))
				   / (MAXCLIP - MINCLIP) ) + MINCLIP;
	    }
	}
	else
	{
	    which = 3;
	    if (fabs(mouseY - farYPos) > 3)
	    /* more than 3 pixels, snap */
	    {
	    	saveValues[3] = (mouseY - (bounds[2] + BOTTOMFUDGE))
				/ (((bounds[3] - TOPFUDGE)
				   - (bounds[2] + BOTTOMFUDGE))
				   / (MAXCLIP - MINCLIP) ) + MINCLIP;
	    }
	}
    }
    SetVar(theControl, CURRENT, NewInt(which));
    MakeMeCurrent(theControl);

    mXold = -1;
    mYold = -1;

    /* track mouse */
    while (Mouse(&mX, &mY))
    {
	/* if new values identical to old values, do squat */
	if (mX == mXold && mY == mYold)
	{
	    continue;
	}
	mXold = mX;
	mYold = mY;

	if (hiResMode != ShiftDown())
	/* state of shift-key changed */
	{
	    hiResMode = ShiftDown();
	    for (i=0; i<4; ++i)
	    {
		saveValues[i] = values[i];
	    }
	    mouseX = mX;
	    mouseY = mY;
	}

#if 0
	if (mX < bounds[0] - SLOP || mX > bounds[1] + SLOP	/* mouse outside rectangle */
	    || mY < bounds[2] - SLOP || mY > bounds[3] + SLOP) 
#else
	/* based only on x axis */
	if (mX < bounds[0] - SLOP || mX > bounds[1] + SLOP)
#endif
	{
	    /* unhighlight here */
	    if ((int) hilites[which])
	    {
		hilites[which] = false;
	    }
	    if (values[which] != oldValues[which])
	    {
		for (i=0; i<4; ++i)
		    values[i] = oldValues[i];
	    }
	}
	else					/* mouse inside rectangle */
	{
	    /* highlight here */
	    if(! (int) hilites[which])
	    {
		hilites[which] = true;
		CArray2Array(hilitesArray, hilites);
	    }

	    deltaX = mX - mouseX;
	    deltaY = mY - mouseY;
	    switch(which)
	    {
		case 0:
	    	    values[0] = saveValues[0] + deltaY / (((bounds[3] - TOPFUDGE)
				   - (bounds[2] + BOTTOMFUDGE))
				   / (MAXEYEDIST - MINEYEDIST));
		    values[2] = values[0] + clipOff1;
		    values[3] = values[0] + clipOff2;
		    if (hiResMode)
		    {
			values[0] = saveValues[0]+(values[0]-saveValues[0])/10;
			values[2] = saveValues[2]+(values[2]-saveValues[2])/10;
			values[3] = saveValues[3]+(values[3]-saveValues[3])/10;
		    }

		    if (values[0] < MINEYEDIST)
			values[0] = MINEYEDIST;
		    if (values[0] > MAXEYEDIST)
			values[0] = MAXEYEDIST;

		    if (values[2] < MINCLIP)
			values[2] = MINCLIP;
		    if (values[2] > MAXCLIP)
			values[2] = MAXCLIP;

		    if (values[3] < MINCLIP)
			values[3] = MINCLIP;
		    if (values[3] > MAXCLIP)
			values[3] = MAXCLIP;

		    break;
		case 1:
		    if (ortho)
		    {
			values[1] = fabs(MAXORTHOFOV *
			    (mX - (bounds[0] + bounds[1])/2.0)
			    / ( ((bounds[1]-SIDEFUDGE)-(bounds[0]+SIDEFUDGE)) / 2.0));
		
			if (hiResMode)
			    values[1]=saveValues[1]+(values[1]-saveValues[1])/10;

			if (values[1] < MINORTHOFOV)
			    values[1] = MINORTHOFOV;
			if (values[1] > MAXORTHOFOV)
			    values[1] = MAXORTHOFOV;
		    }
		    else
		    {
			values[1] = 2.0 * 180.0 * atan2(mX - (bounds[1] + bounds[0]) / 2.0,
						    mY - (bounds[2] + BOTTOMFUDGE)) / MY_PI;
			values[1] = values[1] < 0.0 ? -values[1] : values[1];
			if (hiResMode)
			    values[1]=saveValues[1]+(values[1]-saveValues[1])/10;

			if (values[1] < MINAOV)
			    values[1] = MINAOV;
			if (values[1] > MAXAOV)
			    values[1] = MAXAOV;
		    }
		    break;

		case 2:
	    	    values[2] = saveValues[2] + deltaY / (((bounds[3] - TOPFUDGE)
				   - (bounds[2] + BOTTOMFUDGE))
				   / (MAXCLIP - MINCLIP));
		    if (hiResMode)
			values[2] = saveValues[2]+(values[2]-saveValues[2])/10;

		    if (values[2] < MINCLIP)
			values[2] = MINCLIP;
		    if (values[2] > MAXCLIP - MINCLIPDELTA)
			values[2] = MAXCLIP - MINCLIPDELTA;
		    break;

		case 3:
	    	    values[3] = saveValues[3] + deltaY / (((bounds[3] - TOPFUDGE)
				   - (bounds[2] + BOTTOMFUDGE))
				   / (MAXCLIP - MINCLIP));
		    if (hiResMode)
			values[3] = saveValues[3] + (values[3]-saveValues[3])/10;

		    if (values[3] < MINCLIP + MINCLIPDELTA)
			values[3] = MINCLIP + MINCLIPDELTA;
		    if (values[3] > MAXCLIP)
			values[3] = MAXCLIP;
		    break;
		default:
		    ReportError("PressPerspecControl", "internal error!");
		    break;
	    }
	}
	/* if clip planes reversed, fix 'em */
	if (values[3] < values[2])
	{
	    tempvalue = values[2]; /* swap values */
	    values[2] = values[3];
	    values[3] = tempvalue;
	    tempvalue = hilites[2]; /* swap hilite flags */
	    hilites[2] = hilites[3];
	    hilites[3] = tempvalue;
	    tempvalue = oldValues[2]; /* swap old values, too */
	    oldValues[2] = oldValues[3];
	    oldValues[3] = tempvalue;
	    tempvalue = saveValues[2]; /* ..and the saved values */
	    saveValues[2] = saveValues[3];
	    saveValues[3] = tempvalue;
	    if (which == 3)
	    {
		which = 2;      /* switch to other control part */
	    }
	    else if (which == 2)
	    {
		which = 3;
	    }
	    SetVar(theControl, CURRENT, NewInt(which));
	}
	if (values[3] - values[2] < MINCLIPDELTA)
	{
	    if (values[2] < MINCLIP + MINCLIPDELTA)
	    {
		values[3] = values[2] + MINCLIPDELTA;
	    }
	    else if (values[3] > MAXCLIP - MINCLIPDELTA)
	    {
		values[2] = values[3] - MINCLIPDELTA;
	    }
	    else
	    {
		real ctr;

		ctr = (values[3] + values[2]) / 2.0;
		values[2] = ctr - MINCLIPDELTA / 2.0;
		values[3] = ctr + MINCLIPDELTA / 2.0;
	    }
	}
	CArray2Array(valuesArray, values);
	CArray2Array(hilitesArray, hilites);
	DrawMe(theControl);
	ChangedValue(theControl);
    }
    hilites[0] = false;
    hilites[1] = false;
    hilites[2] = false;
    hilites[3] = false;
    CArray2Array(hilitesArray, hilites);
    ImInvalid(theControl);
    ChangedValue(theControl);
    if (logging) LogControl(theControl);

    return ObjTrue;
#endif /* INTERACTIVE */
}

ObjPtr KeyDownPerspecControl(theControl, key, flags)
/* KEYDOWN event for PerspecControls */
ObjPtr theControl;
int key;
long flags;
{
#ifdef INTERACTIVE
    int		bounds[4];              /* meat of boundsArray */
    ObjPtr	valuesArray;		/* array of data for complex control */
    real	values[4];		/* meat of valuesArray */
    real	oldValues[4];		/* old meat of valuesArray */
    int		isChanged;              /* return value of changer */
    ObjPtr	theWhich;		/* Int thing holding which control */
    int		which;			/* which control part we're moving */

    if (!AmICurrent(theControl))
    {
	return ObjFalse;
    }

    /* BORK temporary kludge */

    theWhich = GetIntVar("KeyDownPerspecControl", theControl, CURRENT);
    if (!theWhich)
    {
	/* can't find what part is current */
	return ObjFalse;
    }

    which = GetInt(theWhich);
    if (which < 0 || which > 3)
    {
	/* CURRENT value was invalid */
	return ObjFalse;
    }

    if (!Get2DIntBounds(theControl,&bounds[0],&bounds[1],&bounds[2],&bounds[3]))
    {
        return NULLOBJ;
    }
    valuesArray = GetFixedArrayVar("KeyDownPerspecControl", theControl, VALUE, 1, 4L);
    if (valuesArray)
    {
	Array2CArray(values, valuesArray);
    }
    else
    {
        return ObjFalse;
    }
    switch(key)
    {
	case '\0':
	    if (logging) LogControl(theControl);
	    return ObjTrue;
	    break;
	case FK_UP_ARROW:
	case FK_RIGHT_ARROW:
	    switch(which)
	    {
		case 0:
	    	    values[0] = values[0] + PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[0] < MINEYEDIST)
			values[0] = MINEYEDIST;
		    if (values[0] > MAXEYEDIST)
			values[0] = MAXEYEDIST;
		    break;
		case 1:
		    if (GetPredicate(theControl, ORTHO))
		    {
			values[1] = values[1] + 0.2;
			if (values[1] < MINORTHOFOV)
			    values[1] = MINORTHOFOV;
			if (values[1] > MAXORTHOFOV)
			    values[1] = MAXORTHOFOV;
		    }
		    else
		    {
			values[1] = values[1] + 0.5;
			if (values[1] < MINAOV)
			    values[1] = MINAOV;
			if (values[1] > MAXAOV)
			    values[1] = MAXAOV;
		    }
		    break;

		case 2:
	    	    values[2] = values[2] + PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[2] < MINCLIP)
			values[2] = MINCLIP;
		    if (values[2] > MAXCLIP)
			values[2] = MAXCLIP;
		    break;

		case 3:
	    	    values[3] = values[3] + PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[3] < MINCLIP)
			values[3] = MINCLIP;
		    if (values[3] > MAXCLIP)
			values[3] = MAXCLIP;
		    break;
		default:
		    ReportError("KeyDownPerspecControl", "internal error!");
		    break;
	    }
	    break;
	case FK_DOWN_ARROW:
	case FK_LEFT_ARROW:
	    switch(which)
	    {
		case 0:
	    	    values[0] = values[0] - PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[0] < MINEYEDIST)
			values[0] = MINEYEDIST;
		    if (values[0] > MAXEYEDIST)
			values[0] = MAXEYEDIST;
		    break;
		case 1:
		    if (GetPredicate(theControl, ORTHO))
		    {
			values[1] = values[1] - 0.2;
			if (values[1] < MINORTHOFOV)
			    values[1] = MINORTHOFOV;
			if (values[1] > MAXORTHOFOV)
			    values[1] = MAXORTHOFOV;
		    }
		    else
		    {
			values[1] = values[1] - 0.5;
			if (values[1] < MINAOV)
			    values[1] = MINAOV;
			if (values[1] > MAXAOV)
			    values[1] = MAXAOV;
		    }
		    break;

		case 2:
	    	    values[2] = values[2] - PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[2] < MINCLIP)
			values[2] = MINCLIP;
		    if (values[2] > MAXCLIP)
			values[2] = MAXCLIP;
		    break;

		case 3:
	    	    values[3] = values[3] - PERSPECSCALE
					    * (MAXEYEDIST - MINEYEDIST)
					    / ((bounds[3] - TOPFUDGE)
						- (bounds[2] + BOTTOMFUDGE));
		    if (values[3] < MINCLIP)
			values[3] = MINCLIP;
		    if (values[3] > MAXCLIP)
			values[3] = MAXCLIP;
		    break;
		default:
		    ReportError("KeyDownPerspecControl", "internal error");
		    break;
	    }
	    break;
	default:
	    if (F_SHIFTDOWN & flags)
	    {
		MakePerspecOrtho(theControl, !GetPredicate(theControl, ORTHO));
	    }
	    return ObjTrue; /* acknowledge call, but ignore key */
	    break;
    }
    CArray2Array(valuesArray, values);
    ImInvalid(theControl);
    ChangedValue(theControl);
    return ObjTrue;
#endif /* INTERACTIVE */
}

ObjPtr	SetPerspecControlValue(theControl, theNewValues)
/* sets values on perspective control */
ObjPtr	theControl, theNewValues;
{
    ObjPtr	theOldValues;
    real	newValues[4], temp;
    real	oldValues[4];
    int		changed;
    int		i;

    changed = false;
    theOldValues = GetVar(theControl, VALUE);
    if (!theOldValues)
    {
	changed = true;
    }
    else if (!IsArray(theOldValues))
    {
	changed = true;
    }
    else
    {
	Array2CArray(oldValues, theOldValues);
    }
    Array2CArray(newValues, theNewValues);
    newValues[0] = newValues[0] < MINEYEDIST ? MINEYEDIST : newValues[0];
    newValues[0] = newValues[0] > MAXEYEDIST ? MAXEYEDIST : newValues[0];
    if (GetPredicate(theControl, ORTHO))
    {
	newValues[1] = newValues[1] < MINORTHOFOV ? MINORTHOFOV : newValues[1];
	newValues[1] = newValues[1] > MAXORTHOFOV ? MAXORTHOFOV : newValues[1];
    }
    else
    {
	newValues[1] = newValues[1] < MINAOV ? MINAOV : newValues[1];
	newValues[1] = newValues[1] > MAXAOV ? MAXAOV : newValues[1];
    }
    newValues[2] = newValues[2] < MINCLIP ? MINCLIP : newValues[2];
    newValues[2] = newValues[2] > MAXCLIP ? MAXCLIP : newValues[2];
    newValues[3] = newValues[3] < MINCLIP ? MINCLIP : newValues[3];
    newValues[3] = newValues[3] > MAXCLIP ? MAXCLIP : newValues[3];
    if (newValues[2] > newValues[3])
    {
	temp = newValues[2];
	newValues[2] = newValues[3];
	newValues[3] = temp;
    }
    for (i = 0; !changed && i < 4; ++i)
    {
	if (newValues[i] != oldValues[i])
	    changed = true;
    }
    CArray2Array(theNewValues, newValues);
    SetVar(theControl, VALUE, theNewValues);
    if (changed)
    {
	ImInvalid(theControl);
	ChangedValue(theControl);
	if (logging) LogControl(theControl);
	return ObjTrue;
    }
    if (logging) LogControl(theControl);
    return ObjFalse;
}

ObjPtr	GetPerspecControlValue(theControl, values)
ObjPtr	theControl;
real	values[4];
{
    ObjPtr	theValues;
    int		i;

    theValues = GetFixedArrayVar("GetPerspecControlValue", theControl, VALUE, 1, 4L);
    if (!theValues)
    {
	for (i=0;i<4;++i) values[i] = 0.0;
	return ObjFalse;
    }

    /* successful retrieval of values */
    Array2CArray(values, theValues);
    return ObjTrue;
}

#ifdef PROTO
Bool MakePerspecOrtho(ObjPtr control, Bool flag)
#else
Bool MakePerspecOrtho(control, flag)
ObjPtr control;
Bool flag;
#endif
{
    if (flag != GetPredicate(control, ORTHO))
    {
	ObjPtr valsArray;
	real vals[4];
	real dist, fov;		/* object distance, field of view */
	ObjPtr oldValue;

	SetVar(control, ORTHO, flag ? ObjTrue : ObjFalse);
	valsArray = GetFixedArrayVar("MakePerspecOrtho", control, VALUE, 1, 4L);
	Array2CArray(vals, valsArray);
	dist = vals[0];
	fov = vals[1];
	if (flag)
	{
	    /* if control was perspec, is now ORTHO */
	    /* obsolete?
	    fov = (dist / cos((fov / 2.0) * (MY_PI / 180.0)))
		  * sin((fov / 2.0) * (MY_PI / 180.0));
	    */
	    oldValue = GetVar(control, OLDVALUE);
	    if (oldValue)
	    {
		fov = GetReal(oldValue);
	    }
	    else
	    {
		fov = INITFOV;
	    }

	    if (fov < 0.0)
		fov = -fov;
	    if (fov > MAXORTHOFOV)
		fov = MAXORTHOFOV;
	}
	else
	{
	    /* control was ORTHO, is now perspec again */
	    oldValue = GetVar(control, OLDVALUE);
	    if (oldValue)
	    {
		fov = GetReal(oldValue);
	    }
	    else
	    {
		fov = INITAOV;
	    }
	}

	SetVar(control, OLDVALUE, NewReal(vals[1]));
	vals[1] = fov;
	CArray2Array(valsArray, vals);
	ChangedValue(control);
	ImInvalid(control);
	return true;
    }
    else
    {
	return false;
    }
}

void	InitPerspecControls()
/* sets up perspective controls stuff */
{
    perspecControlClass = NewObject(controlClass, 0);
    AddToReferenceList((ThingPtr) perspecControlClass);
    SetMethod(perspecControlClass, DRAW, DrawPerspecControl);
    SetMethod(perspecControlClass, PRESS, PressPerspecControl);
    SetMethod(perspecControlClass, SETVAL, SetPerspecControlValue);
    SetMethod(perspecControlClass, GETVAL, GetPerspecControlValue);
    SetMethod(perspecControlClass, KEYDOWN, KeyDownPerspecControl);
    SetVar(perspecControlClass, COLOR, NewInt(UIBACKGROUND));
    SetVar(perspecControlClass, WHICHICON, NewInt(ICONOBSERVER));
    SetVar(perspecControlClass, TYPESTRING, NewString("perspective control"));
    SetVar(perspecControlClass, HELPSTRING, NewString(
"This control affects the perspective view in the Space. Clicking and \
dragging the central cube object will move the viewed fields towards or \
away from the Eye. You can move the front and back clipping planes by \
clicking on and dragging the two horizontal lines. Clicking and \
dragging either of the two diagonal lines will change the angle of view."));
}

void	KillPerspecControls()
/* unsets-up perspective controls stuff */
{
    RemoveFromReferenceList(perspecControlClass);
    perspecControlClass = 0;
}

#define XFUDGE -4.0
#define YFUDGE -7.0

ObjPtr	NewPerspecControl(left, right, bottom, top, name)
int	left, right, bottom, top;
char *name;
/*Makes a new panel with bounds left, right, bottom, top, and label label*/
{
    real	values[4], hilites[4];
    ObjPtr	valuesArray, hilitesArray;
    ObjPtr	retVal;
    ObjPtr	labelPtr;
    ObjPtr	hilightPtr;
    real	loc[2];
    ObjPtr	locArray;

    values[0] = INITEYEDIST;
    values[1] = INITAOV;
    values[2] = INITNEARCLIP;
    values[3] = INITFARCLIP;

    hilites[0] = false;
    hilites[1] = false;
    hilites[2] = false;
    hilites[3] = false;

    loc[0] = XFUDGE;
    loc[1] = YFUDGE;

    valuesArray = NewRealArray(1, (long) 4);
    hilitesArray = NewRealArray(1, (long) 4);
    locArray = NewRealArray(1, (long) 2);
    retVal = NewObject(perspecControlClass, 0);
    if (valuesArray && hilitesArray && retVal)
    {
        CArray2Array(valuesArray, values);
	SetVar(retVal, VALUE, (ThingPtr) valuesArray);

	CArray2Array(hilitesArray, hilites);
	SetVar(retVal, HIGHLIGHTED, (ThingPtr) hilitesArray);

	CArray2Array(locArray, loc);
	SetVar(retVal, ICONLOC, locArray);

	Set2DIntBounds(retVal, left, right, bottom, top);
	SetVar(retVal, NAME, NewString(name));
	SetVar(retVal, ORTHO, ObjFalse);
	return retVal;
    }
    else
    {
	return NULLOBJ;
    }
}
