/**********************************************************************

	Title :			Camera Module 
	Filename :		camera.c
	Author :		Thomas Szirtes
	Revision :		1.0

	Notes :			Camera abstraction for ninja view

	Copyright :		(C) SEGA Europe 1998

  **********************************************************************/

/*
 *	Include
 */
#include "camera.h"

void njExecuteFade(Float f);

/* Initialize function */
/*--------------------------------------------------------------------------

  void camera_Perspective(int p)
 
  Parameters:
 	p				perspective angle
 
  Returns:			None
 
  Notes:
 
  --------------------------------------------------------------------------*/

void camera_Perspective(int p)
{
	njSetPerspective(NJM_DEG_ANG(p));	
}


/*--------------------------------------------------------------------------
 
  void camera_Init(CAMERA *c)
 
  Parameters:
 	c				CAMERA OBJECT
 
  Returns:			None
 
  Notes:
 
 --------------------------------------------------------------------------*/

void	camera_Init			(CAMERA *c)
{
	c->base_angle.xz	=	NJM_DEG_ANG(0.0f);
	c->base_angle.yz	=	NJM_DEG_ANG(0.0f);

	c->head_angle.xz	=	NJM_DEG_ANG(0.0f);
	c->head_angle.yz	=	NJM_DEG_ANG(0.0f);
	c->head_angle.roll	=	NJM_DEG_ANG(0.0f);

	c->fogenable	= FALSE;

	njInitView(&c->view);
}


/* Core Functions */

/*--------------------------------------------------------------------------
 
  void camera_Position(CAMERA *c, float x, float y, float z)
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			x,y,z coordinates
 
  Returns:			None
 
  Notes:
 	Places the camera at the specified position. View takes
  effect of immediately. Not compatible with utility functions
 
 --------------------------------------------------------------------------*/

void	camera_Position		(CAMERA *c, float x, float y, float z)
{
	c->view.px	=	x;
	c->view.py	=	y;
	c->view.pz	=	z;
	njSetView(&c->view);
}


/*--------------------------------------------------------------------------
 
  void camera_SetDirection(CAMERA *c, NJS_VECTOR *v)
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			target x,y,z coordinates
 
  Returns:			None
 
  Notes:
 	Can specify a vector as the direction to face the camera
  in - exclusive of all utility settings
 
 --------------------------------------------------------------------------*/

void	camera_SetDirection (CAMERA *c, NJS_VECTOR *v)
{
	c->view.vx	=	v->x;
	c->view.vy	=	v->y;
	c->view.vz	=	v->z;
	njSetView(&c->view);
}


/*--------------------------------------------------------------------------
 
  void camera_GetDirVector(CAMERA *c, NJS_VECTOR *vd)
 
  Parameters:
 	c				CAMERA OBJECT
 	vd				destination direction vector
 
  Returns:			None
 
  Notes:
 	Sets the view to use this camera.
  
 --------------------------------------------------------------------------*/

void	camera_GetDirVector	(CAMERA *c, NJS_VECTOR *vd)
{
	Angle rotx, roty;
	NJS_VECTOR	v;

	rotx	=	c->head_angle.xz + c->base_angle.xz;
	roty	=	c->head_angle.yz + c->base_angle.yz;

	/* Convert xz,yz angles to direction vector */
	v.x = 0.f; 
	v.y = 0.f; 
	v.z = -1.0f;			/* initilize reference unit vector */

	njPushMatrix(NULL);
	njRotateXYZ(NULL,rotx,roty,(Angle)0.0f);
	njCalcVector( NULL, &v, vd );
	njPopMatrix(1);

	/* vd should contain direction vector */
}


/*--------------------------------------------------------------------------

  void camera_View(CAMERA *c)
 
  Parameters:
 	c				CAMERA OBJECT
 
  Returns:			None
 
  Notes:
 	Sets the view to use this camera.
  
 --------------------------------------------------------------------------*/

void	camera_View			(CAMERA *c)
{
	Angle rotx, roty;

	rotx	=	c->head_angle.xz + c->base_angle.xz;
	roty	=	c->head_angle.yz + c->base_angle.yz + NJM_DEG_ANG(180.f);
	
	njInitView(&c->view);

	njRotateViewXRelative(&c->view,rotx);
	njRotateViewYRelative(&c->view,roty);
	njRotateViewZRelative(&c->view,c->head_angle.roll);

	c->view.px	=	c->pos.x;
	c->view.py	=	c->pos.y;
	c->view.pz	=	c->pos.z;

	njSetView(&c->view);
}


/* Utilitiy Functions */

/*--------------------------------------------------------------------------

  void camera_LookAt(CAMERA *c, float x, float y, float z)
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			target x,y,z coordinates
 
  Returns:			None
 
  Notes:
 	Rotates the camera head such that the target object is
  in the centre of the view without rotating the body of the camera
  
 --------------------------------------------------------------------------*/

void	camera_LookAt		(CAMERA *c, float tx, float ty, float tz)
{
	NJS_VECTOR	delta;

	delta.x		=	c->pos.x - tx;		/*view.px */
	delta.y		=	c->pos.y - ty;
	delta.z		=	c->pos.z - tz;

	njUnitVector(&delta);

	c->head_angle.xz	=	(njArcSin( -delta.y ) - c->base_angle.xz);

	
	if(delta.z<=0.0f)
		c->head_angle.yz	=	njArcTan( delta.x/delta.z ) - c->base_angle.yz;
	else
		c->head_angle.yz	=	NJM_DEG_ANG(180.0f) + (njArcTan( delta.x/delta.z ) - c->base_angle.yz);
	
}



/*--------------------------------------------------------------------------
 
  void camera_Move(CAMERA *c, float d, NJS_POINT3 *prjct, 
 					bool doit)
 
  Parameters:
 	c				CAMERA OBJECT
 	d				distance to move
 	prjct			projected new position
 	doit			TRUE - update camera, FALSE - do not
 
  Returns:			None
 
  Notes:
 	Moves the camera in the direction of the base orientation
 	by distance d (d can be negative or positive)
 
 --------------------------------------------------------------------------*/

void	camera_Move			(CAMERA *c, float d, NJS_POINT3 *prjct, BOOL doit)
{

	prjct->x	=	c->pos.x - d*njSin(c->base_angle.yz);
	prjct->y	=	c->pos.y + d*njSin(c->base_angle.xz);
	prjct->z	=	c->pos.z - d*njCos(c->base_angle.yz);

	if(doit)
	{
		c->pos.x	=	prjct->x;
		c->pos.y	=	prjct->y;
		c->pos.z	=	prjct->z;
	}
}


/*--------------------------------------------------------------------------

  void camera_Strafe(CAMERA *c, float d, NJS_POINT3 *prjct, 
 					bool doit)
 
  Parameters:
 	c				CAMERA OBJECT
 	d				distance to strafe (+ to right, - to left)
 	prjct			projected new position
 	doit			TRUE - update camera, FALSE - do not
 
  Returns:			None
 
  Notes:
 	Moves the camera in the direction of the base orientation
 	by distance d (d can be negative or positive)
 
  --------------------------------------------------------------------------*/

void	camera_Strafe		(CAMERA *c, float d, NJS_POINT3 *prjct, BOOL doit)
{

	prjct->x	=	c->pos.x - d*njSin((c->base_angle.yz+NJM_DEG_ANG(90.0f)));
	prjct->y	=	c->pos.y + d*njSin(c->base_angle.xz);
	prjct->z	=	c->pos.z - d*njCos((c->base_angle.yz+NJM_DEG_ANG(90.0f)));

	if(doit)
	{
		c->pos.x	=	prjct->x;
		c->pos.y	=	prjct->y;
		c->pos.z	=	prjct->z;
	}
}



/*--------------------------------------------------------------------------

  void camera_MoveTo(CAMERA *c, float x, float y, float z)
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			x,y,z axis positional values
 
  Returns:			None
 
  Notes:
 	Moves the camera to position x,y,z. Does not affect
  any other camera attributes.
 
 --------------------------------------------------------------------------*/

void	camera_MoveTo		(CAMERA *c, float x, float y, float z)
{
	c->pos.x	=	x;
	c->pos.y	=	y;
	c->pos.z	=	z;
	
}



/*--------------------------------------------------------------------------
 
  void camera_Track(CAMERA *c, float x, float y, float z,
 					 NJS_POINT3 *prjct, bool doit)
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			x,y,z translation values
 	prjct			projected new position
 	doit			TRUE - update camera, FALSE - do not
 
  Returns:			None
 
  Notes:
 	Translates the camera regardless of orientation in three
 	axes.
 
 --------------------------------------------------------------------------*/

void	camera_Track		(CAMERA *c, float x, float y, float z, NJS_POINT3 *prjct, BOOL doit)
{

	prjct->x	=	c->pos.x + x;
	prjct->y	=	c->pos.y + y;
	prjct->z	=	c->pos.z + z;

	if(doit)
	{
		c->pos.x	=	prjct->x;
		c->pos.y	=	prjct->y;
		c->pos.z	=	prjct->z;
	}

}



/*--------------------------------------------------------------------------

  void camera_Orient(CAMERA *c, Angle xz, Angle yz)
  void camera_OrientRelative(CAMERA *c, Angle xz, Angle yz)
 
  Parameters:
 	c				CAMERA OBJECT
 	xz,yz			xz and yz rotation angles
 
  Returns:			None
 
  Notes:
 	Rotates the camera base (and therefore also the head)
 	Absolute and Relative versions available.
 
 --------------------------------------------------------------------------*/

void	camera_Orient		(CAMERA *c, Angle xz, Angle yz)
{
	c->base_angle.xz	=	xz;
	c->base_angle.yz	=	yz;
}

void	camera_OrientRelative (CAMERA *c, Angle xz, Angle yz)
{
	c->base_angle.xz	-=	xz;
	c->base_angle.yz	-=	yz;
}


/*--------------------------------------------------------------------------
 
  void camera_Pan(CAMERA *c, Angle xz, Angle yz)
  void camera_PanRelative(CAMERA *c, Angle xz, Angle yz)
 
  Parameters:
 	c				CAMERA OBJECT
 	xz,yz			xz and yz rotation angles
 
  Returns:			None
 
  Notes:
 	Pans (rotates) the camera head without moving base.
 	Absolute and Relative versions available.
 
 --------------------------------------------------------------------------*/

void	camera_Pan			(CAMERA *c, Angle xz, Angle yz)
{
	c->head_angle.xz	=	xz;
	c->head_angle.yz	=	yz;
}



void	camera_PanRelative	(CAMERA *c, Angle xz, Angle yz)
{
	c->head_angle.xz	-=	xz;
	c->head_angle.yz	-=	yz;
}


/*--------------------------------------------------------------------------
 
  void camera_OrientToPan(CAMERA *c)
 
  Parameters:
 	c				CAMERA OBJECT
 
  Returns:			None
 
  Notes:
 	Moves the camera base into alignment with camera head.
   Any further moves will be in direction camera is looking
 
 --------------------------------------------------------------------------*/

void	camera_OrientToPan	(CAMERA *c)
{
	c->base_angle.xz	+=	c->head_angle.xz;
	c->base_angle.yz	+=	c->head_angle.yz;
	c->head_angle.xz	=	NJM_DEG_ANG(0.0);
	c->head_angle.yz	=	NJM_DEG_ANG(0.0);

}



/*--------------------------------------------------------------------------
 
  void camera_Roll(CAMERA *c, Angle r)
  void camera_RollRelative(CAMERA *c, Angle r)
 
  Parameters:
 	c				CAMERA OBJECT
 	r				Angle of roll/to-roll camera
 
  Returns:			None
 
  Notes:
 	Rolls the camera around its own (z) axis - ie. banking
 
 --------------------------------------------------------------------------*/

void	camera_Roll			(CAMERA *c, Angle r)
{
	c->head_angle.roll	=	r;
}


void	camera_RollRelative	(CAMERA *c, Angle r)
{
	c->head_angle.roll	+=	r;	
}


/*--------------------------------------------------------------------------
 
  void camera_BackColour(CAMERA *, float r, float g, float b)
 
  Parameters:
 	c				CAMERA OBJECT
 	r,g,b			3D coordinates of flare source
 
  Returns:			None
 
  Notes:
  	All background/fading/fogging must be handled by camera 
  module otherwise flare can interfere.
 
 
 --------------------------------------------------------------------------*/

void	camera_BackColour	(CAMERA *c, float r, float g, float b)
{
	Uint32 bc;

	c->back_r	=	r;
	c->back_g	=	g;
	c->back_b	=	b;
	bc=((unsigned char)r<<16)+((unsigned char)g<<8)+((unsigned char)b);

/*	njSetBackColor( bc );	 */
	njSetBackColor( bc,bc,bc);
}


/*--------------------------------------------------------------------------

  void camera_FogOn(CAMERA *c)
  void camera_FogOff(CAMERA *c)
 
  Parameters:
 	c				CAMERA OBJECT
 
  Returns:			None
 
  Notes:
 	Enables or disables fogging
 	All background/fading/fogging must be handled by camera 
    module otherwise flare can interfere.
 
 --------------------------------------------------------------------------*/

void	camera_FogOn		(CAMERA *c)
{
	c->fogenable=TRUE;
	njFogEnable();
}


void	camera_FogOff		(CAMERA *c)
{
	c->fogenable=FALSE;
	njFogDisable();
}


/*--------------------------------------------------------------------------
 
  void camera_FogTable(CAMERA *c, NJS_FOG_TABLE *f)
 
  Parameters:
 	c				CAMERA OBJECT
 	f				pointer to user fog table
 
  Returns:			None
  
  Notes:
 	Allows user to specify own fog table if they don't want to
  use camera_FogInstant(). Will make own copy of table - so
  table can be removed after this call.
 
 -------------------------------------------------------------------------*/

void	camera_FogTable		(CAMERA *c, NJS_FOG_TABLE *f)
{

	/* Pointer to user fog table */
	/*c->fogtable=f; */
	int i;

	for(i=0;i<64;i++)
	{
		c->fogtable[i]=*f[i];
	}
	njSetFogTable(c->fogtable);

}


/*-------------------------------------------------------------------------
 
  void camera_FogInstant(CAMERA *c, float dnear, float dfar
 
  Parameters:
 	c				CAMERA OBJECT
 	dnear			z distance of front fogging value (0)
 	dfar			z distance of back fogging value (255)
 
  Returns:			None
 
  Notes:
 	Equivalent of njInstantFog3() for camera module. Fog density
  is interpolated between front and back fog planes.
 
 -------------------------------------------------------------------------*/

void	camera_FogInstant	(CAMERA *c, float dnear, float dfar)
{	/* Create fog table */
/*	njSetFogDensity(0xff0); */
	njGenerateFogTable3(c->fogtable,dnear,dfar);
/*	njGenerateFogTable(c->fogtable); */
	njSetFogTable(c->fogtable);

}


/*-------------------------------------------------------------------------
 
  void camera_FogColour(CAMERA *c, float r, float g, float b
 
  Parameters:
 	c				CAMERA OBJECT
 	r,g,b			rgb components of fog colour
 
  Returns:			None
 
  Notes:
 
 -------------------------------------------------------------------------*/

void	camera_FogColour	(CAMERA *c, float r, float g, float b)
{
	c->fog_r=r;
	c->fog_b=b;
	c->fog_g=g;
	njSetFogColor(	(((unsigned char)r)<<16)+
					(((unsigned char)g)<<8)+
					(((unsigned char)b)) );

}


/*-------------------------------------------------------------------------
 
  void camera_Flare3D(float x, float y, float z, FLARES *flare
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y,z			3D coordinates of flare source
 	flare			pointer to flare structure 
 
  Returns:			None
  
  Notes:
 
 -------------------------------------------------------------------------*/

void	 camera_Flare3D		(CAMERA *c, float x, float y, float z, FLARES *fl)
{
	NJS_POINT3 p;
	NJS_POINT2 p2;

	p.x = x;
	p.y = y;
	p.z = z;

	njProjectScreen(NULL, &p, &p2);
	if(p2.x>0.0f && p2.x<640.0f && p2.y>0.0f && p2.y<480.0f)
	{
		camera_FlareAt(c,p2.x,p2.y,z,fl);

	}

}



/*-------------------------------------------------------------------------
 
  void camera_FlareAt(float x, float y, float z, FLARES *flare
 
  Parameters:
 	c				CAMERA OBJECT
 	x,y				screen coordinates of flare source
 	z				distance of flare source (for size)
 	flare			pointer to flare structure 
 
  Returns:			None
 
  Notes:
 
 -------------------------------------------------------------------------*/

void	camera_FlareAt		(CAMERA *c, float x, float y, float z, FLARES *fl)
{
	float	dx,dy,dm;			/* delta distances */
	float	n=-30.f;			/* sorting */
	int		i;					/* counter */
	FLAREGFX *f;				/* temporary pointer */
	float	t;					/* temporary float */
	static	NJS_SPRITE	sprite;	/* sprite */

	sprite.tlist = fl->texlist;

	/* Check for resizable flare */
	if(fl->type&FLARE_FLAG_RESIZE)
		sprite.p.z	= z;
	else
		sprite.p.z	= 0;

/*		n=z; */

	dx = fl->cx - x;			/* distance from centre */
	dy = fl->cy - y;			
	
	/* Check for flickering effect */
	if(fl->type&FLARE_FLAG_FLICKER)
		fl->flicker = 126.f-fl->flicker;

	/* Check for fading effect */
	if(fl->type&FLARE_FLAG_FADE)
	{
		dm = njSqrt((dx*dx)+(dy*dy));
		if (dm<64) 
			camera_Filter(c,(64-dm)/64, 255.0f,200.f,200.f);
		else
			camera_Filter(c,0, 255.0f,200.f,200.f);
	} 
	else
	{
		dm = 0;
		njFadeDisable();
	}

	f=fl->fl;

	
	/* First flare of list is always main flare */
	if(dm>127) 
		f->argb.a = 255.0f-((dm-127.0f)/1.5f)-fl->flicker;
	else 
		f->argb.a = 255-fl->flicker;
	
	njSetConstantMaterial(&f->argb);
	sprite.tanim= fl->texanim;
	if ((f->size-z)>0) 
	{
		sprite.sx	= sprite.sy = (f->size - z);
	}
	else
	{
		sprite.sx = sprite.sy = 0;
	}
	sprite.p.x	= x;
	sprite.p.y	= y;

	/* Check for rotating flare */
	if(fl->type&FLARE_FLAG_NOROTATE)
		sprite.ang = (Angle)0.f;
	else
		sprite.ang	= (Angle)(x*(NJM_DEG_ANG(180.0f)/ 640.0f) + y*(NJM_DEG_ANG(90.0f)/ 480.0f));
	
	/* DRAW MAIN FLARE */
	njDrawSprite2D( &sprite, f->tex, n, NJD_SPRITE_COLOR|NJD_SPRITE_ANGLE);

	/* Check for secondary flare */
	if(fl->type&(FLARE_FLAG_SECONDARY) && fl->count>1)
	{
		f++;			/* next flare in list */
		n++;
		
		if (dm<64.f) 
			t=(64.f-dm)/8.f;
		else 
			t=0.f;
		
		f->argb.a	= 35.f;
	
		njSetConstantMaterial(&f->argb);
		if ((f->size-z)>0) 
			sprite.sy = (f->size - z);
		else
			sprite.sx = sprite.sy = 0;
		
		sprite.sx	= sprite.sy*t;
/*		sprite.sy	= f->size; */
		sprite.p.x	= x;
		sprite.p.y	= y;
		sprite.tanim= fl->texanim;

		/* DRAW SECONDARY FLARE */
		njDrawSprite2D( &sprite, f->tex, n, NJD_SPRITE_ANGLE|NJD_SPRITE_COLOR);
	}

	/* Now draw rest of flares in order */
	for(i=2;i<fl->count;i++)
	{
		f++;				/* increment to next flare */
		n+=1.0f;			/* draw in front */
		
		if (dm>127.f) 
			f->argb.a = 255.0f-((dm-127.0f)/1.5f);
		else 
			f->argb.a = 255;

		njSetConstantMaterial(&f->argb);
		if ((f->size-z)>0) 
			sprite.sx	= sprite.sy = (f->size - z);
		else
			sprite.sx = sprite.sy = 0;
	
/*		sprite.sx	= sprite.sy	= f->size; */
		sprite.p.x	= fl->cx + (dx * f->pos);
		sprite.p.y	= fl->cy + (dy * f->pos);
		sprite.tanim= fl->texanim;

		/* DRAW MINOR FLARES */
		njDrawSprite2D( &sprite, f->tex, n, NJD_SPRITE_COLOR|NJD_SPRITE_ANGLE);
	}

}


/*-------------------------------------------------------------------------
 
  void camera_Filter(CAMERA *c, float ratio, float r,g,b
 
  Parameters:
 	c				CAMERA OBJECT
 	ratio			ratio of source to dest color 0-1
 	r,g,b			rgb components of destination colour
 
  Returns:			None
  
  Notes:
 	Will colour everything on the screen towards the specified
  colour. The amount of colour applied is specified by the ratio
 
 -------------------------------------------------------------------------*/

void	camera_Filter		(CAMERA *c, float ratio, float r, float g, float b)
{

	Uint32	argb;
	int		n;
	NJS_FOG_TABLE fog;
	if (ratio>1.0f) ratio=1.0f;

	/* mix up the colour by ratio */
	argb=	((unsigned char)(((1.0-ratio)*c->back_r) + (ratio*r))<<16) +
			((unsigned char)(((1.0-ratio)*c->back_g) + (ratio*g))<<8) +
			((unsigned char)(((1.0-ratio)*c->back_b) + (ratio*b)));

	/* Do Background */
/*	njSetBackColor( argb ); */
	njSetBackColor( argb,argb,argb );

	/* Do fog if using */
	if (c->fogenable)
	{
		argb=	((unsigned char)(((1.0-ratio)*c->fog_r) + (ratio*r))<<16) +
				((unsigned char)(((1.0-ratio)*c->fog_g) + (ratio*g))<<8) +
				((unsigned char)(((1.0-ratio)*c->fog_b) + (ratio*b)));
		njSetFogColor(argb);
		/* Fade fog */
		for(n=0;n<64;n++)
		{
			fog[n]=(unsigned char)(((1.0-ratio)*c->fogtable[n])+(ratio*255));
		}
		njSetFogTable(fog);
	}
	else
	/* Use fade function (fade and fog cannot be used together) */
	/* this function will emulate the effect */
	{
		njFadeEnable();
		njSetFadeColor(	((unsigned char)r<<16)+
						((unsigned char)g<<8)+
						((unsigned char)b));
		njExecuteFade((float)255-(unsigned char)(ratio*255.f));
	}


}


/*-------------------------------------------------------------------------
 
  void camera_FXReset(CAMERA *c)
 
  Parameters:
 	c				CAMERA OBJECT
 
  Returns:			None
 
  Notes:
 	Call this when changing over cameras to ensure fogging, fades
  and stuff are not kept over from old camera view
 
 -------------------------------------------------------------------------*/

void	camera_FXReset		(CAMERA *c)
{
	if (c->fogenable)
		njFogEnable();
	else
		njFogDisable();

	camera_Filter(c,0,0,0,0);

}