// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using UltimateXR.Animation.Interpolation; using UltimateXR.Core; using UltimateXR.Core.Settings; using UltimateXR.Extensions.Unity.Math; using UnityEngine; using UnityEngine.XR; namespace UltimateXR.Devices { /// /// Base class for standard tracking of left+right VR controllers. /// public abstract class UxrControllerTracking : UxrTrackingDevice, IUxrControllerTracking { #region Inspector Properties/Serialized Fields [Header("Device sensor tracking positions:")] [SerializeField] private Transform _leftHandSensor; [SerializeField] private Transform _rightHandSensor; [Header("Update avatar using sensors:")] [SerializeField] private bool _updateAvatarLeftHand = true; [SerializeField] private bool _updateAvatarRightHand = true; [SerializeField] [Range(0, 1)] private float _smoothPosition; [SerializeField] [Range(0, 1)] private float _smoothRotation; #endregion #region Public Types & Data /// /// Gets if the avatar's left hand needs to be updated each time we get new sensor data for it /// public bool UpdateAvatarLeftHand => _updateAvatarLeftHand; /// /// Gets if the avatar's right hand needs to be updated each time we get new sensor data for it /// public bool UpdateAvatarRightHand => _updateAvatarRightHand; #endregion #region Implicit IUxrControllerTracking /// public abstract Type RelatedControllerInputType { get; } /// public virtual bool HeadsetIs6Dof => true; /// public bool HasLeftHandSensorSetup => _leftHandSensor != null; /// public bool HasRightHandSensorSetup => _rightHandSensor != null; /// public Vector3 SensorLeftPos => Avatar.transform.TransformPoint(LocalAvatarLeftHandSensorPos); /// public Vector3 SensorRightPos => Avatar.transform.TransformPoint(LocalAvatarRightHandSensorPos); /// public Quaternion SensorLeftRot => Avatar.transform.rotation * LocalAvatarLeftHandSensorRot; /// public Quaternion SensorRightRot => Avatar.transform.rotation * LocalAvatarRightHandSensorRot; /// public Vector3 SensorLeftHandPos { get { Quaternion leftHandSensorRot = SensorLeftRot; if (!leftHandSensorRot.IsValid()) { return Vector3.zero; } Matrix4x4 mtxLeftHandSensor = Matrix4x4.TRS(SensorLeftPos, leftHandSensorRot.normalized, Vector3.one); return mtxLeftHandSensor.MultiplyPoint(_localSensorLeftHandPos); } } /// public Vector3 SensorRightHandPos { get { Quaternion rightHandSensorRot = SensorRightRot; if (!rightHandSensorRot.IsValid()) { return Vector3.zero; } Matrix4x4 mtxRightHandSensor = Matrix4x4.TRS(SensorRightPos, rightHandSensorRot.normalized, Vector3.one); return mtxRightHandSensor.MultiplyPoint(_localSensorRightHandPos); } } /// public Quaternion SensorLeftHandRot => SensorLeftRot * _localSensorLeftHandRot; /// public Quaternion SensorRightHandRot => SensorRightRot * _localSensorRightHandRot; #endregion #region Unity /// /// Stores if the component was enabled from the beginning to know later if it should be enabled when the device gets /// connected. /// protected override void Awake() { base.Awake(); if (!SetupSensor(_leftHandSensor, Avatar.LeftHandBone, ref _localSensorLeftHandPos, ref _localSensorLeftHandRot)) { if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Warnings) { Debug.LogWarning($"{UxrConstants.DevicesModule} {name}: Avatar Rig has no left wrist setup or left sensor was not specified in the tracking component. Avatar's left hand position may not be updated."); } } if (!SetupSensor(_rightHandSensor, Avatar.RightHandBone, ref _localSensorRightHandPos, ref _localSensorRightHandRot)) { if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Warnings) { Debug.LogWarning($"{UxrConstants.DevicesModule} {name}: Avatar Rig has no right wrist setup or right sensor was not specified in the tracking component. Avatar's right hand position may not be updated."); } } // Start disabled and wait for input controllers to be connected. This way we don't have to check for controller presence in both input components and tracking components. // Controller connected events will be raised either: // a) In Start(), whenever a new scene is loaded and controllers are already enabled. The system will force a Connect event even if the controllers themselves don't send any. // b) At any point during execution enabled = false; UxrControllerInput.GlobalControllerConnected += UxrControllerInput_GlobalControllerConnected; } /// /// Unsubscribes from events /// protected override void OnDestroy() { base.OnDestroy(); UxrControllerInput.GlobalControllerConnected -= UxrControllerInput_GlobalControllerConnected; } /// /// Starts the coroutine that tries to set up the camera /// protected override void OnEnable() { base.OnEnable(); if (!_cameraInitialized) { StartCoroutine(RepeatSetupCameraCoroutine()); } } /// /// Sets the camera at floor level in 6DOF configurations, so that the camera is updated correctly /// protected override void Start() { base.Start(); if (Avatar && HeadsetIs6Dof) { //Avatar.SetCameraAtFloorLevel(); } } #endregion #region Coroutines /// /// Coroutine that tries to set up the camera /// /// Coroutine IEnumerator private IEnumerator RepeatSetupCameraCoroutine() { while (!_cameraInitialized) { _cameraInitialized = SetupCamera(); yield return null; } } #endregion #region Event Handling Methods /// /// Called whenever a controller input device is connected. We will check if it has a related tracking device type /// associated, and if so enable or disable it accordingly. /// /// Derived object that sent the event /// Event args private void UxrControllerInput_GlobalControllerConnected(object sender, UxrDeviceConnectEventArgs e) { if (RelatedControllerInputType != null && sender.GetType() == RelatedControllerInputType) { // Compatible device. if (UxrGlobalSettings.Instance.LogLevelDevices >= UxrLogLevel.Relevant) { Debug.Log($"{UxrConstants.DevicesModule} Found compatible tracking component {GetType()}. Setting enabled to {e.IsConnected}"); } enabled = e.IsConnected; OnDeviceConnected(e); } } #endregion #region Protected Overrides UxrTrackingDevice /// protected override void UpdateAvatar() { base.UpdateAvatar(); Transform wristLeft = Avatar.LeftHandBone; Transform wristRight = Avatar.RightHandBone; if (_updateAvatarLeftHand && wristLeft != null) { wristLeft.SetPositionAndRotation(SensorLeftHandPos, SensorLeftHandRot); } if (_updateAvatarRightHand && wristRight != null) { wristRight.SetPositionAndRotation(SensorRightHandPos, SensorRightHandRot); } } #endregion #region Protected Methods /// /// Updates the sensor data of an XR controller, using smoothing if required. /// /// Which side the sensor belongs to /// Controller position in local tracking space /// Controller rotation in local tracking space protected void UpdateSensor(UxrHandSide side, Vector3 localPos, Quaternion localRot) { if (side == UxrHandSide.Left) { LocalAvatarLeftHandSensorPos = UxrInterpolator.SmoothDampPosition(_lastLeftSensorLocalPos, localPos, _leftSensorInitialized ? _smoothPosition : 0.0f); LocalAvatarLeftHandSensorRot = UxrInterpolator.SmoothDampRotation(_lastLeftSensorLocalRot, localRot, _leftSensorInitialized ? _smoothRotation : 0.0f); _leftSensorInitialized = true; _lastLeftSensorLocalPos = LocalAvatarLeftHandSensorPos; _lastLeftSensorLocalRot = LocalAvatarLeftHandSensorRot; } else if (side == UxrHandSide.Right) { LocalAvatarRightHandSensorPos = UxrInterpolator.SmoothDampPosition(_lastRightSensorLocalPos, localPos, _rightSensorInitialized ? _smoothPosition : 0.0f); LocalAvatarRightHandSensorRot = UxrInterpolator.SmoothDampRotation(_lastRightSensorLocalRot, localRot, _rightSensorInitialized ? _smoothRotation : 0.0f); _rightSensorInitialized = true; _lastRightSensorLocalPos = LocalAvatarRightHandSensorPos; _lastRightSensorLocalRot = LocalAvatarRightHandSensorRot; } } #endregion #region Private Methods /// /// Tries to set up the camera /// /// Boolean telling whether the camera could be set up private bool SetupCamera() { List inputSubsystems = new List(); SubsystemManager.GetInstances(inputSubsystems); if (inputSubsystems.Count == 0) { return false; } bool initialized = true; foreach (XRInputSubsystem subSystem in inputSubsystems) { initialized &= SetupCamera(subSystem); } return initialized; } /// /// Tries to set up the camera of a given /// What it tries to do is set the camera tracking origin to floor /// /// Input subsystem to try to set up /// Boolean telling whether the camera was set up private bool SetupCamera(XRInputSubsystem subsystem) { if (subsystem == null) { return false; } TrackingOriginModeFlags supportedModes = subsystem.GetSupportedTrackingOriginModes(); TrackingOriginModeFlags requestedMode = TrackingOriginModeFlags.Floor; // We need to check for Unknown because we may not be in a state where we can read this data yet. if ((supportedModes & (TrackingOriginModeFlags.Floor | TrackingOriginModeFlags.Unknown)) == 0) { return false; } return subsystem.TrySetTrackingOriginMode(requestedMode); } /// /// The goal of each left and right sensors is to position the visual hand in the correct position. This method /// computes the initial bone position and rotation in local sensor coordinates in order to be able to reposition the /// hand whenever the sensors get updated. /// /// The given sensor's transform /// The bone transform this sensor should position and orientate /// Gets the bone position in local coordinates of the sensor transform /// Gets the bone rotation in local coordinates of the sensor transform /// private bool SetupSensor(Transform sensorTransform, Transform boneTransform, ref Vector3 localBonePos, ref Quaternion localBoneRot) { if (sensorTransform != null) { if (boneTransform != null) { localBonePos = sensorTransform.InverseTransformPoint(boneTransform.position); localBoneRot = Quaternion.Inverse(sensorTransform.rotation) * boneTransform.rotation; return true; } return false; } return false; } #endregion #region Protected Types & Data /// /// Gets the left hand sensor position in local avatar coordinates /// protected Vector3 LocalAvatarLeftHandSensorPos { get; private set; } /// /// Gets the left hand sensor rotation in local avatar coordinates /// protected Quaternion LocalAvatarLeftHandSensorRot { get; private set; } /// /// Gets the right hand sensor position in local avatar coordinates /// protected Vector3 LocalAvatarRightHandSensorPos { get; private set; } /// /// Gets the right hand sensor rotation in local avatar coordinates /// protected Quaternion LocalAvatarRightHandSensorRot { get; private set; } #endregion #region Private Types & Data private bool _cameraInitialized; private Vector3 _localSensorLeftHandPos; private Vector3 _localSensorRightHandPos; private Quaternion _localSensorLeftHandRot; private Quaternion _localSensorRightHandRot; private bool _leftSensorInitialized; private bool _rightSensorInitialized; private Vector3 _lastLeftSensorLocalPos; private Vector3 _lastRightSensorLocalPos; private Quaternion _lastLeftSensorLocalRot; private Quaternion _lastRightSensorLocalRot; #endregion } }