devNotes 8-03-16 Vive Input Module

https://www.reddit.com/r/Vive/wiki/skins

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: Render model of associated tracked object
//
//=============================================================================

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
using Valve.VR;

[ExecuteInEditMode]
public class SteamVR_RenderModel : MonoBehaviour
{
	public SteamVR_TrackedObject.EIndex index = SteamVR_TrackedObject.EIndex.None;
	public string modelOverride;

	// Shader to apply to model.
	public Shader shader;

	// Enable to print out when render models are loaded.
	public bool verbose = false;

	// If available, break down into separate components instead of loading as a single mesh.
	public bool createComponents = true;

	// Update transforms of components at runtime to reflect user action.
	public bool updateDynamically = true;

	// Additional controller settings for showing scrollwheel, etc.
	public RenderModel_ControllerMode_State_t controllerModeState;

	// Name of the sub-object which represents the "local" coordinate space for each component.
	public const string k_localTransformName = "attach";

	// Cached name of this render model for updating component transforms at runtime.
	public string renderModelName { get; private set; }

	// If someone knows how to keep these from getting cleaned up every time
	// you exit play mode, let me know.  I've tried marking the RenderModel
	// class below as [System.Serializable] and switching to normal public
	// variables for mesh and material to get them to serialize properly,
	// as well as tried marking the mesh and material objects as
	// DontUnloadUnusedAsset, but Unity was still unloading them.
	// The hashtable is preserving its entries, but the mesh and material
	// variables are going null.

	public class RenderModel
	{
		public RenderModel(Mesh mesh, Material material)
		{
			this.mesh = mesh;
			this.material = material;
		}
		public Mesh mesh { get; private set; }
		public Material material { get; private set; }
	}

	public static Hashtable models = new Hashtable();
	public static Hashtable materials = new Hashtable();

	// Helper class to load render models interface on demand and clean up when done.
	public sealed class RenderModelInterfaceHolder : System.IDisposable
	{
		private bool needsShutdown, failedLoadInterface;
		private CVRRenderModels _instance;
		public CVRRenderModels instance
		{
			get
			{
				if (_instance == null && !failedLoadInterface)
				{
					if (!SteamVR.active && !SteamVR.usingNativeSupport)
					{
						var error = EVRInitError.None;
						OpenVR.Init(ref error, EVRApplicationType.VRApplication_Other);
						needsShutdown = true;
					}

					_instance = OpenVR.RenderModels;
					if (_instance == null)
					{
						Debug.LogError("Failed to load IVRRenderModels interface version " + OpenVR.IVRRenderModels_Version);
						failedLoadInterface = true;
                    }
				}
				return _instance;
			}
		}
		public void Dispose()
		{
			if (needsShutdown)
				OpenVR.Shutdown();
		}
	}

	private void OnModelSkinSettingsHaveChanged(params object[] args)
	{
		if (!string.IsNullOrEmpty(renderModelName))
		{
			renderModelName = "";
			UpdateModel();
		}
	}

	private void OnHideRenderModels(params object[] args)
	{
		bool hidden = (bool)args[0];
		var meshRenderer = GetComponent<MeshRenderer>();
		if (meshRenderer != null)
			meshRenderer.enabled = !hidden;
		foreach (var child in transform.GetComponentsInChildren<MeshRenderer>())
			child.enabled = !hidden;
    }

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

		var connected = (bool)args[1];
		if (connected)
		{
			UpdateModel();
		}
	}

	public void UpdateModel()
	{
		var system = OpenVR.System;
		if (system == null)
			return;

		var error = ETrackedPropertyError.TrackedProp_Success;
		var capacity = system.GetStringTrackedDeviceProperty((uint)index, ETrackedDeviceProperty.Prop_RenderModelName_String, null, 0, ref error);
		if (capacity <= 1)
		{
			Debug.LogError("Failed to get render model name for tracked object " + index);
			return;
		}

		var buffer = new System.Text.StringBuilder((int)capacity);
		system.GetStringTrackedDeviceProperty((uint)index, ETrackedDeviceProperty.Prop_RenderModelName_String, buffer, capacity, ref error);

		var s = buffer.ToString();
		if (renderModelName != s)
		{
			renderModelName = s;
			StartCoroutine(SetModelAsync(s));
		}
	}

	IEnumerator SetModelAsync(string renderModelName)
	{
		if (string.IsNullOrEmpty(renderModelName))
			yield break;

		// Preload all render models before asking for the data to create meshes.
		using (var holder = new RenderModelInterfaceHolder())
		{
			var renderModels = holder.instance;
			if (renderModels == null)
				yield break;

			// Gather names of render models to preload.
			string[] renderModelNames;

			var count = renderModels.GetComponentCount(renderModelName);
			if (count > 0)
			{
				renderModelNames = new string[count];

				for (int i = 0; i < count; i++)
				{
					var capacity = renderModels.GetComponentName(renderModelName, (uint)i, null, 0);
					if (capacity == 0)
						continue;

					var componentName = new System.Text.StringBuilder((int)capacity);
					if (renderModels.GetComponentName(renderModelName, (uint)i, componentName, capacity) == 0)
						continue;

					capacity = renderModels.GetComponentRenderModelName(renderModelName, componentName.ToString(), null, 0);
					if (capacity == 0)
						continue;

					var name = new System.Text.StringBuilder((int)capacity);
					if (renderModels.GetComponentRenderModelName(renderModelName, componentName.ToString(), name, capacity) == 0)
						continue;

					var s = name.ToString();

					// Only need to preload if not already cached.
					var model = models[s] as RenderModel;
					if (model == null || model.mesh == null)
					{
						renderModelNames[i] = s;
					}
				}
			}
			else
			{
				// Only need to preload if not already cached.
				var model = models[renderModelName] as RenderModel;
				if (model == null || model.mesh == null)
				{
					renderModelNames = new string[] { renderModelName };
				}
				else
				{
					renderModelNames = new string[0];
				}
			}

			// Keep trying every 100ms until all components finish loading.
			while (true)
			{
				var loading = false;
				foreach (var name in renderModelNames)
				{
					if (string.IsNullOrEmpty(name))
						continue;

					var pRenderModel = System.IntPtr.Zero;

					var error = renderModels.LoadRenderModel_Async(name, ref pRenderModel);
                    if (error == EVRRenderModelError.Loading)
					{
						loading = true;
					}
					else if (error == EVRRenderModelError.None)
					{
						// Preload textures as well.
						var renderModel = (RenderModel_t)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_t));

						// Check the cache first.
						var material = materials[renderModel.diffuseTextureId] as Material;
						if (material == null || material.mainTexture == null)
						{
							var pDiffuseTexture = System.IntPtr.Zero;

							error = renderModels.LoadTexture_Async(renderModel.diffuseTextureId, ref pDiffuseTexture);
							if (error == EVRRenderModelError.Loading)
							{
								loading = true;
							}
						}
					}
				}

				if (loading)
				{
					yield return new WaitForSeconds(0.1f);
				}
				else
				{
					break;
				}
			}
		}

		bool success = SetModel(renderModelName);
		SteamVR_Utils.Event.Send("render_model_loaded", this, success);
	}

	private bool SetModel(string renderModelName)
	{
		StripMesh(gameObject);

		using (var holder = new RenderModelInterfaceHolder())
		{
			if (createComponents)
			{
				if (LoadComponents(holder, renderModelName))
				{
					UpdateComponents();
					return true;
				}

				Debug.Log("[" + gameObject.name + "] Render model does not support components, falling back to single mesh.");
			}

			if (!string.IsNullOrEmpty(renderModelName))
			{
				var model = models[renderModelName] as RenderModel;
				if (model == null || model.mesh == null)
				{
					var renderModels = holder.instance;
					if (renderModels == null)
						return false;

					if (verbose)
						Debug.Log("Loading render model " + renderModelName);

					model = LoadRenderModel(renderModels, renderModelName, renderModelName);
					if (model == null)
						return false;

					models[renderModelName] = model;
				}

				gameObject.AddComponent<MeshFilter>().mesh = model.mesh;
				gameObject.AddComponent<MeshRenderer>().sharedMaterial = model.material;
				return true;
			}
		}

		return false;
	}

	RenderModel LoadRenderModel(CVRRenderModels renderModels, string renderModelName, string baseName)
	{
        var pRenderModel = System.IntPtr.Zero;

		EVRRenderModelError error;
		while ( true )
		{
			error = renderModels.LoadRenderModel_Async(renderModelName, ref pRenderModel);
			if (error != EVRRenderModelError.Loading)
				break;

			System.Threading.Thread.Sleep(1);
		}

		if (error != EVRRenderModelError.None)
		{
			Debug.LogError(string.Format("Failed to load render model {0} - {1}", renderModelName, error.ToString()));
			return null;
		}

        var renderModel = (RenderModel_t)Marshal.PtrToStructure(pRenderModel, typeof(RenderModel_t));

		var vertices = new Vector3[renderModel.unVertexCount];
		var normals = new Vector3[renderModel.unVertexCount];
		var uv = new Vector2[renderModel.unVertexCount];

		var type = typeof(RenderModel_Vertex_t);
		for (int iVert = 0; iVert < renderModel.unVertexCount; iVert++)
		{
			var ptr = new System.IntPtr(renderModel.rVertexData.ToInt64() + iVert * Marshal.SizeOf(type));
			var vert = (RenderModel_Vertex_t)Marshal.PtrToStructure(ptr, type);

			vertices[iVert] = new Vector3(vert.vPosition.v0, vert.vPosition.v1, -vert.vPosition.v2);
			normals[iVert] = new Vector3(vert.vNormal.v0, vert.vNormal.v1, -vert.vNormal.v2);
			uv[iVert] = new Vector2(vert.rfTextureCoord0, vert.rfTextureCoord1);
		}

		int indexCount = (int)renderModel.unTriangleCount * 3;
		var indices = new short[indexCount];
		Marshal.Copy(renderModel.rIndexData, indices, 0, indices.Length);

		var triangles = new int[indexCount];
		for (int iTri = 0; iTri < renderModel.unTriangleCount; iTri++)
		{
			triangles[iTri * 3 + 0] = (int)indices[iTri * 3 + 2];
			triangles[iTri * 3 + 1] = (int)indices[iTri * 3 + 1];
			triangles[iTri * 3 + 2] = (int)indices[iTri * 3 + 0];
		}

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

		mesh.Optimize();
		//mesh.hideFlags = HideFlags.DontUnloadUnusedAsset;

		// Check cache before loading texture.
		var material = materials[renderModel.diffuseTextureId] as Material;
		if (material == null || material.mainTexture == null)
		{
			var pDiffuseTexture = System.IntPtr.Zero;

			while (true)
			{
				error = renderModels.LoadTexture_Async(renderModel.diffuseTextureId, ref pDiffuseTexture);
				if (error != EVRRenderModelError.Loading)
					break;

				System.Threading.Thread.Sleep(1);
			}

			if (error == EVRRenderModelError.None)
			{
				var diffuseTexture = (RenderModel_TextureMap_t)Marshal.PtrToStructure(pDiffuseTexture, typeof(RenderModel_TextureMap_t));
				var texture = new Texture2D(diffuseTexture.unWidth, diffuseTexture.unHeight, TextureFormat.ARGB32, false);
				if (SystemInfo.graphicsDeviceVersion.StartsWith("OpenGL"))
				{
					var textureMapData = new byte[diffuseTexture.unWidth * diffuseTexture.unHeight * 4]; // RGBA
					Marshal.Copy(diffuseTexture.rubTextureMapData, textureMapData, 0, textureMapData.Length);

					var colors = new Color32[diffuseTexture.unWidth * diffuseTexture.unHeight];
					int iColor = 0;
					for (int iHeight = 0; iHeight < diffuseTexture.unHeight; iHeight++)
					{
						for (int iWidth = 0; iWidth < diffuseTexture.unWidth; iWidth++)
						{
							var r = textureMapData[iColor++];
							var g = textureMapData[iColor++];
							var b = textureMapData[iColor++];
							var a = textureMapData[iColor++];
							colors[iHeight * diffuseTexture.unWidth + iWidth] = new Color32(r, g, b, a);
						}
					}

					texture.SetPixels32(colors);
					texture.Apply();
				}
				else
				{
					texture.Apply();

					while (true)
					{
						error = renderModels.LoadIntoTextureD3D11_Async(renderModel.diffuseTextureId, texture.GetNativeTexturePtr());
						if (error != EVRRenderModelError.Loading)
							break;

						System.Threading.Thread.Sleep(1);
					}
				}

				material = new Material(shader != null ? shader : Shader.Find("Standard"));
				material.mainTexture = texture;
				//material.hideFlags = HideFlags.DontUnloadUnusedAsset;

				materials[renderModel.diffuseTextureId] = material;

				renderModels.FreeTexture(pDiffuseTexture);
			}
			else
			{
				Debug.Log("Failed to load render model texture for render model " + renderModelName);
			}
		}

		// Delay freeing when we can since we'll often get multiple requests for the same model right
		// after another (e.g. two controllers or two basestations).
#if UNITY_EDITOR
		if (!Application.isPlaying)
			renderModels.FreeRenderModel(pRenderModel);
		else
#endif
			StartCoroutine(FreeRenderModel(pRenderModel));

		return new RenderModel(mesh, material);
	}

	IEnumerator FreeRenderModel(System.IntPtr pRenderModel)
	{
		yield return new WaitForSeconds(1.0f);

		using (var holder = new RenderModelInterfaceHolder())
		{
			var renderModels = holder.instance;
			renderModels.FreeRenderModel(pRenderModel);
		}
	}

	public Transform FindComponent(string componentName)
	{
		var t = transform;
		for (int i = 0; i < t.childCount; i++)
		{
			var child = t.GetChild(i);
			if (child.name == componentName)
				return child;
		}
		return null;
	}

	private void StripMesh(GameObject go)
	{
		var meshRenderer = go.GetComponent<MeshRenderer>();
		if (meshRenderer != null)
			DestroyImmediate(meshRenderer);

		var meshFilter = go.GetComponent<MeshFilter>();
		if (meshFilter != null)
			DestroyImmediate(meshFilter);
	}

	private bool LoadComponents(RenderModelInterfaceHolder holder, string renderModelName)
	{
		// Disable existing components (we will re-enable them if referenced by this new model).
		// Also strip mesh filter and renderer since these will get re-added if the new component needs them.
		var t = transform;
		for (int i = 0; i < t.childCount; i++)
		{
			var child = t.GetChild(i);
			child.gameObject.SetActive(false);
			StripMesh(child.gameObject);
		}

		// If no model specified, we're done; return success.
		if (string.IsNullOrEmpty(renderModelName))
			return true;

		var renderModels = holder.instance;
		if (renderModels == null)
			return false;

		var count = renderModels.GetComponentCount(renderModelName);
		if (count == 0)
			return false;

		for (int i = 0; i < count; i++)
		{
			var capacity = renderModels.GetComponentName(renderModelName, (uint)i, null, 0);
			if (capacity == 0)
				continue;

			var componentName = new System.Text.StringBuilder((int)capacity);
			if (renderModels.GetComponentName(renderModelName, (uint)i, componentName, capacity) == 0)
				continue;

			// Create (or reuse) a child object for this component (some components are dynamic and don't have meshes).
			t = FindComponent(componentName.ToString());
			if (t != null)
			{
				t.gameObject.SetActive(true);
			}
			else
			{
				t = new GameObject(componentName.ToString()).transform;
				t.parent = transform;
				t.gameObject.layer = gameObject.layer;

				// Also create a child 'attach' object for attaching things.
				var attach = new GameObject(k_localTransformName).transform;
				attach.parent = t;
				attach.localPosition = Vector3.zero;
				attach.localRotation = Quaternion.identity;
				attach.localScale = Vector3.one;
				attach.gameObject.layer = gameObject.layer;
			}

			// Reset transform.
			t.localPosition = Vector3.zero;
			t.localRotation = Quaternion.identity;
			t.localScale = Vector3.one;

			capacity = renderModels.GetComponentRenderModelName(renderModelName, componentName.ToString(), null, 0);
			if (capacity == 0)
				continue;

			var componentRenderModelName = new System.Text.StringBuilder((int)capacity);
			if (renderModels.GetComponentRenderModelName(renderModelName, componentName.ToString(), componentRenderModelName, capacity) == 0)
				continue;

			// Check the cache or load into memory.
			var model = models[componentRenderModelName] as RenderModel;
			if (model == null || model.mesh == null)
			{
				if (verbose)
					Debug.Log("Loading render model " + componentRenderModelName);

				model = LoadRenderModel(renderModels, componentRenderModelName.ToString(), renderModelName);
				if (model == null)
					continue;

				models[componentRenderModelName] = model;
			}

			t.gameObject.AddComponent<MeshFilter>().mesh = model.mesh;
			t.gameObject.AddComponent<MeshRenderer>().sharedMaterial = model.material;
		}

		return true;
	}

	void OnEnable()
	{
#if UNITY_EDITOR
		if (!Application.isPlaying)
			return;
#endif
		if (!string.IsNullOrEmpty(modelOverride))
		{
			Debug.Log("Model override is really only meant to be used in the scene view for lining things up; using it at runtime is discouraged.  Use tracked device index instead to ensure the correct model is displayed for all users.");
			enabled = false;
			return;
		}

		var system = OpenVR.System;
		if (system != null && system.IsTrackedDeviceConnected((uint)index))
		{
			UpdateModel();
		}

		SteamVR_Utils.Event.Listen("device_connected", OnDeviceConnected);
		SteamVR_Utils.Event.Listen("hide_render_models", OnHideRenderModels);
		SteamVR_Utils.Event.Listen("ModelSkinSettingsHaveChanged", OnModelSkinSettingsHaveChanged);
	}

	void OnDisable()
	{
#if UNITY_EDITOR
		if (!Application.isPlaying)
			return;
#endif
		SteamVR_Utils.Event.Remove("device_connected", OnDeviceConnected);
		SteamVR_Utils.Event.Remove("hide_render_models", OnHideRenderModels);
		SteamVR_Utils.Event.Remove("ModelSkinSettingsHaveChanged", OnModelSkinSettingsHaveChanged);
	}

#if UNITY_EDITOR
	Hashtable values;
#endif
	void Update()
	{
#if UNITY_EDITOR
		if (!Application.isPlaying)
		{
			// See if anything has changed since this gets called whenever anything gets touched.
			var fields = GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

			bool modified = false;

			if (values == null)
			{
				modified = true;
			}
			else
			{
				foreach (var f in fields)
				{
					if (!values.Contains(f))
					{
						modified = true;
						break;
					}

					var v0 = values[f];
                    var v1 = f.GetValue(this);
					if (v1 != null)
					{
						if (!v1.Equals(v0))
						{
							modified = true;
							break;
						}
					}
					else if (v0 != null)
					{
						modified = true;
						break;
					}
				}
			}

			if (modified)
			{
				if (renderModelName != modelOverride)
				{
					renderModelName = modelOverride;
					SetModel(modelOverride);
				}

				values = new Hashtable();
				foreach (var f in fields)
					values[f] = f.GetValue(this);
			}

			return; // Do not update transforms (below) when not playing in Editor (to avoid keeping OpenVR running all the time).
		}
#endif
		// Update component transforms dynamically.
		if (updateDynamically)
			UpdateComponents();
	}

	public void UpdateComponents()
	{
		var t = transform;
		if (t.childCount == 0)
			return;

		using (var holder = new RenderModelInterfaceHolder())
		{
			var controllerState = (index != SteamVR_TrackedObject.EIndex.None) ?
				SteamVR_Controller.Input((int)index).GetState() : new VRControllerState_t();

			for (int i = 0; i < t.childCount; i++)
			{
				var child = t.GetChild(i);

				var renderModels = holder.instance;
				if (renderModels == null)
					break;

				var componentState = new RenderModel_ComponentState_t();
                if (!renderModels.GetComponentState(renderModelName, child.name, ref controllerState, ref controllerModeState, ref componentState))
					continue;

				var componentTransform = new SteamVR_Utils.RigidTransform(componentState.mTrackingToComponentRenderModel);
				child.localPosition = componentTransform.pos;
				child.localRotation = componentTransform.rot;

				var attach = child.FindChild(k_localTransformName);
				if (attach != null)
				{
					var attachTransform = new SteamVR_Utils.RigidTransform(componentState.mTrackingToComponentLocal);
					attach.position = t.TransformPoint(attachTransform.pos);
					attach.rotation = t.rotation * attachTransform.rot;
				}

				bool visible = (componentState.uProperties & (uint)EVRComponentProperty.IsVisible) != 0;
				if (visible != child.gameObject.activeSelf)
				{
					child.gameObject.SetActive(visible);
				}
			}
		}
	}

	public void SetDeviceIndex(int index)
	{
		this.index = (SteamVR_TrackedObject.EIndex)index;
		modelOverride = "";

		if (enabled)
		{
			UpdateModel();
		}
	}
}

//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: For controlling in-game objects with tracked devices.
//
//=============================================================================

using UnityEngine;
using Valve.VR;

public class SteamVR_TrackedObject : MonoBehaviour
{
	public enum EIndex
	{
		None = -1,
		Hmd = (int)OpenVR.k_unTrackedDeviceIndex_Hmd,
		Device1,
		Device2,
		Device3,
		Device4,
		Device5,
		Device6,
		Device7,
		Device8,
		Device9,
		Device10,
		Device11,
		Device12,
		Device13,
		Device14,
		Device15
	}

	public EIndex index;
	public Transform origin; // if not set, relative to parent
    public bool isValid = false;

	private void OnNewPoses(params object[] args)
	{
		if (index == EIndex.None)
			return;

		var i = (int)index;

        isValid = false;
		var poses = (Valve.VR.TrackedDevicePose_t[])args[0];
		if (poses.Length <= i)
			return;

		if (!poses[i].bDeviceIsConnected)
			return;

		if (!poses[i].bPoseIsValid)
			return;

        isValid = true;

		var pose = new SteamVR_Utils.RigidTransform(poses[i].mDeviceToAbsoluteTracking);

		if (origin != null)
		{
			pose = new SteamVR_Utils.RigidTransform(origin) * pose;
			pose.pos.x *= origin.localScale.x;
			pose.pos.y *= origin.localScale.y;
			pose.pos.z *= origin.localScale.z;
			transform.position = pose.pos;
			transform.rotation = pose.rot;
		}
		else
		{
			transform.localPosition = pose.pos;
			transform.localRotation = pose.rot;
		}
	}

	void OnEnable()
	{
		var render = SteamVR_Render.instance;
		if (render == null)
		{
			enabled = false;
			return;
		}

		SteamVR_Utils.Event.Listen("new_poses", OnNewPoses);
	}

	void OnDisable()
	{
		SteamVR_Utils.Event.Remove("new_poses", OnNewPoses);
		isValid = false;
	}

	public void SetDeviceIndex(int index)
	{
		if (System.Enum.IsDefined(typeof(EIndex), index))
			this.index = (EIndex)index;
	}
}

dfgghdfgh1

https://youtu.be/Gh33IkBLmxE

 

sdfgsfgsdf1

sdfsdfsdf1

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using System.Collections;
using System.Collections.Generic;

public class ViveControllerInput : BaseInputModule
{
    public static ViveControllerInput Instance;

    [Header(" [Cursor setup]")]
    public Sprite CursorSprite;
    public Material CursorMaterial;
    public float NormalCursorScale = 0.00025f;

    [Space(10)]

    [Header(" [Runtime variables]")]
    [Tooltip("Indicates whether or not the gui was hit by any controller this frame")]
    public bool GuiHit;

    [Tooltip("Indicates whether or not a button was used this frame")]
    public bool ButtonUsed;

    [Tooltip("Generated cursors")]
    public RectTransform[] Cursors;

    private GameObject[] CurrentPoint;
    private GameObject[] CurrentPressed;
    private GameObject[] CurrentDragging;

    private PointerEventData[] PointEvents;

    private bool Initialized = false;

    [Tooltip("Generated non rendering camera (used for raycasting ui)")]
    public Camera ControllerCamera;

    private SteamVR_ControllerManager ControllerManager;
    private SteamVR_TrackedObject[] Controllers;
    private SteamVR_Controller.Device[] ControllerDevices;

    protected override void Start()
    {
        base.Start();

        if (Initialized == false)
        {
            Instance = this;

            ControllerCamera = new GameObject("Controller UI Camera").AddComponent<Camera>();
            ControllerCamera.clearFlags = CameraClearFlags.Nothing; //CameraClearFlags.Depth;
            ControllerCamera.cullingMask = 0; // 1 << LayerMask.NameToLayer("UI"); 

            ControllerManager = GameObject.FindObjectOfType<SteamVR_ControllerManager>();
            Controllers = new SteamVR_TrackedObject[] { ControllerManager.left.GetComponent<SteamVR_TrackedObject>(), ControllerManager.right.GetComponent<SteamVR_TrackedObject>() };
            ControllerDevices = new SteamVR_Controller.Device[Controllers.Length];
            Cursors = new RectTransform[Controllers.Length];

            for (int index = 0; index < Cursors.Length; index++)
            {
                GameObject cursor = new GameObject("Cursor " + index);
                Canvas canvas = cursor.AddComponent<Canvas>();
                cursor.AddComponent<CanvasRenderer>();
                cursor.AddComponent<CanvasScaler>();
                cursor.AddComponent<UIIgnoreRaycast>();
                cursor.AddComponent<GraphicRaycaster>();

                canvas.renderMode = RenderMode.WorldSpace;
                canvas.sortingOrder = 1000; //set to be on top of everything

                Image image = cursor.AddComponent<Image>();
                image.sprite = CursorSprite;
                image.material = CursorMaterial;


                if (CursorSprite == null)
                    Debug.LogError("Set CursorSprite on " + this.gameObject.name + " to the sprite you want to use as your cursor.", this.gameObject);

                Cursors[index] = cursor.GetComponent<RectTransform>();
            }

            CurrentPoint = new GameObject[Cursors.Length];
            CurrentPressed = new GameObject[Cursors.Length];
            CurrentDragging = new GameObject[Cursors.Length];
            PointEvents = new PointerEventData[Cursors.Length];

            Canvas[] canvases = GameObject.FindObjectsOfType<Canvas>();
            foreach (Canvas canvas in canvases)
            {
                canvas.worldCamera = ControllerCamera;
            }

            Initialized = true;
        }
    }

    // use screen midpoint as locked pointer location, enabling look location to be the "mouse"
    private bool GetLookPointerEventData(int index)
    {
        if (PointEvents[index] == null)
            PointEvents[index] = new PointerEventData(base.eventSystem);
        else
            PointEvents[index].Reset();

        PointEvents[index].delta = Vector2.zero;
        PointEvents[index].position = new Vector2(Screen.width / 2, Screen.height / 2);
        PointEvents[index].scrollDelta = Vector2.zero;

        base.eventSystem.RaycastAll(PointEvents[index], m_RaycastResultCache);
        PointEvents[index].pointerCurrentRaycast = FindFirstRaycast(m_RaycastResultCache);
        if (PointEvents[index].pointerCurrentRaycast.gameObject != null)
        {
            GuiHit = true; //gets set to false at the beginning of the process event
        }

        m_RaycastResultCache.Clear();

        return true;
    }

    // update the cursor location and whether it is enabled
    // this code is based on Unity's DragMe.cs code provided in the UI drag and drop example
    private void UpdateCursor(int index, PointerEventData pointData)
    {
        if (PointEvents[index].pointerCurrentRaycast.gameObject != null)
        {
            Cursors[index].gameObject.SetActive(true);

            if (pointData.pointerEnter != null)
            {
                RectTransform draggingPlane = pointData.pointerEnter.GetComponent<RectTransform>();
                Vector3 globalLookPos;
                if (RectTransformUtility.ScreenPointToWorldPointInRectangle(draggingPlane, pointData.position, pointData.enterEventCamera, out globalLookPos))
                {
                    Cursors[index].position = globalLookPos;
                    Cursors[index].rotation = draggingPlane.rotation;

                    // scale cursor based on distance to camera
                    float lookPointDistance = (Cursors[index].position - Camera.main.transform.position).magnitude;
                    float cursorScale = lookPointDistance * NormalCursorScale;
                    if (cursorScale < NormalCursorScale)
                    {
                        cursorScale = NormalCursorScale;
                    }

                    Cursors[index].localScale = Vector3.one * cursorScale;
                }
            }
        }
        else
        {
            Cursors[index].gameObject.SetActive(false);
        }
    }

    // clear the current selection
    public void ClearSelection()
    {
        if (base.eventSystem.currentSelectedGameObject)
        {
            base.eventSystem.SetSelectedGameObject(null);
        }
    }

    // select a game object
    private void Select(GameObject go)
    {
        ClearSelection();

        if (ExecuteEvents.GetEventHandler<ISelectHandler>(go))
        {
            base.eventSystem.SetSelectedGameObject(go);
        }
    }

    // send update event to selected object
    // needed for InputField to receive keyboard input
    private bool SendUpdateEventToSelectedObject()
    {
        if (base.eventSystem.currentSelectedGameObject == null)
            return false;

        BaseEventData data = GetBaseEventData();

        ExecuteEvents.Execute(base.eventSystem.currentSelectedGameObject, data, ExecuteEvents.updateSelectedHandler);

        return data.used;
    }

    private void UpdateCameraPosition(int index)
    {
        ControllerCamera.transform.position = Controllers[index].transform.position;
        ControllerCamera.transform.forward = Controllers[index].transform.forward;
    }

    private void InitializeControllers()
    {
        for (int index = 0; index < Controllers.Length; index++)
        {
            if (Controllers[index] != null && Controllers[index].index != SteamVR_TrackedObject.EIndex.None)
            {
                ControllerDevices[index] = SteamVR_Controller.Input((int)Controllers[index].index);
            }
            else
            {
                ControllerDevices[index] = null;
            }
        }
    }

    // Process is called by UI system to process events
    public override void Process()
    {
        InitializeControllers();

        GuiHit = false;
        ButtonUsed = false;

        // send update events if there is a selected object - this is important for InputField to receive keyboard events
        SendUpdateEventToSelectedObject();

        // see if there is a UI element that is currently being looked at
        for (int index = 0; index < Cursors.Length; index++)
        {
            if (Controllers[index].gameObject.activeInHierarchy == false)
            {
                if (Cursors[index].gameObject.activeInHierarchy == true)
                {
                    Cursors[index].gameObject.SetActive(false);
                }
                continue;
            }

            UpdateCameraPosition(index);

            bool hit = GetLookPointerEventData(index);
            if (hit == false)
                continue;

            CurrentPoint[index] = PointEvents[index].pointerCurrentRaycast.gameObject;

            // handle enter and exit events (highlight)
            base.HandlePointerExitAndEnter(PointEvents[index], CurrentPoint[index]);

            // update cursor
            UpdateCursor(index, PointEvents[index]);

            if (Controllers[index] != null)
            {
                if (ButtonDown(index))
                {
                    ClearSelection();

                    PointEvents[index].pressPosition = PointEvents[index].position;
                    PointEvents[index].pointerPressRaycast = PointEvents[index].pointerCurrentRaycast;
                    PointEvents[index].pointerPress = null;

                    if (CurrentPoint[index] != null)
                    {
                        CurrentPressed[index] = CurrentPoint[index];

                        GameObject newPressed = ExecuteEvents.ExecuteHierarchy(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerDownHandler);

                        if (newPressed == null)
                        {
                            // some UI elements might only have click handler and not pointer down handler
                            newPressed = ExecuteEvents.ExecuteHierarchy(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerClickHandler);
                            if (newPressed != null)
                            {
                                CurrentPressed[index] = newPressed;
                            }
                        }
                        else
                        {
                            CurrentPressed[index] = newPressed;
                            // we want to do click on button down at same time, unlike regular mouse processing
                            // which does click when mouse goes up over same object it went down on
                            // reason to do this is head tracking might be jittery and this makes it easier to click buttons
                            ExecuteEvents.Execute(newPressed, PointEvents[index], ExecuteEvents.pointerClickHandler);
                        }

                        if (newPressed != null)
                        {
                            PointEvents[index].pointerPress = newPressed;
                            CurrentPressed[index] = newPressed;
                            Select(CurrentPressed[index]);
                            ButtonUsed = true;
                        }

                        ExecuteEvents.Execute(CurrentPressed[index], PointEvents[index], ExecuteEvents.beginDragHandler);
                        PointEvents[index].pointerDrag = CurrentPressed[index];
                        CurrentDragging[index] = CurrentPressed[index];
                    }
                }
                
                if (ButtonUp(index))
                {
                    if (CurrentDragging[index])
                    {
                        ExecuteEvents.Execute(CurrentDragging[index], PointEvents[index], ExecuteEvents.endDragHandler);
                        if (CurrentPoint[index] != null)
                        {
                            ExecuteEvents.ExecuteHierarchy(CurrentPoint[index], PointEvents[index], ExecuteEvents.dropHandler);
                        }
                        PointEvents[index].pointerDrag = null;
                        CurrentDragging[index] = null;
                    }
                    if (CurrentPressed[index])
                    {
                        ExecuteEvents.Execute(CurrentPressed[index], PointEvents[index], ExecuteEvents.pointerUpHandler);
                        PointEvents[index].rawPointerPress = null;
                        PointEvents[index].pointerPress = null;
                        CurrentPressed[index] = null;
                    }
                }

                // drag handling
                if (CurrentDragging[index] != null)
                {
                    ExecuteEvents.Execute(CurrentDragging[index], PointEvents[index], ExecuteEvents.dragHandler);
                }
            }
        }
    }

    private bool ButtonDown(int index)
    {
        return (ControllerDevices[index] != null && ControllerDevices[index].GetPressDown(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger) == true);
    }

    private bool ButtonUp(int index)
    {
        return (ControllerDevices[index] != null && ControllerDevices[index].GetPressUp(Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger) == true);
    }
}