/* Use "lc -Lm -cw 3droll.c" to compile (Lattice) */

/* (C) 1990 Keith Gravlin */

/* Program draws a three-d object which can be manipulated with the mouse. */
/* Left and right (pick button up) will adjust yaw. */
/* Up and down (pick button up) adjust pitch. */
/* Left and right (pick button down) adjust roll. */
/* Up and down (pick button down) adjust zoom in/out. */

#include "exec/types.h"
#include "exec/memory.h"
#include "intuition/intuition.h"
#include "graphics/gfxmacros.h"
#include "math.h"

long GfxBase = NULL;
long IntuitionBase = NULL;

extern struct Screen *OpenScreen();
extern struct Window *OpenWindow();

struct Screen *MyScreen = NULL;
struct Window *MyWindow = NULL;

struct IntuiMessage *MyIntuiMessage = NULL;
struct IntuiMessage IntuiMessageBuffer;

struct RastPort *MyRastPort = NULL;


#define SWIDTH 640
#define SHEIGHT 400
#define SDEPTH 1

struct NewScreen MyScreenData = {
0,0,				/* start postion */
SWIDTH,SHEIGHT,SDEPTH,		/* width, height, depth */
0,1,				/* detail pen, block pen */
HIRES | LACE,			/* viewing mode */
CUSTOMSCREEN,			/* screen type */
NULL,				/* font to use */
NULL,				/* screen title */
NULL				/* pointer to gadgets */
};

#define	WWIDTH SWIDTH
#define	WHEIGHT SHEIGHT

struct	NewWindow MyWindowData = {
0,0,				/* upper left corner of window */
WWIDTH, WHEIGHT,		/* width, height */
0,1,				/* detail pen, block pen */
CLOSEWINDOW | MOUSEBUTTONS | MOUSEMOVE | REFRESHWINDOW,	/* IDCMP flags */
REPORTMOUSE | WINDOWCLOSE | BORDERLESS | SIMPLE_REFRESH, /* window flags */
NULL,				/* pointer to first gadget */
NULL,				/* pointer to checkmark */
"Box Roll",			/* window title */
NULL,				/* screen pointer to screen */
NULL,				/* pointer to superbitmap */
0,0,				/* MinWidth,MinHeight */
0,0,				/* MaxWidth,MaxHeight */
CUSTOMSCREEN };

double	TransX = 0.0, TransY = 0.0, TransZ = -235.0;	/* translation variables */
double	DisplayX = 0.0, DisplayY = 0.0;	/* for display coords */
double	Distortion = 1500.0;	/* distortion/perspective factor */
double	Yaw = 0.0;
double	Roll = 0.0;
double	Pitch = 0.0;
double	SinYaw, CosYaw;		/* intermediate values */
double	SinRoll, CosRoll;
double	SinPitch, CosPitch;

int QuitFlag = FALSE;		/* set TRUE to quit */
int PickButtonDown = FALSE;	/* holds current state of pick button */
int FixedX = NULL, FixedY = NULL;	/* holds fixed part of position */

/* Add/delete/change triplets to produce things other than boxes. */
/* Have fun ! */
int Triplets[][3] = {
	-5,-5,5,
	-5,5,5,
	5,5,5,
	5,-5,5,
	-5,-5,5,
	-5,-5,-5,
	-5,5,-5,
	-5,5,5,
	-5,5,-5,
	5,5,-5,
	5,5,5,
	5,5,-5,
	5,-5,-5,
	5,-5,5,
	5,-5,-5,
	-5,-5,-5,
	5,5,-5,
	5,-5,-5,
	-5,5,-5
};

#define	TRIPLETCOUNT sizeof(Triplets)/(3*sizeof(int))


void	CalcYRP()	/* calc values for current rotations */
{
	SinYaw = sin(Yaw);
	CosYaw = cos(Yaw);
	SinRoll = sin(Roll);
	CosRoll = cos(Roll);
	SinPitch = sin(Pitch);
	CosPitch = cos(Pitch);
}

void	Calc3D(double x,double y,double z)	/* here's my work Ma'm */
{
	double TempX, TempY, TempZ;
	
	TempX = (CosYaw*x)-(SinYaw*z);
	TempY = (CosRoll*y)-(SinRoll*TempX);
	TempZ = (SinYaw*x)+(CosYaw*z);
	x = (CosRoll*TempX)+(SinRoll*y)+TransX;
	y = (SinPitch*TempZ)+(CosPitch*TempY)+TransY;
	z = (CosPitch*TempZ)-(SinPitch*TempY)+TransZ;
	
	DisplayX = ((Distortion*x/z)+319)*WWIDTH/640.0;	/* adjust aspect */
	DisplayY = ((Distortion*y/z)+239)*WHEIGHT/480.0;
}

void	CalcBox(boxarray)
SHORT	*boxarray;
{
	int	x;

	for (x=0;x<TRIPLETCOUNT;x++) {		/* for each corner */
		Calc3D((double)Triplets[x][0],(double)Triplets[x][1],(double)Triplets[x][2]);
		boxarray[x*2] = DisplayX;
		boxarray[x*2+1] = DisplayY;
	}		
}

void	HandleEvent()
{
	switch(MyIntuiMessage->Class) {
		case MOUSEMOVE:
			MouseMove();
			break;
		case MOUSEBUTTONS:
			switch(MyIntuiMessage->Code) {
				case SELECTDOWN:
					PickButtonDown = TRUE;
					break;
				case SELECTUP:
					PickButtonDown = FALSE;
					break;
				default:
					break;
			}
			ReplyMsg(MyIntuiMessage);
			MyIntuiMessage = NULL;
			break;
		case REFRESHWINDOW:
			ReplyMsg(MyIntuiMessage);
			MyIntuiMessage = NULL;
			break;		
		case CLOSEWINDOW:
			ReplyMsg(MyIntuiMessage);
			MyIntuiMessage = NULL;
			QuitFlag = TRUE;
			break;
		default:
			ReplyMsg(MyIntuiMessage);
			MyIntuiMessage = NULL;
			break;
	}
}

#define WIDTHCENTER (WWIDTH-1)/2
#define	HEIGHTCENTER (WHEIGHT-1)/2

MouseMove()
{
	int MyMouseX = NULL, MyMouseY = NULL;
	
	SwallowMoves(10);	/* suck up rapid movements */

	MyMouseX = IntuiMessageBuffer.MouseX - WIDTHCENTER;
	MyMouseY = IntuiMessageBuffer.MouseY - HEIGHTCENTER;

/* Uncommenting the following code will fix x or y, whichever goes */
/* non-zero first. Returning that coord to zero (center of screen) will */
/* free it and allow the other to become fixed. */
/* This was just a way to play with one axis at a time. */
/*	if (FixedX && MyMouseX)
		MyMouseX = FixedX;
	else {
		FixedX = NULL;
		if (FixedY && MyMouseY)
			MyMouseY = FixedY;
		else {
			FixedY = NULL;
			FixedX = MyMouseX;
		}
	}
	if (!FixedX)
		FixedY = MyMouseY;
*/
	if (PickButtonDown) {
		Roll = MyMouseX * 6.283 / WWIDTH;
		TransZ = MyMouseY - HEIGHTCENTER - 35;
	}
	else {
		Yaw = MyMouseX * 6.283 / WWIDTH;
		Pitch = -MyMouseY * 6.283 / WHEIGHT;
	}
}

/* Calling this function with a lower count will decrease jerkyness but */
/* increase the number of redraws, thus causing slower response. */
/* Have it your way ! */
SwallowMoves(count)	/* swallow up to 'count' rapid mouse moves */
int	count;
{
	int i, j;
	UBYTE *source, *dest;
	struct IntuiMessage *NewIntuiMessage = NULL;

	for (j=0;j<count;j++) {
		if (NewIntuiMessage = (struct IntuiMessage *)GetMsg(MyWindow->UserPort)) {
			if (NewIntuiMessage->Class == MOUSEMOVE) {
				ReplyMsg(MyIntuiMessage);
				MyIntuiMessage = NewIntuiMessage;
				NewIntuiMessage = NULL;
			}
			else
				break;
		}
		else
			break;
	}				
	source = (UBYTE *)MyIntuiMessage;
	dest = (UBYTE *)&IntuiMessageBuffer;
	for (i=0;i<sizeof(struct IntuiMessage);i++)
		*dest++ = *source++;
	ReplyMsg(MyIntuiMessage);
	MyIntuiMessage = NewIntuiMessage;
}

main()	/* street USA */
{
	SHORT	BoxArray1[TRIPLETCOUNT*2], BoxArray2[TRIPLETCOUNT*2];
	SHORT	*OldBox, *NewBox;

	if (!(GfxBase = OpenLibrary("graphics.library",0))) {
		printf("Unable to open graphics library\n");
		CleanUp(1000);
	}

	if (!(IntuitionBase = OpenLibrary("intuition.library",0))) {
		printf("Unable to open intuition library\n");
		CleanUp(1000);
	}
	if (!(MyScreen = OpenScreen(&MyScreenData))) {
		CleanUp(1);
	}
	MyWindowData.Screen = MyScreen;

	if (!(MyWindow = OpenWindow(&MyWindowData))) {
		CleanUp(1);
	}
	
	MyRastPort = MyWindow->RPort;
	SetDrMd(MyRastPort,JAM1);

	NewBox = BoxArray1;
	OldBox = BoxArray2;

	CalcYRP();			/* calc initial rotations */
	CalcBox(OldBox);		/* for first erasure */

	while (!QuitFlag) {

		CalcYRP();		/* calc current rotations */
		CalcBox(NewBox);	/* calc corners for rotation */

		SetAPen(MyRastPort,0);	/* get background for erasure */
		Move(MyRastPort,OldBox[0],OldBox[1]);	/* erase old box */
		PolyDraw(MyRastPort,TRIPLETCOUNT-1,&OldBox[2]);

		SetAPen(MyRastPort,1);	/* get foreground for drawing */
		Move(MyRastPort,NewBox[0],NewBox[1]);	/* draw new box */
		PolyDraw(MyRastPort,TRIPLETCOUNT-1,&NewBox[2]);

		WritePixel(MyRastPort,WIDTHCENTER,HEIGHTCENTER); /* dot */

		if ((OldBox = NewBox) == BoxArray1)	/* swap arrays */
			NewBox = BoxArray2;
		else
			NewBox = BoxArray1;

		if (!MyIntuiMessage) {	/* if no message is waiting */
			WaitPort(MyWindow->UserPort);	/* wait for one */
			MyIntuiMessage = (struct IntuiMessage *)GetMsg(MyWindow->UserPort);	/* get it */
		}
		HandleEvent();		/* handle it */
	}
	CleanUp(0);
}

CleanUp(code)
int	code;
{
	if (MyWindow)
		CloseWindow(MyWindow);
	if (MyScreen)
		CloseScreen(MyScreen);
	if (GfxBase)
		CloseLibrary(GfxBase);
	if (IntuitionBase)
		CloseLibrary(IntuitionBase);

	exit(code);		/* the fat lady is singing */
}
