//This is the main camera control script, it's an extension of the existing OrbitCamera class and adds smoothing and better collision testing.
//It uses the target transform's forward (local z) axis for direction, while takes input in for the Y Axis movement, it then smoothly moves
//towards its' desired position.
//It has a number of variables but the defaults should be fine for most uses.

//The object you wish the camera to track
var target : Transform;

//The offset from the target's position the camera should centre on, e.g. for over the should up and right a bit.
var targetOffset = Vector3.zero;

//The distance the camera should try to maintain from the target
var distance = 1.0;

//This is a list of object layers it will ignore collisions with, only change this for very specific reasons.
var lineOfSightMask : LayerMask = 0;

//The minimum distance the camera should be from a wall it is infront of, this will be relative
//to the camera's near clip plane and is generally found through trial and error (0.2 is good for 0.1 near clip plane).
var backMinDistance : float = 0.2;

//Minimum distance to the side, this ca nbe less than the back minimum distance due to the object usually being first
//moved by the back minimum distance check
var sideMinDistance : float = 0.1;

//The amount which the camera lags behind its intended position when changing distance, most notably visible when using
//the Sights script.
var distanceLag : float = 0.5;

//The sensitivity of the mouse's y axis rotation.
var ySpeed = 600.0;
var xSpeed = 600.0;

//The minimum and maximum limits, note that the minimum limit is actually for looking upwards, while
//the maximum limit is for looking down, this is due to the camera being behind the player.
var yMinLimit = -60;
var yMaxLimit = 120;

//The amount in which the camera lags behind laterally in both directions.
var horizontalLag : float = 0.5;
var verticalLag : float = 0.2;

var invertY : boolean = false;

var xMaxLimit : float = 90.0;
var xMinLimit : float = -90.0;

private var extraXRot = 0.0;

//Variables used internally
private var currentDistance = 10.0;
private var x = 0.0;
private var y = 0.0;
private var distanceVelocity = 0.0;
private var xVelocity = 0.0;
private var yVelocity = 0.0;
private var newY:float = 0;

function Start () 
{
    
    x = target.eulerAngles.y;
	currentDistance = distance;
	
	// Make the rigid body not change rotation
   	if (rigidbody)
		rigidbody.freezeRotation = true;
	
	//Locks the cursor to the screen, although only works consistantly in standalone mode.
	Screen.lockCursor = true;
	Screen.showCursor = false;
	
	//xMinLimit = xMinLimit * (Mathf.PI / 180.0);
	//xMaxLimit = xMaxLimit * (Mathf.PI / 180.0);
}

function Update()
{
	if(Input.GetKeyUp(KeyCode.Escape))
	{
		Application.Quit();
	}
}

function LateUpdate () 
{
	//Check if target still exists (i.e. hasn't been destroyed)
    if (target) 
	{
		//First we compute the newY by taking the old Y, applying the effects of input and then clamping it within the desired values.
		newY = y;
		var mouseY = invertY ? Input.GetAxis("Mouse Y") * -1 : Input.GetAxis("Mouse Y");
        newY -= Input.GetAxis("Mouse Y") * ySpeed * Time.deltaTime;
		newY = Mathf.Clamp(newY, yMinLimit, yMaxLimit);
		
		
		//extraXRot += Input.GetAxis("Mouse X") * xSpeed * Time.deltaTime;
        //extraXRot = Mathf.Clamp(extraXRot, xMinLimit, xMaxLimit);
        
        var newX = x + extraXRot;
        //newX = Mathf.Clamp(newX, xMinLimit, xMaxLimit);
		
		//Then we smooth both X and Y, X using the current rotation of the player for its desired position, while Y using the computed newY.
		x = Mathf.SmoothDampAngle(x, target.rotation.eulerAngles.y + extraXRot, xVelocity, horizontalLag);
		y = Mathf.SmoothDampAngle(y, newY, yVelocity, verticalLag);
		
		//Now a rotation is created using the X and Y angles, note that X and Y in this script are in the mouse co ordinates, but are applied
		//to the camera on the Y and X rotational axis respectively. This is because a rotation around the X axis will change whether it looks
		//up or down and a Y axis rotation will change whether it looks left or right.
        var rotation = Quaternion.Euler(y, newX, 0);
        var targetPos = target.position + targetOffset; 
		
		//The rotation is then calculated by rotation the axis that points behind the character, the direction the camera will be moved back along.
        var direction = rotation * -Vector3.forward;
		
		//The distance is then smoothed and the resulting position is then created by moving backwards along the direction vector to the correct place
		currentDistance = Mathf.SmoothDamp(currentDistance, distance, distanceVelocity, distanceLag);
		var newPos =  targetPos + direction * currentDistance;
        
		//The camera then looks at the target using the rotation it was translated backwards on.
        transform.rotation = rotation;
		
		//The new position is then checked to see whether the camera can see the target or not and moved accordingly if it can't
		//newPos = AdjustLineOfSight(newPos, target.position);
		
		//Finally the position of the camera is updated to the final position.
		transform.position = newPos;
		
		//This function checks the sides of the camera and moves the camera to the left or right accordingly, note that it can
		//be commented out if needed.
		AdjustSideCollisions();
		
        
        //transform.Rotate(0, extraXRot, 0);
        
    }
}

function AdjustLineOfSight (newPosition : Vector3, target : Vector3)
{
	//This function performs a linecast from the target to the new position to check if the target
	//can see the camera, this provides a collision point which will be the furthest point along then
	//camera's axis that can see the target.
	var hit : RaycastHit;
	if (Physics.Linecast (target, newPosition, hit, lineOfSightMask.value))
	{
		//The new position is then found and used to find a direction from the point to the target
		//so the camera can move along this direction to prevent visible clipping by ensuring a 
		//minimum distance from the object it is touching.
		var diff:Vector3 = target - hit.point;
		distanceVelocity = 0.0;
		return hit.point + (diff.normalized * backMinDistance);
	}
	
	return newPosition;
}

function AdjustSideCollisions()
{
	//This function performs the same procedure as the line of sight check to ensure the camera doesn't
	//get too close to walls.

	//Check left side
	var hit : RaycastHit;
	if (Physics.Raycast(transform.position, -transform.right, hit, sideMinDistance, lineOfSightMask.value))
	{
		//Debug.Log("Side collision left!");
		var diff:Vector3 = transform.position - hit.point;
		transform.position = hit.point + (diff.normalized * sideMinDistance);
	}
	
	//Check right side
	var hit2 : RaycastHit;
	if (Physics.Raycast(transform.position, transform.right, hit2, sideMinDistance, lineOfSightMask.value))
	{
		//Debug.Log("Side collision right!");
		var diff2:Vector3 = transform.position - hit2.point;
		transform.position = hit2.point + (diff2.normalized * sideMinDistance);
	}
}

@script RequireComponent(Camera)
@script AddComponentMenu("Third Person Camera/Smooth Mouse Orbit")