//========= Copyright 2014, Valve Corporation, All rights reserved. ===========
//
// Purpose: Generates a mesh based on field of view.
//
//=============================================================================

using UnityEngine;
using Valve.VR;

[ExecuteInEditMode, RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
public class SteamVR_Frustum : MonoBehaviour
{
	public SteamVR_TrackedObject.EIndex index;

	public float fovLeft = 45, fovRight = 45, fovTop = 45, fovBottom = 45, nearZ = 0.5f, farZ = 2.5f;

	public void UpdateModel()
	{
		fovLeft = Mathf.Clamp(fovLeft, 1, 89);
		fovRight = Mathf.Clamp(fovRight, 1, 89);
		fovTop = Mathf.Clamp(fovTop, 1, 89);
		fovBottom = Mathf.Clamp(fovBottom, 1, 89);
		farZ = Mathf.Max(farZ, nearZ + 0.01f);
		nearZ = Mathf.Clamp(nearZ, 0.01f, farZ - 0.01f);

		var lsin = Mathf.Sin(-fovLeft * Mathf.Deg2Rad);
		var rsin = Mathf.Sin(fovRight * Mathf.Deg2Rad);
		var tsin = Mathf.Sin(fovTop * Mathf.Deg2Rad);
		var bsin = Mathf.Sin(-fovBottom * Mathf.Deg2Rad);

		var lcos = Mathf.Cos(-fovLeft * Mathf.Deg2Rad);
		var rcos = Mathf.Cos(fovRight * Mathf.Deg2Rad);
		var tcos = Mathf.Cos(fovTop * Mathf.Deg2Rad);
		var bcos = Mathf.Cos(-fovBottom * Mathf.Deg2Rad);

		var corners = new Vector3[] {
			new Vector3(lsin * nearZ / lcos, tsin * nearZ / tcos, nearZ), //tln
			new Vector3(rsin * nearZ / rcos, tsin * nearZ / tcos, nearZ), //trn
			new Vector3(rsin * nearZ / rcos, bsin * nearZ / bcos, nearZ), //brn
			new Vector3(lsin * nearZ / lcos, bsin * nearZ / bcos, nearZ), //bln
			new Vector3(lsin * farZ  / lcos, tsin * farZ  / tcos, farZ ), //tlf
			new Vector3(rsin * farZ  / rcos, tsin * farZ  / tcos, farZ ), //trf
			new Vector3(rsin * farZ  / rcos, bsin * farZ  / bcos, farZ ), //brf
			new Vector3(lsin * farZ  / lcos, bsin * farZ  / bcos, farZ ), //blf
		};

		var triangles = new int[] {
		//	0, 1, 2, 0, 2, 3, // near
		//	0, 2, 1, 0, 3, 2, // near
		//	4, 5, 6, 4, 6, 7, // far
		//	4, 6, 5, 4, 7, 6, // far
			0, 4, 7, 0, 7, 3, // left
			0, 7, 4, 0, 3, 7, // left
			1, 5, 6, 1, 6, 2, // right
			1, 6, 5, 1, 2, 6, // right
			0, 4, 5, 0, 5, 1, // top
			0, 5, 4, 0, 1, 5, // top
			2, 3, 7, 2, 7, 6, // bottom
			2, 7, 3, 2, 6, 7, // bottom
		};

		int j = 0;
		var vertices = new Vector3[triangles.Length];
		var normals = new Vector3[triangles.Length];
		for (int i = 0; i < triangles.Length / 3; i++)
		{
			var a = corners[triangles[i * 3 + 0]];
			var b = corners[triangles[i * 3 + 1]];
			var c = corners[triangles[i * 3 + 2]];
			var n = Vector3.Cross(b - a, c - a).normalized;
			normals[i * 3 + 0] = n;
			normals[i * 3 + 1] = n;
			normals[i * 3 + 2] = n;
			vertices[i * 3 + 0] = a;
			vertices[i * 3 + 1] = b;
			vertices[i * 3 + 2] = c;
			triangles[i * 3 + 0] = j++;
			triangles[i * 3 + 1] = j++;
			triangles[i * 3 + 2] = j++;
		}

		var mesh = new Mesh();
		mesh.vertices = vertices;
		mesh.normals = normals;
		mesh.triangles = triangles;

		GetComponent<MeshFilter>().mesh = mesh;
	}

	private void OnDeviceConnected(params object[] args)
	{
		var i = (int)args[0];
		if (i != (int)index)
			return;

		GetComponent<MeshFilter>().mesh = null;

		var connected = (bool)args[1];
		if (connected)
		{
			var system = OpenVR.System;
			if (system != null && system.GetTrackedDeviceClass((uint)i) == ETrackedDeviceClass.TrackingReference)
			{
				var error = ETrackedPropertyError.TrackedProp_Success;
				var result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_FieldOfViewLeftDegrees_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					fovLeft = result;

				result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_FieldOfViewRightDegrees_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					fovRight = result;

				result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_FieldOfViewTopDegrees_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					fovTop = result;

				result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_FieldOfViewBottomDegrees_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					fovBottom = result;

				result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_TrackingRangeMinimumMeters_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					nearZ = result;

				result = system.GetFloatTrackedDeviceProperty((uint)i, ETrackedDeviceProperty.Prop_TrackingRangeMaximumMeters_Float, ref error);
				if (error == ETrackedPropertyError.TrackedProp_Success)
					farZ = result;

				UpdateModel();
			}
		}
	}

	void OnEnable()
	{
		GetComponent<MeshFilter>().mesh = null;
		SteamVR_Utils.Event.Listen("device_connected", OnDeviceConnected);
	}

	void OnDisable()
	{
		SteamVR_Utils.Event.Remove("device_connected", OnDeviceConnected);
		GetComponent<MeshFilter>().mesh = null;
	}

#if UNITY_EDITOR
	void Update()
	{
		if (!Application.isPlaying)
			UpdateModel();
	}
#endif
}
