///////////////////////////////////////////////////////////////////////////////
//	hierarchy.c
//
//	hierachical model vertex transformation code
///////////////////////////////////////////////////////////////////////////////

#include	"gl_local.h"

///////////////////////////////////////////////////////////////////////////////
//	globals
///////////////////////////////////////////////////////////////////////////////

vec3_t			hr_forward, hr_right, hr_up;

float			hr_oldworldtransform[3][4];
float			hr_worldtransform[3][4];
float			hr_transform[3][4];

daliasframe_t	*hr_thisframe;
daliasframe_t	*hr_lastframe;
vec3_t          hr_lerp_frontv;
vec3_t			hr_lerp_backv; 
vec3_t			hr_lerp_move;

///////////////////////////////////////////////////////////////////////////////
//	hr_TransformVector
///////////////////////////////////////////////////////////////////////////////

void hr_TransformVector (vec3_t in, vec3_t out)
{
	out [0] = DotProduct (in, hr_transform [0]) + hr_transform [0][3];
	out [1] = DotProduct (in, hr_transform [1]) + hr_transform [1][3];
	out [2] = DotProduct (in, hr_transform [2]) + hr_transform [2][3];
}

///////////////////////////////////////////////////////////////////////////////
//	hr_SetUpFrames
//
///////////////////////////////////////////////////////////////////////////////

void hr_SetUpFrames (dmdl_t *pmdl, int thisframe, int lastframe)
{
	if ((thisframe >= pmdl->num_frames) || (thisframe < 0))
		thisframe = 0;
	if ((lastframe >= pmdl->num_frames) || (lastframe < 0))
		lastframe = 0;

	hr_thisframe = (daliasframe_t *)((byte *)pmdl + pmdl->ofs_frames 
		+ thisframe * pmdl->framesize);

	hr_lastframe = (daliasframe_t *)((byte *)pmdl + pmdl->ofs_frames 
		+ lastframe * pmdl->framesize);
}

///////////////////////////////////////////////////////////////////////////////
//
//
///////////////////////////////////////////////////////////////////////////////

void hr_SetUpLerpData( dmdl_t *pmdl, float backlerp, vec3_t origin, vec3_t oldorigin, vec3_t angles)
{
	float	frontlerp;
	vec3_t	translation, vectors[3];
	int		i;

	frontlerp = 1.0F - backlerp;

	/*
	** convert entity's angles into discrete vectors for R, U, and F
	*/
	AngleVectors (angles, vectors[0], vectors[1], vectors[2]);

	/*
	** translation is the vector from last position to this position
	*/
	VectorSubtract (oldorigin, origin, translation);

	/*
	** move should be the delta back to the previous frame * backlerp
	*/
	hr_lerp_move [0] =  DotProduct (translation, vectors [0]);	// forward
	hr_lerp_move [1] = -DotProduct (translation, vectors [1]);	// left
	hr_lerp_move [2] =  DotProduct (translation, vectors [2]);	// up

	VectorAdd (hr_lerp_move, hr_lastframe->translate, hr_lerp_move);

	for (i=0 ; i<3 ; i++)
		hr_lerp_move [i] = backlerp * hr_lerp_move [i] + frontlerp
							* hr_thisframe->translate [i];

	for (i=0 ; i<3 ; i++)
	{
		hr_lerp_frontv [i] = frontlerp * hr_thisframe->scale [i];
		hr_lerp_backv [i]  = backlerp  * hr_lastframe->scale [i];
	}
}

///////////////////////////////////////////////////////////////////////////////
//	hr_TransformFinalVert
//
//	interpolates between two vertices in worldspace 
//
//	won't correctly work on shells (will compute verts that are slightly off)
///////////////////////////////////////////////////////////////////////////////

void hr_TransformFinalVert (dtrivertx_t *oldv, dtrivertx_t *newv, vec3_t fv)
{
	vec3_t  lerped_vert;

	lerped_vert [0] = hr_lerp_move [0] + oldv->v [0] * hr_lerp_backv [0] 
						+ newv->v [0] * hr_lerp_frontv [0];
	lerped_vert [1] = hr_lerp_move [1] + oldv->v [1] * hr_lerp_backv [1] 
						+ newv->v [1] * hr_lerp_frontv [1];
	lerped_vert [2] = hr_lerp_move [2] + oldv->v [2] * hr_lerp_backv [2] 
						+ newv->v [2] * hr_lerp_frontv [2];

	//	disable lerping for now
	lerped_vert [0] = newv->v [0];
	lerped_vert [1] = newv->v [1];
	lerped_vert [2] = newv->v [2];

	//	plightnormal = r_avertexnormals [newv->lightnormalindex];
	//	disregard powersuits
	/*
	if ( currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
	{
		//	Nelno: this just scales the tri's out from their original location
		//	along the same vector as their lighting normal to make the shell
		lerped_vert[0] += plightnormal[0] * POWERSUIT_SCALE;
		lerped_vert[1] += plightnormal[1] * POWERSUIT_SCALE;
		lerped_vert[2] += plightnormal[2] * POWERSUIT_SCALE;
	}
	*/

	//	transform
	fv [0] = DotProduct (lerped_vert, hr_transform [0]) + hr_transform [0][3];
	fv [1] = DotProduct (lerped_vert, hr_transform [1]) + hr_transform [1][3];
	fv [2] = DotProduct (lerped_vert, hr_transform [2]) + hr_transform [2][3];
}

///////////////////////////////////////////////////////////////////////////////
//	hr_SetUpTransform
//
//	set up the transformation matrix for passed values
//
//	r_origin == view_org, will this cause a lag problem if we do this
//	inbetween frames?  Or will one frame of lag be unoticeable anyway...
//
//	maybe turning off lerping for hierarchical models makes more sense...
//	we may not be able to get valid lerp info since we're calling these
//	routines at a different point than when the model is being drawn...
//	but then, that's only when we're doing hr stuff from the server side.
///////////////////////////////////////////////////////////////////////////////

void hr_SetUpTransform (vec3_t angles, vec3_t origin, vec3_t oldorigin)
{
	int				i;
	static float	viewmatrix [3][4];
	static float	tmatrix [3][4], t2matrix [3][4];

/*
//	angles[ROLL] = ent->angles [ROLL];
//	angles[PITCH] = ent->angles [PITCH];
//	angles[YAW] = ent->angles [YAW];
	AngleVectors (angles, hr_forward, hr_right, hr_up);

	memset (hr_worldtransform, 0, sizeof (hr_worldtransform));
	memset (hr_oldworldtransform, 0, sizeof (hr_oldworldtransform));

	for (i=0 ; i<3 ; i++)
	{
		hr_oldworldtransform [i][0] = hr_worldtransform [i][0] =  hr_forward [i];
		hr_oldworldtransform [i][0] = hr_worldtransform [i][1] = -hr_right [i];
		hr_oldworldtransform [i][0] = hr_worldtransform [i][2] =  hr_up [i];
	}

	hr_worldtransform [0][3] = origin [0] - r_origin [0];
	hr_worldtransform [1][3] = origin [1] - r_origin [1];
	hr_worldtransform [2][3] = origin [2] - r_origin [2];

	//	NelnoFIXME:	need old origins???
	hr_oldworldtransform [0][3] = oldorigin [0] - r_origin [0];
	hr_oldworldtransform [1][3] = oldorigin [1] - r_origin [1];
	hr_oldworldtransform [2][3] = oldorigin [2] - r_origin [2];

	VectorCopy (vright, viewmatrix [0]);
	VectorCopy (vup, viewmatrix [1]);
	VectorInverse (viewmatrix [1]);
	VectorCopy (vpn, viewmatrix [2]);

	viewmatrix [0][3] = 0;
	viewmatrix [1][3] = 0;
	viewmatrix [2][3] = 0;

	R_ConcatTransforms (viewmatrix, hr_worldtransform, hr_transform);

	hr_worldtransform [0][3] = origin [0];
	hr_worldtransform [1][3] = origin [1];
	hr_worldtransform [2][3] = origin [2];

	//	NelnoFIXME:	need this?
	hr_oldworldtransform [0][3] = oldorigin [0];
	hr_oldworldtransform [1][3] = oldorigin [1];
	hr_oldworldtransform [2][3] = oldorigin [2];
*/

	///////////////////////////////////////////////////////////////////////////
	//	these are the Quake 1 transformations
	//	they do not currently take frame interpolation into account
	///////////////////////////////////////////////////////////////////////////

	AngleVectors (angles, hr_forward, hr_right, hr_up);

	tmatrix [0][0] = hr_thisframe->scale [0];
	tmatrix [1][1] = hr_thisframe->scale [1];
	tmatrix [2][2] = hr_thisframe->scale [2];

	tmatrix [0][3] = hr_thisframe->translate [0];
	tmatrix [1][3] = hr_thisframe->translate [1];
	tmatrix [2][3] = hr_thisframe->translate [2];

	for (i=0 ; i<3 ; i++)
	{
		t2matrix [i][0] = hr_forward [i];
		t2matrix [i][1] = -hr_right [i];
		t2matrix [i][2] = hr_up [i];
	}

	t2matrix [0][3] = origin [0];
	t2matrix [1][3] = origin [1];
	t2matrix [2][3] = origin [2];

	R_ConcatTransforms (t2matrix, tmatrix, hr_transform);
}

///////////////////////////////////////////////////////////////////////////////
//	
//
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
//	hr_VertIndex
//
//	returns an absolute index (ie. index out of all vertices in the model) for
//	a vertex index relative to a surface
//
//	Nelno:	could speed this up by having a pre-computed list of verts for
//			each surface, then just index into that list rather than doing
//			the for loop.  Need to do this once hr code is running.
///////////////////////////////////////////////////////////////////////////////

int	hr_TriIndex (model_t *mod, int surfIndex, int rel_triIndex)
{
	dmdl_t			*paliashdr;
	extinfo_t		*extra_info;
	extpoly_t		*poly_info;
	int				abs_index, rel_index;

	paliashdr = (dmdl_t *)mod->extradata;
	extra_info = (extinfo_t *) ((byte *) paliashdr + paliashdr->ofs_end);

	poly_info = (extpoly_t *) ((byte *)extra_info + extra_info->ofs_poly);

	for (rel_index = abs_index = 0; abs_index < paliashdr->num_xyz; abs_index++, *poly_info++)
	{
		if (poly_info->surfid == surfIndex)
		{
			//	this poly is in the correct surface
			if (rel_index == rel_triIndex)
				//	this poly's surface-relative index 
				return	abs_index;

			rel_index++;
		}
	}

	return	-1;
}

///////////////////////////////////////////////////////////////////////////////
//	hr_VertInfo
//
//	get the transformed location of a vertex in a surface
///////////////////////////////////////////////////////////////////////////////

int hr_VertInfo (entity_t *ent, int surfaceIndex, int triIndex, vec3_t vert)
{
	dmdl_t			*paliashdr;
	dtrivertx_t		*verts, *oldverts;
	extinfo_t		*extra_info;
	extsurf_t		*surf_info;
	int				abs_triIndex;
	vec3_t			v;

	paliashdr = (dmdl_t *)ent->model->extradata;

	extra_info = (extinfo_t *) ((byte *) paliashdr + paliashdr->ofs_end);

	//	check for invalid surface index
	if (surfaceIndex >= extra_info->num_surfs)
		return false;

	if (ent->frame >= paliashdr->num_frames)
		return	false;

	if (triIndex >= paliashdr->num_xyz)
		return	false;

	//	pointer to models surface info
	surf_info = (extsurf_t *) ((byte *) extra_info + extra_info->ofs_surf);

	hr_SetUpFrames (ent->model->extradata, ent->frame, ent->oldframe);

	//	won't work for Quake 2 type models
//	abs_vertIndex = hr_VertIndex (ent->model, surfaceIndex, vertIndex);
	abs_triIndex = triIndex;

	//	FIXME:	always vertex 0 for testing 
	verts = hr_thisframe->verts + abs_triIndex;
	oldverts = hr_lastframe->verts + abs_triIndex;

	//	set up the transformation matrix for this model
	hr_SetUpTransform (ent->angles, ent->origin, ent->oldorigin);

	VectorCopy (verts->v, v);
	hr_TransformVector (v, vert);

//	hr_SetUpLerpData (paliashdr, ent->backlerp, ent->origin, ent->oldorigin, ent->angles);

//	hr_TransformFinalVert (verts, oldverts, vert);

	return	true;
}

///////////////////////////////////////////////////////////////////////////////
//	hr_TriVerts
//
//	get the transformed location of three vertices in a model, from a particular
//	frame and surface, at specified location with specified rotation
//
//	returns false if operation failed
//
//	FIXME:	doesn't account for frame lerping, origin lerping
///////////////////////////////////////////////////////////////////////////////

int hr_TriVerts (entity_t *ent, int surfaceIndex, int triIndex, vec3_t v1, vec3_t v2, vec3_t v3)
{
	dmdl_t			*paliashdr;
	dtrivertx_t		*verts;
	extinfo_t		*extra_info;
	extsurf_t		*surf_info;
	dtriangle_t		*tris;
	int				abs_triIndex;
	vec3_t			v;

	paliashdr = (dmdl_t *)ent->model->extradata;

	extra_info = (extinfo_t *) ((byte *) paliashdr + paliashdr->ofs_end);

	//	check for invalid surface index
	if (surfaceIndex >= extra_info->num_surfs)
		return false;

	if (ent->frame >= paliashdr->num_frames)
		return	false;

	if (triIndex >= paliashdr->num_xyz)
		return	false;

	//	pointer to models surface info
	surf_info = (extsurf_t *) ((byte *) extra_info + extra_info->ofs_surf);

	hr_SetUpFrames (ent->model->extradata, ent->frame, ent->oldframe);

	//	set up the transformation matrix for this model
	hr_SetUpTransform (ent->angles, ent->origin, ent->oldorigin);

	//	won't work for Quake 2 type models
	abs_triIndex = hr_TriIndex (ent->model, surfaceIndex, triIndex);
	if (abs_triIndex < 0)
		abs_triIndex = 0;
	tris = (dtriangle_t *) ((byte *) paliashdr + paliashdr->ofs_tris);
	tris += abs_triIndex;

	//verts = hr_thisframe->verts;
	verts = hr_thisframe->verts + tris->index_xyz [0];

	VectorCopy (verts->v, v);
	hr_TransformVector (v, v1);

	verts = hr_thisframe->verts + tris->index_xyz [1];
	//verts++;

	VectorCopy (verts->v, v);
	hr_TransformVector (v, v2);

	verts = hr_thisframe->verts + tris->index_xyz [2];
	//verts++;

	VectorCopy (verts->v, v);
	hr_TransformVector (v, v3);

	return	true;
}

///////////////////////////////////////////////////////////////////////////////
//	R_SurfIndex
//
//	returns the surface index for name
///////////////////////////////////////////////////////////////////////////////

int	R_SurfIndex (model_t *mod, char *name)
{
	dmdl_t			*paliashdr;
	extinfo_t		*extra_info;
	extsurf_t		*surf_info;
	int				i;

	paliashdr = (dmdl_t *)mod->extradata;
	extra_info = (extinfo_t *) ((byte *) paliashdr + paliashdr->ofs_end);

	//	pointer to models surface info
	surf_info = (extsurf_t *) ((byte *) extra_info + extra_info->ofs_surf);

	for (i = 0; i < extra_info->num_surfs; i++, *surf_info++)
	{
		if (surf_info->name [0] != name [0])
			continue;

		if (!strcmp (surf_info->name, name))
			return	i;
	}

	return	-1;
}

///////////////////////////////////////////////////////////////////////////////
//	R_VertInfo
//
///////////////////////////////////////////////////////////////////////////////

int	R_VertInfo (model_t *mod, int surfIndex, int frameIndex, int vertIndex, vec3_t origin, 
				vec3_t oldorigin, vec3_t angles, vec3_t vert)
{
	entity_t	ent;

	VectorCopy (origin, ent.origin);
	VectorCopy (oldorigin, ent.oldorigin);
	VectorCopy (angles, ent.angles);
	
	ent.frame = frameIndex;
	ent.oldframe = frameIndex;

	ent.backlerp = 0;

	ent.model = mod;

	return hr_VertInfo (&ent, surfIndex, vertIndex, vert);
}
				
///////////////////////////////////////////////////////////////////////////////
//	R_TriVerts
//
//	get 3 transformed vertices
///////////////////////////////////////////////////////////////////////////////

int	R_TriVerts (model_t *mod, int surfIndex, int frameIndex, int vertIndex, vec3_t origin, 
				vec3_t oldorigin, vec3_t angles, vec3_t v1, vec3_t v2, vec3_t v3)
{
	entity_t	ent;

	VectorCopy (origin, ent.origin);
	VectorCopy (oldorigin, ent.oldorigin);
	VectorCopy (angles, ent.angles);
	
	ent.frame = frameIndex;
	ent.oldframe = frameIndex;

	ent.backlerp = 0;

	ent.model = mod;

	return hr_TriVerts (&ent, surfIndex, vertIndex, v1, v2, v3);
}