/********************************************************************************************************************/
/*                                                                                                                  */
/* Hair Control                                                                                      G.Rummery 1997 */
/*                                                                                                                  */
/********************************************************************************************************************/

/*---------------------------------------------------------------------------
 *	Import Headers
\*--------------------------------------------------------------------------*/

#ifdef PSX_VERSION
#include <gtemac.h>
#include <inline_c.h>
#include "../spec_psx/typedefs.h"
#include "../spec_psx/specific.h"
#include "../spec_psx/maths.h"
#else
#include "../specific/stypes.h"
#endif

#include "objects.h"
#include "lara.h"
#include "laraanim.h"
#include "control.h"
#include "sphere.h"
#include "effect2.h"

extern short rcossin_tbl[];
extern long SmokeWindX, SmokeWindZ;

/*---------------------------------------------------------------------------
 *	Constants
\*--------------------------------------------------------------------------*/

#define HAIR_SEGMENTS 6

/*---------------------------------------------------------------------------
 *	Locals
\*--------------------------------------------------------------------------*/

static int first_hair;
static PHD_3DPOS hair[HAIR_SEGMENTS+1];
static PHD_VECTOR hvel[HAIR_SEGMENTS+1];

/*---------------------------------------------------------------------------
 *	Public Functions
\*--------------------------------------------------------------------------*/

void InitialiseHair()
{
	int i;
	sint32 *bone;

	/* Don't calculate hair on first HairControl(), just get its position */
	first_hair = 1;

	bone = bones + objects[HAIR].bone_index;

	hair[0].y_rot = 0;
	hair[0].x_rot = -0x4000;

	for ( i=1 ; i<HAIR_SEGMENTS+1 ; i++, bone+=4 )
	{
		hair[i].x_pos = *(bone+1);
		hair[i].y_pos = *(bone+2);
		hair[i].z_pos = *(bone+3);
		hair[i].x_rot = -0x4000;
		hair[i].y_rot = hair[i].z_rot = 0;

		hvel[i].x = hvel[i].y = hvel[i].z = 0;
	}
}


void HairControl(int in_cutscene)
{
	OBJECT_INFO *object;
	sint32 *bone, distance;
	sint16 *rotation;
	sint16 *frame, *objptr, room_number;
	PHD_VECTOR pos;
	FLOOR_INFO *floor;
	sint32 i, water_level, height, size;
	SPHERE sphere[5];
	sint32 j, x, y, z;
	static int wind=0;
	static int wind_angle = 2048, dwind_angle = 2048;

	/* Start by calculating the hair position */
	object = &objects[LARA];

	/* Need to respond to spaz frames which are not under normal control */
	if (lara.hit_direction >= 0)
	{
		sint16 spaz;

		switch (lara.hit_direction)
		{
			case NORTH:
				if (lara.IsDucked)
					spaz = SPAZ_DUCKF_A;
				else
					spaz = SPAZ_FORWARD_A;
				break;

			case SOUTH:
				if (lara.IsDucked)
					spaz = SPAZ_DUCKB_A;
				else
					spaz = SPAZ_BACK_A;
				break;

			case EAST:
				if (lara.IsDucked)
					spaz = SPAZ_DUCKR_A;
				else
					spaz = SPAZ_RIGHT_A;
				break;

			default:
				if (lara.IsDucked)
					spaz = SPAZ_DUCKL_A;
				else
					spaz = SPAZ_LEFT_A;
				break;
		}

		frame = anims[spaz].frame_ptr;
		size = anims[spaz].interpolation>>8;

		frame += (int)( lara.hit_frame*size );
	}
	else
		frame = GetBestFrame(lara_item);

	phd_PushUnitMatrix();

#ifdef PSX_VERSION
	mSetTrans(lara_item->pos.x_pos, lara_item->pos.y_pos, lara_item->pos.z_pos);
#else
	*(phd_mxptr+M03) = lara_item->pos.x_pos << W2V_SHIFT;
	*(phd_mxptr+M13) = lara_item->pos.y_pos << W2V_SHIFT;
	*(phd_mxptr+M23) = lara_item->pos.z_pos << W2V_SHIFT;
#endif
	phd_RotYXZ(lara_item->pos.y_rot, lara_item->pos.x_rot, lara_item->pos.z_rot);

	/* Switch to sint32 pointer for packed_rotation values */
	rotation = frame+9;
	bone = bones + object->bone_index;

	/* Pass through minimum parts of hierarchy in order to find hair location */
	phd_TranslateRel((sint32)*(frame+6), (sint32)*(frame+7), (sint32)*(frame+8));
	gar_RotYXZsuperpack(&rotation, 0);

	// Hips sphere
	phd_PushMatrix();
	objptr = lara.mesh_ptrs[HIPS];
	phd_TranslateRel(*objptr, *(objptr+1), *(objptr+2));
#ifdef PSX_VERSION
	gte_sttr(&sphere[0].x);
#else
	sphere[0].x = *(phd_mxptr+M03) >> W2V_SHIFT;
	sphere[0].y = *(phd_mxptr+M13) >> W2V_SHIFT;
	sphere[0].z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	sphere[0].r = (sint32)*(objptr+3);
	phd_PopMatrix();

	phd_TranslateRel( *(bone+1+24), *(bone+2+24), *(bone+3+24) );

	if (lara.weapon_item!=NO_ITEM && lara.gun_type==LG_M16 &&
		 (items[lara.weapon_item].current_anim_state==0 || items[lara.weapon_item].current_anim_state==2 || items[lara.weapon_item].current_anim_state==4))
	{
		rotation = lara.right_arm.frame_base + lara.right_arm.frame_number * (anims[lara.right_arm.anim_number].interpolation>>8) + 9;
		gar_RotYXZsuperpack(&rotation, 7);
	}
	else
		gar_RotYXZsuperpack(&rotation, 6);
	phd_RotYXZ(lara.torso_y_rot, lara.torso_x_rot, lara.torso_z_rot);

	// Torso sphere
	phd_PushMatrix();
	objptr = lara.mesh_ptrs[TORSO];
	phd_TranslateRel(*objptr, *(objptr+1), *(objptr+2));
#ifdef PSX_VERSION
	gte_sttr(&sphere[1].x);
#else
	sphere[1].x = *(phd_mxptr+M03) >> W2V_SHIFT;
	sphere[1].y = *(phd_mxptr+M13) >> W2V_SHIFT;
	sphere[1].z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	sphere[1].r = (sint32)*(objptr+3);
	phd_PopMatrix();

	phd_PushMatrix();
	phd_TranslateRel( *(bone+1+28), *(bone+2+28), *(bone+3+28) );
	gar_RotYXZsuperpack(&rotation, 0);

	// Right arm sphere
	objptr = lara.mesh_ptrs[UARM_R];
	phd_TranslateRel(*objptr, *(objptr+1), *(objptr+2));
#ifdef PSX_VERSION
	gte_sttr(&sphere[3].x);
#else
	sphere[3].x = *(phd_mxptr+M03) >> W2V_SHIFT;
	sphere[3].y = *(phd_mxptr+M13) >> W2V_SHIFT;
	sphere[3].z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	sphere[3].r = (sint32)*(objptr+3) * 3/2; // 1.5 times sphere size gives better result
	phd_PopMatrix();

	phd_PushMatrix();
	phd_TranslateRel( *(bone+1+40), *(bone+2+40), *(bone+3+40) );
	gar_RotYXZsuperpack(&rotation, 2);

	// Left arm sphere
	objptr = lara.mesh_ptrs[UARM_R];
	phd_TranslateRel(*objptr, *(objptr+1), *(objptr+2));
#ifdef PSX_VERSION
	gte_sttr(&sphere[4].x);
#else
	sphere[4].x = *(phd_mxptr+M03) >> W2V_SHIFT;
	sphere[4].y = *(phd_mxptr+M13) >> W2V_SHIFT;
	sphere[4].z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	sphere[4].r = (sint32)*(objptr+3) * 3/2;
	phd_PopMatrix();

	phd_TranslateRel( *(bone+1+52), *(bone+2+52), *(bone+3+52) );
	gar_RotYXZsuperpack(&rotation, 2);
	phd_RotYXZ(lara.head_y_rot, lara.head_x_rot, lara.head_z_rot);

	// Head sphere
	phd_PushMatrix();
	objptr = lara.mesh_ptrs[HEAD];
	phd_TranslateRel(*objptr, *(objptr+1), *(objptr+2));
#ifdef PSX_VERSION
	gte_sttr(&sphere[2].x);
#else
	sphere[2].x = *(phd_mxptr+M03) >> W2V_SHIFT;
	sphere[2].y = *(phd_mxptr+M13) >> W2V_SHIFT;
	sphere[2].z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	sphere[2].r = (sint32)*(objptr+3);
	phd_PopMatrix();

	phd_TranslateRel(0, -23, -55);

#ifdef PSX_VERSION
	gte_sttr(&pos);
#else
	pos.x = *(phd_mxptr+M03) >> W2V_SHIFT;
	pos.y = *(phd_mxptr+M13) >> W2V_SHIFT;
	pos.z = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
	phd_PopMatrix();

	bone = bones + objects[HAIR].bone_index;

	if (first_hair)
	{
		first_hair = 0;

		hair[0].x_pos = pos.x;
		hair[0].y_pos = pos.y;
		hair[0].z_pos = pos.z;

		for (i=0; i<HAIR_SEGMENTS; i++, bone+=4)
		{
			phd_PushUnitMatrix();

#ifdef PSX_VERSION
			mSetTrans(hair[i].x_pos, hair[i].y_pos, hair[i].z_pos);
#else
			*(phd_mxptr+M03) = hair[i].x_pos << W2V_SHIFT;
			*(phd_mxptr+M13) = hair[i].y_pos << W2V_SHIFT;
			*(phd_mxptr+M23) = hair[i].z_pos << W2V_SHIFT;
#endif
			phd_RotYXZ(hair[i].y_rot, hair[i].x_rot, 0);
			phd_TranslateRel(*(bone+1), *(bone+2), *(bone+3));

#ifdef PSX_VERSION
			gte_sttr(&hair[i+1].x_pos);
#else
			hair[i+1].x_pos = *(phd_mxptr+M03) >> W2V_SHIFT;
			hair[i+1].y_pos = *(phd_mxptr+M13) >> W2V_SHIFT;
			hair[i+1].z_pos = *(phd_mxptr+M23) >> W2V_SHIFT;
#endif
			phd_PopMatrix();
		}

		wind = SmokeWindX = SmokeWindZ = 0;
		wind_angle = dwind_angle = 2048;
	}

/* -------- calculate hair movement */

	else
	{
	/*
	 * Calc angles to new pos of top link (y and x), then reduce the angles
	 * according to gravity etc, and use them to calc pos of this link. Then
	 * do same for next link.
	 *
	 */
		hair[0].x_pos = pos.x;
		hair[0].y_pos = pos.y;
		hair[0].z_pos = pos.z;

		room_number = lara_item->room_number;

		if (in_cutscene)
			water_level = NO_HEIGHT;
		else
		{
			x = lara_item->pos.x_pos + (frame[0] + frame[1])/2;
			y = lara_item->pos.y_pos + (frame[2] + frame[3])/2;
			z = lara_item->pos.z_pos + (frame[4] + frame[5])/2;
			water_level = GetWaterHeight(x, y, z, room_number);
		}

//		floor = GetFloor(hair[0].x_pos, hair[0].y_pos, hair[0].z_pos, &room_number);
//		height = GetHeight(floor, hair[0].x_pos, hair[0].y_pos, hair[0].z_pos);

//		if (height < hair[0].y_pos)
//			height = lara_item->floor;

		/* It's windy outside */
//		if (room[room_number].flags & NOT_INSIDE)
		{
			long diff;
			i = (GetRandomControl() & 7);
			wind += i - 3;
			if (wind <= -2)
				wind++;
			else if (wind >= 9)
				wind--;

			dwind_angle += ((GetRandomControl()&63)-32)<<1;
			dwind_angle &= 8190;

			if (dwind_angle < 1024)
				dwind_angle += (1024-dwind_angle)<<1;
			else if (dwind_angle > 3072)
				dwind_angle -= (dwind_angle-3072)<<1;

			diff = dwind_angle - wind_angle;
			wind_angle += diff>>3;
			wind_angle &= 8190;
		}

		SmokeWindX = (((rcossin_tbl[wind_angle])*wind)>>12);
		SmokeWindZ = (((rcossin_tbl[wind_angle+1])*wind)>>12);

		for ( i=1 ; i<HAIR_SEGMENTS+1 ; i++,bone+=4 )
		{
		/* Store current position in hvel[0] (which isn't used otherwise) */
			hvel[0].x = hair[i].x_pos;
			hvel[0].y = hair[i].y_pos;
			hvel[0].z = hair[i].z_pos;

			if (!in_cutscene)
			{
				floor = GetFloor(hair[i].x_pos, hair[i].y_pos, hair[i].z_pos, &room_number);
				height = GetHeight(floor, hair[i].x_pos, hair[i].y_pos, hair[i].z_pos);
			}
			else
				height = 32767;

		/* Add velocity */
			hair[i].x_pos += hvel[i].x * 3/4;
			hair[i].y_pos += hvel[i].y * 3/4;
			hair[i].z_pos += hvel[i].z * 3/4;

			if (lara.water_status == LARA_ABOVEWATER && room[room_number].flags & NOT_INSIDE)
			{
				hair[i].x_pos += SmokeWindX;
				hair[i].z_pos += SmokeWindZ;
			}

			switch (lara.water_status)
			{
				case LARA_ABOVEWATER:
				/* Gravity on next hair segment point */
					hair[i].y_pos += 10;
					if (water_level != NO_HEIGHT && hair[i].y_pos > water_level)
						hair[i].y_pos = water_level;
					else if (hair[i].y_pos > height)	// Not needed, causes hair to stick to walls.
					{
						hair[i].x_pos = hvel[0].x;
	//					hair[i].y_pos = hvel[0].y;
						hair[i].z_pos = hvel[0].z;
					}
					break;

				case LARA_UNDERWATER:
				case LARA_SURFACE:
				case LARA_WADE:
				/* Don't allow hair above water surface or below floor */
					if (hair[i].y_pos < water_level)
						hair[i].y_pos = water_level;
					else if (hair[i].y_pos > height)
						hair[i].y_pos = height;
					break;
			}

		/*
		 * Do collision with lara spheres to see if this point is inside;
		 * if so, move out.
		 * NOTE: won't guarantee that point is moved to "correct" side of
		 * sphere; may end up on opposite side and so force hair segment
		 * the wrong way
		 *
		 */
			for ( j=0 ; j<5 ; j++ )
			{
				x = hair[i].x_pos - sphere[j].x;
				y = hair[i].y_pos - sphere[j].y;
				z = hair[i].z_pos - sphere[j].z;

				distance = x*x + y*y + z*z;

				if (distance < SQUARE(sphere[j].r))
				{
					distance = phd_sqrt(distance);

					if (distance == 0)	// Oops Gav, this was causing a class A matey...  Fixed by Dude late on submission night...
						distance = 1;	// Dunno if 1 is a good idea but hey, less crashes...

					hair[i].x_pos = sphere[j].x + x * sphere[j].r / distance;
					hair[i].y_pos = sphere[j].y + y * sphere[j].r / distance;
					hair[i].z_pos = sphere[j].z + z * sphere[j].r / distance;
				}
			}

		/*
		 * Could work out all the sin/cos values directly here and use them
		 * to do the rotations in DrawHair rather than calcing the angles.
		 * Then again, this code only happens once per frame for 6 hair
		 * segments...
		 *
		 */
			distance = mSqrt(SQUARE(hair[i].z_pos - hair[i-1].z_pos) + SQUARE(hair[i].x_pos - hair[i-1].x_pos));
			hair[i-1].y_rot = phd_atan((hair[i].z_pos - hair[i-1].z_pos), (hair[i].x_pos - hair[i-1].x_pos));
		 	hair[i-1].x_rot = -phd_atan(distance, hair[i].y_pos - hair[i-1].y_pos);

			phd_PushUnitMatrix();

#ifdef PSX_VERSION
			mSetTrans(hair[i-1].x_pos, hair[i-1].y_pos, hair[i-1].z_pos);
#else
			*(phd_mxptr+M03) = hair[i-1].x_pos << W2V_SHIFT;
			*(phd_mxptr+M13) = hair[i-1].y_pos << W2V_SHIFT;
			*(phd_mxptr+M23) = hair[i-1].z_pos << W2V_SHIFT;
#endif
			phd_RotYXZ(hair[i-1].y_rot, hair[i-1].x_rot, 0);

			if (i == HAIR_SEGMENTS)
				phd_TranslateRel(*(bone-3), *(bone-2), *(bone-1));
			else
				phd_TranslateRel(*(bone+1), *(bone+2), *(bone+3));

#ifdef PSX_VERSION
			gte_sttr(&hair[i].x_pos);
#else
			hair[i].x_pos = *(phd_mxptr+M03)>>W2V_SHIFT;
			hair[i].y_pos = *(phd_mxptr+M13)>>W2V_SHIFT;
			hair[i].z_pos = *(phd_mxptr+M23)>>W2V_SHIFT;
#endif
		/* Calc velocity from change in position */

			hvel[i].x = hair[i].x_pos - hvel[0].x;
			hvel[i].y = hair[i].y_pos - hvel[0].y;
			hvel[i].z = hair[i].z_pos - hvel[0].z;

			phd_PopMatrix();
		}
	}
}

void DrawHair()
{
	int i;
	OBJECT_INFO *object;
	sint16 **mesh;

	object = &objects[HAIR];

	// frame data is packed rotations and stuff - may need to use this format to light hair correctly?
	mesh = &meshes[object->mesh_index];

	for (i=0; i<HAIR_SEGMENTS; i++)
	{
		phd_PushMatrix();

		phd_TranslateAbs(hair[i].x_pos, hair[i].y_pos, hair[i].z_pos);
		phd_RotY(hair[i].y_rot);
		phd_RotX(hair[i].x_rot);
		phd_PutPolygons(*mesh++, 1);

		phd_PopMatrix();
	}
}

