VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/Interaction/Controllers/XRBaseController.cs
2025-07-29 13:45:50 +03:00

594 lines
23 KiB
C#

using System;
using Unity.XR.CoreUtils;
using UnityEngine.Serialization;
using UnityEngine.XR.Interaction.Toolkit.Inputs.Haptics;
namespace UnityEngine.XR.Interaction.Toolkit
{
/// <summary>
/// Interprets feature values on a tracked input controller device into XR Interaction states, such as Select.
/// Additionally, it applies the current pose value of a tracked device to the transform of the GameObject.
/// </summary>
/// <seealso cref="ActionBasedController"/>
[DefaultExecutionOrder(XRInteractionUpdateOrder.k_Controllers)]
[DisallowMultipleComponent]
[Obsolete("XRBaseController has been deprecated in version 3.0.0. Its functionality has been distributed into different components.")]
public abstract class XRBaseController : MonoBehaviour, IXRHapticImpulseProvider
{
/// <summary>
/// The time within the frame that controller pose will be sampled.
/// </summary>
/// <seealso cref="updateTrackingType"/>
public enum UpdateType
{
/// <summary>
/// Sample input at both update and directly before rendering. For smooth controller pose tracking,
/// we recommend using this value as it will provide the lowest input latency for the device.
/// This is the default value for the UpdateType option.
/// </summary>
UpdateAndBeforeRender,
/// <summary>
/// Only sample input during the update phase of the frame.
/// </summary>
Update,
/// <summary>
/// Only sample input directly before rendering.
/// </summary>
BeforeRender,
/// <summary>
/// Sample input corresponding to <see cref="FixedUpdate"/>.
/// </summary>
Fixed,
}
/// <summary>
/// (Deprecated) Gets the state of the controller.
/// </summary>
/// <param name="controllerState">When this method returns, contains the <see cref="XRControllerState"/> object representing the state of the controller.</param>
/// <returns>Returns <see langword="false"/>.</returns>
/// <seealso cref="currentControllerState"/>
[Obsolete("GetControllerState has been deprecated. Use currentControllerState instead.", true)]
public virtual bool GetControllerState(out XRControllerState controllerState)
{
controllerState = default;
return default;
}
/// <summary>
/// (Deprecated) Sets the state of the controller.
/// </summary>
/// <param name="controllerState">The state of the controller to set.</param>
/// <seealso cref="currentControllerState"/>
[Obsolete("SetControllerState has been deprecated. Use currentControllerState instead.", true)]
public virtual void SetControllerState(XRControllerState controllerState)
{
}
/// <inheritdoc cref="modelParent"/>
/// <remarks><c>modelTransform</c> has been deprecated due to being renamed. Use <see cref="modelParent"/> instead.</remarks>
[Obsolete("modelTransform has been deprecated due to being renamed. Use modelParent instead. (UnityUpgradable) -> modelParent", true)]
public Transform modelTransform
{
get => default;
set => _ = value;
}
/// <summary>
/// (Deprecated) Defines the deadzone values for device-based input when performing translate or rotate anchor actions.
/// </summary>
/// <seealso cref="XRRayInteractor.TranslateAnchor"/>
/// <seealso cref="XRRayInteractor.RotateAnchor"/>
/// <remarks>
/// <c>anchorControlDeadzone</c> has been deprecated. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.
/// </remarks>
[Obsolete("anchorControlDeadzone is obsolete. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.", true)]
public float anchorControlDeadzone
{
get => default;
set => _ = value;
}
/// <summary>
/// (Deprecated) Defines the off-axis deadzone values for device-based input when performing translate or rotate anchor actions.
/// </summary>
/// <seealso cref="Application.onBeforeRender"/>
/// <remarks>
/// <c>anchorControlOffAxisDeadzone</c> has been deprecated. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.
/// </remarks>
[Obsolete("anchorControlOffAxisDeadzone is obsolete. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.", true)]
public float anchorControlOffAxisDeadzone
{
get => default;
set => _ = value;
}
[SerializeField]
UpdateType m_UpdateTrackingType = UpdateType.UpdateAndBeforeRender;
/// <summary>
/// The time within the frame that the controller samples tracking input.
/// </summary>
/// <seealso cref="UpdateType"/>
public UpdateType updateTrackingType
{
get => m_UpdateTrackingType;
set => m_UpdateTrackingType = value;
}
[SerializeField]
bool m_EnableInputTracking = true;
/// <summary>
/// Whether input pose tracking is enabled for this controller.
/// When enabled, Unity reads the current tracking pose input of the controller device each frame.
/// </summary>
/// <remarks>
/// You can disable this in order to drive the controller state manually instead of from reading current inputs,
/// such as when playing back recorded pose inputs.
/// </remarks>
/// <seealso cref="enableInputActions"/>
public bool enableInputTracking
{
get => m_EnableInputTracking;
set => m_EnableInputTracking = value;
}
[SerializeField]
bool m_EnableInputActions = true;
/// <summary>
/// Whether input for XR Interaction events is enabled for this controller.
/// When enabled, Unity reads the current input of the controller device each frame.
/// </summary>
/// <remarks>
/// You can disable this in order to drive the controller state manually instead of from reading current inputs,
/// such as when playing back recorded inputs.
/// </remarks>
/// <seealso cref="enableInputTracking"/>
public bool enableInputActions
{
get => m_EnableInputActions;
set => m_EnableInputActions = value;
}
[SerializeField]
Transform m_ModelPrefab;
/// <summary>
/// The prefab of a controller model to show for this controller that this behavior automatically instantiates.
/// </summary>
/// <remarks>
/// This behavior automatically instantiates an instance of the prefab as a child
/// of <see cref="modelParent"/> upon startup unless <see cref="model"/> is already set,
/// in which case this value is ignored.
/// </remarks>
/// <seealso cref="model"/>
public Transform modelPrefab
{
get => m_ModelPrefab;
set => m_ModelPrefab = value;
}
[SerializeField, FormerlySerializedAs("m_ModelTransform")]
Transform m_ModelParent;
/// <summary>
/// The transform that this behavior uses as the parent for the model prefab when it is instantiated.
/// </summary>
/// <remarks>
/// Automatically instantiated and set in <see cref="Awake"/> if not already set.
/// Setting this will not automatically destroy the previous object.
/// </remarks>
public Transform modelParent
{
get => m_ModelParent;
set
{
m_ModelParent = value;
if (m_Model != null)
m_Model.parent = m_ModelParent;
}
}
[SerializeField]
Transform m_Model;
/// <summary>
/// The instance of the controller model in the scene. You can set this to an existing object instead of using <see cref="modelPrefab"/>.
/// </summary>
/// <remarks>
/// If set, it should reference a child GameObject of this behavior so it will update with the controller pose.
/// </remarks>
public Transform model
{
get => m_Model;
set => m_Model = value;
}
[SerializeField]
bool m_AnimateModel;
/// <summary>
/// Whether to animate the model in response to interaction events. When enabled, the animation trigger will be set for the corresponding
/// animator component on the controller model when a select or deselect interaction events occurs.
/// </summary>
/// <seealso cref="modelSelectTransition"/>
/// <seealso cref="modelDeSelectTransition"/>
public bool animateModel
{
get => m_AnimateModel;
set => m_AnimateModel = value;
}
[SerializeField]
string m_ModelSelectTransition;
/// <summary>
/// The animation trigger name to activate upon selecting.
/// </summary>
/// <seealso cref="Animator.SetTrigger(string)"/>
public string modelSelectTransition
{
get => m_ModelSelectTransition;
set => m_ModelSelectTransition = value;
}
[SerializeField]
string m_ModelDeSelectTransition;
/// <summary>
/// The animation trigger name to activate upon deselecting.
/// </summary>
/// <seealso cref="Animator.SetTrigger(string)"/>
public string modelDeSelectTransition
{
get => m_ModelDeSelectTransition;
set => m_ModelDeSelectTransition = value;
}
bool m_HideControllerModel;
/// <summary>
/// Whether to hide the controller model.
/// </summary>
/// <seealso cref="model"/>
/// <seealso cref="XRBaseInputInteractor.hideControllerOnSelect"/>
public bool hideControllerModel
{
get => m_HideControllerModel;
set
{
m_HideControllerModel = value;
if (m_Model != null)
m_Model.gameObject.SetActive(!m_HideControllerModel);
}
}
InteractionState m_SelectInteractionState;
/// <summary>
/// (Read Only) The current select interaction state.
/// </summary>
public InteractionState selectInteractionState => m_SelectInteractionState;
InteractionState m_ActivateInteractionState;
/// <summary>
/// (Read Only) The current activate interaction state.
/// </summary>
public InteractionState activateInteractionState => m_ActivateInteractionState;
InteractionState m_UIPressInteractionState;
/// <summary>
/// (Read Only) The current UI press interaction state.
/// </summary>
public InteractionState uiPressInteractionState => m_UIPressInteractionState;
Vector2 m_UIScrollValue;
/// <summary>
/// (Read Only) The current UI scroll value.
/// </summary>
public Vector2 uiScrollValue => m_UIScrollValue;
XRControllerState m_ControllerState;
/// <summary>
/// The current state of the controller.
/// </summary>
public XRControllerState currentControllerState
{
get
{
SetupControllerState();
return m_ControllerState;
}
set
{
m_ControllerState = value;
m_CreateControllerState = false;
}
}
bool m_CreateControllerState = true;
#if ANIMATION_MODULE_PRESENT
/// <summary>
/// The <see cref="Animator"/> on <see cref="model"/>.
/// </summary>
Animator m_ModelAnimator;
#endif
bool m_HasWarnedAnimatorMissing;
/// <summary>
/// A boolean value that indicates setup should be performed on Update.
/// </summary>
bool m_PerformSetup = true;
HapticImpulseChannel m_HapticChannel;
HapticImpulseSingleChannelGroup m_HapticChannelGroup;
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected virtual void Awake()
{
// Create empty container transform for the model if none specified.
// This is not strictly necessary to create since this GameObject could be used
// as the parent for the instantiated prefab, but doing so anyway for backwards compatibility.
if (m_ModelParent == null)
{
m_ModelParent = new GameObject($"[{gameObject.name}] Model Parent").transform;
m_ModelParent.SetParent(transform, false);
m_ModelParent.SetLocalPose(Pose.identity);
}
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected virtual void OnEnable()
{
Application.onBeforeRender += OnBeforeRender;
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected virtual void OnDisable()
{
Application.onBeforeRender -= OnBeforeRender;
}
/// <summary>
/// See <see cref="MonoBehaviour"/>.
/// </summary>
protected void Update()
{
UpdateController();
}
void SetupModel()
{
if (m_Model == null)
{
var prefab = GetModelPrefab();
if (prefab != null)
m_Model = Instantiate(prefab, m_ModelParent).transform;
}
if (m_Model != null)
m_Model.gameObject.SetActive(!m_HideControllerModel);
}
void SetupControllerState()
{
if (m_ControllerState == null && m_CreateControllerState)
m_ControllerState = new XRControllerState();
}
/// <summary>
/// Gets the prefab that should be instantiated upon startup.
/// </summary>
/// <returns>Returns the prefab that should be instantiated upon startup.</returns>
protected virtual GameObject GetModelPrefab()
{
return m_ModelPrefab != null ? m_ModelPrefab.gameObject : null;
}
/// <summary>
/// Updates the controller every frame.
/// This is called automatically from <see cref="Update"/>.
/// </summary>
protected virtual void UpdateController()
{
if (m_PerformSetup)
{
SetupModel();
SetupControllerState();
m_PerformSetup = false;
}
if (m_EnableInputTracking &&
(m_UpdateTrackingType == UpdateType.Update ||
m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender))
{
UpdateTrackingInput(m_ControllerState);
}
if (m_EnableInputActions)
{
UpdateInput(m_ControllerState);
UpdateControllerModelAnimation();
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.Dynamic, m_ControllerState);
}
/// <summary>
/// This method is automatically called for "Just Before Render" input updates for VR devices.
/// </summary>
/// <seealso cref="Application.onBeforeRender"/>
protected virtual void OnBeforeRender()
{
if (m_EnableInputTracking &&
(m_UpdateTrackingType == UpdateType.BeforeRender ||
m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender))
{
UpdateTrackingInput(m_ControllerState);
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.OnBeforeRender, m_ControllerState);
}
/// <summary>
/// Corresponds to <see cref="FixedUpdate"/>. It has the frequency of the physics system and is called
/// every fixed framerate frame.
/// </summary>
protected virtual void FixedUpdate()
{
if (m_EnableInputTracking && m_UpdateTrackingType == UpdateType.Fixed)
{
UpdateTrackingInput(m_ControllerState);
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.Fixed, m_ControllerState);
}
/// <summary>
/// Applies the given controller state to this <see cref="XRBaseController"/>.
/// Depending on the update phase, the XR Interaction states may be copied
/// and/or the pose value may be applied to the transform of the GameObject.
/// Unity calls this automatically from <see cref="FixedUpdate"/>, <see cref="OnBeforeRender"/>, and <see cref="UpdateController"/>.
/// </summary>
/// <param name="updatePhase">The update phase during this call.</param>
/// <param name="controllerState">The state of the controller to apply.</param>
protected virtual void ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase updatePhase, XRControllerState controllerState)
{
if (controllerState == null)
return;
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
{
// Sync the controller actions from the interaction state in the controller
m_SelectInteractionState = controllerState.selectInteractionState;
m_ActivateInteractionState = controllerState.activateInteractionState;
m_UIPressInteractionState = controllerState.uiPressInteractionState;
m_UIScrollValue = controllerState.uiScrollValue;
}
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic ||
updatePhase == XRInteractionUpdateOrder.UpdatePhase.OnBeforeRender ||
updatePhase == XRInteractionUpdateOrder.UpdatePhase.Fixed)
{
var hasPosition = (controllerState.inputTrackingState & InputTrackingState.Position) != 0;
var hasRotation = (controllerState.inputTrackingState & InputTrackingState.Rotation) != 0;
if (hasPosition && hasRotation)
transform.SetLocalPose(new Pose(controllerState.position, controllerState.rotation));
else if (hasPosition)
transform.localPosition = controllerState.position;
else if (hasRotation)
transform.localRotation = controllerState.rotation;
}
}
/// <summary>
/// Updates the pose values in the given controller state based on the current tracking input of the controller device.
/// Unity calls this automatically from <see cref="FixedUpdate"/>, <see cref="OnBeforeRender"/>, and <see cref="UpdateController"/> so explicit calls
/// to this function are not required.
/// </summary>
/// <param name="controllerState">The state of the controller.</param>
protected virtual void UpdateTrackingInput(XRControllerState controllerState)
{
}
/// <summary>
/// Updates the XR Interaction states in the given controller state based on the current inputs of the controller device.
/// Unity calls this automatically during <see cref="UpdateController"/> so explicit calls to this function are not required.
/// </summary>
/// <param name="controllerState">The state of the controller.</param>
protected virtual void UpdateInput(XRControllerState controllerState)
{
}
/// <summary>
/// Updates the animation on the model instance (if the model contains an <see cref="Animator"/>).
/// Unity calls this automatically from <see cref="UpdateController"/>.
/// </summary>
/// <seealso cref="animateModel"/>
/// <seealso cref="modelSelectTransition"/>
/// <seealso cref="modelDeSelectTransition"/>
protected virtual void UpdateControllerModelAnimation()
{
#if ANIMATION_MODULE_PRESENT
if (m_AnimateModel && m_Model != null)
{
// Update the Animator reference if necessary
if (m_ModelAnimator == null || m_ModelAnimator.gameObject != m_Model.gameObject)
{
if (!m_Model.TryGetComponent(out m_ModelAnimator))
{
if (!m_HasWarnedAnimatorMissing)
{
Debug.LogWarning("Animate Model is enabled, but there is no Animator component on the model." +
" Unable to activate named triggers to animate the model.", this);
m_HasWarnedAnimatorMissing = true;
}
return;
}
}
if (m_SelectInteractionState.activatedThisFrame)
m_ModelAnimator.SetTrigger(m_ModelSelectTransition);
else if (m_SelectInteractionState.deactivatedThisFrame)
m_ModelAnimator.SetTrigger(m_ModelDeSelectTransition);
}
#endif
}
/// <summary>
/// Play a haptic impulse on the controller if one is available.
/// </summary>
/// <param name="amplitude">Amplitude (from 0.0 to 1.0) to play impulse at.</param>
/// <param name="duration">Duration (in seconds) to play haptic impulse.</param>
/// <returns>Returns <see langword="true"/> if successful. Otherwise, returns <see langword="false"/>.</returns>
public virtual bool SendHapticImpulse(float amplitude, float duration) => false;
/// <inheritdoc />
IXRHapticImpulseChannelGroup IXRHapticImpulseProvider.GetChannelGroup()
{
m_HapticChannel ??= new HapticImpulseChannel(this);
return m_HapticChannelGroup ??= new HapticImpulseSingleChannelGroup(m_HapticChannel);
}
class HapticImpulseChannel : IXRHapticImpulseChannel
{
readonly XRBaseController m_Controller;
bool m_WarningLogged;
public HapticImpulseChannel(XRBaseController controller)
{
m_Controller = controller;
}
/// <inheritdoc />
public bool SendHapticImpulse(float amplitude, float duration, float frequency)
{
if (frequency > 0f && !m_WarningLogged)
{
Debug.LogWarning($"Frequency is not supported when using {m_Controller} as the haptic impulse channel." +
$" You may need to update the {nameof(HapticImpulsePlayer)} to use an Input Action Reference with a Haptic control binding" +
" rather than using an Object Reference to the controller.", m_Controller);
m_WarningLogged = true;
}
return m_Controller.SendHapticImpulse(amplitude, duration);
}
}
}
}