using System;
using UnityEngine.InputSystem.LowLevel;
namespace UnityEngine.InputSystem.XR
{
///
/// The component applies the current pose value of a tracked device
/// to the of the .
/// can track multiple types of devices including XR HMDs, controllers, and remotes.
///
///
/// For and , if an action is directly defined
/// in the , as opposed to a reference to an action externally defined
/// in an , the action will automatically be enabled and disabled by this
/// behavior during and . The enabled state for actions
/// externally defined must be managed externally from this behavior.
///
[Serializable]
[AddComponentMenu("XR/Tracked Pose Driver (Input System)")]
public class TrackedPoseDriver : MonoBehaviour, ISerializationCallbackReceiver
{
///
/// Options for which properties to update.
///
///
public enum TrackingType
{
///
/// Update both rotation and position.
///
RotationAndPosition,
///
/// Update rotation only.
///
RotationOnly,
///
/// Update position only.
///
PositionOnly,
}
///
/// These bit flags correspond with UnityEngine.XR.InputTrackingState
/// but that enum is not used to avoid adding a dependency to the XR module.
/// Only the Position and Rotation flags are used by this class, so velocity and acceleration flags are not duplicated here.
///
[Flags]
enum TrackingStates
{
///
/// Position and rotation are not valid.
///
None,
///
/// Position is valid.
/// See InputTrackingState.Position.
///
Position = 1 << 0,
///
/// Rotation is valid.
/// See InputTrackingState.Rotation.
///
Rotation = 1 << 1,
}
[SerializeField, Tooltip("Which Transform properties to update.")]
TrackingType m_TrackingType;
///
/// The tracking type being used by the Tracked Pose Driver
/// to control which properties to update.
///
///
public TrackingType trackingType
{
get => m_TrackingType;
set => m_TrackingType = value;
}
///
/// Options for which phases of the player loop will update properties.
///
///
///
public enum UpdateType
{
///
/// Update after the Input System has completed an update and right before rendering.
/// This is the recommended and default option to minimize lag for XR tracked devices.
///
///
UpdateAndBeforeRender,
///
/// Update after the Input System has completed an update except right before rendering.
///
///
/// This may be dynamic update, fixed update, or a manual update depending on the Update Mode
/// project setting for Input System.
///
Update,
///
/// Update after the Input System has completed an update right before rendering.
///
///
/// Note that this update mode may not trigger if there are no XR devices added which use before render timing.
///
///
///
BeforeRender,
}
[SerializeField, Tooltip("Updates the Transform properties after these phases of Input System event processing.")]
UpdateType m_UpdateType = UpdateType.UpdateAndBeforeRender;
///
/// The update type being used by the Tracked Pose Driver
/// to control which phases of the player loop will update properties.
///
///
public UpdateType updateType
{
get => m_UpdateType;
set => m_UpdateType = value;
}
[SerializeField, Tooltip("Ignore Tracking State and always treat the input pose as valid.")]
bool m_IgnoreTrackingState;
///
/// Ignore tracking state and always treat the input pose as valid when updating the properties.
/// The recommended value is so the tracking state input is used.
///
///
public bool ignoreTrackingState
{
get => m_IgnoreTrackingState;
set => m_IgnoreTrackingState = value;
}
[SerializeField, Tooltip("The input action to read the position value of a tracked device. Must be a Vector 3 control type.")]
InputActionProperty m_PositionInput;
///
/// The input action to read the position value of a tracked device.
/// Must support reading a value of type .
///
///
public InputActionProperty positionInput
{
get => m_PositionInput;
set
{
if (Application.isPlaying)
UnbindPosition();
m_PositionInput = value;
if (Application.isPlaying && isActiveAndEnabled)
BindPosition();
}
}
[SerializeField, Tooltip("The input action to read the rotation value of a tracked device. Must be a Quaternion control type.")]
InputActionProperty m_RotationInput;
///
/// The input action to read the rotation value of a tracked device.
/// Must support reading a value of type .
///
///
public InputActionProperty rotationInput
{
get => m_RotationInput;
set
{
if (Application.isPlaying)
UnbindRotation();
m_RotationInput = value;
if (Application.isPlaying && isActiveAndEnabled)
BindRotation();
}
}
[SerializeField, Tooltip("The input action to read the tracking state value of a tracked device. Identifies if position and rotation have valid data. Must be an Integer control type.")]
InputActionProperty m_TrackingStateInput;
///
/// The input action to read the tracking state value of a tracked device.
/// Identifies if position and rotation have valid data.
/// Must support reading a value of type .
///
///
/// See [InputTrackingState](xref:UnityEngine.XR.InputTrackingState) enum for values the input action represents.
///
/// -
/// [InputTrackingState.None](xref:UnityEngine.XR.InputTrackingState.None) (0)
/// to indicate neither position nor rotation is valid.
///
/// -
/// [InputTrackingState.Position](xref:UnityEngine.XR.InputTrackingState.Position) (1)
/// to indicate position is valid.
///
/// -
/// [InputTrackingState.Rotation](xref:UnityEngine.XR.InputTrackingState.Rotation) (2)
/// to indicate rotation is valid.
///
/// -
/// [InputTrackingState.Position](xref:UnityEngine.XR.InputTrackingState.Position) | [InputTrackingState.Rotation](xref:UnityEngine.XR.InputTrackingState.Rotation) (3)
/// to indicate position and rotation is valid.
///
///
///
///
public InputActionProperty trackingStateInput
{
get => m_TrackingStateInput;
set
{
if (Application.isPlaying)
UnbindTrackingState();
m_TrackingStateInput = value;
if (Application.isPlaying && isActiveAndEnabled)
BindTrackingState();
}
}
Vector3 m_CurrentPosition = Vector3.zero;
Quaternion m_CurrentRotation = Quaternion.identity;
TrackingStates m_CurrentTrackingState = TrackingStates.Position | TrackingStates.Rotation;
bool m_RotationBound;
bool m_PositionBound;
bool m_TrackingStateBound;
bool m_IsFirstUpdate = true;
void BindActions()
{
BindPosition();
BindRotation();
BindTrackingState();
}
void UnbindActions()
{
UnbindPosition();
UnbindRotation();
UnbindTrackingState();
}
void BindPosition()
{
if (m_PositionBound)
return;
var action = m_PositionInput.action;
if (action == null)
return;
action.performed += OnPositionPerformed;
action.canceled += OnPositionCanceled;
m_PositionBound = true;
if (m_PositionInput.reference == null)
{
action.Rename($"{gameObject.name} - TPD - Position");
action.Enable();
}
}
void BindRotation()
{
if (m_RotationBound)
return;
var action = m_RotationInput.action;
if (action == null)
return;
action.performed += OnRotationPerformed;
action.canceled += OnRotationCanceled;
m_RotationBound = true;
if (m_RotationInput.reference == null)
{
action.Rename($"{gameObject.name} - TPD - Rotation");
action.Enable();
}
}
void BindTrackingState()
{
if (m_TrackingStateBound)
return;
var action = m_TrackingStateInput.action;
if (action == null)
return;
action.performed += OnTrackingStatePerformed;
action.canceled += OnTrackingStateCanceled;
m_TrackingStateBound = true;
if (m_TrackingStateInput.reference == null)
{
action.Rename($"{gameObject.name} - TPD - Tracking State");
action.Enable();
}
}
void UnbindPosition()
{
if (!m_PositionBound)
return;
var action = m_PositionInput.action;
if (action == null)
return;
if (m_PositionInput.reference == null)
action.Disable();
action.performed -= OnPositionPerformed;
action.canceled -= OnPositionCanceled;
m_PositionBound = false;
}
void UnbindRotation()
{
if (!m_RotationBound)
return;
var action = m_RotationInput.action;
if (action == null)
return;
if (m_RotationInput.reference == null)
action.Disable();
action.performed -= OnRotationPerformed;
action.canceled -= OnRotationCanceled;
m_RotationBound = false;
}
void UnbindTrackingState()
{
if (!m_TrackingStateBound)
return;
var action = m_TrackingStateInput.action;
if (action == null)
return;
if (m_TrackingStateInput.reference == null)
action.Disable();
action.performed -= OnTrackingStatePerformed;
action.canceled -= OnTrackingStateCanceled;
m_TrackingStateBound = false;
}
void OnPositionPerformed(InputAction.CallbackContext context)
{
m_CurrentPosition = context.ReadValue();
}
void OnPositionCanceled(InputAction.CallbackContext context)
{
m_CurrentPosition = Vector3.zero;
}
void OnRotationPerformed(InputAction.CallbackContext context)
{
m_CurrentRotation = context.ReadValue();
}
void OnRotationCanceled(InputAction.CallbackContext context)
{
m_CurrentRotation = Quaternion.identity;
}
void OnTrackingStatePerformed(InputAction.CallbackContext context)
{
m_CurrentTrackingState = (TrackingStates)context.ReadValue();
}
void OnTrackingStateCanceled(InputAction.CallbackContext context)
{
m_CurrentTrackingState = TrackingStates.None;
}
///
/// This function is called when the user hits the Reset button in the Inspector's context menu
/// or when adding the component the first time. This function is only called in editor mode.
///
protected void Reset()
{
m_HasMigratedActions = true;
m_PositionInput = new InputActionProperty(new InputAction("Position", expectedControlType: "Vector3"));
m_RotationInput = new InputActionProperty(new InputAction("Rotation", expectedControlType: "Quaternion"));
m_TrackingStateInput = new InputActionProperty(new InputAction("Tracking State", expectedControlType: "Integer"));
}
///
/// This function is called when the script instance is being loaded.
///
protected virtual void Awake()
{
#if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR
if (HasStereoCamera(out var cameraComponent))
{
UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(cameraComponent, true);
}
#endif
}
///
/// This function is called when the object becomes enabled and active.
///
protected void OnEnable()
{
InputSystem.onAfterUpdate += UpdateCallback;
BindActions();
// Read current input values when becoming enabled,
// but wait until after the input update so the input is read at a consistent time
m_IsFirstUpdate = true;
}
///
/// This function is called when the object becomes disabled or inactive.
///
protected void OnDisable()
{
UnbindActions();
InputSystem.onAfterUpdate -= UpdateCallback;
}
///
/// This function is called when the will be destroyed.
///
protected virtual void OnDestroy()
{
#if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR
if (HasStereoCamera(out var cameraComponent))
{
UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(cameraComponent, false);
}
#endif
}
///
/// The callback method called after the Input System has completed an update and processed all pending events.
///
///
protected void UpdateCallback()
{
if (m_IsFirstUpdate)
{
// Update current input values if this is the first update since becoming enabled
// since the performed callbacks may not have been executed
if (m_PositionInput.action != null)
m_CurrentPosition = m_PositionInput.action.ReadValue();
if (m_RotationInput.action != null)
m_CurrentRotation = m_RotationInput.action.ReadValue();
ReadTrackingState();
m_IsFirstUpdate = false;
}
if (InputState.currentUpdateType == InputUpdateType.BeforeRender)
OnBeforeRender();
else
OnUpdate();
}
void ReadTrackingState()
{
var trackingStateAction = m_TrackingStateInput.action;
if (trackingStateAction != null && !trackingStateAction.enabled)
{
// Treat a disabled action as the default None value for the ReadValue call
m_CurrentTrackingState = TrackingStates.None;
return;
}
if (trackingStateAction == null || trackingStateAction.m_BindingsCount == 0)
{
// Treat an Input Action Reference with no reference the same as
// an enabled Input Action with no authored bindings, and allow driving the Transform pose.
m_CurrentTrackingState = TrackingStates.Position | TrackingStates.Rotation;
return;
}
// Grab state.
var actionMap = trackingStateAction.GetOrCreateActionMap();
actionMap.ResolveBindingsIfNecessary();
var state = actionMap.m_State;
// Get list of resolved controls to determine if a device actually has tracking state.
var hasResolvedControl = false;
if (state != null)
{
var actionIndex = trackingStateAction.m_ActionIndexInState;
var totalBindingCount = state.totalBindingCount;
for (var i = 0; i < totalBindingCount; ++i)
{
unsafe
{
ref var bindingState = ref state.bindingStates[i];
if (bindingState.actionIndex != actionIndex)
continue;
if (bindingState.isComposite)
continue;
if (bindingState.controlCount > 0)
{
hasResolvedControl = true;
break;
}
}
}
}
// Retain the current value if there is no resolved binding.
// Since the field initializes to allowing position and rotation,
// this allows for driving the Transform pose always when the device
// doesn't support reporting the tracking state.
if (hasResolvedControl)
m_CurrentTrackingState = (TrackingStates)trackingStateAction.ReadValue();
}
///
/// This method is called after the Input System has completed an update and processed all pending events
/// when the type of update is not .
///
protected virtual void OnUpdate()
{
if (m_UpdateType == UpdateType.Update ||
m_UpdateType == UpdateType.UpdateAndBeforeRender)
{
PerformUpdate();
}
}
///
/// This method is called after the Input System has completed an update and processed all pending events
/// when the type of update is .
///
protected virtual void OnBeforeRender()
{
if (m_UpdateType == UpdateType.BeforeRender ||
m_UpdateType == UpdateType.UpdateAndBeforeRender)
{
PerformUpdate();
}
}
///
/// Updates properties with the current input pose values that have been read,
/// constrained by tracking type and tracking state.
///
///
protected virtual void PerformUpdate()
{
SetLocalTransform(m_CurrentPosition, m_CurrentRotation);
}
///
/// Updates properties, constrained by tracking type and tracking state.
///
/// The new local position to possibly set.
/// The new local rotation to possibly set.
protected virtual void SetLocalTransform(Vector3 newPosition, Quaternion newRotation)
{
var positionValid = m_IgnoreTrackingState || (m_CurrentTrackingState & TrackingStates.Position) != 0;
var rotationValid = m_IgnoreTrackingState || (m_CurrentTrackingState & TrackingStates.Rotation) != 0;
if (rotationValid &&
(m_TrackingType == TrackingType.RotationAndPosition ||
m_TrackingType == TrackingType.RotationOnly))
{
transform.localRotation = newRotation;
}
if (positionValid &&
(m_TrackingType == TrackingType.RotationAndPosition ||
m_TrackingType == TrackingType.PositionOnly))
{
transform.localPosition = newPosition;
}
}
bool HasStereoCamera(out Camera cameraComponent)
{
return TryGetComponent(out cameraComponent) && cameraComponent.stereoEnabled;
}
#region DEPRECATED
// Disable warnings that these fields are never assigned to. They are set during Unity deserialization and migrated.
// ReSharper disable UnassignedField.Local
#pragma warning disable 0649
[Obsolete]
[SerializeField, HideInInspector]
InputAction m_PositionAction;
///
/// (Deprecated) The action to read the position value of a tracked device.
/// Must support reading a value of type .
///
///
public InputAction positionAction
{
get => m_PositionInput.action;
set => positionInput = new InputActionProperty(value);
}
[Obsolete]
[SerializeField, HideInInspector]
InputAction m_RotationAction;
///
/// (Deprecated) The action to read the rotation value of a tracked device.
/// Must support reading a value of type .
///
///
public InputAction rotationAction
{
get => m_RotationInput.action;
set => rotationInput = new InputActionProperty(value);
}
#pragma warning restore 0649
// ReSharper restore UnassignedField.Local
///
/// Stores whether the fields of type have been migrated to fields of type .
///
[SerializeField, HideInInspector]
bool m_HasMigratedActions;
///
void ISerializationCallbackReceiver.OnBeforeSerialize()
{
}
///
void ISerializationCallbackReceiver.OnAfterDeserialize()
{
if (m_HasMigratedActions)
return;
#pragma warning disable 0612
m_PositionInput = new InputActionProperty(m_PositionAction);
m_RotationInput = new InputActionProperty(m_RotationAction);
m_HasMigratedActions = true;
#pragma warning restore 0612
}
#endregion
}
}