/* animballs.c -- code to handle actual rotating images */
#include <exec/types.h>
#include <stdio.h>
#include <math.h>
#include <intuition/intuition.h>

#include "globals.h"

#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>

int woffset = WOFFSET;
#define SETANDBOUND(ii,ff)	{ii = 0.5 + ff * 15; \
				 if (ii > 15) ii = 15; \
				 else if (ii < 0) ii = 0;}

float ka, kd, ks, lx, ly, lz, n, r;	/* coloring parameters */
long int ix,iy,iz, jx,jy,jz, kx,ky,kz;
static char title[81];
int bgcolor = 0;

int scx = WIDTH / 2, scy = SMHEIGHT / 2;
int xstart = -1; ystart = -1;

#define ISIZE 14
#define FTOI(x) ((long int) (x * (1 << ISIZE)))
#define MULT(x,y) ((x * y) >> ISIZE)
#define LSIZE 10

#define MAKEL(x) ((long int) x * (1 << LSIZE))

struct Ball {
	long int x,y,z;
	float r,g,b,rad;
	long int xp,yp,zp;
	int y1,y2,intr,size;
	int dup;
	};

int nball;
struct Ball **ballptr;
struct Ball *balls;
int maxsize = 0;
int maskw = 0, maskh = 1;
int maxextent = 0;

long int intsin[360];
long int intcos[360];

void
readballs(name)
char *name;
{
	int i, j, extent;
	int c;
	float x,y,z,rad,r,g,b;
	FILE *fp, *fopen();
	int ir,ig,ib;

	if ((fp = fopen(name,"r")) == NULL) {
		fprintf(stderr,"Can't open balls file - '%s'",name);
		panic("");
	}
	strcpy(title,"Drag Mouse to Rotate");
	if ((c = getc(fp)) != '"') ungetc(c,fp);
	else {
		i = 0;
		c = getc(fp);
		while ((c != '"') && (c != EOF)) {
			if (i < 80) title[i++] = c;
			c = getc(fp);
		}
		if (c == EOF) panic("Error - title lacks final \"");
		title[i] = 0;
	}
	if ((c = fscanf(fp,"%f %f %f",&r,&g,&b)) != 3) {
		fprintf(stderr,"Error reading background color %d",c+1);
		panic("");
	} else {
		SETANDBOUND(ir,r);
		SETANDBOUND(ig,g);
		SETANDBOUND(ib,b);
		bgcolor = match(ir,ig,ib);
	}
	if (fscanf(fp,"%d",&nball) != 1)
		panic("Error - Can't read number of balls");
	balls = (struct Ball *) malloc(sizeof(struct Ball)*nball);
	if (balls == 0) panic("Not enough memory for balls");
	ballptr = (struct Ball **) malloc(sizeof(long int)*nball);
	if (ballptr == 0) panic("Not enough memory for ballptr");
	for (i = 0; i < nball; i++) {
	    if ((c=fscanf(fp,"%f %f %f %f %f %f %f",&x,&y,&z,&rad,&r,&g,&b))!=7)
	    	{fprintf(stderr,
			"Error in reading item %d in ball description %d",
			c+1,i+1);
		 panic("");
	    }
	    balls[i].x = MAKEL(x);
	    balls[i].y = MAKEL(y);
	    balls[i].z = MAKEL(z);
	    balls[i].rad = rad;
	    balls[i].r = r;
	    balls[i].g = g;
	    balls[i].b = b;
	    balls[i].intr = rad + 0.5;
	    balls[i].size = 2 * balls[i].intr + 1;
	    extent = 0.9 + sqrt(x*x + y*y +z*z) + balls[i].intr;
	    if (extent > maxextent) maxextent = extent;
	    if (balls[i].size > maxsize) maxsize = balls[i].size;
	    balls[i].dup = -1;

	    for (j = 0; j < i; j++) 	/* check for duplicate entries */
		if ((balls[j].rad == rad) && (balls[j].r == r) &&
		    (balls[j].g == g) && (balls[j].b == b)) {
		    	balls[i].dup = j;
			break;
		}
	    if (balls[i].dup != -1) {
	    	balls[i].y1 = balls[balls[i].dup].y1;
		balls[i].y2 = balls[balls[i].dup].y2;
	    } else {
		balls[i].y1 = maskh;
		maskh += balls[i].size;
		if (!bw) {
	    	    balls[i].y2 = maskh;
	    	    maskh += balls[i].size;
		} else balls[i].y2 = 0;
	    }
	}
	maskw = ((maxsize + 15) & ~15) + 16;
	fclose(fp);
	checkextent();
}

void
checkextent()
{
    int largest,i;
    long int scale;
    double fscale;

    largest = scy - 10;
    if (scx < largest) largest = scx;
    if (maxextent > largest) {
	fprintf(stderr,"Warning, balls are too far apart, I will re-scale.\n");
	Delay(250);
	fscale = (((double) largest) / ((double) maxextent));
	scale = FTOI(fscale);
	maxsize = 0;
	maskw = 0;
	maskh = 1;
	for (i=0; i < nball; i++) {
		balls[i].x = MULT(balls[i].x,scale);
		balls[i].y = MULT(balls[i].y,scale);
		balls[i].z = MULT(balls[i].z,scale);
		balls[i].rad *= fscale;
		balls[i].intr = balls[i].rad + 0.5;
		balls[i].size = 2 * balls[i].intr + 1;
		if (balls[i].size > maxsize) maxsize = balls[i].size;
		if (balls[i].dup != -1 ) {
		    balls[i].y1 = balls[balls[i].dup].y1;
		    balls[i].y2 = balls[balls[i].dup].y2;
		} else {
		    balls[i].y1 = maskh;
		    maskh += balls[i].size;
		    if (!bw) {
	    		balls[i].y2 = maskh;
	    		maskh += balls[i].size;
		    } else balls[i].y2 = 0;
		}
	}
	maskw = ((maxsize + 15) & ~15) + 16;
    }
}

void
initsin()
{
	int i;
	float r,s;

	for (i=0; i < 360; i++) {
		r = i * (3.14159/180.0);
		s = sin(r);
		intsin[i] = FTOI(s);
		s = cos(r);
		intcos[i] = FTOI(s);
	}
}

void
isin(x,c,s)
int x;		/* x is degrees */
long int *c,*s;
{
	while (x >= 360) x -=360;
	while (x < 0) x += 360;
	*c = intcos[x];
	*s = intsin[x];
}

void
initrender()
{
	float m;

	initsin();
	ka = .2; kd = .5; ks = .65;
	lx = ly = lz = 1;
	m = sqrt(lx*lx + ly*ly + lz*lz);
	lx /= m;
	ly /= m;
	lz /= m;
	n = 10; r = 9.5;

	ix = FTOI(1); iy = iz = 0;
	jy = FTOI(1); jx = jz = 0;
	kz = FTOI(1); kx = ky = 0;
}

void
render()
{
	int i;
	int x, y;
	int cont, MouseMoved;
	struct IntuiMessage *message;
	ULONG class;
	USHORT code,qual;
	
	for (i = 0; i < nball; i++) renderball(i);

	SetWindowTitles(mywindow,title,((char *)(-1)));
	mylinecopy(sbitmap,0,10,WOFFSET);

	showballs();

	cont = 1;
	while(cont) {
		Wait(1 << (mywindow->UserPort->mp_SigBit));
		MouseMoved = FALSE;
		while(message = (struct IntuiMessage *)
				GetMsg(mywindow->UserPort)) {
			class = message->Class;
			code  = message->Code;
			x     = message->MouseX;
			y     = message->MouseY;
			qual  = message->Qualifier;
			ReplyMsg((struct Message *)message);

			if (class == MOUSEMOVE) MouseMoved = TRUE;
			else if (class == CLOSEWINDOW) {
				cont = 0;
				break;
			} else if (class == MOUSEBUTTONS)
				mbutton(code,x,y);
		}
		if (MouseMoved && cont) mmove(x,y,qual);
	}
}

void
showballs()
{
	int i,j,sx,sy;
	struct Ball *ball;
	long int x,y;

	for (i = 0; i < nball; i++) {
	    ball = ballptr[i] = &(balls[i]);
	    ball->xp = ((ball->x)*ix + (ball->y)*jx + (ball->z)*kx) >> ISIZE;
	    ball->yp = ((ball->x)*iy + (ball->y)*jy + (ball->z)*ky) >> ISIZE;
	    ball->zp = ((ball->x)*iz + (ball->y)*jz + (ball->z)*kz) >> ISIZE;
	}
	for (i = nball-1; i > 0; i--)
	    for (j = 0; j < i; j++) {
		if (ballptr[i]->zp < ballptr[j]->zp) {
			ball = ballptr[i];
			ballptr[i]=ballptr[j];
			ballptr[j]=ball;
		}
	    }

	myblankc(sbitmap,10+woffset,190+woffset,bgcolor);
	if (!bw) myblankc(&tbitmap,0,SMHEIGHT-1,bgcolor);

	for (i = 0; i < nball; i++) {
		ball = ballptr[i];
		x = (ball->xp + (1 << (LSIZE - 1))) >> LSIZE;
		y = (ball->yp + (1 << (LSIZE - 1))) >> LSIZE;
		sx = scx + x - ball->intr;
		sy = scy - y - ball->intr;

		     BltBitMask(&ibitmap,0,ball->y1,
				 sbitmap,sx,sy+woffset,
				&mbitmap,0,ball->y1,0,
				ball->size,ball->size);
		if (!bw) {
		     BltBitMask(&ibitmap,0,ball->y2,
				&tbitmap,sx,sy,
				&mbitmap,0,ball->y1,0,
				ball->size,ball->size);
		     BltBitMask(&tbitmap,sx+ball->intr+1,sy,
				 sbitmap,sx+ball->intr+1,sy+woffset,
				&mbitmap,ball->intr,ball->y2,0,
				ball->intr+1,ball->size);
		}
	}
	flip();
}

void
scrollx(d)
int d;
{
	long int c,s;
	long int t;

	isin(-d,&c,&s);

	t  = (c * ix  -  s * iz) >> ISIZE;
	iz = (s * ix  +  c * iz) >> ISIZE;
	ix = t;
	t  = (c * jx  -  s * jz) >> ISIZE;
	jz = (s * jx  +  c * jz) >> ISIZE;
	jx = t;
	t  = (c * kx  -  s * kz) >> ISIZE;
	kz = (s * kx  +  c * kz) >> ISIZE;
	kx = t;
}
void
scrolly(d)
int d;
{
	long int c,s;
	long int t;

	isin(d,&c,&s);

	t  = (c * iy  -  s * iz) >> ISIZE;
	iz = (s * iy  +  c * iz) >> ISIZE;
	iy = t;
	t  = (c * jy  -  s * jz) >> ISIZE;
	jz = (s * jy  +  c * jz) >> ISIZE;
	jy = t;
	t  = (c * ky  -  s * kz) >> ISIZE;
	kz = (s * ky  +  c * kz) >> ISIZE;
	ky = t;
}
void
scrollz(d)
int d;
{
	long int c,s;
	long int t;

	isin(d,&c,&s);

	t  = (c * iy  -  s * ix) >> ISIZE;
	ix = (s * iy  +  c * ix) >> ISIZE;
	iy = t;
	t  = (c * jy  -  s * jx) >> ISIZE;
	jx = (s * jy  +  c * jx) >> ISIZE;
	jy = t;
	t  = (c * ky  -  s * kx) >> ISIZE;
	kx = (s * ky  +  c * kx) >> ISIZE;
	ky = t;
}

void
mbutton(code,x,y)
int code, x, y;
{
	if (code == SELECTDOWN) {
		xstart = x;
		ystart = y;
	} else if (code == SELECTUP) {
		xstart = ystart = -1;
	}
}

void
mmove(x,y,qual)
int x,y;
USHORT qual;
{
	register int shift;

	if (qual & 0x30) shift = 2;		/* L-ALT, R-ALT */
	else if (qual & 0x07) shift = 0;	/* SHIFT */
	else shift = 1;

	if (xstart != -1) {
		if (qual & 0x08)		/* CNTL */
			scrollz((x - xstart)<<shift);
		else {
			scrolly((y - ystart)<<shift);
			scrollx((x - xstart)<<shift);
		}

		xstart = x;
		ystart = y;

		showballs();
	}
}

void
flip()
{
	Forbid();
	WaitTOF();
	WaitBlit();
	Disable();
	if (woffset) {
		vp->RasInfo->RyOffset = WOFFSET;
	} else {
		vp->RasInfo->RyOffset = 0;
	}
	ScrollVPort(vp);
	Enable();
	Permit();
	if (woffset) {
		woffset = 0;
	} else {
		woffset = WOFFSET;
	} 
}

void
renderball(j)
int j;
{
	struct Ball *ball;
	int intr, size, y1, y2;
	int x, y, xp, yp, ired, igreen, iblue;
	int start;
	float nx, ny, nz, rz, id, is;
	
	ball = balls+j;

	if (ball->dup != -1) return;

	y1 = ball->y1;
	y2 = ball->y2;
	intr = ball->intr;
	size = ball->size;

/* special mask is offset -1 in x */

	for (y = 0; y < size; y++) {
	    start = 0;
	    for (x = 0; x < size; x++) {
		xp = x - intr;
		yp = intr - y;
		nz = (ball->rad * ball->rad) - (xp * xp) - (yp * yp);
		if (nz >= 0.0) {
			mywritepixel1(&mbitmap,x,y+y1);
			nx = xp / ball->rad;
			ny = yp / ball->rad;
			nz = sqrt(nz) / ball->rad;
			id = lx*nx + ly*ny + lz*nz;	/* diffuse intensity */
			rz = (nz + nz) * id - lz;
			if (rz < 0) rz = 0;
			is = ks * pow(rz,n);
			id = ka + kd * id;
			SETANDBOUND(ired,  (id * ball->r + is));
			SETANDBOUND(igreen,(id * ball->g + is));
			SETANDBOUND(iblue, (id * ball->b + is));
			setcolor(x,y+y1,y+y2,ired,iblue,igreen,!start);
			if (start == 0) start = 1;
		} else if (start == 1) {
			dolast();
			if (!bw) mywritepixel1(&mbitmap,x-1,y+y2);
			start = 2;
		}
	    }
	    if (start == 1) {
		dolast();
		if (!bw) mywritepixel1(&mbitmap,x-1,y+y2);
	    }
	}
}
