using UnityEngine; using UnityEngine.XR; using System.Collections.Generic; using CW.Common; namespace PaintIn3D { /// This component attached the current GameObject to a tracked hand. [HelpURL(P3dCommon.HelpUrlPrefix + "P3dVrManager")] [AddComponentMenu(P3dCommon.ComponentMenuPrefix + "VR Manager")] public class P3dVrManager : MonoBehaviour { class SimulatedState { public XRNode Node; public bool Set; public Vector3 Position; public Quaternion Rotation = Quaternion.identity; public SimulatedState(XRNode newNode) { Node = newNode; } } /// This key allows you to reset the VR orientation. //public KeyCode RecenterKey { set { recenterKey = value; } get { return recenterKey; } } [SerializeField] private KeyCode recenterKey = KeyCode.Space; /// The default distance in world space a hand must be to grab a tool. public float GrabDistance { set { grabDistance = value; } get { return grabDistance; } } [SerializeField] private float grabDistance = 0.3f; /// This key allows you to simulate a left hand VR trigger. public KeyCode SimulatedLeftTrigger { set { simulatedLeftTrigger = value; } get { return simulatedLeftTrigger; } } [SerializeField] private KeyCode simulatedLeftTrigger = KeyCode.Mouse0; /// This key allows you to simulate a left hand VR grip. public KeyCode SimulatedLeftGrip { set { simulatedLeftGrip = value; } get { return simulatedLeftGrip; } } [SerializeField] private KeyCode simulatedLeftGrip = KeyCode.LeftControl; /// This key allows you to simulate a right hand VR trigger. public KeyCode SimulatedRightTrigger { set { simulatedRightTrigger = value; } get { return simulatedRightTrigger; } } [SerializeField] private KeyCode simulatedRightTrigger = KeyCode.Mouse1; /// This key allows you to simulate a right hand VR grip. public KeyCode SimulatedRightGrip { set { simulatedRightGrip = value; } get { return simulatedRightGrip; } } [SerializeField] private KeyCode simulatedRightGrip = KeyCode.RightControl; /// When simulating a VR tool, it will be offset by this Euler rotation. public Vector3 SimulatedTilt { set { simulatedTilt = value; } get { return simulatedTilt; } } [SerializeField] private Vector3 simulatedTilt = new Vector3(0.0f, -15.0f, 0.0f); /// When simulating a VR tool, it will be offset by this local position. public Vector3 SimulatedOffset { set { simulatedOffset = value; } get { return simulatedOffset; } } [SerializeField] private Vector3 simulatedOffset = new Vector3(0.0f, 0.0f, -0.2f); /// When simulating a VR tool, it will be moved away from the hit surface by this. public float SimulatedDistanceMax { set { simulatedReach = value; } get { return simulatedReach; } } [SerializeField] private float simulatedReach = 1.0f; /// The simulated left VR eye will be offset this much. public Vector3 SimulatedEyeOffset { set { simulatedEyeOffset = value; } get { return simulatedEyeOffset; } } [SerializeField] private Vector3 simulatedEyeOffset = new Vector3(-0.0325f, 0.0f, 0.0f); /// When simulating a VR tool, this will control how much the hit surface normal influences the tool rotation. public float SimulatedNormalInfluence { set { simulatedNormalInfluence = value; } get { return simulatedNormalInfluence; } } [Range(0.0f, 1.0f)] [SerializeField] private float simulatedNormalInfluence = 0.25f; private SimulatedState[] simulatedStates = new SimulatedState[] { new SimulatedState(XRNode.LeftEye), new SimulatedState(XRNode.RightEye), new SimulatedState(XRNode.CenterEye), new SimulatedState(XRNode.Head), new SimulatedState(XRNode.LeftHand), new SimulatedState(XRNode.RightHand) }; private float hitDistance; private Quaternion hitRotation = Quaternion.identity; public bool LeftTrigger; public bool RightTrigger; public bool LeftGrip; public bool RightGrip; public bool PrevLeftTrigger; public bool PrevRightTrigger; public bool PrevLeftGrip; public bool PrevRightGrip; private static List states = new List(); private static List tempTools = new List(); /// This stores all active and enabled instances in the open scenes. public static LinkedList Instances { get { return instances; } } private static LinkedList instances = new LinkedList(); private LinkedListNode instancesNode; public bool IsSimulation { get { return XRSettings.enabled == false; } } #if ENABLE_INPUT_SYSTEM && __INPUTSYSTEM__ public void SetRightTrigger(UnityEngine.InputSystem.InputAction.CallbackContext context) { RightTrigger = context.ReadValueAsButton(); } public void SetLeftTrigger(UnityEngine.InputSystem.InputAction.CallbackContext context) { LeftTrigger = context.ReadValueAsButton(); } public void SetLeftGrip(UnityEngine.InputSystem.InputAction.CallbackContext context) { LeftGrip = context.ReadValueAsButton(); } public void SetRightGrip(UnityEngine.InputSystem.InputAction.CallbackContext context) { RightGrip = context.ReadValueAsButton(); } #endif public void SetRightTrigger(bool value) { RightTrigger = value; } public void SetLeftTrigger(bool value) { LeftTrigger = value; } public void SetLeftGrip(bool value) { LeftGrip = value; } public void SetRightGrip(bool value) { RightGrip = value; } public bool GetTrigger(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftTrigger == true; case XRNode.RightHand: return RightTrigger == true; } return false; } public bool GetTriggerPressed(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftTrigger == true && PrevLeftTrigger == false; case XRNode.RightHand: return RightTrigger == true && PrevRightTrigger == false; } return false; } public bool GetTriggerReleased(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftTrigger == false && PrevLeftTrigger == true; case XRNode.RightHand: return RightTrigger == false && PrevRightTrigger == true; } return false; } public bool GetGrip(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftGrip == true; case XRNode.RightHand: return RightGrip == true; } return false; } public bool GetGripPressed(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftGrip == true && PrevLeftGrip == false; case XRNode.RightHand: return RightGrip == true && PrevRightGrip == false; } return false; } public bool GetGripReleased(XRNode node) { switch (node) { case XRNode.LeftHand: return LeftGrip == false && PrevLeftGrip == true; case XRNode.RightHand: return RightGrip == false && PrevRightGrip == true; } return false; } public XRNode GetClosestNode(Vector3 point, float maximumDistance) { var bestNode = (XRNode)(-1); var bestDistance = maximumDistance; var position = default(Vector3); if (TryGetPosition(XRNode.LeftHand, ref position) == true) { var distance = Vector3.Distance(point, position); if (distance < bestDistance) { bestDistance = distance; bestNode = XRNode.LeftHand; } } if (TryGetPosition(XRNode.RightHand, ref position) == true) { var distance = Vector3.Distance(point, position); if (distance < bestDistance) { bestDistance = distance; bestNode = XRNode.RightHand; } } return bestNode; } private void SetSimulatedState(XRNode node, Vector3 position, Quaternion rotation) { foreach (var simulatedState in simulatedStates) { if (simulatedState.Node == node) { simulatedState.Set = true; simulatedState.Position = position; simulatedState.Rotation = rotation; return; } } } public bool TryGetPosition(XRNode node, ref Vector3 position) { if (IsSimulation == true) { foreach (var simulatedState in simulatedStates) { if (simulatedState.Node == node) { if (simulatedState.Set == false) { return false; } position = simulatedState.Position; return true; } } } else { InputTracking.GetNodeStates(states); foreach (var state in states) { if (state.nodeType == node) { return state.TryGetPosition(out position); } } } return false; } public bool TryGetRotation(XRNode node, ref Quaternion rotation) { if (IsSimulation == true) { foreach (var simulatedState in simulatedStates) { if (simulatedState.Node == node) { if (simulatedState.Set == false) { return false; } rotation = simulatedState.Rotation; return true; } } } else { InputTracking.GetNodeStates(states); foreach (var state in states) { if (state.nodeType == node) { return state.TryGetRotation(out rotation); } } } return false; } protected virtual void OnEnable() { instancesNode = instances.AddLast(this); } protected virtual void OnDisable() { instances.Remove(instancesNode); instancesNode = null; } protected virtual void Start() { //Recenter(); hitDistance = simulatedReach * 0.25f; } protected virtual void Update() { if (IsSimulation == true) { LeftTrigger = CwInput.GetKeyIsHeld(simulatedLeftTrigger); LeftGrip = CwInput.GetKeyIsHeld(simulatedLeftGrip); RightTrigger = CwInput.GetKeyIsHeld(simulatedRightTrigger); RightGrip = CwInput.GetKeyIsHeld(simulatedRightGrip); if (CwInputManager.PointOverGui(CwInput.GetMousePosition()) == true) { LeftTrigger = false; LeftGrip = false; RightTrigger = false; RightGrip = false; } } //if (CwInputManager.IsDown(recenterKey) == true) //{ // Recenter(); //} } protected virtual void LateUpdate() { var camera = CwHelper.GetCamera(null); if (camera != null) { var ray = camera.ScreenPointToRay(CwInput.GetMousePosition()); var hit = default(RaycastHit); var cam = camera.transform.rotation; if (Physics.Raycast(ray, out hit, simulatedReach) == true) { hitDistance = hit.distance; hitRotation = Quaternion.Inverse(cam) * Quaternion.LookRotation(-hit.normal); } var leftHandRot = Quaternion.Slerp(cam, cam * hitRotation, simulatedNormalInfluence) * Quaternion.Euler(simulatedTilt.x, -simulatedTilt.y, simulatedTilt.z); var leftHandPos = ray.GetPoint(hitDistance) + leftHandRot * new Vector3(simulatedOffset.x, simulatedOffset.y, simulatedOffset.z); SetSimulatedState(XRNode.LeftHand, leftHandPos, leftHandRot); var rightHandRot = Quaternion.Slerp(cam, cam * hitRotation, simulatedNormalInfluence) * Quaternion.Euler(simulatedTilt.x, simulatedTilt.y, simulatedTilt.z); var rightHandPos = ray.GetPoint(hitDistance) + rightHandRot * new Vector3(simulatedOffset.x, -simulatedOffset.y, simulatedOffset.z); SetSimulatedState(XRNode.RightHand, rightHandPos, rightHandRot); SetSimulatedState(XRNode.Head, camera.transform.position, camera.transform.rotation); SetSimulatedState(XRNode.CenterEye, camera.transform.position, camera.transform.rotation); SetSimulatedState(XRNode.LeftEye, camera.transform.TransformPoint(simulatedEyeOffset.x, simulatedEyeOffset.y, simulatedEyeOffset.z), camera.transform.rotation); SetSimulatedState(XRNode.RightEye, camera.transform.TransformPoint(-simulatedEyeOffset.x, simulatedEyeOffset.y, simulatedEyeOffset.z), camera.transform.rotation); } for (var i = 0; i <= 8; i++) { UpdateTools((XRNode)i); } PrevLeftTrigger = LeftTrigger; PrevLeftGrip = LeftGrip; PrevRightTrigger = RightTrigger; PrevRightGrip = RightGrip; } private void UpdateTools(XRNode node) { P3dVrTool.GetTools(node, ref tempTools); foreach (var tool in tempTools) { if (tool != null && tool.Node == node) { tool.UpdateGripped(this); } } } //[ContextMenu("Recenter")] //public void Recenter() //{ // if (XRSettings.enabled == true) // { // InputTracking.Recenter(); // } //} } } #if UNITY_EDITOR namespace PaintIn3D { using UnityEditor; using TARGET = P3dVrManager; [CanEditMultipleObjects] [CustomEditor(typeof(TARGET))] public class P3dVrManager_Editor : CwEditor { protected override void OnInspector() { TARGET tgt; TARGET[] tgts; GetTargets(out tgt, out tgts); if (Application.isPlaying == true && XRSettings.enabled == false) { Warning("VR is disabled in your project settings, so simulated fingers will be used. If you have a VR device then you can enable it."); } //Draw("recenterKey", "This key allows you to reset the VR orientation."); Draw("grabDistance", "The default distance in world space a hand must be to grab a tool."); Separator(); Draw("simulatedLeftTrigger", "This key allows you to simulate a left hand VR trigger."); Draw("simulatedLeftGrip", "This key allows you to simulate a left hand VR grip."); Draw("simulatedRightTrigger", "This key allows you to simulate a right hand VR trigger."); Draw("simulatedRightGrip", "This key allows you to simulate a right hand VR grip."); Separator(); Draw("simulatedTilt", "When simulating a VR tool, it will be offset by this Euler rotation."); Draw("simulatedOffset", "When simulating a VR tool, it will be offset by this local position."); Draw("simulatedReach", "When simulating a VR tool, it will be moved away from the hit surface by this."); Draw("simulatedEyeOffset", "The simulated left VR eye will be offset this much."); Draw("simulatedNormalInfluence", "When simulating a VR tool, this will control how much the hit surface normal influences the tool rotation."); Separator(); BeginDisabled(); EditorGUILayout.Toggle("Left Trigger", tgt.LeftTrigger); EditorGUILayout.Toggle("Left Grip", tgt.LeftGrip); EditorGUILayout.Toggle("Right Trigger", tgt.RightTrigger); EditorGUILayout.Toggle("Right Grip", tgt.RightGrip); EndDisabled(); } } } #endif