/*********************************************************************************************/
/*                                                                                           */
/* Camera Control Code                                                                       */
/*                                                                                           */
/*********************************************************************************************/

#include "game.h"

/********************************** GLOBAL VARIABLES *****************************************/

CAMERA_INFO camera;

#define COM_SQUARE SQUARE(WALL_L/2)
#define MIN_SQUARE SQUARE(WALL_L/3)
#define MIN_CAM_ANGLE (ONE_DEGREE/2)

#define GROUND_SHIFT (STEP_L)

#define MAX_ELEVATION (ONE_DEGREE*85)

/* Look around limits for automatic looking at targets */
#define MAX_HEAD_ROTATION 	(50*ONE_DEGREE)
//#define MAX_HEAD_TILT	  	(25*ONE_DEGREE)
//#define MIN_HEAD_TILT 	  	(-45*ONE_DEGREE)
#define MAX_HEAD_TILT	  	(85*ONE_DEGREE)
#define MIN_HEAD_TILT 	  	(-85*ONE_DEGREE)
#define HEAD_TURN 		  	(4*ONE_DEGREE) //double what's needed because lara.c will reset it 2 degrees

/* Divider speeds for cameras to move to target i.e. pos += distance / speed */
#define CHASE_SPEED 10
#define COMBAT_SPEED 8
#define COMBAT_DISTANCE (WALL_L*5/2)
#define LOOK_SPEED   4

GAME_VECTOR last_ideal;

/*********************************** FUNCTION CODE *******************************************/

void InitialiseCamera(void)
{
	/* Initialise position of camera */
	camera.target.x = lara_item->pos.x_pos;
	camera.target.y = camera.shift = lara_item->pos.y_pos-WALL_L;
	camera.target.z = lara_item->pos.z_pos;
	camera.target.room_number = lara_item->room_number;

	camera.pos.x = camera.target.x;
	camera.pos.y = camera.target.y;
	camera.pos.z = camera.target.z-100;
	camera.pos.room_number = camera.target.room_number;

	camera.target_distance = WALL_L*3/2;
	camera.item = NULL;

	camera.number_frames = 1;
	if (!lara.extra_anim)
		camera.type = CHASE_CAMERA;
	camera.speed = 1;
	camera.flags = 0;

	camera.bounce = 0;

	/* No fixed camera */
	camera.number = NO_CAMERA;
	camera.fixed_camera = 0;
	AlterFOV(GAME_FOV*ONE_DEGREE);

	CalculateCamera();
}


void MoveCamera(GAME_VECTOR *ideal, int speed)
{
	sint32 height, ceiling, shake;
	FLOOR_INFO *floor;

//#define STRAIGHT_MOVE
#ifdef STRAIGHT_MOVE
	/* Move along straight line to ideal position */
	camera.pos.x += (ideal->x - camera.pos.x) / speed;
	camera.pos.z += (ideal->z - camera.pos.z) / speed;
#else
	/* Move along radius to ideal position */
	sint32 x, z, distance, c_distance;
	sint16 angle, c_angle;

	ideal->x = (ideal->x + (last_ideal.x*3))>>2;
	ideal->y = (ideal->y + (last_ideal.y*3))>>2;
	ideal->z = (ideal->z + (last_ideal.z*3))>>2;
	last_ideal.x = ideal->x;
	last_ideal.y = ideal->y;
	last_ideal.z = ideal->z;

	z = ideal->z - camera.target.z;
	x = ideal->x - camera.target.x;
	angle = phd_atan(z, x);
	distance = phd_sqrt(z*z + x*x);

	// TODO: + need current angle and rotation, so worth adding to camera structure
	z = camera.pos.z - camera.target.z;
	x = camera.pos.x - camera.target.x;
	c_angle = phd_atan(z, x);
	c_distance = phd_sqrt(z*z + x*x);

	/* Linearly change both angle and distance to ideal values */
	angle -= c_angle;
	c_angle += angle / speed;
	c_distance += (distance - c_distance) / speed;
	camera.pos.z = camera.target.z + (c_distance * phd_cos(c_angle) >> W2V_SHIFT);
	camera.pos.x = camera.target.x + (c_distance * phd_sin(c_angle) >> W2V_SHIFT);
#endif
	camera.pos.y += (ideal->y - camera.pos.y) / speed;
	camera.pos.room_number = ideal->room_number;

	/* NOTE: For final stuff positioning, chunky floor heights are too harsh, BUT can lead
				to juddering if Lara swims on and off very steep slopes */
	chunky_flag = 0;

	floor = GetFloor(camera.pos.x, camera.pos.y, camera.pos.z, &camera.pos.room_number);
	height = GetHeight(floor, camera.pos.x, camera.pos.y, camera.pos.z) - GROUND_SHIFT;

	/* This position is not valid so cut using LOS */
	if (camera.pos.y >= height && ideal->y >= height)
	{
		LOS(&camera.target, &camera.pos);
		floor = GetFloor(camera.pos.x, camera.pos.y, camera.pos.z, &camera.pos.room_number);
		height = GetHeight(floor, camera.pos.x, camera.pos.y, camera.pos.z) - GROUND_SHIFT;
	}

	/* Floor/ceiling check to avoid going underground */
	ceiling = GetCeiling(floor, camera.pos.x, camera.pos.y, camera.pos.z) + GROUND_SHIFT;
	if (height < ceiling)
		height = ceiling = (height + ceiling) >> 1;

	/* Add bounce effect */
	if (camera.bounce)
	{
		if (camera.bounce > 0)
		{
			camera.pos.y += camera.bounce;
			camera.target.y += camera.bounce;
			camera.bounce = 0;
		}
		else
		{
			/* Shake camera randomly */
			shake = (GetRandomControl()-0x4000) * camera.bounce / 0x7fff;
			camera.pos.x += shake;
			camera.target.y += shake;
			shake = (GetRandomControl()-0x4000) * camera.bounce / 0x7fff;
			camera.pos.y += shake;
			camera.target.y += shake;
			shake = (GetRandomControl()-0x4000) * camera.bounce / 0x7fff;
			camera.pos.z += shake;
			camera.target.z += shake;
			camera.bounce += 5;
		}
	}

	/* Push camera up/down if floor/ceiling in the way */
	if (camera.pos.y > height)
		camera.shift = height - camera.pos.y;
	else if (camera.pos.y < ceiling)
		camera.shift = ceiling - camera.pos.y;
	else
		camera.shift = 0;

	/* Final check of room number */
	GetFloor(camera.pos.x, camera.pos.y + camera.shift, camera.pos.z, &camera.pos.room_number);

	/* Either: shift target and viewpoint -> juddering	up and down + total loss of Lara from view
	           shift viewpoint only -> weird view angles */
#ifdef SHIFT_BOTH
	phd_LookAt(camera.pos.x, camera.pos.y + camera.shift, camera.pos.z,
				  camera.target.x, camera.target.y + camera.shift, camera.target.z, 0);
#else
	phd_LookAt(camera.pos.x, camera.pos.y + camera.shift, camera.pos.z,
				  camera.target.x, camera.target.y, camera.target.z, 0);
#endif

	/* For stereo sound effects need to know camera facing */
#ifdef PC_VERSION
	if (camera.mike_at_lara)
	{
		camera.actual_angle=lara_item->pos.y_rot+lara.head_y_rot+lara.torso_y_rot;
		camera.mike_pos.x=lara_item->pos.x_pos;
		camera.mike_pos.y=lara_item->pos.y_pos;
		camera.mike_pos.z=lara_item->pos.z_pos;
	}
	else
#endif
	{
		camera.actual_angle = phd_atan(camera.target.z - camera.pos.z, camera.target.x - camera.pos.x);
		camera.mike_pos.x=camera.pos.x+((phd_persp*phd_sin(camera.actual_angle))>>W2V_SHIFT);
		camera.mike_pos.z=camera.pos.z+((phd_persp*phd_cos(camera.actual_angle))>>W2V_SHIFT);
		camera.mike_pos.y=camera.pos.y;
	}
}


void ClipCamera(sint32 *x, sint32 *y, sint32 *h, sint32 target_x, sint32 target_y, sint32 target_h,
					 sint32 left, sint32 top, sint32 right, sint32 bottom)
{
	/* Don't do either clip if target point is outside of bounds too */

	/* Clip to left first; failing that, may need a shift clamp */
	if ((right > left) ^ (target_x < left))
	{
		*y = target_y + (*y - target_y) * (left - target_x) / (*x - target_x);
		*h = target_h + (*h - target_h) * (left - target_x) / (*x - target_x);
		*x = left;
	}

	/* May need clip to top as well */
	if ((bottom > top && target_y > top && *y < top) ||
		 (bottom < top && target_y < top && *y > top))
	{
		*x = target_x + (*x - target_x) * (top - target_y) / (*y - target_y);
		*h = target_h + (*h - target_h) * (top - target_y) / (*y - target_y);
		*y = top;
	}
}


void ShiftCamera(sint32 *x, sint32 *y, sint32 *h, sint32 target_x, sint32 target_y, sint32 target_h,
					  sint32 left, sint32 top, sint32 right, sint32 bottom)
{
	/* Routine to move camera to ideal clipped position - assumes that position is clipping to left
		and that target position is lower than camera, so secondary clip to top.
		BEWARE: Negative square roots occur if target is further from clip edge than camera! */
	sint32 shift, TL_square, BL_square, TR_square;

	TL_square = SQUARE(target_x - left) + SQUARE(target_y - top);
	BL_square = SQUARE(target_x - left) + SQUARE(target_y - bottom);
	TR_square = SQUARE(target_x - right) + SQUARE(target_y - top);

	if (camera.target_square < TL_square)
	{
		/* Ideal position */
		*x = left;
		shift = camera.target_square - SQUARE(target_x - left);
		if (shift < 0)
			return;
		shift = phd_sqrt(shift);
		*y = target_y + ((top < bottom)? -shift : shift);
	}
	else if (TL_square > MIN_SQUARE)
	{
		/* Jammed to corner instead */
		*x = left;
		*y = top;
	}
	else if (camera.target_square < BL_square)
	{
		/* Shifted in opposite direction, but against primary clipping edge */
		*x = left;
		shift = camera.target_square - SQUARE(target_x - left);
		if (shift < 0)
			return;
		shift = phd_sqrt(shift);
		*y = target_y + ((top < bottom)? shift : -shift);
	}
	else if ((camera.target_square<<1) < TR_square)
	{
		/* Shifted along secondary clip edge; but further away so you can see in front of Lara */
		shift = (camera.target_square<<1) - SQUARE(target_y - top);
		if (shift < 0)
			return;
		shift = phd_sqrt(shift);
		*x = target_x + ((left < right)? shift : -shift);
		*y = top;
	}
	else if (BL_square > TR_square) // may want BL_square + a chunk > TR_square, to favour BL when close
	{
		/* If opposite corner on prime edge is further away, then secondary edge corner, use that */
		*x = left;
		*y = bottom;
	}
	else
	{
		/* Jammed to corner on secondary edge */
		*x = right;
		*y = top;
	}
}


FLOOR_INFO *GoodPosition(sint32 x, sint32 y, sint32 z, sint16 room_number)
{
	/* Return floor or NULL if not a good position */
	FLOOR_INFO *floor;

	floor = GetFloor(x, y, z, &room_number);
	if (y > GetHeight(floor, x, y, z) || y < GetCeiling(floor, x, y, z))
		return (NULL);

	return (floor);
}


/* Wow! Major function pointer argument */
void SmartShift(GAME_VECTOR *ideal,
	void (*shift)(sint32 *x, sint32 *y, sint32 *h, sint32 target_x, sint32 target_y, sint32 target_h,
					  sint32 left, sint32 top, sint32 right, sint32 bottom))
{
	int noclip, preferA;
	ROOM_INFO *r;
	sint16 item_box, camera_box;
	BOX_INFO *box;
	int x_floor, y_floor;
	sint32 left, right, top, bottom, test, edge;
	FLOOR_INFO *good_left, *good_right, *good_top, *good_bottom;
	GAME_VECTOR A, B;

	LOS(&camera.target, ideal);

	r = &room[camera.target.room_number];
	x_floor = (camera.target.z - r->z) >> WALL_SHIFT;
	y_floor = (camera.target.x - r->x) >> WALL_SHIFT;
	item_box = r->floor[x_floor + y_floor * r->x_size].box;

	/* Use item's box, unless camera is not in it */
	box = &boxes[item_box];

	left = (sint32)box->left << WALL_SHIFT;
	right = ((sint32)box->right << WALL_SHIFT) - 1;
	top = (sint32)box->top << WALL_SHIFT;
	bottom = ((sint32)box->bottom << WALL_SHIFT) - 1;

	/* Get camera box, and do bounding box checks to get improved camera position */
	r = &room[ideal->room_number];
	x_floor = (ideal->z - r->z) >> WALL_SHIFT;
	y_floor = (ideal->x - r->x) >> WALL_SHIFT;
	camera_box = r->floor[x_floor + y_floor * r->x_size].box;

	/* BUG: Can get dodgy LOS results - best to ignore them */
	if (camera_box != NO_BOX && (ideal->z < left || ideal->z > right || ideal->x < top || ideal->x > bottom))
	{
		box = &boxes[camera_box];
		left = (sint32)box->left << WALL_SHIFT;
		right = ((sint32)box->right << WALL_SHIFT) - 1;
		top = (sint32)box->top << WALL_SHIFT;
		bottom = ((sint32)box->bottom << WALL_SHIFT) - 1;
	}

	/* Check for possible box enlargements (or constrictions caused by low roof) around camera position */
	test = (ideal->z - WALL_L) | (WALL_L-1);
	good_left = GoodPosition(ideal->x, ideal->y, test, ideal->room_number);
	if (good_left)
	{
		camera_box = good_left->box;
		edge = (sint32)boxes[camera_box].left << WALL_SHIFT;
		if (camera_box != NO_ITEM && edge < left)
			left = edge;
	}
	else
		left = test;

	test = (ideal->z + WALL_L) & ~(WALL_L-1);
	good_right = GoodPosition(ideal->x, ideal->y, test, ideal->room_number);
	if (good_right)
	{
		camera_box = good_right->box;
		edge = ((sint32)boxes[camera_box].right << WALL_SHIFT) - 1;
		if (camera_box != NO_ITEM && edge > right)
			right = edge;
	}
	else
		right = test;

	test = (ideal->x - WALL_L) | (WALL_L-1);
	good_top = GoodPosition(test, ideal->y, ideal->z, ideal->room_number);
	if (good_top)
	{
		camera_box = good_top->box;
		edge = (sint32)boxes[camera_box].top << WALL_SHIFT;
		if (camera_box != NO_ITEM && edge < top)
			top = edge;
	}
	else
		top = test;

	test = (ideal->x + WALL_L) & ~(WALL_L-1);
	good_bottom = GoodPosition(test, ideal->y, ideal->z, ideal->room_number);
	if (good_bottom)
	{
		camera_box = good_bottom->box;
		edge = ((sint32)boxes[camera_box].bottom << WALL_SHIFT) - 1;
		if (camera_box != NO_ITEM && edge > bottom)
			bottom = edge;
	}
	else
		bottom = test;

	if (camera.type != LOOK_CAMERA)
	{
		left += STEP_L<<1;
		right -= STEP_L<<1;
		top += STEP_L<<1;
		bottom -= STEP_L<<1;
	}
	else
	{
		left += STEP_L;
		right -= STEP_L;
		top += STEP_L;
		bottom -= STEP_L;
	}

	A.x = B.x = ideal->x;
	A.y = B.y = ideal->y;
	A.z = B.z = ideal->z;
	A.room_number = B.room_number = ideal->room_number;

	/* Shift camera position around according to clip against bounding box */
	noclip = 1;
	if (ABS(ideal->z - camera.target.z) > ABS(ideal->x - camera.target.x))
	{
		/* z axis is longest, check left/right clip first */
		if (ideal->z < left && !good_left)
		{
			noclip = 0;
			/* (A) Camera favours the side that the camera is currently on */
			preferA = (camera.pos.x < camera.target.x);
			(*shift)(&A.z, &A.x, &A.y, camera.target.z, camera.target.x, camera.target.y, left, top, right, bottom);
			(*shift)(&B.z, &B.x, &B.y, camera.target.z, camera.target.x, camera.target.y, left, bottom, right, top);
		}
		else if (ideal->z > right && !good_right)
		{
			noclip = 0;
			preferA = (camera.pos.x < camera.target.x);
			(*shift)(&A.z, &A.x, &A.y, camera.target.z, camera.target.x, camera.target.y, right, top, left, bottom);
			(*shift)(&B.z, &B.x, &B.y, camera.target.z, camera.target.x, camera.target.y, right, bottom, left, top);
		}

		/* If it didn't clip left/right, maybe it will top/bottom */
		if (noclip)
		{
			/* (B) Camera favours the side where the ideal position is on */
			if (ideal->x < top && !good_top)
			{
				noclip = 0;
				preferA = (ideal->z < camera.target.z);
				(*shift)(&A.x, &A.z, &A.y, camera.target.x, camera.target.z, camera.target.y, top, left, bottom, right);
				(*shift)(&B.x, &B.z, &B.y, camera.target.x, camera.target.z, camera.target.y, top, right, bottom, left);
			}
			else if (ideal->x > bottom && !good_bottom)
			{
				noclip = 0;
				preferA = (ideal->z < camera.target.z);
				(*shift)(&A.x, &A.z, &A.y, camera.target.x, camera.target.z, camera.target.y, bottom, left, top, right);
				(*shift)(&B.x, &B.z, &B.y, camera.target.x, camera.target.z, camera.target.y, bottom, right, top, left);
			}
		}
	}
	else
	{
		/* x axis is longest, check top/bottom clip first */
		if (ideal->x < top && !good_top)
		{
			noclip = 0;
			preferA = (camera.pos.z < camera.target.z);
			(*shift)(&A.x, &A.z, &A.y, camera.target.x, camera.target.z, camera.target.y, top, left, bottom, right);
			(*shift)(&B.x, &B.z, &B.y, camera.target.x, camera.target.z, camera.target.y, top, right, bottom, left);
		}
		else if (ideal->x > bottom && !good_bottom)
		{
			noclip = 0;
			preferA = (camera.pos.z < camera.target.z);
			(*shift)(&A.x, &A.z, &A.y, camera.target.x, camera.target.z, camera.target.y, bottom, left, top, right);
			(*shift)(&B.x, &B.z, &B.y, camera.target.x, camera.target.z, camera.target.y, bottom, right, top, left);
		}

		/* If it didn't clip top/bottom, maybe it will left/right */
		if (noclip)
		{
			if (ideal->z < left && !good_left)
			{
				noclip = 0;
				preferA = (ideal->x < camera.target.x);
				(*shift)(&A.z, &A.x, &A.y, camera.target.z, camera.target.x, camera.target.y, left, top, right, bottom);
				(*shift)(&B.z, &B.x, &B.y, camera.target.z, camera.target.x, camera.target.y, left, bottom, right, top);
			}
			else if (ideal->z > right && !good_right)
			{
				noclip = 0;
				preferA = (ideal->x < camera.target.x);
				(*shift)(&A.z, &A.x, &A.y, camera.target.z, camera.target.x, camera.target.y, right, top, left, bottom);
				(*shift)(&B.z, &B.x, &B.y, camera.target.z, camera.target.x, camera.target.y, right, bottom, left, top);
			}
		}
	}

	/* May not have needed adjustment */
	if (noclip)
		return;

	/* Check LOS; if bad, then use second choice instead */
	if (preferA && !LOS(&camera.target, &A))
		preferA = 0;
	else if (!preferA && !LOS(&camera.target, &B))
		preferA = 1;

	if (preferA)
	{
		ideal->x = A.x;
		ideal->y = A.y;
		ideal->z = A.z;
	}
	else
	{
		ideal->x = B.x;
		ideal->y = B.y;
		ideal->z = B.z;
	}

	GetFloor(ideal->x, ideal->y, ideal->z, &ideal->room_number);
}


void ChaseCamera(ITEM_INFO *item)
{
	/* Standard follow Lara camera */
	GAME_VECTOR ideal;
	sint32 distance;
	sint16 angle;

	/* Limit target elevation else get horrible camera twists when nearly vertical */
	camera.target_elevation += item->pos.x_rot;
	if (camera.target_elevation > MAX_ELEVATION)
		camera.target_elevation = MAX_ELEVATION;
	else if (camera.target_elevation < -MAX_ELEVATION)
		camera.target_elevation = -MAX_ELEVATION;

	distance = camera.target_distance * phd_cos(camera.target_elevation) >> W2V_SHIFT;
	ideal.y = camera.target.y + (camera.target_distance * phd_sin(camera.target_elevation) >> W2V_SHIFT);

	/* Used in ShiftCamera() */
	camera.target_square = SQUARE(distance);

	/* Place camera according to preset angles */
	angle = 	item->pos.y_rot + camera.target_angle;
	ideal.x = camera.target.x - (distance * phd_sin(angle) >> W2V_SHIFT);
	ideal.z = camera.target.z - (distance * phd_cos(angle) >> W2V_SHIFT);
	ideal.room_number = camera.pos.room_number;

	/* Work out best position for camera, then move it towards it */
	SmartShift(&ideal, ShiftCamera);

	MoveCamera(&ideal, camera.speed);
}


int ShiftClamp(GAME_VECTOR *pos, sint32 clamp)
{
	/* Check the position vector: if in a bad position, then clamp it to nearest allowable
		position. Doesn't do y coord, but returns the required shift instead (makes sense in
		the context of the calling function) */
	sint32 height, ceiling, shift, x, y, z;
	sint32 left, right, top, bottom;
	FLOOR_INFO *floor;
	BOX_INFO *box;

	x = pos->x;
	y = pos->y;
	z = pos->z;

	floor = GetFloor(x, y, z, &pos->room_number);

	/* Clamp to this box? */
	box = &boxes[floor->box];
	left = ((sint32)box->left << WALL_SHIFT) + clamp;
	right = ((sint32)box->right << WALL_SHIFT) - clamp - 1;
	if (z < left && !GoodPosition(x, y, z - clamp, pos->room_number))
		pos->z = left;
	else if (z > right && !GoodPosition(x, y, z + clamp, pos->room_number))
		pos->z = right;

	top = ((sint32)box->top << WALL_SHIFT) + clamp;
	bottom = ((sint32)box->bottom << WALL_SHIFT) - clamp - 1;
	if (x < top && !GoodPosition(x - clamp, y, z, pos->room_number))
		pos->x = top;
	else if (x > bottom && !GoodPosition(x + clamp, y, z, pos->room_number))
		pos->x = bottom;

	height = GetHeight(floor, x, y, z) - clamp;
	ceiling = GetCeiling(floor, x, y, z) + clamp;

	if (height < ceiling)
		height = ceiling = (height + ceiling) >> 1;

	/* Push up/down if floor/ceiling in the way */
	if (y > height)
		shift = height - y;
	else if (pos->y < ceiling)
		shift = ceiling - y;
	else
		shift = 0;

	return (shift);
}


void CombatCamera(ITEM_INFO *item)
{
	/* Looks in direction of Lara's head unless close to wall */
	sint32 distance, water_height;
	GAME_VECTOR ideal;

	camera.target.z = item->pos.z_pos;
	camera.target.x = item->pos.x_pos;

	/* If Target Selected Look At It Else uses Lara's head position facing */
	if ( lara.target )
	{
		camera.target_angle = item->pos.y_rot + lara.target_angles[0];
		camera.target_elevation = item->pos.x_rot + lara.target_angles[1];
	}
	else
	{
		camera.target_angle = item->pos.y_rot + lara.head_y_rot + lara.torso_y_rot;
		camera.target_elevation = item->pos.x_rot + lara.head_x_rot + lara.torso_x_rot;
	}
	camera.target_distance = COMBAT_DISTANCE;

	distance = camera.target_distance * phd_cos(camera.target_elevation) >> W2V_SHIFT;

	ideal.x = camera.target.x - (distance * phd_sin(camera.target_angle) >> W2V_SHIFT);
	ideal.y = camera.target.y + (camera.target_distance * phd_sin(camera.target_elevation) >> W2V_SHIFT);
	ideal.z = camera.target.z - (distance * phd_cos(camera.target_angle) >> W2V_SHIFT);
	ideal.room_number = camera.pos.room_number;

	if (lara.water_status == LARA_UNDERWATER)
	{
		/* If HARPOON is being used underwater, then don't allow camera to go above surface */
		water_height = lara_item->pos.y_pos + lara.water_surface_dist;
		if (camera.target.y > water_height && water_height > ideal.y)
		{
			ideal.z = camera.target.z + (ideal.z - camera.target.z) * (water_height - camera.target.y) / (ideal.y - camera.target.y);
			ideal.x = camera.target.x + (ideal.x - camera.target.x) * (water_height - camera.target.y) / (ideal.y - camera.target.y);
			ideal.y = water_height;
		}
	}

	SmartShift(&ideal, ShiftCamera);
	/* Try this if want to Clip camera LOS rather than shift */
//	SmartShift(&ideal, ClipCamera);

	MoveCamera(&ideal, camera.speed);
}


void LookCamera(ITEM_INFO *item)
{
	/* Looks in direction of Lara's head. If LOOK_CAMERA, shifts forwards and backwards
		when look up/down; if COMBAT_CAMERA doesn't do this */
	sint32 distance;
	GAME_VECTOR ideal, old;

	/* Remember old position to smooth camera movement */
	old.z = camera.target.z;
	old.x = camera.target.x;

	camera.target.z = item->pos.z_pos;
	camera.target.x = item->pos.x_pos;

	/* Uses Lara's head position facing */
	camera.target_angle = item->pos.y_rot + lara.head_y_rot + lara.torso_y_rot;
	camera.target_elevation = item->pos.x_rot + lara.head_x_rot + lara.torso_x_rot;
	camera.target_distance = WALL_L*3/2;

	distance = camera.target_distance * phd_cos(camera.target_elevation) >> W2V_SHIFT;

	/* Move target so as not to look in Lara's backpack */
	camera.shift = -STEP_L*2 * phd_sin(camera.target_elevation) >> W2V_SHIFT;
	camera.target.z += camera.shift * phd_cos(item->pos.y_rot) >> W2V_SHIFT;
	camera.target.x += camera.shift * phd_sin(item->pos.y_rot) >> W2V_SHIFT;

	/* Shift may take camera through a wall - so need to check that */
	if (!GoodPosition(camera.target.x, camera.target.y, camera.target.z, camera.target.room_number))
	{
		camera.target.x = item->pos.x_pos;
		camera.target.z = item->pos.z_pos;
	}

	/* Clamp target point away from wall as a starting point */
	camera.target.y += ShiftClamp(&camera.target, STEP_L+50);

	ideal.x = camera.target.x - (distance * phd_sin(camera.target_angle) >> W2V_SHIFT);
	ideal.y = camera.target.y + (camera.target_distance * phd_sin(camera.target_elevation) >> W2V_SHIFT);
	ideal.z = camera.target.z - (distance * phd_cos(camera.target_angle) >> W2V_SHIFT);
	ideal.room_number = camera.pos.room_number;

	SmartShift(&ideal, ClipCamera);

	/* Move target pos along line from its old position to the desired new one too */
	camera.target.z = old.z + (camera.target.z - old.z) / camera.speed;
	camera.target.x = old.x + (camera.target.x - old.x) / camera.speed;

	MoveCamera(&ideal, camera.speed);
}


void FixedCamera(void)
{
	/* Move to fixed camera position */
	GAME_VECTOR ideal;
	OBJECT_VECTOR *fixed;

	fixed = &camera.fixed[camera.number];
	ideal.x = fixed->x;
	ideal.y = fixed->y;
	ideal.z = fixed->z;
	ideal.room_number = fixed->data;

	/* If something gets between target and fixed camera, need to clip it */
	if (!LOS(&camera.target, &ideal))
		ShiftClamp(&ideal, STEP_L);

	camera.fixed_camera = 1;

	MoveCamera(&ideal, camera.speed);

	/* Deal with timer (if any) */
	if (camera.timer)
	{
		camera.timer--;
		if (!camera.timer)
			camera.timer = -1;
	}
}

void CalculateCamera(void)
{
	/* Calculate the position of the viewpoint (based eventually on all kinds of stuff) */
	ITEM_INFO 	*item;
	OBJECT_INFO *object;
	FLOOR_INFO 	*floor;
	sint16 		*bounds, angle, tilt, change;
	sint32	 	shift, fixed_camera, y;

   /* Make nice underWater sound if camera underWater */
  	if (room[camera.pos.room_number].flags & UNDERWATER)
  	{
  		SoundEffect( 60, NULL, SFX_ALWAYS );
		if (!camera.underwater)
		{
			S_CDVolume(0);
			camera.underwater = 1;
		}
  	}
	else
	{
		if (camera.underwater)
		{
	     	//mn_stop_sound_effect(60, NULL);
			//SOUND_Stop();
			if (Option_Music_Volume)
				S_CDVolume(Option_Music_Volume * 25 + 5);
			camera.underwater = 0;
		}
	}

	if (camera.type == CINEMATIC_CAMERA)
 	{
 		InGameCinematicCamera();
 		return;
	}

	/* Alter the behaviour of the GetHeight/Ceiling routines */
	if (camera.flags != NO_CHUNKY)
		chunky_flag = 1;

	/* Target may not be Lara if it has been set and fixed camera in operation */
	fixed_camera = (camera.item && (camera.type == FIXED_CAMERA || camera.type == HEAVY_CAMERA));
	item = fixed_camera? camera.item : lara_item;

	/* Setup target (which is item's mid point filtered to remove excess shake) */
	object = &objects[item->object_number];
	bounds = GetBoundsAccurate(item);

	y = item->pos.y_pos;
	if (!fixed_camera)
		y += bounds[3] + ((bounds[2] - bounds[3]) * 3 >> 2);
	else
		y += (bounds[2] + bounds[3])/2;

	/* TARGET_CAMERA: If target but no camera, then make Lara's head turn to look at it */
	if (camera.item && !fixed_camera)
	{
		shift = phd_sqrt(SQUARE(camera.item->pos.z_pos - item->pos.z_pos) + SQUARE(camera.item->pos.x_pos - item->pos.x_pos));
		angle = phd_atan(camera.item->pos.z_pos - item->pos.z_pos, camera.item->pos.x_pos - item->pos.x_pos) - item->pos.y_rot;
		bounds = GetBoundsAccurate(camera.item);
		tilt = phd_atan(shift, y - (camera.item->pos.y_pos + (bounds[2] + bounds[3])/2));
		angle >>= 1;
		tilt >>= 1;
		if (angle > -MAX_HEAD_ROTATION && angle < MAX_HEAD_ROTATION && tilt > MIN_HEAD_TILT && tilt < MAX_HEAD_TILT)
		{
			change = angle - lara.head_y_rot;
			if (change > HEAD_TURN)
				lara.head_y_rot += HEAD_TURN;
			else if (change < -HEAD_TURN)
				lara.head_y_rot -= HEAD_TURN;
			else
				lara.head_y_rot += change;
			lara.torso_y_rot = lara.head_y_rot;

			change = tilt - lara.head_x_rot;
			if (change > HEAD_TURN)
				lara.head_x_rot += HEAD_TURN;
			else if (change < -HEAD_TURN)
				lara.head_x_rot -= HEAD_TURN;
			else
				lara.head_x_rot += change;
			lara.torso_x_rot = lara.head_x_rot;

			camera.type = LOOK_CAMERA;

			/* Flag that this item has been 'spotted' by Lara */
			camera.item->looked_at = 1;
		}
	}

	if (camera.type == LOOK_CAMERA || camera.type == COMBAT_CAMERA)
	{
		/* To do the midly unpleasant target shift when look up down, need to know old position */
		y -= STEP_L;
		camera.target.room_number = item->room_number;

		/* Don't smooth target shift if coming from FIXED_CAMERA/HEAVY_CAMERA */
		if (camera.fixed_camera)
		{
			camera.target.y = y;
			camera.speed = 1;
		}
		else
		{
			camera.target.y += ((y - camera.target.y) >> 2);
			camera.speed = (camera.type == LOOK_CAMERA)? LOOK_SPEED : COMBAT_SPEED;
		}

		/* Fixed camera allows speed to be set for camera movement (as well as remembering if last
			camera was fixed with non-Lara target) */
		camera.fixed_camera = 0;

		if (camera.type == LOOK_CAMERA)
			LookCamera(item);
		else
			CombatCamera(item);
	}
	else
	{
		/* Shift to follow centre of bounds if required (e.g. Push/PullBlock) */
		camera.target.x = item->pos.x_pos;
		camera.target.z = item->pos.z_pos;

		if (camera.flags == FOLLOW_CENTRE)
		{
			shift = (bounds[4] + bounds[5])/2;
			camera.target.z += phd_cos(item->pos.y_rot) * shift >> W2V_SHIFT;
			camera.target.x += phd_sin(item->pos.y_rot) * shift >> W2V_SHIFT;
		}
		camera.target.room_number = item->room_number;

		/* Don't smooth target shift if switching between fixed cameras (on or off) */
		if (camera.fixed_camera ^ fixed_camera)
		{
			camera.target.y = y;
			camera.fixed_camera = 1;
			camera.speed = 1;
		}
		else
		{
			camera.target.y += (y - camera.target.y)/4;
			camera.fixed_camera = 0;
		}

		/* Possible for item's centre point to be shifted into another room */
		floor = GetFloor( camera.target.x, camera.target.y, camera.target.z, &camera.target.room_number);

		/* A target on a steep slope may cause camera to go mad if chunky GetHeight is used */
		if (camera.target.y > GetHeight(floor, camera.target.x, camera.target.y, camera.target.z))
			chunky_flag = 0;

		if (camera.type == CHASE_CAMERA || camera.flags == CHASE_OBJECT)
			ChaseCamera(item);
		else
			FixedCamera();
	}

	camera.last = camera.number;
	camera.fixed_camera = fixed_camera;

	if (camera.type != HEAVY_CAMERA || camera.timer == -1)
	{
		/* Reset camera stuff for next time */
		camera.type = CHASE_CAMERA;
		camera.speed = CHASE_SPEED;
		camera.number = NO_CAMERA;
		camera.last_item = camera.item;
		camera.item = NULL;
		camera.target_angle = camera.target_elevation = 0;
		camera.target_distance = WALL_L*3/2;
		camera.flags = 0;
	}

	chunky_flag = 0;
}
