/*

------------------------------------------------------------------

Black Nebula

File :				Clip.c
Programmer:		Colin Adams
Date:				23/4/91
Last Modified :	10/6/91

------------------------------------------------------------------

*/

#include "3d.h"
#include <proto/dos.h>

#define BOUNDARY_COUNT 4
#define MAX_LIMIT 500


extern short U_rot, W_rot, View_Angle;
extern short num_active[];

static point s[BOUNDARY_COUNT], first_point[BOUNDARY_COUNT];
static short new_edge[BOUNDARY_COUNT];
static polygon *clip_poly;
static object *sort_obj; /* global for sort */
static point clip_point;
static int Depth[TOTAL_MAX_OBJECTS]; /* array to sort in */

static int xp[3], yp[3], zp[3];

enum
{
	TOP,
	RIGHT,
	BOTTOM,
	LEFT
};

/*	------------------------------------------------------------------
		Fast Square Root Code for IEEE floating point numbers
		from Graphics Gems, Academic Press 1990
		
		Supplied by Peter Stephenson
		------------------------------------------------------------------
*/

static short sqrttab[0x100];

void build_table(void)
{
	unsigned short i;
	float f;
	unsigned int *fi = (int *) &f;
	
	for(i=0; i<= 0x7f; i++)
	{
		/* build a float with the bit pattern i as
		mantissa and an exponent of 0, stored as 127 */
		
		*fi = (i << 16) | ( 127 << 23);
		f = sqrt(f);
		
		/* take the square root then strip the first 7 bits of the
		mantissa into the table */
		
		sqrttab[i] = (*fi & 0x7fffff) >> 16;
		
		/* repeat the process, this time with an exponent of 1,
		stored as 128 */
		
		*fi = (i << 16) | (128 << 23);
		f = sqrt(f);
		
		sqrttab[i+0x80] = (*fi & 0x7fffff) >> 16;
	}
}

float fsqrt(float n)
{
	unsigned int *num = (int *) &n;
	short e;
	
	if(n==0) return 0;
	
	e = (*num >> 23) - 127;
	
	*num &= 0x7fffff;
	
	if(e & 0x01)
		*num |= 0x800000;
		
	e >>= 1;
	
	*num = ((sqrttab[*num >> 16]) << 16) | ((e + 127) << 23);
	return n;
}

/*	------------------------------------------------------------------
		Clipping Routines
		
		The clipping algorithm is Sutherland and Hodgeman's.  The
		description of the algorithm is in Computer Graphics by
		D. Hearn & M. Baker.
		------------------------------------------------------------------
*/

short inside(point *p, short edge)
/* returns true if the point is inside the given edge, false if not */
{
	switch(edge)
	{
		case TOP:	/* top line */
			if(p->y >= Y_MIN)
				return 1;
			return 0;
		case RIGHT:	/* right hand side */
			if(p->x <= X_MAX)
				return 1;
			return 0;
		case BOTTOM:	/* bottom line */
			if(p->y <= Y_MAX)
				return 1;
			return 0;
		case LEFT:	/* left hand side */
			if(p->x >= X_MIN)
				return 1;
			return 0;
	}	
}


short cross(point *p, point *p2, short edge)
/* returns true if the 2 points cross the given edge */
{
	/*
	
	This first bit is a slight hack to make points generated a mile
	apart when on the other side of the eye, not appear! May or may
	not be fixed.
	
	2/6/91
	
	It is now very unlikely to be fixed, as I don't think it
	can be!!  But it seems to work...
	
	*/

	if(abs(p->x) > MAX_LIMIT || abs(p2->x) > MAX_LIMIT ||
		abs(p->y) > MAX_LIMIT || abs(p2->y) > MAX_LIMIT)
		return 0;

	/* end of horrible speed reducing hack */
	
	switch(edge)
	{
		case TOP: /* top edge */
			if(p->y > Y_MIN)
			{
				if(p2->y <= Y_MIN)
					return 1;
			}
			else if(p2->y > Y_MIN)
				return 1;
			return 0;
			
		case RIGHT: /* right hand edge */
			if(p->x > X_MAX)
			{
				if(p2->x <= X_MAX)
					return 1;
			}
			else if(p2->x > X_MAX)
				return 1;
			return 0;
		
		case BOTTOM: /* bottom edge */
			if(p->y > Y_MAX)
			{
				if(p2->y <= Y_MAX)
					return 1;
			}
			else if(p2->y > Y_MAX)
				return 1;
			return 0;
	
		case LEFT: /* left hand edge */
			if(p->x > X_MIN)
			{
				if(p2->x <= X_MIN)
					return 1;
			}
			else if(p2->x > X_MIN)
				return 1;
			return 0;
	}
}

void find_intersection(point *p1, point *p2, short edge, point *i)
/*
Find intersection point of line, uses fixed point maths.  This routine
is heavily optimized, requiring only 1 integer division and 1 integer
multiplication to find the intersection!!!  If you know a faster
method please tell me!
*/
{
	register int mnumer, mdenom;
	
	/* get gradient */
	
	mnumer = (p2->y - p1->y);
	mdenom = (p2->x - p1->x);
	
	switch(edge)
	{
		case TOP:
		{
			register int j = Y_MIN - p1->y;
			i->y = Y_MIN;
			i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0);
			return;
		}		
		case RIGHT:
		{
			register int j = (X_MAX - p1->x);
			i->x = X_MAX;
			i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0);
			return;
		}
		case BOTTOM:
		{
			register int j = Y_MAX - p1->y;
			i->y = Y_MAX;
			i->x = p1->x + (mnumer ? (j * mdenom) / mnumer : 0);
			return;
		}		
		case LEFT:
		{
			register int j = (X_MIN - p1->x);
			i->x = X_MIN;
			i->y = p1->y + (mdenom ? (mnumer*j)/mdenom : 0);
			return;
		}
	}
}

void clip_this(point *p, short edge)
/* recursively checks a point against all the boundary edges */
{
	point i;
	
	if(new_edge[edge])
	{
		first_point[edge].x = p->x;
		first_point[edge].y = p->y;
		new_edge[edge] = 0;
	}
	else if(cross(p, &s[edge], edge))
	{
		find_intersection(p, &s[edge], edge, &i);
		
		if(edge < 3)
			clip_this(&i, edge+1);
		else
		{
			clip_poly->clip_num++;
			clip_poly->clip_x[clip_poly->clip_num] = i.x;
			clip_poly->clip_y[clip_poly->clip_num] = i.y;
		}	
	}
	
	s[edge].x = p->x;
	s[edge].y = p->y;
	
	if(inside(p, edge))
	{
		if(edge < 3)
			clip_this(p, edge+1);
		else
		{
			clip_poly->clip_num++;
			clip_poly->clip_x[clip_poly->clip_num] = p->x;
			clip_poly->clip_y[clip_poly->clip_num] = p->y;
		}
	}
}

void clip_closer(void)
/*
Closing routine. For each window edge, clips the line connecting
the last saved vertex and the first_point processed against the
edge
*/
{
	point i;
	register short edge;
	
	for(edge=0; edge<BOUNDARY_COUNT; edge++)
	{
		if(cross(&s[edge], &first_point[edge], edge))
		{
			find_intersection(&s[edge], &first_point[edge],edge,&i);
			
			if(edge<3)
				clip_this(&i, edge+1);
			else
			{
				clip_poly->clip_num++;
				clip_poly->clip_x[clip_poly->clip_num] = i.x;
				clip_poly->clip_y[clip_poly->clip_num] = i.y;
			}
		}
	}
}

void polygon_clip(polygon *poly)
/* clips the polygon sent against the boundaries */
{
	register int k;

	clip_poly = poly;			/* use a global for speed */
	clip_poly->clip_num = -1; /* no points in output yet */
		
	for(k=0; k<4; k++)
		new_edge[k] = 1;

	for(k=0; k<clip_poly->numpoints; k++)
	{
		clip_point.x = clip_poly->x[k];
		clip_point.y = clip_poly->y[k];
		clip_this(&clip_point, 0);
	}
	clip_closer(); /* close polygon */
	clip_poly->clip_num++;
}
	
/*	------------------------------------------------------------------
		Hidden Surface Routines
		
		Determines the order to draw objects, and polygons within
		an object
		------------------------------------------------------------------
*/


int SpinPolyCentre(object *obj, polygon *poly)
/*
Spin a point and calculate it's depth from the eye.  The point
is the centre point of a polygon, and is used for hidden surface
removal.  This routine should be pretty quick, but if you know a
faster algorithm, I'd love to know it!
*/
{
	register int x,y,z;
	register int xrot, yrot, zrot;
		
	xrot = obj->rot_x;
	yrot = obj->rot_y;
	zrot = obj->rot_z;
		
	x = poly->centre.x;
	y = poly->centre.y;
	z = poly->centre.z;

	/* rotate a point around the z axis */
		
	if(zrot)
	{
		register int temp, temp2, temp3;
		temp = x;
		temp2 = costable[zrot];
		temp3 = sintable[zrot];
			
		x = ((temp2*temp)>>16) - ((temp3*y)>>16);
		y = ((temp3*temp)>>16) + ((temp2*y)>>16);
	}
		
	/* rotate a point around the x axis */
		
	if(xrot)
	{
		register int temp, temp2, temp3;
		temp = y;
		temp2 = costable[xrot];
		temp3 = sintable[xrot];
			
		y = ((temp2*temp)>>16) - ((temp3*z)>>16);
		z = ((temp3*temp)>>16) + ((temp2*z)>>16);
	}
		
	/* rotate a point around the y axis */
		
	if(yrot)
	{
		register int temp, temp2, temp3;
		temp = z;
		temp2 = costable[yrot];
		temp3 = sintable[yrot];
			
		z = ((temp2*temp)>>16) - ((temp3*x)>>16);
		x = ((temp3*temp)>>16) + ((temp2*x)>>16);
	}
		
	/* calculate depths */
		
	x = ex - (x + obj->trans_x);
	y = ey - (y + obj->trans_y);
	z = ez - (z + obj->trans_z);
	
	return (x*x + y*y + z*z);
}	

void qsort_poly(int *left, int *right, int al, int ar)
/*
Quicksort for polygon depths within an object.  This can be written 
faster.
*/
{
	register int *p = left, *q = right, x = *(left+(right-left)/2), w;
	register int tar = ar, tal = al;
	
	do
	{
		while(*p>x)
		{
			p++; tal++;
		}
		
		while(*q<x)
		{
			q--; tar--;
		}
			
		if(p>q) break;

		w = *p;
		*p = *q;
		*q = w;
		
		{
			register polygon *temp;
			temp = sort_obj->draworder[tar];
			sort_obj->draworder[tar] = sort_obj->draworder[tal];
			sort_obj->draworder[tal] = temp;
		}
		
		p++; q--;
		tal++; tar--;
		
	} while (p<=q);

	if(left<q) qsort_poly(left, q, al, tar);
	if(p<right) qsort_poly(p, right, tal, ar);
}

void DepthSort(object *obj)
/* sorts an object's polygons into depths from the eye to determine
the order in which they are to be drawn */
{
	register polygon *pg = obj->poly;
	register short count = 0;
	register char oneobj = 0;
	
	if(!obj->poly->next)
		oneobj = 1;
	
	while(pg)
	{
		obj->draworder[count] = pg;
		
		/* NB. as an optimization, the actual distance from the eye is
		not stored but the square of it is, so the magnitude is
		relevant! */

		/*
		
		Check for back faces, this is the only way to make my
		hidden surface code work properly... I really didn't want to
		add this, but doesn't seem to slow down the code much as
		it doesn't draw/erase polygons which are back faces.
		
		Unfortunately it still sorts faces which aren't visible,
		should be fixed!
		
		*/
		
		if(pg->numpoints>2  && !oneobj)
		{
			register int i, A, B, C, D;
			
			for(i=0; i<3; i++)
			{
				xp[i] = manipulate_x[pg->p[i]];
				yp[i] = manipulate_y[pg->p[i]];
				zp[i] = manipulate_z[pg->p[i]];
			}
			
			/* horrible plane equation calculations */
			
			A = yp[0]*(zp[1]-zp[2]) + yp[1]*(zp[2]-zp[0]) + yp[2]*(zp[0]-zp[1]);
			B = zp[0]*(xp[1]-xp[2]) + zp[1]*(xp[2]-xp[0]) + zp[2]*(xp[0]-xp[1]);
			C = xp[0]*(yp[1]-yp[2]) + xp[1]*(yp[2]-yp[0]) + xp[2]*(yp[0]-yp[1]);
			D = - xp[0]*(yp[1]*zp[2] - yp[2]*zp[1]) - xp[1]*(yp[2]*zp[0] - yp[0]*zp[2])
				- xp[2]*(yp[0]*zp[1] - yp[1]*zp[0]);
				
			if((ex*A + ey*B + ez*C + D)<0)
			{
				pg->back_face = 1;
				pg->clip_num = 0;
			}
			else
			{
				pg->back_face = 0;
			}
				
		}
		else
			pg->back_face = 0;
		
		Depth[count++] = pg->back_face ? 0 : SpinPolyCentre(obj, pg);
		pg = (polygon *) pg->next;
	}

	obj->draworder[count] = NULL; /* terminate list */

	sort_obj = obj;
	if(count)
		qsort_poly(Depth, &Depth[count-1], 0, count-1);
		
}

/* Object sorting routines */

double rad(double degrees)
/* converts degrees to radians */
{
	double one8 = 180;
	return 3.141592654/(one8/degrees);
}

int GetAngle(int *h, short px, short py, short pz)
{
	register int o, angle, tempint;
	register char quad;
	int temp;
	float depth, temp2;
	int d;
	
	tempint = ex-px;
	depth = tempint * tempint;
	tempint = ez - pz;
	depth += tempint * tempint;
	tempint = ey - py;
	*h = depth + tempint * tempint;

	if(px>ex)
	{
		o = px - ex;
		if(pz>ez)
			quad = 4;
		else
		{
			if(pz==ez) return 0;
			quad = 1;
		}
	}
	else
	{
		if(px==ex)
		{
			if(pz<ez)
				return 90;
			else
				return 270;
		}
		
		o = ex - px;
		
		if(pz>ez)
			quad = 3;
		else
		{
			if(pz==ez) return 180;
			quad = 2;
		}
	}
	
	temp = o * 4096;
	temp2 = fsqrt(depth);
	
	d = temp/temp2;
	
	if(d>4096) d = 4096; /* to fix small errors which make d > 4096 */
	
	angle = anti_sin[d];
	
	switch(quad)
	{
		case 1: angle = 90 - angle; break;
		case 2: angle = 90 + angle; break;
		case 3: angle = 180 + (90-angle); break;
		case 4: angle += 270; break;
	}
	return angle;
}


int SpinObjCentre(object *obj)
{
	register int x,y,z;
	register short angle;
	int distance;
	
	x = obj->trans_x + obj->centre_x;
	y = obj->trans_y + obj->centre_y;
	z = obj->trans_z + obj->centre_z;
	
	angle = GetAngle(&distance, x, y, z);

	/* tries to eliminate objects which aren't in the field of view */
	
	if(View_Angle>=45)
	{
		if(View_Angle<=315)
		{
			if(angle>View_Angle+45 || angle<View_Angle-45)
				obj->drawme = 0;
		}
		else
		{
			if(angle<View_Angle-45 && angle>View_Angle-315)
				obj->drawme = 0;
		}
	}
	else
	{
		if(angle>View_Angle+45 && angle<View_Angle+315)
			obj->drawme = 0;
	}

	/* check if I hit something */
	
	if(distance<(obj->radius*5+500) && obj->type<MYMISSILE)
	{
		obj->i_am_dying = 1; /* kill enemy too! */
		obj->explode = 1;
		am_i_dead = 1;
	}
		
	return distance;
}

void qsort_depth(int *left, int *right, int al, int ar)
/* quicksort objects into drawing order */
{
	register int *p = left, *q = right, x = *(left+(right-left)/2), w;
	register int tar = ar, tal = al;
	
	do
	{
		while(*p>x)
		{
			p++; tal++;
		}
		
		while(*q<x)
		{
			q--; tar--;
		}
			
		if(p>q) break;

		w = *p;
		*p = *q;
		*q = w;
		
		{
			register object *temp;
			temp = active_list[tar];
			active_list[tar] = active_list[tal];
			active_list[tal] = temp;
		}
		
		p++; q--;
		tal++; tar--;
		
		
	} while (p<=q);

	if(left<q) qsort_depth(left, q, al, tar);
	if(p<right) qsort_depth(p, right, tal, ar);
}

void CalculateObjects(void)
/* sorts the objects into depths from the eye to determine
the order in which they are to be drawn
*/
{
	register short i,j;
	
	/*
	kills off objects which are floating around in a dead state but
	aren't totally removed from the system yet
	*/
	
	for(i=0; i<no_objects; i++)
	{
		while(i<no_objects && active_list[i]->i_am_dying)
		{
			if(active_list[i]->explode &&	active_list[i]->i_am_dying==1)
				CreateExplosion(active_list[i]);
			
			if(active_list[i]->i_am_dying++>2)
			{
				if(active_list[i]->type==EXPLOSION)
				{
					for(j=0; j<MAX_EXPLOSIONS; j++)
					{
						if(active_list[i] == &explosions[j])
						{
							exp_free[j] = 0;
							break;
						}
					}
				}
				else
				{
					for(j=0; j<MAX_OBJECTS; j++)
					{
						if(active_list[i] == &obj_types[active_list[i]->type][j])
						{
							obj_free[active_list[i]->type][j] = 0;
							break;
						}
					}
				}
				
				num_active[active_list[i]->type]--;
				
				for(j=i; j<no_objects-1; j++)
					active_list[j] = active_list[j+1];
			
				no_objects--;
			}
			else
			{
				active_list[i]->drawme = 0;
				break;
			}
		}
		
		if(i<no_objects)
		{
			if(active_list[i]->drawme)
				PrepareObject(active_list[i]);
		}
	}
	
	for(i=0; i<no_objects; i++)
		Depth[i] = SpinObjCentre(active_list[i]);

	qsort_depth(Depth, &Depth[no_objects-1], 0, no_objects-1);
	
}

void ClearObjects(void)
/* destroys the old image */
{
	register int i;
	
	for(i=0; i<no_objects; i++)
			EraseObject(active_list[i]);
}

void DisplayObjects(void)
{
	register int i;
	for(i=0; i<no_objects; i++)
		DrawObject(active_list[i]);
}
		
/* end of module clip.c */
