﻿using UnityEngine;

public class ComputeFrustum : MonoBehaviour {

	//all dimensions in meters
	public Transform[] screens;  //location of the screen in the physical world, scale.x and scale.y for the physical width and height
	public Camera[] leftCameras; // ref to left cameras, attached to the left eye.
	public bool stereoscopy = true;
	public Camera[] rightCameras; // ref to the right cameras, attached to the right eye.

	// Use this for initialization
	void Start () {
	  	if (screens.Length != leftCameras.Length)
		{
			Debug.LogError("Must have the same number of screens and (left) cameras in a pod");
			if (stereoscopy && (screens.Length != rightCameras.Length))
			{
				Debug.LogError("Must have the same number of screens and (right) cameras in a pod");
			}
		}
	}
	

	//from http://docs.unity3d.com/ScriptReference/Camera-projectionMatrix.html
	Matrix4x4 PerspectiveOffCenter(float left, float right, float bottom, float top, float near, float far) {
		float x = 2.0F * near / (right - left);
		float y = 2.0F * near / (top - bottom);
		float a = (right + left) / (right - left);
		float b = (top + bottom) / (top - bottom);
		float c = -(far + near) / (far - near);
		float d = -(2.0F * far * near) / (far - near);
		float e = -1.0F;
		Matrix4x4 m = new Matrix4x4();
		m[0, 0] = x;
		m[0, 1] = 0;
		m[0, 2] = a;
		m[0, 3] = 0;
		m[1, 0] = 0;
		m[1, 1] = y;
		m[1, 2] = b;
		m[1, 3] = 0;
		m[2, 0] = 0;
		m[2, 1] = 0;
		m[2, 2] = c;
		m[2, 3] = d;
		m[3, 0] = 0;
		m[3, 1] = 0;
		m[3, 2] = e;
		m[3, 3] = 0;
		return m;
	}

	void UpdateCameras(Camera[] cameras)
	{
		for (int i =0;i<cameras.Length;++i)
		{
			if (screens.Length > i)
			{
				Camera camera = cameras[i];
				Transform screen = screens[i];
				//the screen and the camera can move : got to keep the camera aligned to the screen
				camera.transform.rotation = screen.rotation;
				float physicalWidth = screen.localScale.x;
				float physicalHeight = screen.localScale.y;
				//computing top bottom lef right, relative to camera
				Vector3 screenCenter = camera.transform.InverseTransformPoint(screen.position);
				float ratio = 1.0f;
				if (screenCenter.z != 0.0f) 
				{ ratio = camera.nearClipPlane / screenCenter.z; }
				Vector3 screenCenterAtNear = screenCenter * ratio;
				float widthAtNear = physicalWidth * ratio;
				float heightAtNear = physicalHeight * ratio;
				float left = screenCenterAtNear.x-0.5f*widthAtNear;
				float right = screenCenterAtNear.x+0.5f*widthAtNear;
				float top = screenCenterAtNear.y+0.5f*heightAtNear;
				float bottom = screenCenterAtNear.y-0.5f*heightAtNear;
				
				camera.aspect = physicalWidth/physicalHeight;
				camera.fieldOfView = (Mathf.Atan(top/camera.nearClipPlane) - Mathf.Atan(bottom/camera.nearClipPlane)) * 57.2957795f;
				camera.projectionMatrix = PerspectiveOffCenter(left,right,bottom,top,camera.nearClipPlane, camera.farClipPlane);
				camera.SetStereoProjectionMatrix(Camera.StereoscopicEye.Left,camera.projectionMatrix);
				camera.SetStereoProjectionMatrix(Camera.StereoscopicEye.Right,camera.projectionMatrix);
			}
		}

	}

	// Update is called once per frame
	void LateUpdate () {
		UpdateCameras(leftCameras);
		if (stereoscopy)
		{
			UpdateCameras(rightCameras);
		}
	}
}
