/*
 * @(#)Threed.c
 *
 * Copyright 1994 - 2013  David A. Bagley, bagleyd AT verizon.net
 *
 * All rights reserved.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear in
 * supporting documentation, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Methods file for Threed */

#include "file.h"
#include "sound.h"
#include "ThreedP.h"

#ifdef WINVER
#ifndef INIFILE
#define INIFILE "wthreed.ini"
#endif
#ifndef DATAFILE
#define DATAFILE "c:\\Windows\threed.dat"
#endif

#define SECTION "setup"
#else

#ifndef DATAFILE
#define DATAFILE "threed.dat"
#endif

#if defined( USE_SOUND ) && defined( USE_NAS )
Display *dsp;
#endif

static void initializeThreeD(Widget request, Widget renew);
static void exposeThreeD(Widget renew, XEvent *event, Region region);
static void resizeThreeD(ThreeDWidget w);
static void destroyThreeD(Widget old);
static Boolean setValuesThreeD(Widget current, Widget request, Widget renew);
static void quitThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void hideThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void surfaceThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void objectThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void speedThreeD(ThreeDWidget w, XEvent *event, char **args, int nArgs);
static void slowThreeD(ThreeDWidget w, XEvent *event, char **args, int nArgs);
static void soundThreeD(ThreeDWidget w, XEvent *event, char **args, int nArgs);
static void enterThreeD(ThreeDWidget w, XEvent *event, char **args,
	int nArgs);
static void leaveThreeD(ThreeDWidget w, XEvent *event, char **args,
	int nArgs);
static void moveRaisePsiThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveRaisePhiThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveLowerPhiThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveLowerThetaThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveLowerPsiThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveRaiseThetaThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveRaisePhiThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveLowerThetaLowerPhiThreeD(ThreeDWidget w, XEvent *event,
	char **args, int n_args);
static void moveLowerThetaRaisePhiThreeD(ThreeDWidget w, XEvent *event,
	char **args, int n_args);
static void oveRaiseThetaLowerPhiThreeD(ThreeDWidget w, XEvent *event,
	char **args, int n_args);
static void moveRaiseThetaRaisePhiThreeD(ThreeDWidget w, XEvent *event,
	char **args, int n_args);
static void moveLeftThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveRightThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveUpThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveDownThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveOutThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void moveInThreeD(ThreeDWidget w, XEvent *event, char **args,
	int n_args);
static void selectThreeD(ThreeDWidget w, XEvent *event, char **args,
	int nArgs);
static void motionThreeD(ThreeDWidget w, XEvent *event, char **args,
	int nArgs);
static void releaseThreeD(ThreeDWidget w, XEvent *event, char **args,
	int nArgs);

static char defaultTranslationsThreeD[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>F11: MoveRaisePsi()\n\
 <KeyPress>KP_Divide: MoveRaisePsi()\n\
 <KeyPress>R5: MoveRaisePsi()\n\
 <KeyPress>Home: MoveRaiseThetaRaisePhi()\n\
 <KeyPress>KP_7: MoveRaiseThetaRaisePhi()\n\
 <KeyPress>R7: MoveRaiseThetaRaisePhi()\n\
 <KeyPress>Up: MoveRaisePhi()\n\
 <KeyPress>osfUp: MoveRaisePhi()\n\
 <KeyPress>KP_Up: MoveRaisePhi()\n\
 <KeyPress>KP_8: MoveRaisePhi()\n\
 <KeyPress>R8: MoveRaisePhi()\n\
 <Btn4Down>: MoveRaisePhi()\n\
 <KeyPress>Prior: MoveLowerThetaRaisePhi()\n\
 <KeyPress>KP_9: MoveLowerThetaRaisePhi()\n\
 <KeyPress>R9: MoveLowerThetaRaisePhi()\n\
 <KeyPress>Left: MoveRaiseTheta()\n\
 <KeyPress>osfLeft: MoveRaiseTheta()\n\
 <KeyPress>KP_Left: MoveRaiseTheta()\n\
 <KeyPress>KP_4: MoveRaiseTheta()\n\
 <KeyPress>R10: MoveRaiseTheta()\n\
 <KeyPress>F12: MoveLowerPsi()\n\
 <KeyPress>Begin: MoveLowerPsi()\n\
 <KeyPress>KP_5: MoveLowerPsi()\n\
 <KeyPress>R11: MoveLowerPsi()\n\
 <KeyPress>Right: MoveLowerTheta()\n\
 <KeyPress>osfRight: MoveLowerTheta()\n\
 <KeyPress>KP_Right: MoveLowerTheta()\n\
 <KeyPress>KP_6: MoveLowerTheta()\n\
 <KeyPress>R12: MoveLowerTheta()\n\
 <KeyPress>End: MoveRaiseThetaLowerPhi()\n\
 <KeyPress>KP_1: MoveRaiseThetaLowerPhi()\n\
 <KeyPress>R13: MoveRaiseThetaLowerPhi()\n\
 <KeyPress>Down: MoveLowerPhi()\n\
 <KeyPress>osfDown: MoveLowerPhi()\n\
 <KeyPress>KP_Down: MoveLowerPhi()\n\
 <KeyPress>KP_2: MoveLowerPhi()\n\
 <KeyPress>R14: MoveLowerPhi()\n\
 <Btn5Down>: MoveLowerPhi()\n\
 <KeyPress>Next: MoveLowerThetaLowerPhi()\n\
 <KeyPress>KP_3: MoveLowerThetaLowerPhi()\n\
 <KeyPress>R15: MoveLowerThetaLowerPhi()\n\
 <KeyPress>l: MoveLeft()\n\
 <KeyPress>r: MoveRight()\n\
 <KeyPress>u: moveUp()\n\
 <KeyPress>d: moveDown()\n\
 <KeyPress>o: MoveOut()\n\
 <KeyPress>i: MoveIn()\n\
 <Btn1Down>: Select()\n\
 <Btn1Motion>: Motion()\n\
 <Btn1Up>: Release()\n\
 <KeyPress>s: Surface()\n\
 <KeyPress>b: Object()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsListThreeD[] = {
	{(char *) "Quit", (XtActionProc) quitThreeD},
	{(char *) "Hide", (XtActionProc) hideThreeD},
	{(char *) "MoveRaisePsi", (XtActionProc) moveRaisePsiThreeD},
	{(char *) "MoveLowerPhi", (XtActionProc) moveLowerPhiThreeD},
	{(char *) "MoveLowerTheta", (XtActionProc) moveLowerThetaThreeD},
	{(char *) "MoveLowerPsi", (XtActionProc) moveLowerPsiThreeD},
	{(char *) "MoveRaiseTheta", (XtActionProc) moveRaiseThetaThreeD},
	{(char *) "MoveRaisePhi", (XtActionProc) moveRaisePhiThreeD},
	{(char *) "MoveLowerThetaLowerPhi",
		(XtActionProc) moveLowerThetaLowerPhiThreeD},
	{(char *) "MoveLowerThetaRaisePhi",
		(XtActionProc) moveLowerThetaRaisePhiThreeD},
	{(char *) "MoveRaiseThetaLowerPhi",
		(XtActionProc) oveRaiseThetaLowerPhiThreeD},
	{(char *) "MoveRaiseThetaRaisePhi",
		(XtActionProc) moveRaiseThetaRaisePhiThreeD},
	{(char *) "MoveLeft", (XtActionProc) moveLeftThreeD},
	{(char *) "MoveRight", (XtActionProc) moveRightThreeD},
	{(char *) "moveUp", (XtActionProc) moveUpThreeD},
	{(char *) "moveDown", (XtActionProc) moveDownThreeD},
	{(char *) "MoveOut", (XtActionProc) moveOutThreeD},
	{(char *) "MoveIn", (XtActionProc) moveInThreeD},
	{(char *) "Select", (XtActionProc) selectThreeD},
	{(char *) "Motion", (XtActionProc) motionThreeD},
	{(char *) "Release", (XtActionProc) releaseThreeD},
	{(char *) "Surface", (XtActionProc) surfaceThreeD},
	{(char *) "Object", (XtActionProc) objectThreeD},
	{(char *) "Speed", (XtActionProc) speedThreeD},
	{(char *) "Slow", (XtActionProc) slowThreeD},
	{(char *) "Sound", (XtActionProc) soundThreeD},
	{(char *) "Enter", (XtActionProc) enterThreeD},
	{(char *) "Leave", (XtActionProc) leaveThreeD}
};

static XtResource resourcesThreeD[] = {
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(ThreeDWidget, core.width),
	 XtRString, (caddr_t) "400"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(ThreeDWidget, core.height),
	 XtRString, (caddr_t) "200"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(ThreeDWidget, threed.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(ThreeDWidget, threed.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.frameColor),
	 XtRString, (caddr_t) "cyan" /* XtDefaultForeground*/},
	{XtNwhiteBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[WHITE_BRUSH]),
	 XtRString, (caddr_t) "Yellow"},
	{XtNltgrayBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[LTGRAY_BRUSH]),
	 XtRString, (caddr_t) "Green"},
	{XtNgrayBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[GRAY_BRUSH]),
	 XtRString, (caddr_t) "Red"},
	{XtNdkgrayBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[DKGRAY_BRUSH]),
	 XtRString, (caddr_t) "Blue"},
	{XtNblackBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[BLACK_BRUSH]),
	 XtRString, (caddr_t) "Black"},
	{XtNanotherBrush, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.shadeColor[ANOTHER_BRUSH]),
	 XtRString, (caddr_t) "Brown"},
	{XtNborderColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(ThreeDWidget, threed.borderColor),
	 XtRString, (caddr_t) "gray75" /* XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(ThreeDWidget, threed.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNbumpSound, XtCBumpSound, XtRString, sizeof (String),
	 XtOffset(ThreeDWidget, threed.bumpSound),
	 XtRString, (caddr_t) BUMPSOUND},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(ThreeDWidget, threed.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNxDistance, XtCXDistance, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.distance.x),
	 XtRString, (caddr_t) "0"},
	{XtNyDistance, XtCYDistance, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.distance.y),
	 XtRString, (caddr_t) "0"},
	{XtNzDistance, XtCZDistance, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.distance.z),
	 XtRString, (caddr_t) "50"},
	{XtNthetaDegrees, XtCThetaDegrees, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.angle.theta),
	 XtRString, (caddr_t) "0"},
	{XtNphiDegrees, XtCPhiDegrees, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.angle.phi),
	 XtRString, (caddr_t) "0"},
	{XtNpsiDegrees, XtCPsiDegrees, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.angle.psi),
	 XtRString, (caddr_t) "0"},
	{XtNsurface, XtCSurface, XtRBoolean, sizeof (Boolean),
	 XtOffset(ThreeDWidget, threed.surface),
	 XtRString, (caddr_t) "TRUE"}, /* DEFAULT_SURFACE */
	{XtNobject, XtCObject, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.object),
	 XtRString, (caddr_t) "6"},
	{XtNobjectName, XtCObjectName, XtRString, sizeof (String),
	 XtOffset(ThreeDWidget, threed.name),
	 XtRString, (caddr_t) "None"},
	{XtNobjectNumber, XtCObjectNumber, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.numObjects),
	 XtRString, (caddr_t) "0"},
	{XtNobjectList, XtCObjectList, XtRString, sizeof (String),
	 XtOffset(ThreeDWidget, threed.list),
	 XtRString, (caddr_t) "None"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(ThreeDWidget, threed.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(ThreeDWidget, threed.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(ThreeDWidget, threed.select), XtRCallback, (caddr_t) NULL}
};

ThreeDClassRec threedClassRec =
{
	{
		 (WidgetClass) & widgetClassRec,	/* superclass */
		 (char *) "ThreeD",	/* class name */
		 sizeof (ThreeDRec),	/* widget size */
		 NULL,		/* class initialize */
		 NULL,		/* class part initialize */
		 FALSE,		/* class inited */
		 (XtInitProc) initializeThreeD,	/* initialize */
		 NULL,		/* initialize hook */
		 XtInheritRealize,	/* realize */
		 actionsListThreeD,	/* actions */
		 XtNumber(actionsListThreeD),	/* num actions */
		 resourcesThreeD,	/* resources */
		 XtNumber(resourcesThreeD),	/* num resources */
		 NULLQUARK,	/* xrm class */
		 TRUE,		/* compress motion */
		 TRUE,		/* compress exposure */
		 TRUE,		/* compress enterleave */
		 TRUE,		/* visible interest */
		 (XtWidgetProc) destroyThreeD,	/* destroy */
		 (XtWidgetProc) resizeThreeD,	/* resize */
		 (XtExposeProc) exposeThreeD,	/* expose */
		 (XtSetValuesFunc) setValuesThreeD,	/* set values */
		 NULL,		/* set values hook */
		 XtInheritSetValuesAlmost,	/* set values almost */
		 NULL,		/* get values hook */
		 NULL,		/* accept focus */
		 XtVersion,	/* version */
		 NULL,		/* callback private */
		 defaultTranslationsThreeD,	/* tm table */
		 NULL,		/* query geometry */
		 NULL,		/* display accelerator */
		 NULL		/* extension */
	 }
	,
	{
		0		/* ignore */
	}
};

WidgetClass threedWidgetClass = (WidgetClass) & threedClassRec;

static void
setThreed(ThreeDWidget w, int reason)
{
	threedCallbackStruct cb;

	cb.reason = reason;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}

#endif

#define RADIANS(x) (M_PI*(x)/ST_ANGLE)
#define DEGREES(x) ((x)/M_PI*ST_ANGLE)

#ifdef DEPTHINDEX
static Object3D *OBJ;
#endif

static void
checkGraphicsThreeD(ThreeDWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->threed.distance.x < MINDISTANCE ||
			w->threed.distance.x > MAX_DISTANCE) {
		w->threed.distance.x = (MAX_DISTANCE + MINDISTANCE) / 2;
		intCat(&buf1,
			"Distance out of bounds, use ",
			MIN_DEPTH);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_DEPTH);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->threed.distance.x);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
	}
	if (w->threed.distance.y < MINDISTANCE ||
			w->threed.distance.y > MAX_DISTANCE) {
		w->threed.distance.y = (MAX_DISTANCE + MINDISTANCE) / 2;
		intCat(&buf1,
			"Distance out of bounds, use ",
			MIN_DEPTH);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_DEPTH);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->threed.distance.y);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
	}
	if (w->threed.distance.z < MIN_DEPTH ||
			w->threed.distance.z > MAX_DEPTH) {
		w->threed.distance.z = (MAX_DEPTH + MIN_DEPTH) / 2;
		intCat(&buf1,
			"Distance out of bounds, use ",
			MIN_DEPTH);
		stringCat(&buf2, buf1, "..");
		free(buf1);
		intCat(&buf1, buf2, MAX_DEPTH);
		free(buf2);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->threed.distance.z);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
	}
	if (w->threed.angle.theta < MIN_DEGREES ||
			w->threed.angle.theta >= NUM_DEGREES) {
		w->threed.angle.theta = (NUM_DEGREES +
			w->threed.angle.theta) % NUM_DEGREES;
	}
	if (w->threed.angle.phi < MIN_DEGREES ||
			w->threed.angle.phi >= NUM_DEGREES) {
		w->threed.angle.phi = (NUM_DEGREES +
			w->threed.angle.phi) % NUM_DEGREES;
	}
	if (w->threed.angle.psi < MIN_DEGREES ||
			w->threed.angle.psi >= NUM_DEGREES) {
		w->threed.angle.psi = (NUM_DEGREES +
			w->threed.angle.psi) % NUM_DEGREES;
	}
	if (w->threed.numObjects == 0) {
		DISPLAY_WARNING("No objects");
	} else if (w->threed.object < 0 ||
			w->threed.object >= w->threed.numObjects) {
		w->threed.object = 0;
		intCat(&buf1, "Object type number out of bounds, use 0..",
			w->threed.numObjects - 1);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, w->threed.object);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
	}

}

#ifdef __cplusplus
extern "C" {
#endif
static int
#ifdef WINVER
__cdecl
#endif
compareObject(const void *elem1, const void *elem2)
#ifdef DEPTHINDEX
{
	double z1, z2;

	z1 = OBJ->vertex[((Surface *) elem1)->depthIndex].eye.z;
	z2 = OBJ->vertex[((Surface *) elem2)->depthIndex].eye.z;
	if (z1 > z2)
		return -1;
	if (z2 > z1)
		return 1;
	return 0;
}
#else
{
	const Surface *surface1, *surface2;

	surface1 = (const Surface *) elem1;
	surface2 = (const Surface *) elem2;
	if (surface1->averageDepth > surface2->averageDepth)
		return -1;
	if (surface2->averageDepth > surface1->averageDepth)
		return 1;
	return 0;
}
#endif
#ifdef __cplusplus
}
#endif

#define EPSILON 0.000001
#define ASPECTRATIO 1.0
#define viewDistance ((w->core.width + w->core.height) / 2)

static void
realizeObject(ThreeDWidget w, Object3D *obj)
{
	int vert, surf;
	double z;

	if (w->threed.numObjects == 0)
		return;
	rotateObject(obj, &(w->threed.deltaAngle));
	w->threed.deltaAngle.theta = 0;
	w->threed.deltaAngle.phi = 0;
	w->threed.deltaAngle.psi = 0;
	for (vert = 0; vert < obj->numVertices; vert++) {
		Vertex *vertex = &(obj->vertex[vert]);

		vertex->eye.x = obj->local[vert].x +
			w->threed.distance.x - obj->origin.x;
		vertex->eye.y = obj->local[vert].y +
			w->threed.distance.y - obj->origin.y;
		vertex->eye.z = obj->local[vert].z +
			w->threed.distance.z - obj->origin.z;
		/* Avoid divide by zero */
		if (vertex->eye.z < EPSILON && vertex->eye.z >= 0.0)
			vertex->eye.z = EPSILON;
		if (vertex->eye.z < 0.0 && vertex->eye.z > -EPSILON)
			vertex->eye.z = -EPSILON;
		vertex->screen.x = (int) (w->threed.center.x +
			vertex->eye.x *
			viewDistance / vertex->eye.z);
		vertex->screen.y = (int) (w->threed.center.y -
			ASPECTRATIO * vertex->eye.y *
			viewDistance / vertex->eye.z);
#ifdef DEBUG
		(void) printf("%d: x %g, y %g, z %g\n",
			vert, vertex->eye.x, vertex->eye.y, vertex->eye.z);
#endif
	}
#ifndef DEPTHINDEX
	for (surf = 0; surf < obj->numSurfaces; surf++) {
		Surface *surface = &(obj->surface[surf]);
		int mapIndex = surface->mapIndex;

		z = 0.0;
		for (vert = 0; vert < surface->numVertices;
				vert++, mapIndex++) {
			z += obj->vertex[obj->map[mapIndex]].eye.z;
		}
		surface->averageDepth = z / ((double) surface->numVertices);
	}
#endif
}

static void
drawObject(ThreeDWidget w, Object3D *obj)
{
	int surf, vert, vertex, mapIndex, clickSurface = -1;
	Point points[MAX_POLYGON_VERTICES + 1];
	Pixmap *dr;
	double s1 = 0.0, s2 = 0.0, s3 = 0.0;
	Point3D *v1, *v2, *v3;
	GC lineGC, fillGC;

	dr = &(w->threed.bufferObjects[0]);
	/* compute position of object in world */
	/* if (w->threed.surface) */ {
#ifdef DEPTHINDEX
		OBJ = obj;
#endif
		(void) qsort((void *) obj->surface,
			(unsigned int) obj->numSurfaces,
			sizeof(Surface), compareObject);
	}
	for (surf = 0; surf < obj->numSurfaces; surf++) {
		mapIndex = obj->surface[surf].mapIndex;

		if (obj->surface[surf].clipped)
			continue;
		/* (void) printf("polygon #%d\n", surf); */
		v1 = &obj->vertex[obj->map[mapIndex]].eye;
		v2 = &obj->vertex[obj->map[mapIndex + 1]].eye;
		v3 = &obj->vertex[obj->map[mapIndex + 2]].eye;
		s1 = -v1->x * (v2->y * v3->z - v3->y * v2->z);
		s2 = -v2->x * (v3->y * v1->z - v1->y * v3->z);
		s3 = -v3->x * (v1->y * v2->z - v2->y * v1->z);
		if (w->threed.surface && -s1 - s2 - s3 > 0.0) {
			obj->surface[surf].visible = -1;
			continue;
		} else if (-s1 - s2 - s3 == 0.0) {
			obj->surface[surf].visible = 0;
		} else {
			obj->surface[surf].visible = 1;
		}
		for (vert = 0; vert < obj->surface[surf].numVertices;
				vert++, mapIndex++) {
			vertex = obj->map[mapIndex];
			if (vert < MAX_POLYGON_VERTICES) {
				points[vert].x = obj->vertex[vertex].screen.x;
				points[vert].y = obj->vertex[vertex].screen.y;
			} else {
				DISPLAY_WARNING("Ignoring extra polygon points");
				break;
			}
		}
		if (pointInPolygon(&(w->threed.currentPosition),
				&(points[0]), vert)) {
			clickSurface = surf;
		}
	}
	for (surf = 0; surf < obj->numSurfaces; surf++) {
		mapIndex = obj->surface[surf].mapIndex;
		if (obj->surface[surf].visible == -1 ||
				obj->surface[surf].clipped)
			continue;
		for (vert = 0; vert < obj->surface[surf].numVertices;
				vert++, mapIndex++) {
			vertex = obj->map[mapIndex];
			if (vert < MAX_POLYGON_VERTICES) {
				/* Its not dynamic but we are really after
				   speed here */
				points[vert].x = obj->vertex[vertex].screen.x;
				points[vert].y = obj->vertex[vertex].screen.y;
			} else {
				break;
			}
		}
		points[vert].x = points[0].x;
		points[vert].y = points[0].y;
		if (clickSurface >= 0 && obj->surface[surf].depthIndex ==
				obj->surface[clickSurface].depthIndex) {
			fillGC = w->threed.frameGC;
			lineGC = w->threed.frameGC;
		} else {
			fillGC = w->threed.shadeGC[obj->surface[surf].color];
			lineGC = w->threed.shadeGC[obj->surface[surf].color];
		}
		if (!w->threed.surface || obj->surface[surf].visible == 0) {
			if (clickSurface < 0 ||
					obj->surface[surf].depthIndex !=
					obj->surface[clickSurface].depthIndex) {
				POLYLINE(w, *dr, fillGC,
					&points[0], vert + 1, True);
			}
		} else {
			POLYGON(w, *dr, fillGC, lineGC,
				&points[0], vert, obj->convex, True);
#ifdef OUTLINE
#ifndef WINVER
			if (w->threed.inverseGC[1] != fillGC) {
				int i;

				for (i = 0; i < vert; i++) {
					int diff0, diff1;

					diff0 = points[i].y - points[i].x;
					diff1 = points[i + 1].y - points[i + 1].x;
					if (diff0 > diff1) {
						POLYLINE(w, *dr, w->threed.whiteGC,
							&(points[i]), 2, True);
					} else {
						POLYLINE(w, *dr, w->threed.blackGC,
							&(points[i]), 2, True);
					}
				}
			}
#endif
#endif
		}
	}
	for (surf = 0; surf < obj->numSurfaces; surf++) {
		mapIndex = obj->surface[surf].mapIndex;
		if (obj->surface[surf].visible == -1 ||
				obj->surface[surf].clipped)
			continue;
		for (vert = 0; vert < obj->surface[surf].numVertices;
				vert++, mapIndex++) {
			vertex = obj->map[mapIndex];
			if (vert < MAX_POLYGON_VERTICES) {
				/* Its not dynamic but we are really after
				   speed here */
				points[vert].x = obj->vertex[vertex].screen.x;
				points[vert].y = obj->vertex[vertex].screen.y;
			} else {
				break;
			}
		}
		points[vert].x = points[0].x;
		points[vert].y = points[0].y;
		if (clickSurface >= 0 && obj->surface[surf].depthIndex ==
				obj->surface[clickSurface].depthIndex) {
			fillGC = w->threed.frameGC;
			lineGC = w->threed.shadeGC[obj->surface[surf].color];
			POLYLINE(w, *dr,
				(w->threed.surface && lineGC) ?
				lineGC : w->threed.frameGC,
				&points[0], vert + 1, True);
		}
	}
#ifdef USE_SOUND
	if (w->threed.sound) {
		static int oldSurface = -1;

		if (clickSurface != oldSurface && clickSurface >= 0) {
			playSound(BUMPSOUND);
			oldSurface = clickSurface;
		} else if (clickSurface < 0) {
			oldSurface = -1;
		}
	}
#endif

}

static void
copyFromBuffer(ThreeDWidget w)
{
#ifdef WINVER
	w->core.hOldBitmap = (HBITMAP) SelectObject(w->core.memDC,
		w->threed.bufferObjects[0]);
	BitBlt(w->core.hDC,
		0, 0, /* dest */
		w->core.width, w->core.height,
		w->core.memDC,
		0, 0, /* src */
		SRCCOPY);
	(void) SelectObject(w->core.memDC, w->core.hOldBitmap);
#else
	XSetGraphicsExposures(XtDisplay(w), w->threed.graphicsGC, False);
	XCopyArea(XtDisplay(w), w->threed.bufferObjects[0], XtWindow(w),
		w->threed.graphicsGC,
		0, 0, /* src */
		w->core.width, w->core.height,
		0, 0); /* dest */
#endif
}

#define BRIGHT_FACTOR 0.8
#define DARK_FACTOR 0.75
#ifdef WINVER
#define MAX_INTENSITY 0xFF
static int
brighter(const int light)
{
	int i = (int) ((1 - BRIGHT_FACTOR) * MAX_INTENSITY);
	int temp = light;

	if (temp < i)
		temp = i;
	return MIN(temp / BRIGHT_FACTOR, MAX_INTENSITY);
}

static int
darker(const int light) {
	return (int) (light * DARK_FACTOR);
}

#else
#define MAX_INTENSITY 0xFFFF

static Pixel
brighter(ThreeDWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));
	int i = (int) ((1 - BRIGHT_FACTOR) * MAX_INTENSITY);

	color.pixel = pixel;
	XQueryColor(XtDisplay(w), colormap, &color);
	if (color.red < i)
		color.red = i;
	if (color.green < i)
		color.green = i;
	if (color.blue < i)
		color.blue = i;
	color.red = (unsigned short) MIN(color.red / BRIGHT_FACTOR, MAX_INTENSITY);
	color.green = (unsigned short) MIN(color.green / BRIGHT_FACTOR, MAX_INTENSITY);
	color.blue = (unsigned short) MIN(color.blue / BRIGHT_FACTOR, MAX_INTENSITY);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static Pixel
darker(ThreeDWidget w, Pixel pixel)
{
	XColor color;
	Colormap colormap = DefaultColormapOfScreen(XtScreen(w));

	color.pixel = pixel;
	XQueryColor(XtDisplay(w), colormap, &color);
	color.red = (unsigned short) (color.red * DARK_FACTOR);
	color.green = (unsigned short) (color.green * DARK_FACTOR);
	color.blue = (unsigned short) (color.blue * DARK_FACTOR);
	if (XAllocColor(XtDisplay(w), colormap, &color))
		return color.pixel;
	return pixel;
}

static void
draw3DFrame(const ThreeDWidget w, Pixmap dr, Boolean inside,
	int x, int y, int width, int height)
{
	GC gc;

	if (inside)
		gc = w->threed.inverseGC[2];
	else
		gc = w->threed.inverseGC[1];
	FILLRECTANGLE(w, dr, gc,
		x, y, width, 1);
	FILLRECTANGLE(w, dr, gc,
		x, y + 1, 1, height - 1);
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + 1, width - 2, 1);
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + 2, 1, height - 3);
	if (inside)
		gc = w->threed.inverseGC[0];
	else
		gc = w->threed.inverseGC[1];
	FILLRECTANGLE(w, dr, gc,
		x + 1, y + height - 1, width - 1, 1);
	FILLRECTANGLE(w, dr, gc,
		x + width - 1, y + 1, 1, height - 2);
	FILLRECTANGLE(w, dr, gc,
		x + 2, y + height - 2, width - 3, 1);
	FILLRECTANGLE(w, dr, gc,
		x + width - 2, y + 2, 1, height - 4);
}
#endif

#if 0
static void
eraseFrame(ThreeDWidget w, Pixmap dr, Boolean focus)
{
	FILLRECTANGLE(w, dr, w->threed.inverseGC[1],
		0, 0, w->core.width, w->core.height);
}
#endif

static void
drawFrame(ThreeDWidget w, Pixmap dr, Boolean focus)
{
#ifdef WINVER
	DRAWRECTANGLE(w, dr, (focus) ? w->threed.frameGC : w->threed.borderGC,
		0, 0, w->core.width - 1, w->core.height - 1);
#else
	draw3DFrame(w, dr, focus,
		1, 1, w->core.width - 2, w->core.height - 2);
	if (focus) {
		DRAWRECTANGLE(w, dr, w->threed.frameGC,
			0, 0, w->core.width - 1, w->core.height - 1);
	} else {
		DRAWRECTANGLE(w, dr, w->threed.inverseGC[1],
			0, 0, w->core.width - 1, w->core.height - 1);
	}
#endif
}

static void
speedObjects(ThreeDWidget w)
{
	w->threed.delay -= 5;
	if (w->threed.delay < 0)
		w->threed.delay = 0;
#ifdef HAVE_MOTIF
	setThreed(w, ACTION_SPEED);
#endif
}

static void
slowObjects(ThreeDWidget w)
{
	w->threed.delay += 5;
#ifdef HAVE_MOTIF
	setThreed(w, ACTION_SPEED);
#endif
}

static void
soundObjects(ThreeDWidget w)
{
	w->threed.sound = !w->threed.sound;
}

static char *
readLine(FILE *fp, char *string, char special)
{
#define MAX_LINE 1024
	char buffer[MAX_LINE];
	int length, i = 0, j = 0;
	Boolean parsed = False;

	for (;;) {
		if (!fgets(buffer, MAX_LINE, fp)) {
			return NULL;
		}
		length = (int) strlen(buffer);
		buffer[length - 1] = '\0';
		i = 0;
		while (special != '\0' && buffer[i] != special &&
				buffer[i] != '\0') {
			if (buffer[i] == '#' ||
					buffer[i] == '!' ||
					buffer[i] == ';') {
				i = length - 1;
			} else
				i++;
		}
		if (special != '\0' && buffer[i] == special) {
			i++;
			special = '\0';
		}
		while (buffer[i] == ' ')
			i++;
		parsed = False;
		j = 0;
		while (!parsed) {
			if (buffer[i] == '#' ||
					buffer[i] == '!' ||
					buffer[i] == ';') {
				string[j] = '\0';
				parsed = True;
			} else {
				string[j] = buffer[i];
				if (string[j] == '\0')
					parsed = True;
				i++;
				j++;
			}
		}
		if (strlen(string)) {
			return string;
		}
	}
}

static void
readCommon(ThreeDWidget w, FILE *fp, char *buffer)
{
	char *string;
	int i, j, k, l;
	double scale;

	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.numObjects));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.distance.x));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.distance.y));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.distance.z));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.angle.theta));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.angle.phi));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.angle.psi));
	w->threed.deltaAngle.theta = 0;
	w->threed.deltaAngle.phi = 0;
	w->threed.deltaAngle.psi = 0;
	w->threed.objects = (Object3D *) malloc(sizeof (Object3D) *
		(size_t) w->threed.numObjects);
#ifdef DEBUG
	(void) fprintf(stderr, "objects%c%d\n", SYMBOL, w->threed.numObjects);
	(void) fprintf(stderr, "distanceX%c%d\n", SYMBOL,
		w->threed.distance.x);
	(void) fprintf(stderr, "distanceY%c%d\n", SYMBOL,
		w->threed.distance.y);
	(void) fprintf(stderr, "distanceZ%c%d\n", SYMBOL,
		w->threed.distance.z);
	(void) fprintf(stderr, "angleTheta%c%d\n", SYMBOL,
		w->threed.angle.theta);
	(void) fprintf(stderr, "anglePhi%c%d\n", SYMBOL,
		w->threed.angle.phi);
	(void) fprintf(stderr, "anglePsi%c%d\n", SYMBOL,
		w->threed.angle.psi);
#endif
	for (i = 0; i < w->threed.numObjects; i++) {
		Object3D *obj = &(w->threed.objects[i]);

		string = readLine(fp, buffer, SYMBOL);
		obj->name = (char *) malloc(strlen(string) + 1);
		(void) strcpy(obj->name, string);
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%lg", &(obj->origin.x));
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%lg", &(obj->origin.y));
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%lg", &(obj->origin.z));
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%lg", &scale);
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%d", &j);
		obj->convex = (j != 0);
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%d", &(obj->numVertices));
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%d", &(obj->numSurfaces));
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%d", &(obj->numEdges));
		obj->local = NULL;
		obj->local = (Point3D *) malloc(sizeof (Point3D) *
			(size_t) obj->numVertices);
		obj->world = NULL;
		obj->world = (Point3D *) malloc(sizeof (Point3D) *
			(size_t) obj->numVertices); /* rui */
		obj->vertex = NULL;
		obj->vertex = (Vertex *) malloc(sizeof (Vertex) *
			(size_t) obj->numVertices); /* rua */
		obj->surface = NULL;
		obj->surface = (Surface *) malloc(sizeof (Surface) *
			(size_t) obj->numSurfaces);
		/* numEdges is 2 * the actual edges of the polyhedron */
		obj->map = NULL;
		obj->map = (int *) malloc(sizeof (int) *
			(size_t) obj->numEdges);
#ifdef DEBUG
		(void) fprintf(stderr, "\nNAME%c%s\n", SYMBOL, obj->name);
		(void) fprintf(stderr, "originX%c%lg\n", SYMBOL,
			obj->origin.x);
		(void) fprintf(stderr, "originY%c%lg\n", SYMBOL,
			obj->origin.y);
		(void) fprintf(stderr, "originZ%c%lg\n", SYMBOL,
			obj->origin.z);
		(void) fprintf(stderr, "Scale%c%g\n", SYMBOL, scale);
		(void) fprintf(stderr, "Convex%c%d\n", SYMBOL, obj->convex);
		(void) fprintf(stderr, "Vertices%c%d\n", SYMBOL,
			obj->numVertices);
		(void) fprintf(stderr, "Surfaces%c%d\n", SYMBOL,
			obj->numSurfaces);
		(void) fprintf(stderr, "Edges%c%d\n", SYMBOL,
			obj->numEdges);
#endif
		string = readLine(fp, buffer, SYMBOL);
		for (j = 0; j < obj->numVertices; j++) {
			(void) sscanf(string, "%lg %lg %lg",
				&(obj->local[j].x),
				&(obj->local[j].y),
				&(obj->local[j].z));
			obj->local[j].x *= scale;
			obj->local[j].y *= scale;
			obj->local[j].z *= scale;
			if (j < obj->numVertices - 1)
				string = readLine(fp, buffer, '\0');
		}
#ifdef DEBUG
		(void) fprintf(stderr, "\nVERTICES%c\n", SYMBOL);
		for (j = 0; j < obj->numVertices; j++)
			(void) fprintf(stderr, "%g\t%g\t%g\n",
				obj->local[j].x / scale,
				obj->local[j].y / scale,
				obj->local[j].z / scale);
#endif
		string = readLine(fp, buffer, SYMBOL);
		l = 0;
		for (j = 0; j < obj->numSurfaces; j++) {
			char *str;

			str = string;
			(void) sscanf(string, "%d",
				&(obj->surface[j].numVertices));
			for (k = 0; k < obj->surface[j].numVertices; k++) {
				while (*str != ' ' &&
						*str != '\t' && *str != '\0')
					str++;
				(void) sscanf(str, "%d", &(obj->map[l++]));
				if (*str != '\0')
					str++;
			}
			if (j < obj->numSurfaces - 1)
				string = readLine(fp, buffer, '\0');

		}
#ifdef DEBUG
		(void) fprintf(stderr, "\nEDGES%c\n", SYMBOL);
		l = 0;
		for (j = 0; j < obj->numSurfaces; j++) {
			(void) fprintf(stderr, "%d\t",
				obj->surface[j].numVertices);
			for (k = 0; k < obj->surface[j].numVertices; k++)
				(void) fprintf(stderr, "%d\t",
					obj->map[l++] + 1);
			(void) fprintf(stderr, "\n");
		}
#endif
		string = readLine(fp, buffer, SYMBOL);
		for (j = 0; j < obj->numSurfaces; j++) {
			(void) sscanf(string, "%d %d %d",
				&(obj->surface[j].mapIndex),
				&(obj->surface[j].depthIndex),
				&(obj->surface[j].color));
			if (j < obj->numSurfaces - 1)
				string = readLine(fp, buffer, '\0');
		}
#ifdef DEBUG
		(void) fprintf(stderr, "\nSURFACES%c\n", SYMBOL);
		for (j = 0; j < obj->numSurfaces; j++)
			(void) fprintf(stderr, "%d\t%d\t%d\n",
				obj->surface[j].mapIndex,
				obj->surface[j].depthIndex,
				obj->surface[j].color);
#endif
		for (j = 0; j < obj->numSurfaces; j++) {
			Vector3D u, v, normal;
			int vertex[3];
			int *vertexList = &(obj->map[obj->surface[j].mapIndex]);

			obj->surface[j].twoSided = False;
			obj->surface[j].visible = 1;
			obj->surface[j].clipped = False;
			obj->surface[j].active = True;
			if (obj->surface[j].numVertices >= 3) {
				vertex[0] = *vertexList;
				vertexList++;
				vertex[1] = *vertexList;
				vertexList++;
				vertex[2] = *vertexList;
				createVector3D(
					&(obj->local[vertex[0]]),
					&(obj->local[vertex[1]]),
					&u);
				createVector3D(
					&(obj->local[vertex[0]]),
					&(obj->local[vertex[2]]),
					&v);
				crossProduct3D(&v, &u, &normal);
				obj->surface[j].normalLength =
					magnitudeVector3D(&normal);
			} else {
				obj->surface[j].normalLength = 0.0;
			}
		}
		obj->radius = maximumObjectRadius(obj);
	}
}

#ifdef WINVER
static FILE *
openIni(const HWND hWnd, const char *filename)
{
	unsigned wReturn;
	char buf[256], szBuf[144];
	FILE *fp;

#ifdef DEBUG
	(void) fprintf(stderr, "INI=%s\n", filename);
#endif
	if ((fp = fopen(filename, "r")) == NULL) {
		wReturn = GetWindowsDirectory((LPSTR) szBuf, sizeof (szBuf));
		if (!wReturn || wReturn > sizeof (szBuf)) {
			(void) MessageBox(hWnd,
				(!wReturn) ? "threed: function failed" :
			"threed: buffer is too small", "GetWindowsDirectory",
					MB_ICONEXCLAMATION);
#ifdef DEBUG
			(void) fclose(fp);
#endif
			exit(1);
		}
		(void) sprintf(buf, "%s\\%s", szBuf, filename);
#ifdef DEBUG
		(void) fprintf(stderr, "INI=%s\n", buf);
#endif
		if ((fp = fopen(buf, "r")) == NULL) {
			(void) sprintf(buf,
				"%s: does not exist in \".\" or windows directory", filename);
			(void) MessageBox(hWnd, buf, "openIni", MB_ICONEXCLAMATION);
#ifdef DEBUG
			(void) fclose(fp);
#endif
			exit(1);
		}
	}
#ifdef DEBUG
	(void) fprintf(stderr, "Exiting openIni\n");
#endif

	return fp;
}
/*-
   Not using GetProfile[String,Int] because most of the data is on
   multiple lines, repeated, and dynamic.  Data must be in proper order
   i.e. information left of "=" is not read.
 */
static void
readThreeD(ThreeDWidget w)
{
	FILE *fp;
	char buffer[MAX_LINE];
	char *string;
	int i;
	struct tagColor {
		int red, green, blue;
	} color;

	fp = openIni(w->core.hWnd, DATAFILE);
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->threed.frameGC = RGB(color.red, color.green, color.blue);
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->threed.inverseGC[1] = RGB(color.red, color.green, color.blue);
	w->threed.inverseGC[0] = RGB(brighter(color.red),
		brighter(color.green), brighter(color.blue));
	w->threed.inverseGC[2] = RGB(darker(color.red),
		darker(color.green), darker(color.blue));
	for (i = 0; i < COLORS; i++) {
		string = readLine(fp, buffer, SYMBOL);
		(void) sscanf(string, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->threed.shadeGC[i] = RGB(color.red, color.green, color.blue);
	}
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->threed.borderGC = RGB(color.red, color.green, color.blue);
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.delay));
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &i);
	w->threed.sound = (i != 0);
	string = readLine(fp, buffer, SYMBOL);
	(void) strncpy(w->threed.bumpSound, string, 80);
	string = readLine(fp, buffer, SYMBOL);
	(void) strncpy(w->threed.moveSound, string, 80);
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &i);
	w->threed.surface = (i != 0);
	string = readLine(fp, buffer, SYMBOL);
	(void) sscanf(string, "%d", &(w->threed.object));
#ifdef DEBUG
	(void) fprintf(stderr, "foreground%c%d %d %d\n", SYMBOL,
		GetRValue(w->threed.frameGC),
		GetGValue(w->threed.frameGC),
		GetBValue(w->threed.frameGC));
	for (i = 0; i < BG_SHADES; i++)
		(void) fprintf(stderr, "inverse%d%c%d %d %d\n", i, SYMBOL,
			GetRValue(w->threed.inverseGC[i]),
			GetGValue(w->threed.inverseGC[i]),
			GetBValue(w->threed.inverseGC[i]));
	for (i = 0; i < COLORS; i++)
		(void) fprintf(stderr, "brush%d%c%d %d %d\n", i, SYMBOL,
			GetRValue(w->threed.shadeGC[i]),
			GetGValue(w->threed.shadeGC[i]),
			GetBValue(w->threed.shadeGC[i]));
	(void) fprintf(stderr, "border%c%d %d %d\n", SYMBOL,
		GetRValue(w->threed.borderGC),
		GetGValue(w->threed.borderGC),
		GetBValue(w->threed.borderGC));
	(void) fprintf(stderr, "surface%c%d\n", SYMBOL,
		(w->threed.surface) ? 1 : 0 );
	(void) fprintf(stderr, "object%c%d\n", SYMBOL, w->threed.object);
#endif
	readCommon(w, fp, buffer);
	(void) fclose(fp);
}

void
destroyThreeD(HBRUSH brush)
{
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

#else

static void
readThreeD(ThreeDWidget w)
{
	FILE *fp;
	char buffer[MAX_LINE];
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, DATAFILE);
	lname = buf1;
	stringCat(&buf1, DATAPATH, FINALDELIM);
	stringCat(&buf2, buf1, DATAFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "r")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "r")) == NULL) {
			stringCat(&buf1, "Can not read ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
	}
	free(lname);
	free(fname);
	readCommon(w, fp, buffer);
	(void) fclose(fp);
}

static Boolean
setValuesThreeD(Widget current, Widget request, Widget renew)
{
	ThreeDWidget c = (ThreeDWidget) current, w = (ThreeDWidget) renew;
	Boolean redraw = FALSE;

	if (w->threed.numObjects == 0)
		return False;
	checkGraphicsThreeD(w);
	if (w->threed.foreground != c->threed.foreground) {
		XGCValues values;
		XtGCMask valueMask;

		valueMask = GCForeground | GCBackground;
		values.foreground = w->threed.foreground;
		values.background = w->core.background_pixel;
		XtReleaseGC(renew, w->threed.graphicsGC);
		w->threed.graphicsGC = XtGetGC(renew, valueMask, &values);
		redraw = TRUE;
	}
	if (w->threed.distance.x != c->threed.distance.x ||
			w->threed.distance.y != c->threed.distance.y ||
			w->threed.distance.z != c->threed.distance.z ||
			w->threed.angle.theta != c->threed.angle.theta ||
			w->threed.angle.phi != c->threed.angle.phi ||
			w->threed.angle.psi != c->threed.angle.psi ||
			w->threed.surface != c->threed.surface ||
			w->threed.object != c->threed.object) {
		w->threed.name = w->threed.objects[w->threed.object].name;
#ifdef DEBUG
		(void) printf("%s\n", w->threed.name);
#endif
		w->threed.deltaAngle.theta = w->threed.angle.theta -
			c->threed.angle.theta;
		w->threed.deltaAngle.phi = w->threed.angle.phi -
			c->threed.angle.phi;
		w->threed.deltaAngle.psi = w->threed.angle.psi -
			c->threed.angle.psi;
		resizeThreeD(w);
		redraw = TRUE;
	}
	if (w->threed.menu != ACTION_IGNORE) {
		int menu = w->threed.menu;

		w->threed.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_SPEED:
			speedObjects(w);
			break;
		case ACTION_SLOW:
			slowObjects(w);
			break;
		case ACTION_SOUND:
			soundObjects(w);
			break;
		default:
			break;
		}
	}
	return (redraw);
}

static void
setAllColors(ThreeDWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int color;

	valueMask = GCForeground | GCBackground;
	if (w->threed.reverse) {
		values.foreground = w->threed.foreground;
		values.background = w->threed.background;
	} else {
		values.foreground = w->threed.background;
		values.background = w->threed.foreground;
	}
	if (w->threed.inverseGC[1])
		XtReleaseGC((Widget) w, w->threed.inverseGC[1]);
	w->threed.inverseGC[1] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->threed.mono) {
		values.foreground = brighter(w, (w->threed.reverse) ?
			w->threed.foreground : w->threed.background);
	}
	if (w->threed.inverseGC[0])
		XtReleaseGC((Widget) w, w->threed.inverseGC[0]);
	w->threed.inverseGC[0] = XtGetGC((Widget) w, valueMask, &values);
	if (!w->threed.mono) {
		values.foreground = darker(w, (w->threed.reverse) ?
			w->threed.foreground : w->threed.background);
	}
	if (w->threed.inverseGC[2])
		XtReleaseGC((Widget) w, w->threed.inverseGC[2]);
	w->threed.inverseGC[2] = XtGetGC((Widget) w, valueMask, &values);
	if (w->threed.reverse) {
		values.background = w->threed.foreground;
		values.foreground = w->threed.background;
	} else {
		values.background = w->threed.background;
		values.foreground = w->threed.foreground;
	}
	if (w->threed.graphicsGC)
		XtReleaseGC((Widget) w, w->threed.graphicsGC);
	w->threed.graphicsGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->threed.mono) {
		if (w->threed.reverse) {
			values.background = w->threed.foreground;
			values.foreground = w->threed.background;
		} else {
			values.background = w->threed.background;
			values.foreground = w->threed.foreground;
		}
	} else {
		values.foreground = w->threed.frameColor;
		values.background = w->threed.borderColor;
	}
	if (w->threed.frameGC)
		XtReleaseGC((Widget) w, w->threed.frameGC);
	w->threed.frameGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->threed.mono) {
		if (w->threed.reverse) {
			values.foreground = w->threed.foreground;
			values.background = w->threed.background;
		} else {
			values.foreground = w->threed.background;
			values.background = w->threed.foreground;
		}
	} else {
		values.foreground = w->threed.borderColor;
		values.background = w->threed.frameColor;
	}
	if (w->threed.borderGC)
		XtReleaseGC((Widget) w, w->threed.borderGC);
	w->threed.borderGC = XtGetGC((Widget) w, valueMask, &values);
	for (color = 0; color < COLORS; color++) {
		if (!w->threed.mono) {
			values.foreground = w->threed.shadeColor[color];
		}
		if (w->threed.shadeGC[color])
			XtReleaseGC((Widget) w, w->threed.shadeGC[color]);
		w->threed.shadeGC[color] = XtGetGC((Widget) w, valueMask,
			&values);
	}
}

static void
quitThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	XtCloseDisplay(XtDisplay(w));
	exit(0);
}

static void
destroyThreeD(Widget old)
{
	ThreeDWidget w = (ThreeDWidget) old;
	int i;

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	(void) shutdown_sound();
#endif
	for (i = 0; i < COLORS; i++)
		XtReleaseGC(old, w->threed.shadeGC[i]);
	XtReleaseGC(old, w->threed.graphicsGC);
	XtReleaseGC(old, w->threed.borderGC);
	XtReleaseGC(old, w->threed.frameGC);
	for (i = 0; i < BG_SHADES; i++)
		XtReleaseGC(old, w->threed.inverseGC[i]);
	XtRemoveCallbacks(old, XtNselectCallback, w->threed.select);
}
#endif

static void
repaintGraphicsThreeD(ThreeDWidget w)
{
	int sel;

#ifdef WINVER
	if (w->core.memDC == NULL) {
		w->core.memDC = CreateCompatibleDC(w->core.hDC);
		if (w->core.memDC == NULL) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
#else
	Display *display = XtDisplay(w);
	Window window = XtWindow(w);
	XWindowAttributes xgwa;

	(void) XGetWindowAttributes(display, window, &xgwa);
	if (w->threed.oldColormap == None) {
		w->threed.mono = (xgwa.depth < 2 || w->threed.mono);
		w->threed.oldColormap = xgwa.colormap;
	}
#endif
	if (w->threed.numObjects == 0)
		return;
	for (sel = 0; sel < 2; sel++) {
#ifdef WINVER
		if (w->threed.bufferObjects[sel] != NULL) {
			DeleteObject(w->threed.bufferObjects[sel]);
			w->threed.bufferObjects[sel] = NULL;
		}
		if ((w->threed.bufferObjects[sel] = CreateCompatibleBitmap(
				w->core.hDC,
				w->core.width, w->core.height)) == NULL) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
#else
		if (w->threed.bufferObjects[sel] != None) {
			XFreePixmap(display, w->threed.bufferObjects[sel]);
			w->threed.bufferObjects[sel] = None;
		}
		if ((w->threed.bufferObjects[sel] = XCreatePixmap(display,
				window, w->core.width, w->core.height,
				xgwa.depth)) == None) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
#endif
	}
#ifndef WINVER
	setAllColors(w);
#endif
	/*eraseFrame(w, 0, w->threed.focus);*/
	/*drawAllBufferedGraphics(w);
	drawAllGraphics(w);
#ifndef WINVER
	XClearWindow(XtDisplay(w), XtWindow(w));
#endif */
	FILLRECTANGLE(w, w->threed.bufferObjects[0], w->threed.inverseGC[1],
		0, 0, w->core.width, w->core.height);
	realizeObject(w, &(w->threed.objects[w->threed.object]));
	drawObject(w, &(w->threed.objects[w->threed.object]));
	copyFromBuffer(w);
}

static void
resizeThreeD(ThreeDWidget w)
{
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif
	w->threed.center.x = w->core.width / 2;
	w->threed.center.y = w->core.height / 2;
	w->threed.size = MIN(w->threed.center.x, w->threed.center.y);
	w->threed.currentPosition.x = 2 * w->core.width;
	w->threed.currentPosition.y = 2 * w->core.height;
}

#ifndef WINVER
static
#endif
void
initializeThreeD(
#ifdef WINVER
ThreeDWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
	int i;

#ifdef WINVER

	w->threed.mono = DEFAULT_MONO;
	w->threed.reverse = DEFAULT_REVERSE;
	w->threed.bufferObjects[0] = NULL;
	w->threed.bufferObjects[1] = NULL;
#else
	ThreeDWidget w = (ThreeDWidget) renew;

	w->threed.mono = (DefaultDepthOfScreen(XtScreen(w)) < 2 ||
		w->threed.mono);
	w->threed.bufferObjects[0] = None;
	w->threed.bufferObjects[1] = None;
	w->threed.colormap = None;
	w->threed.oldColormap = None;
	w->threed.fontInfo = NULL;
	for (i = 0; i < COLORS; i++)
		w->threed.shadeGC[i] = NULL;
	w->threed.graphicsGC = NULL;
	w->threed.borderGC = NULL;
	w->threed.frameGC = NULL;
	for (i = 0; i < BG_SHADES; i++)
		w->threed.inverseGC[i] = NULL;
#endif
	createTrigTables();
	readThreeD(w);
	checkGraphicsThreeD(w);
	if (w->threed.numObjects > 0) {
		w->threed.name = w->threed.objects[w->threed.object].name;
		if (!(w->threed.list = (char **) calloc((unsigned int)
				w->threed.numObjects, sizeof (char **)))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		for (i = 0; i < w->threed.numObjects; i++)
			w->threed.list[i] = w->threed.objects[i].name;
	}
	w->threed.deltaAngle.theta = w->threed.angle.theta;
	w->threed.deltaAngle.phi = w->threed.angle.phi;
	w->threed.deltaAngle.psi = w->threed.angle.psi;
	resizeThreeD(w);
#ifdef DEBUG
	(void) printf("%s\n", w->threed.name);
#endif
#ifdef WINVER
	brush = CreateSolidBrush(w->threed.inverseGC[1]);
	SETBACK(w->core.hWnd, brush);
#else
	setAllColors(w);
#endif
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	(void) init_sound();
#endif
#endif
}

#ifndef WINVER
static
#endif
void
exposeThreeD(
#ifdef WINVER
ThreeDWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	ThreeDWidget w = (ThreeDWidget) renew;

	if (!w->core.visible)
		return;
#endif
	drawFrame(w, 0, w->threed.focus);
	repaintGraphicsThreeD(w);
}

#ifndef WINVER
static
#endif
void
hideThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	setThreed(w, ACTION_HIDE);
}

#ifndef WINVER
static
#endif
void
selectThreeD(ThreeDWidget w
#ifdef WINVER
, const int x, const int y/*, const int control*/
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	/*int control = (int) (event->xkey.state & ControlMask);*/
#endif
	w->threed.currentPosition.x = x;
	w->threed.currentPosition.y = y;
	repaintGraphicsThreeD(w);
}

#ifndef WINVER
static
#endif
void
motionThreeD(ThreeDWidget w
#ifdef WINVER
, const int x, const int y/*, const int shift, const int control*/
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	/*int shift = (int) (event->xbutton.state & (ShiftMask | LockMask));
	int control = (int) (event->xkey.state & ControlMask);*/
#endif
	int oldAngleTheta = w->threed.angle.theta;
	int oldAnglePhi = w->threed.angle.phi;

	/* In X, it appears motion event is generated before a select */
	if (w->threed.currentPosition.x == 2 * w->core.width &&
			w->threed.currentPosition.y == 2 * w->core.height) {
		return;
	}
	w->threed.angle.theta += w->threed.currentPosition.x - x;
	w->threed.angle.phi += w->threed.currentPosition.y - y;
	w->threed.angle.theta = (64 * NUM_DEGREES + w->threed.angle.theta) %
		NUM_DEGREES;
	w->threed.angle.phi = (64 * NUM_DEGREES + w->threed.angle.phi) %
		NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngleTheta;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAnglePhi;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
	w->threed.currentPosition.x = x;
	w->threed.currentPosition.y = y;
}

#ifndef WINVER
static
#endif
void
releaseThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->threed.currentPosition.x = 2 * w->core.width;
	w->threed.currentPosition.y = 2 * w->core.height;
	repaintGraphicsThreeD(w);
}

void
speedThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	speedObjects(w);
}

void
slowThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	slowObjects(w);
}

void
soundThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	soundObjects(w);
}

#ifndef WINVER
static
#endif
void
surfaceThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int n_args
#endif
)
{
	setThreed(w, ACTION_SURFACE);
}

#ifndef WINVER
static
#endif
void
objectThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int n_args
#endif
)
{
	setThreed(w, ACTION_OBJECT);
}

#ifdef WINVER
void
setobjectThreeD(ThreeDWidget w, int object3D)
{
	w->threed.object = object3D;
}
#endif

#ifndef WINVER
void
enterThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->threed.focus = True;
	drawFrame(w, 0, w->threed.focus);
}

void
leaveThreeD(ThreeDWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->threed.focus = False;
	drawFrame(w, 0, w->threed.focus);
}

static void
moveLowerPhiThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.phi;

	w->threed.angle.phi -= DELTADEGREES;
	if (w->threed.angle.phi < MIN_DEGREES)
		w->threed.angle.phi += NUM_DEGREES;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveLowerThetaThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.theta;

	w->threed.angle.theta -= DELTADEGREES;
	if (w->threed.angle.theta < MIN_DEGREES)
		w->threed.angle.theta += NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveRaiseThetaThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.theta;

	w->threed.angle.theta += DELTADEGREES;
	if (w->threed.angle.theta > MAX_DEGREES)
		w->threed.angle.theta -= NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveRaisePhiThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.phi;

	w->threed.angle.phi += DELTADEGREES;
	if (w->threed.angle.phi > MAX_DEGREES)
		w->threed.angle.phi -= NUM_DEGREES;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveLowerPsiThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.psi;

	w->threed.angle.psi -= DELTADEGREES;
	if (w->threed.angle.psi < MIN_DEGREES)
		w->threed.angle.psi += NUM_DEGREES;
	w->threed.deltaAngle.psi = w->threed.angle.psi - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveRaisePsiThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	int oldAngle = w->threed.angle.psi;

	w->threed.angle.psi += DELTADEGREES;
	if (w->threed.angle.psi > MAX_DEGREES)
		w->threed.angle.psi -= NUM_DEGREES;
	w->threed.deltaAngle.psi = w->threed.angle.psi - oldAngle;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveLowerThetaLowerPhiThreeD(ThreeDWidget w, XEvent *event,
		char **args, int n_args)
{
	int oldAngleTheta = w->threed.angle.theta;
	int oldAnglePhi = w->threed.angle.phi;

	w->threed.angle.theta -= DELTADEGREES;
	w->threed.angle.phi -= DELTADEGREES;
	if (w->threed.angle.theta < MIN_DEGREES)
		w->threed.angle.theta += NUM_DEGREES;
	if (w->threed.angle.phi < MIN_DEGREES)
		w->threed.angle.phi += NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngleTheta;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAnglePhi;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveLowerThetaRaisePhiThreeD(ThreeDWidget w, XEvent *event,
		char **args, int n_args)
{
	int oldAngleTheta = w->threed.angle.theta;
	int oldAnglePhi = w->threed.angle.phi;

	w->threed.angle.theta -= DELTADEGREES;
	w->threed.angle.phi += DELTADEGREES;
	if (w->threed.angle.theta < MIN_DEGREES)
		w->threed.angle.theta += NUM_DEGREES;
	if (w->threed.angle.phi > MAX_DEGREES)
		w->threed.angle.phi -= NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngleTheta;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAnglePhi;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
oveRaiseThetaLowerPhiThreeD(ThreeDWidget w, XEvent *event,
		char **args, int n_args)
{
	int oldAngleTheta = w->threed.angle.theta;
	int oldAnglePhi = w->threed.angle.phi;

	w->threed.angle.theta += DELTADEGREES;
	w->threed.angle.phi -= DELTADEGREES;
	if (w->threed.angle.theta > MAX_DEGREES)
		w->threed.angle.theta -= NUM_DEGREES;
	if (w->threed.angle.phi < MIN_DEGREES)
		w->threed.angle.phi += NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngleTheta;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAnglePhi;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveRaiseThetaRaisePhiThreeD(ThreeDWidget w, XEvent *event,
		char **args, int n_args)
{
	int oldAngleTheta = w->threed.angle.theta;
	int oldAnglePhi = w->threed.angle.phi;

	w->threed.angle.theta += DELTADEGREES;
	w->threed.angle.phi += DELTADEGREES;
	if (w->threed.angle.theta > MAX_DEGREES)
		w->threed.angle.theta -= NUM_DEGREES;
	if (w->threed.angle.phi > MAX_DEGREES)
		w->threed.angle.phi -= NUM_DEGREES;
	w->threed.deltaAngle.theta = w->threed.angle.theta - oldAngleTheta;
	w->threed.deltaAngle.phi = w->threed.angle.phi - oldAnglePhi;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveRightThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.x < MAX_DISTANCE)
		w->threed.distance.x += DELTADISTANCE;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveLeftThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.x > MINDISTANCE)
		w->threed.distance.x -= DELTADISTANCE;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveUpThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.y < MAX_DISTANCE)
		w->threed.distance.y += DELTADISTANCE;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveDownThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.y > MINDISTANCE)
		w->threed.distance.y -= DELTADISTANCE;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveOutThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.z < MAX_DEPTH)
		w->threed.distance.z += DELTADEPTH;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}

static void
moveInThreeD(ThreeDWidget w, XEvent *event, char **args, int n_args)
{
	if (w->threed.distance.z > MIN_DEPTH)
		w->threed.distance.z -= DELTADEPTH;
	setThreed(w, ACTION_SET);
	repaintGraphicsThreeD(w);
}
#endif
