/*
 * Copyright (C) 2002  Jeremie Allard (Hufo / N.A.A.)
 * Ported to Linux by Tugrul Galatali <tugrul@galatali.com>
 *
 * hufo_tunnel is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * hufo_tunnel 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#ifndef HUFO_TUNNEL_H
#define HUFO_TUNNEL_H

#include "rsRand.h"
#include "SVector.h"

#ifdef _DEBUG
#include <assert.h>
#endif

// sorry but the following comments are in french, no time to translate :-(

#define HVCtrX 0.0
#define HVCtrY 0.0
float HoleFoc = 2.0f;

#define HoleFocX HoleFoc
#define HoleFocY HoleFoc

#define SHFTHTPS 8
#define HOLEGEN 25
#define HoleNbParImg 64
#define HoleNbImg 128
#define HOLEVIT 8

#define TUNNELCOLORRND 0.15f
#define TUNNELCOLORFACT 0.9999f

int dCoarse;

struct THole {
	float u, v;		// position du point sur le plan
	float c1, c2;		// color coefficients
};
THole Hole[HoleNbImg][HoleNbParImg];
THole RefHole[HoleNbParImg];

struct THoleTraj {
	SVector3D a;		// angles
	SVector3D o, m, n;	// vecteurs base du plan
	float s;		// taille du cercle
};
THoleTraj HoleTraj[HoleNbImg];

void HoleInitPlan (int p, int t, float ss = 1.0)
{
	float c1, c2;
	float s = ss;

	// calc color and position

	for (int i = 0; i < HoleNbParImg; i++) {
		if (dCoarse) {
			c1 = RefHole[i].c1 + rsRandf (2 * TUNNELCOLORRND) - TUNNELCOLORRND;
			if (c1 < 0)
				c1 = 0;
			else if (c1 > 1)
				c1 = 1;
			c2 = RefHole[i].c2 + rsRandf (2 * TUNNELCOLORRND) - TUNNELCOLORRND;
			if (c2 < 0)
				c2 = 0;
			else if (c2 > 1)
				c2 = 1;

			Hole[p][i].c1 = c1;
			Hole[p][i].c2 = c2;
			s = ss * (2.0f - c1);
		}

		Hole[p][i].u = RefHole[i].u * s;
		Hole[p][i].v = RefHole[i].v * s;
	}

	if (dCoarse) {
		// color smoothing
		for (int i = 0; i < HoleNbParImg; i++) {
			RefHole[i].c1 = (Hole[p][i].c1 + Hole[p][(i + 1) & (HoleNbParImg - 1)].c1 + Hole[p][(i - 1) & (HoleNbParImg - 1)].c1) * TUNNELCOLORFACT / 3;
			RefHole[i].c2 = (Hole[p][i].c2 + Hole[p][(i + 1) & (HoleNbParImg - 1)].c2 + Hole[p][(i - 1) & (HoleNbParImg - 1)].c2) * TUNNELCOLORFACT / 3;
		}
	}

	HoleTraj[p].s = s * HOLEGEN;

	if (dCoarse) {
		// calc trajectory (based on c2)

		static float c2a_0 = 0;
		static float c2b_0 = 0;

#define C2_VIT 0.01f

		float c2a = RefHole[0].c2;
		float c2b = RefHole[HoleNbParImg / 2].c2;

		if (c2a < c2a_0 - C2_VIT)
			c2a_0 -= C2_VIT;
		else if (c2a > c2a_0 + C2_VIT)
			c2a_0 += C2_VIT;
		else
			c2a_0 = c2a;

		if (c2b < c2b_0 - C2_VIT)
			c2b_0 -= C2_VIT;
		else if (c2b > c2b_0 + C2_VIT)
			c2b_0 += C2_VIT;
		else
			c2b_0 = c2a;

		float az = 2 * c2a_0 - 1.0f;

		az = az * 2;
		float ax = 2 * c2b_0 - 1.0f;

		ax = ax * 2;

		HoleTraj[p].a.y = 0;	//(float)(sin(t*PI/90)*4*HOLEVIT*PI/1500);
		HoleTraj[p].a.z = (az * az * az * 2) * HOLEVIT * PI / 2500;	//(float)(sin(t*PI/170)*2*HOLEVIT*PI/2500);
		HoleTraj[p].a.x = (ax * ax * ax * 2) * HOLEVIT * PI / 2500;	//(float)(sin(t*PI/170)*2*HOLEVIT*PI/2500);
	} else {
		HoleTraj[p].a.x = (float)(sin (t * PI / 40) * HOLEVIT * PI / 1500);
		HoleTraj[p].a.y = (float)(sin (t * PI / 90) * 4 * HOLEVIT * PI / 1500);
		HoleTraj[p].a.z = (float)(sin (t * PI / 70) * 2 * HOLEVIT * PI / 1500);
	}
}

int HoleLastP;			// dernier plan calculé
int HoleNbImgA;			// nombre de plans affichés
Bool dSinHole = False;
Bool StopHole = False;
void HoleInit ()
{
	int i;

	for (i = 0; i < HoleNbParImg; i++) {
		RefHole[i].u = (float)(sin (i * (2 * PI / HoleNbParImg)) * HOLEGEN);
		RefHole[i].v = (float)(cos (i * (2 * PI / HoleNbParImg)) * HOLEGEN);
		RefHole[i].c1 = 0;
		RefHole[i].c2 = 0;
	}
	HoleLastP = -1;
}

struct BBox2D {
	float u0, v0;
	float u1, v1;
};

/*
 voir calculs a la fin
*/
void InterLnCircle (double u, double v, double w, float *x1, float *y1, float *x2, float *y2)
{
	double d = 1.0 / sqrt (u * u + v * v);

	u *= d;
	v *= d;
	w *= d;
#ifdef _DEBUG
	assert (abs (w) < 1.0);
#endif
	d = sqrt (1.0 - w * w);
	*x1 = w * u - d * v;
	*y1 = w * v + d * u;
	*x2 = w * u + d * v;
	*y2 = w * v - d * u;
#ifdef _DEBUG
	assert (abs ((*x1) * u + (*y1) * v - w) < 1e-6);
	assert (abs ((*x2) * u + (*y2) * v - w) < 1e-6);
	assert (abs (sqr (*x1) + sqr (*y1) - 1.0) < 1e-6);
	assert (abs (sqr (*x2) + sqr (*y2) - 1.0) < 1e-6);
#endif
}

void CalcBBoxPlan (int p, BBox2D * b)
{
	SVector3D o, m, n;

	o = HoleTraj[p].o;
	m = HoleTraj[p].m * HoleTraj[p].s;
	n = HoleTraj[p].n * HoleTraj[p].s;
	float ca1, ca2, sa1, sa2;
	float f1, f2;

	//ca(mxoy-myox)+sa(nyox-nxoy)=mynx-mxny
	InterLnCircle (o.y * m.x - o.x * m.y, o.x * n.y - o.y * n.x, m.y * n.x - m.x * n.y, &ca1, &sa1, &ca2, &sa2);
	f1 = HVCtrX + HoleFocX * (o.x + m.x * sa1 + n.x * ca1) / (o.y + m.y * sa1 + n.y * ca1);
	f2 = HVCtrX + HoleFocX * (o.x + m.x * sa2 + n.x * ca2) / (o.y + m.y * sa2 + n.y * ca2);
	if (f1 < f2) {
		b->u0 = f1;
		b->u1 = f2;
	} else {
		b->u0 = f2;
		b->u1 = f1;
	}
	//ca(mzoy-myoz)+sa(nyoz-nzoy)=mynz-mzny
	InterLnCircle (o.y * m.z - o.z * m.y, o.z * n.y - o.y * n.z, m.y * n.z - m.z * n.y, &ca1, &sa1, &ca2, &sa2);
	f1 = HVCtrY - HoleFocY * (o.z + m.z * sa1 + n.z * ca1) / (o.y + m.y * sa1 + n.y * ca1);
	f2 = HVCtrY - HoleFocY * (o.z + m.z * sa2 + n.z * ca2) / (o.y + m.y * sa2 + n.y * ca2);
	if (f1 < f2) {
		b->v0 = f1;
		b->v1 = f2;
	} else {
		b->v0 = f2;
		b->v1 = f1;
	}
}
void InterBBox (BBox2D * a, BBox2D const *b)
{
#define max(a, b) ((a > b) ? a : b);
#define min(a, b) ((a > b) ? b : a);

	a->u0 = max (a->u0, b->u0);
	a->u1 = min (a->u1, b->u1);
	a->v0 = max (a->v0, b->v0);
	a->v1 = min (a->v1, b->v1);
}
Bool BBoxEmpty (BBox2D const *b)
{
	return (b->u0 > b->u1 || b->v0 > b->v1);
}

void MkBBoxAll (BBox2D * b)
{
	b->u0 = -1e10;
	b->u1 = 1e10;
	b->v0 = -1e10;
	b->v1 = 1e10;
}

BBox2D BBPlan[HoleNbImg];

struct HPT {
	float ex, ey;
	float u, v;
	float c1;
} Pt[HoleNbImg][HoleNbParImg + 1];
float PtDist[HoleNbImg];

void CalcHole (int T)
{
	float ft = (T & ((1 << SHFTHTPS) - 1)) * (1.0 / (1 << SHFTHTPS));
	int it = T >> SHFTHTPS;
	int i, p, s;

	// Premiere etape : calcul de la position des plans
	SVector3D o (0, 0, 0);
	SVector3D m (1, 0, 0);
	SVector3D v (0, HOLEVIT, 0);
	SVector3D n (0, 0, 1);
	SMatrix3D mm;
	BBox2D bplan, bhole;

	MkBBoxAll (&bhole);
	for (i = 0; i < HoleNbImg && !BBoxEmpty (&bhole); i++) {
		p = (i + it) & (HoleNbImg - 1);
		if (i + it > HoleLastP) {	// le plan n'a pas été calculé
			if (StopHole)
				break;	// on ne calcule plus de plan
			HoleInitPlan (p, i + it, (dSinHole ? (sin ((i + it) * PI / 10) + 4.0) / 4 : 1.0));
			HoleLastP = i + it;
		}
		if (!i)
			o += v * (1 - ft);
		else
			o += v;
		HoleTraj[p].o = o;
		HoleTraj[p].m = m;
		HoleTraj[p].n = n;
		if (!i)
			mm.FromAngle (HoleTraj[p].a.x * (1 - ft), HoleTraj[p].a.y * (1 - ft), HoleTraj[p].a.z * (1 - ft));
		else
			mm.FromAngle (HoleTraj[p].a.x, HoleTraj[p].a.y, HoleTraj[p].a.z);
		mm.Apply (&m);
		mm.Apply (&v);
		mm.Apply (&n);
		CalcBBoxPlan (p, &bplan);
		BBPlan[i] = bhole;
		InterBBox (&bhole, &bplan);
	}
	HoleNbImgA = i;
	if (!HoleNbImgA)
		return;
	// Deuxieme etape : position des points
	// Equations:
	// un point de coordonnees (u,v) du plan (O,m,n)
	// on a : s>=o>+u*m>+v*n>
	// px=ox+u*mx+v*nx
	// py=oy+u*my+v*ny
	// pz=oz+u*mz+v*nz
	SVector3D pp;
	float txtv;
	float pu, pv;

//      for (i=HoleNbImgA-1;i>=0;i--)
	for (i = 0; i < HoleNbImgA; ++i) {
		PtDist[i] = i + 1.0 - ft;
		p = (i + it) & (HoleNbImg - 1);
		o = HoleTraj[p].o;
		m = HoleTraj[p].m;
		n = HoleTraj[p].n;
		txtv = (i + it) * (2.0 / HoleNbImg);
		for (s = 0; s <= HoleNbParImg; s += ((dCoarse > 0) ? dCoarse : 1)) {
			pu = Hole[p][s & (HoleNbParImg - 1)].u;
			pv = Hole[p][s & (HoleNbParImg - 1)].v;
			//pp=o+m*pu+n*pv;
			pp.x = o.x + pu * m.x + pv * n.x;
			pp.y = o.y + pu * m.y + pv * n.y;
			pp.z = o.z + pu * m.z + pv * n.z;
			if (pp.y <= 0)
				pp.y = 0.1f;	// en cas de probleme
			Pt[i][s].ex = HVCtrX + HoleFocX * pp.x / pp.y;
			Pt[i][s].ey = HVCtrY - HoleFocY * pp.z / pp.y;
			if (dCoarse)
				Pt[i][s].c1 = 0.25f + 0.75f * Hole[p][s & (HoleNbParImg - 1)].c1;
			Pt[i][s].u = s * (1.0 / HoleNbParImg);
			Pt[i][s].v = txtv;
		}
	}
}

/*
Optimisation de l'affichage du tunnel:
le tunnel est constitué de cercles.
on peut arreter l'affichage du  tunnel quand le reste cache tout l'écran
si on garde une zone de visibilité qui montre le zone encore visible de l'écran
après l'affichage d'une partir du tunnel, on s'arrete quand cete zone est vide.
On a donc besoin:
_ d'une fonction qui calcul la zone du trou du tunnel apres l'affichage d'un plan donné
_ d'une fonction qui calcule l'intersection de deux zones
Pour leur simplicité on va choisir des rectangles
*/

/* calcul du rectangle projeté
  les points suivant sont sur le cercle: P=O+M*sin(a)+N*cos(a)
	Px=ox+sa*mx+ca+nx Py=oy+sa*my+ca+ny Pz=oz+sa*mz+ca+nz
	ex=cx+fx*Px/Py
	ey=cy-fy*Pz/Py
	on cherche les bornes de ex et de ey
	ex=exmin ou exmax <=> dex/da=0
	ey=eymin ou eymax <=> dey/da=0
	dex/da=fx*(Py * dPx/da - Px * dPy/da) / Py² = 0
	<=> Py * dPx/da - Px * dPy/da = 0
	(camx-sanx)(oy+samy+cany)-(ox+samx+canx)(camy-sany)=0
	 camxoy+casamxmy+ca²mxny-sanxoy-sa²nxmy-casanxny
	-camyox-casamymx-ca²mynx+sanyox+sa²nymx+casanxny   =0
	ca(mxoy-myox)+sa(nyox-nxoy)+(ca²+sa²)(mxny-mynx)   =0
	ca(mxoy-myox)+sa(nyox-nxoy)=mynx-mxny

	(oymx-oxmy)ca + (-oynx+oxny)sa + (mxny-mynx)ca² + (mxny-mynx)sa² =0
	(oymx-oxmy)ca + (-oynx+oxny)sa = mynx-mxny et ca²+sa²=1
	U*ca+V*sa=W et ca²+sa²=1

	Ux+Vy=W d=1/sqrt(U²+V²),u=U*d,v=V*d,w=W*d => ux+vy=w (u²+v²=1)
	distance entre la droite et l'origine:d=abs(w)
	point a cette distance:
	vx-uy=0 && ux+vy=w
	=> x=uw
	=> y=vw
	si d>1 -> erreur
	les solutions sont (x,y)+-(1-d)*(-v,u)
	x1=uw-sqrt(1-sqr(d))*v y1=vw+sqrt(1-sqr(d))*u
	x2=uw+sqrt(1-sqr(d))*v y2=vw-sqrt(1-sqr(d))*u
*/

#endif
