/*
 *  acm : an aerial combat simulator for X
 *  Copyright (C) 1991-1994  Riley Rainey
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 dated June, 1991.
 *
 *  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.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program;  if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave., Cambridge, MA 02139, USA.
 */
 
#include <math.h>
#include "pm.h"
#include <stdio.h>
#include <string.h>

#define BORE_CROSS_SIZE	7
#define MUZZLE_VELOCITY (3.2808 * 1036.0)
#define TRACER_MOD	10	
#define OFFSET_ANGLE	(DEGtoRAD(3.0))		/* the gun points up at this angle */
#define RATE_OF_FIRE	(3000.0 / 60.0)		/* rounds per second */
#define FUZZ		(DEGtoRAD(1.0))		/* distribution due to vibration */

int select_m61a1();
int display_m61a1();
int update_m61a1();
int press_m61a1();
int release_m61a1();
extern craftType * lookupCraft();
extern FILE * acm_fopen();
extern void newExplosion();
extern int acm_rand PARAMS((void));
void lcos();

int	cdebug = 0;		/* set to 1 to debug cannon fire */

weaponDesc m61a1Desc = {
	WK_M61A1,
	select_m61a1,		/* select */
	update_m61a1,		/* update */
	display_m61a1,		/* display procedure */
	press_m61a1,		/* fire */
	release_m61a1,		/* fire button release */
	};

/*
 *  We'll take a running average of our pitch and yaw rates to keep the
 *  aiming reticle from jumping all over the screen.
 */

#define HIST	4

typedef struct {
	double	pitch_hist[HIST];
	double	yaw_hist[HIST];
	int	cur;		/* current entry in the histories */
	int	count;		/* number of valid entries */
	double	pitch_total;
	double	yaw_total;
	}	hist_t;

static hist_t	history[MAXPLAYERS];

/*
 *  m61a1 selection function
 *
 *  A selection function normally determines whether there are any weapons
 *  of this type on-board.  If so, and the weapon system is functional
 *  (in other words, undamaged) then return 1; otherwise return 0.
 */

int select_m61a1 (c)
craft *c; {

	hist_t	*p;

	p = &history[c->pIndex];
	p->count = p->cur = 0;
	p->pitch_total = p->yaw_total = 0.0;

/*
 *  The cannon must be located at station zero, for now.  We should
 *  change that.
 */

	if (strcmp(c->station[0].type, "m61a1")==0 && c->station[0].info > 0)
		return 1;
	else
		return 0;

}

/*
 *  m61a1 display function
 *
 *  Update the HUD display strings associated with this weapon system.
 */

/*ARGSUSED*/
int display_m61a1 (c, w, u)
craft *c;
craftType *w;
viewer *u; {

	char	s[16];
	register int tx, ty, m;
	XSegment seg[2];
	hist_t	*p;

	p = &history[c->pIndex];
	if ((m = p->cur = p->cur + 1) >= HIST)
		m = p->cur = 0;
	if (p->count != HIST)
		(p->count) ++;
	else {	
		p->pitch_total -= p->pitch_hist[m];
		p->yaw_total -= p->yaw_hist[m];
	}
	p->pitch_hist[m] = c->q;
	p->yaw_hist[m] = c->r;
	p->pitch_total += c->q;
	p->yaw_total += c->r;

	strcpy (c->leftHUD[0], "LCOS");
	sprintf (s, "%.3d M-61A1", c->station[0].info);
	strcpy (c->leftHUD[1], s);
	if (c->station[0].info3)
		strcpy (c->leftHUD[2], "FIRING");
	else
		strcpy (c->leftHUD[2], "");

/*
 *  Draw the boresight cross
 */

	m = (double) BORE_CROSS_SIZE * u->scaleFactor + 0.5;

	tx = u->xCenter;
	ty = u->yCenter - (int)(3.0 * 9.0 * u->scaleFactor + 0.5);
	seg[0].x1 = tx - m;
	seg[0].x2 = tx + m;
	seg[0].y1 = seg[0].y2 = ty;
	seg[1].x1 = seg[1].x2 = tx;
	seg[1].y1 = ty - m;
	seg[1].y2 = ty + m;
	VDrawSegments (u->v, seg, 2, (Color) u->v->pixel[HUDPixel]);

/*
 *  Plot the reticle.
 */

	lcos (c, u);

}

int	press_m61a1 (c)
craft	*c; {
	c->station[0].info3 |= 1;
	if (c->station[0].info > 0) {
		playContinuousSound(c, SoundCannonFiring);
	}
	return 0;
}

int	release_m61a1 (c)
craft	*c; {
	c->station[0].info3 &= ~1;
	stopSound(c, SoundCannonFiring);
	return 0;
}

int	update_m61a1 (c)
craft	*c; {

	craft	*m;
	int	i;
	VPoint	tmp, mvel;
	double	fuzz, fuzzAngle, phiY, phiZ, tm, mv = MUZZLE_VELOCITY;

	if (c->station[0].info3 == 0)
		return 0;

/*
 *  Got any ammunition ?
 */

	if (c->station[0].info <= 0)
		return -1;

/*
 *  Allocate a projectile record
 */

	for ((i=0, m= &mtbl[0]); i<MAXPROJECTILES; (++i, ++m))
		if (m->type == CT_FREE) {
			m->type = CT_CANNON;
			break;
		}

	if (i == MAXPROJECTILES)
		return -1;

/*
 *  Determine how far we're fuzzed off the ideal boresight.
 */

	fuzz = ((double)(acm_rand() & 32767) +
		(double)(acm_rand() & 32767)) / 32768.0 - 1.0;
	fuzz = fuzz * FUZZ;
	fuzzAngle = 2.0 * pi * (double)(acm_rand() & 511) / 512.0;

/*
 *  Determine the initial velocity of the projectile stream.
 */

	phiZ = fuzz * sin (fuzzAngle);
	phiY = fuzz * cos (fuzzAngle);
	tm = mv * cos (OFFSET_ANGLE + phiZ);

	tmp.x = tm * cos (phiY); 
	tmp.y = tm * sin (phiY); 
	tmp.z = mv * sin (-OFFSET_ANGLE + phiZ);
	VTransform (&tmp, &(c->trihedral), &mvel);

	m->owner = c->pIndex;
	m->createTime = curTime;
	m->Cg = c->Cg;
	m->curRoll = c->curRoll;
	m->curPitch = c->curPitch;
	m->curHeading = c->curHeading;
	m->Cg.x += mvel.x;
	m->Cg.y += mvel.y;
	m->Cg.z += mvel.z;

/*
 *  Determine the initial position.
 */

	VTransform (&(c->cinfo->wStation[0]), &(c->trihedral), &tmp);
	m->Sg.x = c->Sg.x + tmp.x;
	m->Sg.y = c->Sg.y + tmp.y;
	m->Sg.z = c->Sg.z + tmp.z;
	m->prevSg = m->Sg;

/*
 *  Subtract the number of rounds fired.
 */

	m->rounds = RATE_OF_FIRE * deltaT;
	m->rounds = (m->rounds > c->station[0].info) ? c->station[0].info : m->rounds;
	m->tracerMod = TRACER_MOD;
	m->tracerVal = c->station[0].info2 % m->tracerMod;
	c->station[0].info -= m->rounds;
	c->station[0].info2 += m->rounds;
	if (c->station[0].info <= 0) {
		stopSound (c, SoundCannonFiring);
	}

	m->cinfo = lookupCraft ("m61a1 cannon");

	return 0;

}

extern craftType * newCraft ();

int	placeCannon(vp, c, m, poly, cnt)
Viewport *vp;
craft	*c;
VMatrix	*m;
VPolygon **poly;
long	*cnt;
{

	double	t, intervalT;
	VPoint	v, s;
	int	i, j, k, n;
	VPoint	 *q, tmp;
	VPolygon **p;

/*
 *  Reduce the shell path to a set of parametric equations.
 */

	v.x = c->Sg.x - c->prevSg.x;
	v.y = c->Sg.y - c->prevSg.y;
	v.z = c->Sg.z - c->prevSg.z;

/*
 *  Now add each tracer shell to the view.
 */

	intervalT = 1.0 / c->rounds;
	for (t=intervalT*c->tracerVal; t <= 1.0; t += intervalT * TRACER_MOD) {
		s.x = -v.x * t;
		s.y = -v.y * t;
		s.z = -v.z * t;
		if (c->Sg.z + s.z > 0.0)	/* underground? don't plot it */
			continue;
		n = c->cinfo->object->numPolys;
		p = c->cinfo->object->polygon;
		j = *cnt;
		for (i=0; i<n; ++i) {
			poly[j] = VCopyPolygon(p[i]);
			for ((k=0, q=poly[j]->vertex); k<poly[j]->numVtces; (++k, ++q)) {
				VTransform(q, m, &tmp);
				tmp.x += s.x;
				tmp.y += s.y;
				tmp.z += s.z;
				*q = tmp;
			}
			VTransformPolygon (poly[j], &vp->eyeSpace);
			++j;
		}
		*cnt = j;
	}

	return(0);
}

void initm61a1()
{

	craftType	*c;
	FILE    	*f;

	wtbl[1] = m61a1Desc;

	c = newCraft();
	c->name = strdup("m61a1 cannon");

	c->placeProc = placeCannon;

	f = acm_fopen ("tracer.obj", "r");
	c->object = VReadObject(f);
	fclose (f);

}

int cannonCalculations (c)
craft *c; {


/*
 *  Kill projectile streams after 10.0 seconds of flight or when
 *  they strike the ground.
 */

	if (curTime - c->createTime > 10.0)
		return 1;

	if (c->Sg.z > 0.0)
		return 1;

	c->prevSg = c->Sg;

	c->Sg.x += c->Cg.x * deltaT;
	c->Sg.y += c->Cg.y * deltaT;
	c->Sg.z += c->Cg.z * deltaT + a * halfDeltaTSquared;

	c->Cg.z += a * deltaT;
	return 0;

}

/*
 *  plotReticle :  draws the aiming reticle onto the HUD.
 *
 *  An aiming reticle is a circle with an inner arc that clues the
 *  pilot as to the range of the target.  Twelve tic marks are used
 *  to demarcate the range -- each tic represents 1000 feet of
 *  distance.
 */

static struct {
	double	x, y;
	} ticTable[12] = {
 { 0.0, -1.0 },
 { 0.5, -0.866 },
 { 0.866, -0.5 },
 { 1.0, 0.0 },
 { 0.866, 0.5 },
 { 0.5, 0.866 },
 { 0.0, 1.0 },
 { -0.5, 0.866 },
 { -0.866, 0.5 },
 { -1.0, 0.0 },
 { -0.866, -0.5 },
 { -0.5, -0.866 } };
/*ARGSUSED*/
void plotReticle (c, u, x, y, range)
craft	*c;
viewer	*u;
int	x;
int	y;
int	range; {

#define RETICLE_SIZE	65
#define RANGE_SIZE	59
#define TICK_SIZE	(RETICLE_SIZE + 12)

	register int	i, size, xt, yt, size1, xt1, yt1, nseg, arc;
	XSegment	seg[16];
	register double rs, rt;

	size = RETICLE_SIZE * u->scaleFactor + 0.5;
	if ((size & 1) == 0)
		++size;		/* insure it is an odd value */
	xt = x - size / 2;
	yt = y - size / 2;
	rs = size / 2;
	rt = ((TICK_SIZE + 1) / 2) * u->scaleFactor;

	for (i=0; i < 12; i++) {
		seg[i].x1 = x +
			(int) (rs * ticTable[i].x + 0.5);
		seg[i].y1 = y +
			(int) (rs * ticTable[i].y + 0.5);
		seg[i].x2 = x +
			(int) (rt * ticTable[i].x + 0.5);
		seg[i].y2 = y +
			(int) (rt * ticTable[i].y + 0.5);
	}

	nseg = 12;
	VDrawArc (u->v, xt, yt, size, size, 0,
		360 * 64, u->v->pixel[HUDPixel]);

	if (range != -1) {
		if (range > 12000)
			range = 12000;
		arc = - range * 23040 / 12000;
		size1 = RANGE_SIZE * u->scaleFactor + 0.5;
		if ((size1 & 1) == 0)
			++ size1;
		if (size == size1)
			-- size1;
		xt1 = x - size1 / 2;
		yt1 = y - size1 / 2;
		VDrawArc (u->v, xt1, yt1, size1, size1,
			90 * 64, arc, u->v->pixel[HUDPixel]);
	}

	VDrawSegments (u->v, seg, nseg, u->v->pixel[HUDPixel]);

}

void lcos (c, u)
craft *c;
viewer	*u; {


/*
 *  How does this LCOS thing work, anyway?
 *
 *  First, let me say that this may or, more probably, may not look
 *  anything like the technique used in a real fighter's gun sighting
 *  system.  Having said that, here's the basic assumptions and steps
 *  used to generate the reticle on the HUD:
 *
 *  1)  Using radar, we know the distance to the target.  Assuming that the
 *	radar is in working order and is locked onto something.
 *
 *  2)  We know the muzzle velocity of the shells we're firing and can
 *	use that value to closely estimate the flight time of the shells
 *	to a target that's a certain distance away (the target).  Call
 *	this value "fTime".
 *
 *  3)	Use the muzzle velocity vector to determine the
 *	relative position (w.r.t. our craft) of a shell "fTime" seconds into
 *	its flight.  Call this vector "pos".
 *
 *  4)	Gravity will accelerate the shells. Use d = 0.5 * g * t ^ 2
 *	to add a distance (along the Z axis that will approximate the
 *	effect of gravity.  Add that to "pos".
 *
 *  5)	We know our pitch and yaw rates.  These rotations will have the
 *	effect of visually "bending" the cannon stream, from the pilot's
 *	perspective.  Use the values to generate a matrix to transform
 *	"pos" by an amount proportional to our "fTime" value.
 *
 *  6)	Convert our "pos" vector to screen x,y coordinates and call
 *	plotReticle.
 */

	double	fTime, range, gm, pitch_rate, yaw_rate;
	VPoint	pos, tmp, tmp1;
	VPoint	zg, z;
	VMatrix	rotation;
	register int x, y, clue;
	hist_t	*hist;

	hist = &history[c->pIndex];

/*  Step 1  */

	if (c->curRadarTarget >= 0)
		range = c->targetDistance;
	else
		range = 2500.0;

/*  Step 2  */

	fTime = range / MUZZLE_VELOCITY;

/*  Step 3  */

	pos.x = fTime * MUZZLE_VELOCITY * cos (OFFSET_ANGLE);
	pos.y = 0.0;
	pos.z = - fTime * MUZZLE_VELOCITY * sin (OFFSET_ANGLE);

/*  Step 4  */

	zg.x = zg.y = 0.0;
	zg.z = 1.0;

	VTransform (&zg, &(c->Itrihedral), &z);

	gm = 0.5 * a * fTime * fTime;
	pos.x += z.x * gm;
	pos.y += z.y * gm;
	pos.z += z.z * gm;

/*  Step 5  */

	VIdentMatrix (&rotation);

	pitch_rate = hist->pitch_total / (double) hist->count;
	yaw_rate = hist->yaw_total / (double) hist->count;

	if (pitch_rate != 0.0)
		VRotate (&rotation, YRotation, pitch_rate * fTime);
	if (yaw_rate != 0.0)
		VRotate (&rotation, ZRotation, yaw_rate * fTime);

	VTransform (&pos, &rotation, &tmp);

/*  Step 6  */

	tmp1 = tmp;
	x = (u->v->Middl.x + (int)(tmp1.y * u->v->Scale.x / tmp1.x)) >> 2;
	y = (u->v->Middl.y + (int)(tmp1.z * u->v->Scale.y / tmp1.x)) >> 2;

	if (c->curRadarTarget >= 0)
		clue = c->targetDistance + 0.5;
	else
		clue = -1;

	plotReticle (c, u, x, y, clue);
}

/*
 *  lookForCannonImpacts : Track cannon shells and look for impacts with
 *			   aircraft.  This algorithm tracks each shell's
 *			   flight.
 */

typedef struct _entry {
	double	time;
	double	min;
	VPoint	Sg;
	craft	*c;
	struct	_entry *next;
	}	entry;


extern int cdebug;

void	lookForCannonImpacts (m)
craft	*m; {

	craft	*c;
	entry	p[MAXPLAYERS], *list, *q, *r, *rprev;
	VPoint	v, s0, prevSg, Vm;
	double	t, d, roundT, intervalT;
	int	j;

	intervalT = 1.0 / m->rounds;
	for (roundT=0.0; roundT < 1.0; roundT += intervalT) {

		Vm.x = m->Sg.x - m->prevSg.x;
		Vm.y = m->Sg.y - m->prevSg.y;
		Vm.z = m->Sg.z - m->prevSg.z;

		prevSg.x = m->prevSg.x - Vm.x * (1.0 - roundT);
		prevSg.y = m->prevSg.y - Vm.y * (1.0 - roundT);
		prevSg.z = m->prevSg.z - Vm.z * (1.0 - roundT);

		list = (entry *) NULL;
		for (c=ptbl, j=0; j<MAXPLAYERS; ++j, ++c) {

		if (c->type == CT_FREE || m->owner == c->pIndex)
			continue;

/*
 * Reduce the relative motion of this object to a the parametric system
 * of equations:
 *		x(t) = vx * t + s0x
 *		y(t) = vy * t + s0y
 *		z(t) = vz * t + s0z
 *
 * We can then compute the time of perigee (closest pass) along with
 * the associated minimum distance.
 */

			v.x = c->Sg.x - c->prevSg.x - Vm.x;
			v.y = c->Sg.y - c->prevSg.y - Vm.y;
			v.z = c->Sg.z - c->prevSg.z - Vm.z;
			s0.x = c->prevSg.x - prevSg.x;
			s0.y = c->prevSg.y - prevSg.y;
			s0.z = c->prevSg.z - prevSg.z;

/*
 * Compute time of minimum distance between the two objects (note that units
 * here are UPDATE_INTERVAL seconds).
 */

			t = - (v.x * s0.x + v.y * s0.y + v.z * s0.z) /
				(v.x * v.x + v.y * v.y + v.z * v.z);

			if (cdebug)
			printf ("perigee in %g seconds with player %d\n",
				t*deltaT, j);

/*
 *  If the closest pass occurs during this update interval, check for a hit.
 *  We'll build a linked list of all craft that this projectile may strike
 *  during this period, arranged in ascending order by time of "perigee"
 *  (closest pass).  We'll then test for strikes.  If a projectile misses
 *  the first object, then it may have struck subsequent objects in the
 *  list ...
 */

/*
 *  One special case occurs when a target or missile's turn suddenly
 *  changes the perigee time from positive to negative.  If the missile
 *  is within hitting range at t=0 and the time of perigee is negative,
 *  then zap 'em.
 */

			if (t < 0.0) {
				d = sqrt (s0.x *s0.x + s0.y * s0.y +
					s0.z * s0.z);
				if (isCannonHit (d, c)) {
					t = 0.0;
				}
			}

			if (t >= 0.0 && t <= 1.0) {
				q = &p[j];
#ifdef notdef
				q->Sg = c->prevSg;
				q->Sg.x += (c->Sg.x - c->prevSg.x) * t;
				q->Sg.y += (c->Sg.y - c->prevSg.y) * t;
				q->Sg.z += (c->Sg.z - c->prevSg.z) * t;
#endif
				q->Sg = prevSg;
				q->Sg.x += Vm.x * t;
				q->Sg.y += Vm.y * t;
				q->Sg.z += Vm.z * t;

				if (list == (entry *) NULL) {
					q->next = list;
					list = q;
				}
				else if (list->time > t) {
					q->next = list;
					list = q;
				}
				else {
					for (rprev=list, r=list->next; r != (entry *) NULL;) {
						if (r->time > t) break;
						rprev = r;
						r = r->next;
					}
					if (rprev != list) 
						rprev->next = q;
					q->next = r;
				}
				q->time = t;
				q->c = c;
				q->min = sqrt (pow(v.x * t + s0.x, 2.0) +
					pow (v.y * t + s0.y, 2.0) +
					pow (v.z * t + s0.z, 2.0) );
					if (cdebug)
					printf ("perigee %g feet; craft %d.\n",
						q->min, j);
			}
		}

/*
 *  Now look for cannon hits in the list of perigees.
 */

		for (r=list; r != (entry *) NULL; r=r->next)
			if (isCannonHit (r->min, r->c)) {
				newExplosion (&(r->Sg), 3.0, 2.0, 0.5);
				if (absorbDamage (r->c, 3) == 0)
					killPlayer (r->c);
				break;
			}
	}
}

/*ARGSUSED*/
int isCannonHit (min, c)
double min;
craft	*c; {

	return (min < 30.0) ? 1 : 0;
}
