1536 lines
68 KiB
C#
1536 lines
68 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using Unity.Profiling;
|
|
using Unity.XR.CoreUtils;
|
|
using Unity.XR.CoreUtils.Bindings.Variables;
|
|
using Unity.XR.CoreUtils.Collections;
|
|
using UnityEngine.Scripting.APIUpdating;
|
|
using UnityEngine.XR.Interaction.Toolkit.Filtering;
|
|
using UnityEngine.XR.Interaction.Toolkit.Gaze;
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactables.Visuals;
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactors;
|
|
using UnityEngine.XR.Interaction.Toolkit.Interactors.Visuals;
|
|
using UnityEngine.XR.Interaction.Toolkit.Utilities;
|
|
using UnityEngine.XR.Interaction.Toolkit.Utilities.Internal;
|
|
|
|
namespace UnityEngine.XR.Interaction.Toolkit.Interactables
|
|
{
|
|
/// <summary>
|
|
/// Abstract base class from which all interactable behaviors derive.
|
|
/// This class hooks into the interaction system (via <see cref="XRInteractionManager"/>) and provides base virtual methods for handling
|
|
/// hover, selection, and focus.
|
|
/// </summary>
|
|
[MovedFrom("UnityEngine.XR.Interaction.Toolkit")]
|
|
[SelectionBase]
|
|
[DefaultExecutionOrder(XRInteractionUpdateOrder.k_Interactables)]
|
|
public abstract partial class XRBaseInteractable : MonoBehaviour, IXRActivateInteractable, IXRHoverInteractable, IXRSelectInteractable, IXRFocusInteractable, IXRInteractionStrengthInteractable, IXROverridesGazeAutoSelect
|
|
{
|
|
const float k_InteractionStrengthHover = 0f;
|
|
const float k_InteractionStrengthSelect = 1f;
|
|
|
|
/// <summary>
|
|
/// Options for how to process and perform movement of an Interactable.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Each method of movement has tradeoffs, and different values may be more appropriate
|
|
/// for each type of Interactable object in a project.
|
|
/// </remarks>
|
|
/// <seealso cref="XRGrabInteractable.movementType"/>
|
|
public enum MovementType
|
|
{
|
|
#if UNITY_2023_3_OR_NEWER // Change between Rigidbody.linearVelocity and Rigidbody.velocity
|
|
/// <summary>
|
|
/// Move the Interactable object by setting the velocity and angular velocity of the Rigidbody.
|
|
/// Use this if you don't want the object to be able to move through other Colliders without a Rigidbody
|
|
/// as it follows the Interactor, however with the tradeoff that it can appear to lag behind
|
|
/// and not move as smoothly as <see cref="Instantaneous"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unity sets the velocity values during the FixedUpdate function. This Interactable will move at the
|
|
/// framerate-independent interval of the Physics update, which may be slower than the Update rate.
|
|
/// If the Rigidbody is not set to use interpolation or extrapolation, as the Interactable
|
|
/// follows the Interactor, it may not visually update position each frame and be a slight distance
|
|
/// behind the Interactor or controller due to the difference between the Physics update rate
|
|
/// and the render update rate.
|
|
/// </remarks>
|
|
/// <seealso cref="Rigidbody.linearVelocity"/>
|
|
/// <seealso cref="Rigidbody.angularVelocity"/>
|
|
#else
|
|
/// <summary>
|
|
/// Move the Interactable object by setting the velocity and angular velocity of the Rigidbody.
|
|
/// Use this if you don't want the object to be able to move through other Colliders without a Rigidbody
|
|
/// as it follows the Interactor, however with the tradeoff that it can appear to lag behind
|
|
/// and not move as smoothly as <see cref="Instantaneous"/>.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unity sets the velocity values during the FixedUpdate function. This Interactable will move at the
|
|
/// framerate-independent interval of the Physics update, which may be slower than the Update rate.
|
|
/// If the Rigidbody is not set to use interpolation or extrapolation, as the Interactable
|
|
/// follows the Interactor, it may not visually update position each frame and be a slight distance
|
|
/// behind the Interactor or controller due to the difference between the Physics update rate
|
|
/// and the render update rate.
|
|
/// </remarks>
|
|
/// <seealso cref="Rigidbody.velocity"/>
|
|
/// <seealso cref="Rigidbody.angularVelocity"/>
|
|
#endif
|
|
VelocityTracking,
|
|
|
|
/// <summary>
|
|
/// Move the Interactable object by moving the kinematic Rigidbody towards the target position and orientation.
|
|
/// Use this if you want to keep the visual representation synchronized to match its Physics state,
|
|
/// and if you want to allow the object to be able to move through other Colliders without a Rigidbody
|
|
/// as it follows the Interactor.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unity will call the movement methods during the FixedUpdate function. This Interactable will move at the
|
|
/// framerate-independent interval of the Physics update, which may be slower than the Update rate.
|
|
/// If the Rigidbody is not set to use interpolation or extrapolation, as the Interactable
|
|
/// follows the Interactor, it may not visually update position each frame and be a slight distance
|
|
/// behind the Interactor or controller due to the difference between the Physics update rate
|
|
/// and the render update rate. Collisions will be more accurate as compared to <see cref="Instantaneous"/>
|
|
/// since with this method, the Rigidbody will be moved by settings its internal velocity rather than
|
|
/// instantly teleporting to match the Transform pose.
|
|
/// </remarks>
|
|
/// <seealso cref="Rigidbody.MovePosition"/>
|
|
/// <seealso cref="Rigidbody.MoveRotation"/>
|
|
Kinematic,
|
|
|
|
/// <summary>
|
|
/// Move the Interactable object by setting the position and rotation of the Transform every frame.
|
|
/// Use this if you want the visual representation to be updated each frame, minimizing latency,
|
|
/// however with the tradeoff that it will be able to move through other Colliders without a Rigidbody
|
|
/// as it follows the Interactor.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unity will set the Transform values each frame, which may be faster than the framerate-independent
|
|
/// interval of the Physics update. The Collider of the Interactable object may be a slight distance
|
|
/// behind the visual as it follows the Interactor due to the difference between the Physics update rate
|
|
/// and the render update rate. Collisions will not be computed as accurately as <see cref="Kinematic"/>
|
|
/// since with this method, the Rigidbody will be forced to instantly teleport poses to match the Transform pose
|
|
/// rather than moving the Rigidbody through setting its internal velocity.
|
|
/// </remarks>
|
|
/// <seealso cref="Transform.position"/>
|
|
/// <seealso cref="Transform.rotation"/>
|
|
Instantaneous,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Options for how to calculate an Interactable distance to a location in world space.
|
|
/// </summary>
|
|
/// <seealso cref="distanceCalculationMode"/>
|
|
public enum DistanceCalculationMode
|
|
{
|
|
/// <summary>
|
|
/// Calculates the distance using the Interactable's transform position.
|
|
/// This option has low performance cost, but it may have low distance calculation accuracy for some objects.
|
|
/// </summary>
|
|
TransformPosition,
|
|
|
|
/// <summary>
|
|
/// Calculates the distance using the Interactable's colliders list using the shortest distance to each.
|
|
/// This option has moderate performance cost and should have moderate distance calculation accuracy for most objects.
|
|
/// </summary>
|
|
/// <seealso cref="XRInteractableUtility.TryGetClosestCollider"/>
|
|
ColliderPosition,
|
|
|
|
/// <summary>
|
|
/// Calculates the distance using the Interactable's colliders list using the shortest distance to the closest point of each
|
|
/// (either on the surface or inside the Collider).
|
|
/// This option has high performance cost but high distance calculation accuracy.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The Interactable's colliders can only be of type <see cref="BoxCollider"/>, <see cref="SphereCollider"/>, <see cref="CapsuleCollider"/>, or convex <see cref="MeshCollider"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="Collider.ClosestPoint"/>
|
|
/// <seealso cref="XRInteractableUtility.TryGetClosestPointOnCollider"/>
|
|
ColliderVolume,
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public event Action<InteractableRegisteredEventArgs> registered;
|
|
|
|
/// <inheritdoc />
|
|
public event Action<InteractableUnregisteredEventArgs> unregistered;
|
|
|
|
/// <summary>
|
|
/// Overriding callback of this object's distance calculation.
|
|
/// Use this to change the calculation performed in <see cref="GetDistance"/> without needing to create a derived class.
|
|
/// <br />
|
|
/// When a callback is assigned to this property, the <see cref="GetDistance"/> execution calls it to perform the
|
|
/// distance calculation instead of using its default calculation (specified by <see cref="distanceCalculationMode"/> in this base class).
|
|
/// Assign <see langword="null"/> to this property to restore the default calculation.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The assigned callback will be invoked to calculate and return the distance information of the point on this
|
|
/// Interactable (the first parameter) closest to the given location (the second parameter).
|
|
/// The given location and returned distance information are in world space.
|
|
/// </remarks>
|
|
/// <seealso cref="GetDistance"/>
|
|
/// <seealso cref="DistanceInfo"/>
|
|
public Func<IXRInteractable, Vector3, DistanceInfo> getDistanceOverride { get; set; }
|
|
|
|
[SerializeField]
|
|
XRInteractionManager m_InteractionManager;
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> that this Interactable will communicate with (will find one if <see langword="null"/>).
|
|
/// </summary>
|
|
public XRInteractionManager interactionManager
|
|
{
|
|
get => m_InteractionManager;
|
|
set
|
|
{
|
|
m_InteractionManager = value;
|
|
if (Application.isPlaying && isActiveAndEnabled)
|
|
RegisterWithInteractionManager();
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
#pragma warning disable IDE0044 // Add readonly modifier -- readonly fields cannot be serialized by Unity
|
|
List<Collider> m_Colliders = new List<Collider>();
|
|
#pragma warning restore IDE0044
|
|
|
|
/// <summary>
|
|
/// (Read Only) Colliders to use for interaction with this Interactable (if empty, will use any child Colliders).
|
|
/// </summary>
|
|
public List<Collider> colliders => m_Colliders;
|
|
|
|
[SerializeField]
|
|
InteractionLayerMask m_InteractionLayers = 1;
|
|
|
|
/// <summary>
|
|
/// Allows interaction with Interactors whose Interaction Layer Mask overlaps with any Layer in this Interaction Layer Mask.
|
|
/// </summary>
|
|
/// <seealso cref="IXRInteractor.interactionLayers"/>
|
|
/// <seealso cref="IsHoverableBy(IXRHoverInteractor)"/>
|
|
/// <seealso cref="IsSelectableBy(IXRSelectInteractor)"/>
|
|
/// <inheritdoc />
|
|
public InteractionLayerMask interactionLayers
|
|
{
|
|
get => m_InteractionLayers;
|
|
set => m_InteractionLayers = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
DistanceCalculationMode m_DistanceCalculationMode = DistanceCalculationMode.ColliderPosition;
|
|
|
|
/// <summary>
|
|
/// Specifies how this Interactable calculates its distance to a location, either using its Transform position, Collider
|
|
/// position or Collider volume.
|
|
/// </summary>
|
|
/// <seealso cref="GetDistance"/>
|
|
/// <seealso cref="colliders"/>
|
|
/// <seealso cref="DistanceCalculationMode"/>
|
|
public DistanceCalculationMode distanceCalculationMode
|
|
{
|
|
get => m_DistanceCalculationMode;
|
|
set => m_DistanceCalculationMode = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
InteractableSelectMode m_SelectMode = InteractableSelectMode.Single;
|
|
|
|
/// <inheritdoc />
|
|
public InteractableSelectMode selectMode
|
|
{
|
|
get => m_SelectMode;
|
|
set => m_SelectMode = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
InteractableFocusMode m_FocusMode = InteractableFocusMode.Single;
|
|
|
|
/// <inheritdoc />
|
|
public InteractableFocusMode focusMode
|
|
{
|
|
get => m_FocusMode;
|
|
set => m_FocusMode = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
GameObject m_CustomReticle;
|
|
|
|
/// <summary>
|
|
/// The reticle that appears at the end of the line when valid.
|
|
/// </summary>
|
|
public GameObject customReticle
|
|
{
|
|
get => m_CustomReticle;
|
|
set => m_CustomReticle = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_AllowGazeInteraction;
|
|
/// <summary>
|
|
/// Enables interaction with <see cref="XRGazeInteractor"/>.
|
|
/// </summary>
|
|
public bool allowGazeInteraction
|
|
{
|
|
get => m_AllowGazeInteraction;
|
|
set => m_AllowGazeInteraction = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_AllowGazeSelect;
|
|
/// <summary>
|
|
/// Enables <see cref="XRGazeInteractor"/> to select this <see cref="XRBaseInteractable"/>.
|
|
/// </summary>
|
|
/// <seealso cref="XRRayInteractor.hoverToSelect"/>
|
|
public bool allowGazeSelect
|
|
{
|
|
get => m_AllowGazeSelect;
|
|
set => m_AllowGazeSelect = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_OverrideGazeTimeToSelect;
|
|
/// <inheritdoc />
|
|
public bool overrideGazeTimeToSelect
|
|
{
|
|
get => m_OverrideGazeTimeToSelect;
|
|
set => m_OverrideGazeTimeToSelect = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
float m_GazeTimeToSelect = 0.5f;
|
|
/// <inheritdoc />
|
|
public float gazeTimeToSelect
|
|
{
|
|
get => m_GazeTimeToSelect;
|
|
set => m_GazeTimeToSelect = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_OverrideTimeToAutoDeselectGaze;
|
|
/// <inheritdoc />
|
|
public bool overrideTimeToAutoDeselectGaze
|
|
{
|
|
get => m_OverrideTimeToAutoDeselectGaze;
|
|
set => m_OverrideTimeToAutoDeselectGaze = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
float m_TimeToAutoDeselectGaze = 3f;
|
|
/// <inheritdoc />
|
|
public float timeToAutoDeselectGaze
|
|
{
|
|
get => m_TimeToAutoDeselectGaze;
|
|
set => m_TimeToAutoDeselectGaze = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_AllowGazeAssistance;
|
|
/// <summary>
|
|
/// Enables gaze assistance with this interactable.
|
|
/// </summary>
|
|
public bool allowGazeAssistance
|
|
{
|
|
get => m_AllowGazeAssistance;
|
|
set => m_AllowGazeAssistance = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
HoverEnterEvent m_FirstHoverEntered = new HoverEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public HoverEnterEvent firstHoverEntered
|
|
{
|
|
get => m_FirstHoverEntered;
|
|
set => m_FirstHoverEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
HoverExitEvent m_LastHoverExited = new HoverExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public HoverExitEvent lastHoverExited
|
|
{
|
|
get => m_LastHoverExited;
|
|
set => m_LastHoverExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
HoverEnterEvent m_HoverEntered = new HoverEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public HoverEnterEvent hoverEntered
|
|
{
|
|
get => m_HoverEntered;
|
|
set => m_HoverEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
HoverExitEvent m_HoverExited = new HoverExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public HoverExitEvent hoverExited
|
|
{
|
|
get => m_HoverExited;
|
|
set => m_HoverExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
SelectEnterEvent m_FirstSelectEntered = new SelectEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public SelectEnterEvent firstSelectEntered
|
|
{
|
|
get => m_FirstSelectEntered;
|
|
set => m_FirstSelectEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
SelectExitEvent m_LastSelectExited = new SelectExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public SelectExitEvent lastSelectExited
|
|
{
|
|
get => m_LastSelectExited;
|
|
set => m_LastSelectExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
SelectEnterEvent m_SelectEntered = new SelectEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public SelectEnterEvent selectEntered
|
|
{
|
|
get => m_SelectEntered;
|
|
set => m_SelectEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
SelectExitEvent m_SelectExited = new SelectExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public SelectExitEvent selectExited
|
|
{
|
|
get => m_SelectExited;
|
|
set => m_SelectExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
FocusEnterEvent m_FirstFocusEntered = new FocusEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public FocusEnterEvent firstFocusEntered
|
|
{
|
|
get => m_FirstFocusEntered;
|
|
set => m_FirstFocusEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
FocusExitEvent m_LastFocusExited = new FocusExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public FocusExitEvent lastFocusExited
|
|
{
|
|
get => m_LastFocusExited;
|
|
set => m_LastFocusExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
FocusEnterEvent m_FocusEntered = new FocusEnterEvent();
|
|
|
|
/// <inheritdoc />
|
|
public FocusEnterEvent focusEntered
|
|
{
|
|
get => m_FocusEntered;
|
|
set => m_FocusEntered = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
FocusExitEvent m_FocusExited = new FocusExitEvent();
|
|
|
|
/// <inheritdoc />
|
|
public FocusExitEvent focusExited
|
|
{
|
|
get => m_FocusExited;
|
|
set => m_FocusExited = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
ActivateEvent m_Activated = new ActivateEvent();
|
|
|
|
/// <inheritdoc />
|
|
public ActivateEvent activated
|
|
{
|
|
get => m_Activated;
|
|
set => m_Activated = value;
|
|
}
|
|
|
|
[SerializeField]
|
|
DeactivateEvent m_Deactivated = new DeactivateEvent();
|
|
|
|
/// <inheritdoc />
|
|
public DeactivateEvent deactivated
|
|
{
|
|
get => m_Deactivated;
|
|
set => m_Deactivated = value;
|
|
}
|
|
|
|
readonly HashSetList<IXRHoverInteractor> m_InteractorsHovering = new HashSetList<IXRHoverInteractor>();
|
|
|
|
/// <inheritdoc />
|
|
public List<IXRHoverInteractor> interactorsHovering => (List<IXRHoverInteractor>)m_InteractorsHovering.AsList();
|
|
|
|
/// <inheritdoc />
|
|
public bool isHovered { get; private set; }
|
|
|
|
readonly HashSetList<IXRSelectInteractor> m_InteractorsSelecting = new HashSetList<IXRSelectInteractor>();
|
|
|
|
/// <inheritdoc />
|
|
public List<IXRSelectInteractor> interactorsSelecting => (List<IXRSelectInteractor>)m_InteractorsSelecting.AsList();
|
|
|
|
/// <inheritdoc />
|
|
public IXRSelectInteractor firstInteractorSelecting { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
public bool isSelected { get; private set; }
|
|
|
|
readonly HashSetList<IXRInteractionGroup> m_InteractionGroupsFocusing = new HashSetList<IXRInteractionGroup>();
|
|
|
|
/// <inheritdoc />
|
|
public List<IXRInteractionGroup> interactionGroupsFocusing => (List<IXRInteractionGroup>)m_InteractionGroupsFocusing.AsList();
|
|
|
|
/// <inheritdoc />
|
|
public IXRInteractionGroup firstInteractionGroupFocusing { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
public bool isFocused { get; private set; }
|
|
|
|
/// <inheritdoc />
|
|
public bool canFocus => m_FocusMode != InteractableFocusMode.None;
|
|
|
|
[SerializeField]
|
|
[RequireInterface(typeof(IXRHoverFilter))]
|
|
List<Object> m_StartingHoverFilters = new List<Object>();
|
|
|
|
/// <summary>
|
|
/// The hover filters that this object uses to automatically populate the <see cref="hoverFilters"/> List at
|
|
/// startup (optional, may be empty).
|
|
/// All objects in this list should implement the <see cref="IXRHoverFilter"/> interface.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To access and modify the hover filters used after startup, the <see cref="hoverFilters"/> List should
|
|
/// be used instead.
|
|
/// </remarks>
|
|
/// <seealso cref="hoverFilters"/>
|
|
public List<Object> startingHoverFilters
|
|
{
|
|
get => m_StartingHoverFilters;
|
|
set => m_StartingHoverFilters = value;
|
|
}
|
|
|
|
readonly ExposedRegistrationList<IXRHoverFilter> m_HoverFilters = new ExposedRegistrationList<IXRHoverFilter> { bufferChanges = false };
|
|
|
|
/// <summary>
|
|
/// The list of hover filters in this object.
|
|
/// Used as additional hover validations for this Interactable.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// While processing hover filters, all changes to this list don't have an immediate effect. These changes are
|
|
/// buffered and applied when the processing is finished.
|
|
/// Calling <see cref="IXRFilterList{T}.MoveTo"/> in this list will throw an exception when this list is being processed.
|
|
/// </remarks>
|
|
/// <seealso cref="ProcessHoverFilters"/>
|
|
public IXRFilterList<IXRHoverFilter> hoverFilters => m_HoverFilters;
|
|
|
|
[SerializeField]
|
|
[RequireInterface(typeof(IXRSelectFilter))]
|
|
List<Object> m_StartingSelectFilters = new List<Object>();
|
|
|
|
/// <summary>
|
|
/// The select filters that this object uses to automatically populate the <see cref="selectFilters"/> List at
|
|
/// startup (optional, may be empty).
|
|
/// All objects in this list should implement the <see cref="IXRSelectFilter"/> interface.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To access and modify the select filters used after startup, the <see cref="selectFilters"/> List should
|
|
/// be used instead.
|
|
/// </remarks>
|
|
/// <seealso cref="selectFilters"/>
|
|
public List<Object> startingSelectFilters
|
|
{
|
|
get => m_StartingSelectFilters;
|
|
set => m_StartingSelectFilters = value;
|
|
}
|
|
|
|
readonly ExposedRegistrationList<IXRSelectFilter> m_SelectFilters = new ExposedRegistrationList<IXRSelectFilter> { bufferChanges = false };
|
|
|
|
/// <summary>
|
|
/// The list of select filters in this object.
|
|
/// Used as additional select validations for this Interactable.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// While processing select filters, all changes to this list don't have an immediate effect. These changes are
|
|
/// buffered and applied when the processing is finished.
|
|
/// Calling <see cref="IXRFilterList{T}.MoveTo"/> in this list will throw an exception when this list is being processed.
|
|
/// </remarks>
|
|
/// <seealso cref="ProcessSelectFilters"/>
|
|
public IXRFilterList<IXRSelectFilter> selectFilters => m_SelectFilters;
|
|
|
|
[SerializeField]
|
|
[RequireInterface(typeof(IXRInteractionStrengthFilter))]
|
|
List<Object> m_StartingInteractionStrengthFilters = new List<Object>();
|
|
|
|
/// <summary>
|
|
/// The interaction strength filters that this object uses to automatically populate the <see cref="interactionStrengthFilters"/> List at
|
|
/// startup (optional, may be empty).
|
|
/// All objects in this list should implement the <see cref="IXRInteractionStrengthFilter"/> interface.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To access and modify the select filters used after startup, the <see cref="interactionStrengthFilters"/> List should
|
|
/// be used instead.
|
|
/// </remarks>
|
|
/// <seealso cref="interactionStrengthFilters"/>
|
|
public List<Object> startingInteractionStrengthFilters
|
|
{
|
|
get => m_StartingInteractionStrengthFilters;
|
|
set => m_StartingInteractionStrengthFilters = value;
|
|
}
|
|
|
|
readonly ExposedRegistrationList<IXRInteractionStrengthFilter> m_InteractionStrengthFilters = new ExposedRegistrationList<IXRInteractionStrengthFilter> { bufferChanges = false };
|
|
|
|
/// <summary>
|
|
/// The list of interaction strength filters in this object.
|
|
/// Used to modify the default interaction strength of an Interactor relative to this Interactable.
|
|
/// This is useful for interactables that can be poked to report the depth of the poke interactor as a percentage
|
|
/// while the poke interactor is hovering over this object.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// While processing interaction strength filters, all changes to this list don't have an immediate effect. These changes are
|
|
/// buffered and applied when the processing is finished.
|
|
/// Calling <see cref="IXRFilterList{T}.MoveTo"/> in this list will throw an exception when this list is being processed.
|
|
/// </remarks>
|
|
/// <seealso cref="ProcessInteractionStrengthFilters"/>
|
|
public IXRFilterList<IXRInteractionStrengthFilter> interactionStrengthFilters => m_InteractionStrengthFilters;
|
|
|
|
readonly BindableVariable<float> m_LargestInteractionStrength = new BindableVariable<float>();
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyBindableVariable<float> largestInteractionStrength => m_LargestInteractionStrength;
|
|
|
|
bool m_ClearedLargestInteractionStrength;
|
|
|
|
readonly Dictionary<IXRSelectInteractor, Pose> m_AttachPoseOnSelect = new Dictionary<IXRSelectInteractor, Pose>();
|
|
|
|
readonly Dictionary<IXRSelectInteractor, Pose> m_LocalAttachPoseOnSelect = new Dictionary<IXRSelectInteractor, Pose>();
|
|
|
|
readonly Dictionary<IXRInteractor, GameObject> m_ReticleCache = new Dictionary<IXRInteractor, GameObject>();
|
|
|
|
/// <summary>
|
|
/// The set of hovered and/or selected interactors that supports returning a variable select input value,
|
|
/// which is used as the pre-filtered interaction strength.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Uses <see cref="XRBaseInputInteractor"/> as the type to get the select input value to use as the pre-filtered
|
|
/// interaction strength.
|
|
/// </remarks>
|
|
readonly HashSetList<XRBaseInputInteractor> m_VariableSelectInteractors = new HashSetList<XRBaseInputInteractor>();
|
|
|
|
readonly Dictionary<IXRInteractor, float> m_InteractionStrengths = new Dictionary<IXRInteractor, float>();
|
|
|
|
XRInteractionManager m_RegisteredInteractionManager;
|
|
|
|
static readonly ProfilerMarker s_ProcessInteractionStrengthMarker = new ProfilerMarker("XRI.ProcessInteractionStrength.Interactables");
|
|
static readonly ProfilerMarker s_ProcessInteractionStrengthEventMarker = new ProfilerMarker("XRI.ProcessInteractionStrength.InteractablesEvent");
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
[Conditional("UNITY_EDITOR")]
|
|
protected virtual void Reset()
|
|
{
|
|
#if UNITY_EDITOR
|
|
// Don't need to do anything; method kept for backwards compatibility.
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected virtual void Awake()
|
|
{
|
|
// If no colliders were set, populate with children colliders
|
|
if (m_Colliders.Count == 0)
|
|
{
|
|
GetComponentsInChildren(m_Colliders);
|
|
// Skip any that are trigger colliders since these are usually associated with snap volumes.
|
|
// If a user wants to use a trigger collider, they must serialize the reference manually.
|
|
m_Colliders.RemoveAll(col => col.isTrigger);
|
|
}
|
|
|
|
// Setup the starting filters
|
|
m_HoverFilters.RegisterReferences(m_StartingHoverFilters, this);
|
|
m_SelectFilters.RegisterReferences(m_StartingSelectFilters, this);
|
|
m_InteractionStrengthFilters.RegisterReferences(m_StartingInteractionStrengthFilters, this);
|
|
|
|
// Setup Interaction Manager
|
|
FindCreateInteractionManager();
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected virtual void OnEnable()
|
|
{
|
|
FindCreateInteractionManager();
|
|
RegisterWithInteractionManager();
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected virtual void OnDisable()
|
|
{
|
|
UnregisterWithInteractionManager();
|
|
}
|
|
|
|
/// <summary>
|
|
/// See <see cref="MonoBehaviour"/>.
|
|
/// </summary>
|
|
protected virtual void OnDestroy()
|
|
{
|
|
// Don't need to do anything; method kept for backwards compatibility.
|
|
}
|
|
|
|
void FindCreateInteractionManager()
|
|
{
|
|
if (m_InteractionManager != null)
|
|
return;
|
|
|
|
m_InteractionManager = ComponentLocatorUtility<XRInteractionManager>.FindOrCreateComponent();
|
|
}
|
|
|
|
void RegisterWithInteractionManager()
|
|
{
|
|
if (m_RegisteredInteractionManager == m_InteractionManager)
|
|
return;
|
|
|
|
UnregisterWithInteractionManager();
|
|
|
|
if (m_InteractionManager != null)
|
|
{
|
|
m_InteractionManager.RegisterInteractable((IXRInteractable)this);
|
|
m_RegisteredInteractionManager = m_InteractionManager;
|
|
}
|
|
}
|
|
|
|
void UnregisterWithInteractionManager()
|
|
{
|
|
if (m_RegisteredInteractionManager != null)
|
|
{
|
|
m_RegisteredInteractionManager.UnregisterInteractable((IXRInteractable)this);
|
|
m_RegisteredInteractionManager = null;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual Transform GetAttachTransform(IXRInteractor interactor)
|
|
{
|
|
return transform;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Pose GetAttachPoseOnSelect(IXRSelectInteractor interactor)
|
|
{
|
|
return m_AttachPoseOnSelect.TryGetValue(interactor, out var pose) ? pose : Pose.identity;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Pose GetLocalAttachPoseOnSelect(IXRSelectInteractor interactor)
|
|
{
|
|
return m_LocalAttachPoseOnSelect.TryGetValue(interactor, out var pose) ? pose : Pose.identity;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
/// <remarks>
|
|
/// This method calls the <see cref="GetDistance"/> method to perform the distance calculation.
|
|
/// </remarks>
|
|
public virtual float GetDistanceSqrToInteractor(IXRInteractor interactor)
|
|
{
|
|
var interactorAttachTransform = interactor?.GetAttachTransform(this);
|
|
if (interactorAttachTransform == null)
|
|
return float.MaxValue;
|
|
|
|
var interactorPosition = interactorAttachTransform.position;
|
|
var distanceInfo = GetDistance(interactorPosition);
|
|
return distanceInfo.distanceSqr;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the distance from this Interactable to the given location.
|
|
/// This method uses the calculation mode configured in <see cref="distanceCalculationMode"/>.
|
|
/// <br />
|
|
/// This method can be overridden (without needing to subclass) by assigning a callback to <see cref="getDistanceOverride"/>.
|
|
/// To restore the previous calculation mode configuration, assign <see langword="null"/> to <see cref="getDistanceOverride"/>.
|
|
/// </summary>
|
|
/// <param name="position">Location in world space to calculate the distance to.</param>
|
|
/// <returns>Returns the distance information (in world space) from this Interactable to the given location.</returns>
|
|
/// <remarks>
|
|
/// This method is used by other methods and systems to calculate this Interactable distance to other objects and
|
|
/// locations (<see cref="GetDistanceSqrToInteractor(IXRInteractor)"/>).
|
|
/// </remarks>
|
|
public virtual DistanceInfo GetDistance(Vector3 position)
|
|
{
|
|
if (getDistanceOverride != null)
|
|
return getDistanceOverride(this, position);
|
|
|
|
switch (m_DistanceCalculationMode)
|
|
{
|
|
case DistanceCalculationMode.TransformPosition:
|
|
var thisObjectPosition = transform.position;
|
|
var offset = thisObjectPosition - position;
|
|
var distanceInfo = new DistanceInfo
|
|
{
|
|
point = thisObjectPosition,
|
|
distanceSqr = offset.sqrMagnitude
|
|
};
|
|
return distanceInfo;
|
|
|
|
case DistanceCalculationMode.ColliderPosition:
|
|
XRInteractableUtility.TryGetClosestCollider(this, position, out distanceInfo);
|
|
return distanceInfo;
|
|
|
|
case DistanceCalculationMode.ColliderVolume:
|
|
XRInteractableUtility.TryGetClosestPointOnCollider(this, position, out distanceInfo);
|
|
return distanceInfo;
|
|
|
|
default:
|
|
Debug.Assert(false, $"Unhandled {nameof(DistanceCalculationMode)}={m_DistanceCalculationMode}.", this);
|
|
goto case DistanceCalculationMode.TransformPosition;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public float GetInteractionStrength(IXRInteractor interactor)
|
|
{
|
|
if (m_InteractionStrengths.TryGetValue(interactor, out var interactionStrength))
|
|
return interactionStrength;
|
|
|
|
return 0f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a given Interactor can hover over this Interactable.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check for a valid hover state with.</param>
|
|
/// <returns>Returns <see langword="true"/> if hovering is valid this frame. Returns <see langword="false"/> if not.</returns>
|
|
/// <seealso cref="IXRHoverInteractor.CanHover"/>
|
|
public virtual bool IsHoverableBy(IXRHoverInteractor interactor)
|
|
{
|
|
return m_AllowGazeInteraction || !(interactor is XRGazeInteractor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if a given Interactor can select this Interactable.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check for a valid selection with.</param>
|
|
/// <returns>Returns <see langword="true"/> if selection is valid this frame. Returns <see langword="false"/> if not.</returns>
|
|
/// <seealso cref="IXRSelectInteractor.CanSelect"/>
|
|
public virtual bool IsSelectableBy(IXRSelectInteractor interactor)
|
|
{
|
|
return (m_AllowGazeInteraction && m_AllowGazeSelect) || !(interactor is XRGazeInteractor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines whether this Interactable is currently being hovered by the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check.</param>
|
|
/// <returns>Returns <see langword="true"/> if this Interactable is currently being hovered by the Interactor.
|
|
/// Otherwise, returns <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// In other words, returns whether <see cref="interactorsHovering"/> contains <paramref name="interactor"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="interactorsHovering"/>
|
|
public bool IsHovered(IXRHoverInteractor interactor) => isHovered && m_InteractorsHovering.Contains(interactor);
|
|
|
|
/// <summary>
|
|
/// Determines whether this Interactable is currently being selected by the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check.</param>
|
|
/// <returns>Returns <see langword="true"/> if this Interactable is currently being selected by the Interactor.
|
|
/// Otherwise, returns <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// In other words, returns whether <see cref="interactorsSelecting"/> contains <paramref name="interactor"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="interactorsSelecting"/>
|
|
public bool IsSelected(IXRSelectInteractor interactor) => isSelected && m_InteractorsSelecting.Contains(interactor);
|
|
|
|
/// <summary>
|
|
/// Determines whether this Interactable is currently being hovered by the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check.</param>
|
|
/// <returns>Returns <see langword="true"/> if this Interactable is currently being hovered by the Interactor.
|
|
/// Otherwise, returns <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// In other words, returns whether <see cref="interactorsHovering"/> contains <paramref name="interactor"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="interactorsHovering"/>
|
|
/// <seealso cref="IsHovered(IXRHoverInteractor)"/>
|
|
protected bool IsHovered(IXRInteractor interactor) => interactor is IXRHoverInteractor hoverInteractor && IsHovered(hoverInteractor);
|
|
|
|
/// <summary>
|
|
/// Determines whether this Interactable is currently being selected by the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor to check.</param>
|
|
/// <returns>Returns <see langword="true"/> if this Interactable is currently being selected by the Interactor.
|
|
/// Otherwise, returns <see langword="false"/>.</returns>
|
|
/// <remarks>
|
|
/// In other words, returns whether <see cref="interactorsSelecting"/> contains <paramref name="interactor"/>.
|
|
/// </remarks>
|
|
/// <seealso cref="interactorsSelecting"/>
|
|
/// <seealso cref="IsSelected(IXRSelectInteractor)"/>
|
|
protected bool IsSelected(IXRInteractor interactor) => interactor is IXRSelectInteractor selectInteractor && IsSelected(selectInteractor);
|
|
|
|
/// <summary>
|
|
/// Looks for the current custom reticle that is attached based on a specific Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor that is interacting with this Interactable.</param>
|
|
/// <returns>Returns <see cref="GameObject"/> that represents the attached custom reticle.</returns>
|
|
/// <seealso cref="AttachCustomReticle(IXRInteractor)"/>
|
|
public virtual GameObject GetCustomReticle(IXRInteractor interactor)
|
|
{
|
|
if (m_ReticleCache.TryGetValue(interactor, out var reticle))
|
|
{
|
|
return reticle;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Attaches the custom reticle to the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor that is interacting with this Interactable.</param>
|
|
/// <remarks>
|
|
/// If the custom reticle has an <see cref="IXRInteractableCustomReticle"/> component, this will call
|
|
/// <see cref="IXRInteractableCustomReticle.OnReticleAttached"/> on it.
|
|
/// </remarks>
|
|
/// <seealso cref="RemoveCustomReticle(IXRInteractor)"/>
|
|
public virtual void AttachCustomReticle(IXRInteractor interactor)
|
|
{
|
|
var interactorTransform = interactor?.transform;
|
|
if (interactorTransform == null)
|
|
return;
|
|
|
|
// Try and find any attached reticle and swap it
|
|
var reticleProvider = interactorTransform.GetComponent<IXRCustomReticleProvider>();
|
|
if (reticleProvider != null)
|
|
{
|
|
if (m_ReticleCache.TryGetValue(interactor, out var prevReticle))
|
|
{
|
|
Destroy(prevReticle);
|
|
m_ReticleCache.Remove(interactor);
|
|
}
|
|
|
|
if (m_CustomReticle != null)
|
|
{
|
|
var reticleInstance = Instantiate(m_CustomReticle);
|
|
m_ReticleCache.Add(interactor, reticleInstance);
|
|
reticleProvider.AttachCustomReticle(reticleInstance);
|
|
var customReticleBehavior = reticleInstance.GetComponent<IXRInteractableCustomReticle>();
|
|
if (customReticleBehavior != null)
|
|
customReticleBehavior.OnReticleAttached(this, reticleProvider);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes the custom reticle from the Interactor.
|
|
/// </summary>
|
|
/// <param name="interactor">Interactor that is no longer interacting with this Interactable.</param>
|
|
/// <remarks>
|
|
/// If the custom reticle has an <see cref="IXRInteractableCustomReticle"/> component, this will call
|
|
/// <see cref="IXRInteractableCustomReticle.OnReticleDetaching"/> on it.
|
|
/// </remarks>
|
|
/// <seealso cref="AttachCustomReticle(IXRInteractor)"/>
|
|
public virtual void RemoveCustomReticle(IXRInteractor interactor)
|
|
{
|
|
var interactorTransform = interactor?.transform;
|
|
if (interactorTransform == null)
|
|
return;
|
|
|
|
// Try and find any attached reticle and swap it
|
|
var reticleProvider = interactorTransform.GetComponent<IXRCustomReticleProvider>();
|
|
if (reticleProvider != null)
|
|
{
|
|
if (m_ReticleCache.TryGetValue(interactor, out var reticleInstance))
|
|
{
|
|
var customReticleBehavior = reticleInstance.GetComponent<IXRInteractableCustomReticle>();
|
|
if (customReticleBehavior != null)
|
|
customReticleBehavior.OnReticleDetaching();
|
|
|
|
Destroy(reticleInstance);
|
|
m_ReticleCache.Remove(interactor);
|
|
reticleProvider.RemoveCustomReticle();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Capture the current Attach Transform pose.
|
|
/// This method is automatically called by Unity to capture the pose during the moment of selection.
|
|
/// </summary>
|
|
/// <param name="interactor">The specific Interactor as context to get the attachment point for.</param>
|
|
/// <remarks>
|
|
/// Unity automatically calls this method during <see cref="OnSelectEntering(SelectEnterEventArgs)"/>
|
|
/// and should not typically need to be called by a user.
|
|
/// </remarks>
|
|
/// <seealso cref="GetAttachPoseOnSelect"/>
|
|
/// <seealso cref="GetLocalAttachPoseOnSelect"/>
|
|
/// <seealso cref="XRBaseInteractor.CaptureAttachPose"/>
|
|
protected void CaptureAttachPose(IXRSelectInteractor interactor)
|
|
{
|
|
var thisAttachTransform = GetAttachTransform(interactor);
|
|
if (thisAttachTransform != null)
|
|
{
|
|
m_AttachPoseOnSelect[interactor] = thisAttachTransform.GetWorldPose();
|
|
m_LocalAttachPoseOnSelect[interactor] = thisAttachTransform.GetLocalPose();
|
|
}
|
|
else
|
|
{
|
|
m_AttachPoseOnSelect.Remove(interactor);
|
|
m_LocalAttachPoseOnSelect.Remove(interactor);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public virtual void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
void IXRInteractionStrengthInteractable.ProcessInteractionStrength(XRInteractionUpdateOrder.UpdatePhase updatePhase) => ProcessInteractionStrength(updatePhase);
|
|
|
|
/// <inheritdoc />
|
|
void IXRInteractable.OnRegistered(InteractableRegisteredEventArgs args) => OnRegistered(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRInteractable.OnUnregistered(InteractableUnregisteredEventArgs args) => OnUnregistered(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRActivateInteractable.OnActivated(ActivateEventArgs args) => OnActivated(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRActivateInteractable.OnDeactivated(DeactivateEventArgs args) => OnDeactivated(args);
|
|
|
|
/// <inheritdoc />
|
|
bool IXRHoverInteractable.IsHoverableBy(IXRHoverInteractor interactor)
|
|
{
|
|
return IsHoverableBy(interactor) && ProcessHoverFilters(interactor);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
void IXRHoverInteractable.OnHoverEntering(HoverEnterEventArgs args) => OnHoverEntering(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRHoverInteractable.OnHoverEntered(HoverEnterEventArgs args) => OnHoverEntered(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRHoverInteractable.OnHoverExiting(HoverExitEventArgs args) => OnHoverExiting(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRHoverInteractable.OnHoverExited(HoverExitEventArgs args) => OnHoverExited(args);
|
|
|
|
/// <inheritdoc />
|
|
bool IXRSelectInteractable.IsSelectableBy(IXRSelectInteractor interactor)
|
|
{
|
|
return IsSelectableBy(interactor) && ProcessSelectFilters(interactor);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
void IXRSelectInteractable.OnSelectEntering(SelectEnterEventArgs args) => OnSelectEntering(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRSelectInteractable.OnSelectEntered(SelectEnterEventArgs args) => OnSelectEntered(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRSelectInteractable.OnSelectExiting(SelectExitEventArgs args) => OnSelectExiting(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRSelectInteractable.OnSelectExited(SelectExitEventArgs args) => OnSelectExited(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRFocusInteractable.OnFocusEntering(FocusEnterEventArgs args) => OnFocusEntering(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRFocusInteractable.OnFocusEntered(FocusEnterEventArgs args) => OnFocusEntered(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRFocusInteractable.OnFocusExiting(FocusExitEventArgs args) => OnFocusExiting(args);
|
|
|
|
/// <inheritdoc />
|
|
void IXRFocusInteractable.OnFocusExited(FocusExitEventArgs args) => OnFocusExited(args);
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when this Interactable is registered with it.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction Manager that registered this Interactable.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="XRInteractionManager.RegisterInteractable(IXRInteractable)"/>
|
|
protected virtual void OnRegistered(InteractableRegisteredEventArgs args)
|
|
{
|
|
if (args.manager != m_InteractionManager)
|
|
Debug.LogWarning($"An Interactable was registered with an unexpected {nameof(XRInteractionManager)}." +
|
|
$" {this} was expecting to communicate with \"{m_InteractionManager}\" but was registered with \"{args.manager}\".", this);
|
|
|
|
registered?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when this Interactable is unregistered from it.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction Manager that unregistered this Interactable.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="XRInteractionManager.UnregisterInteractable(IXRInteractable)"/>
|
|
protected virtual void OnUnregistered(InteractableUnregisteredEventArgs args)
|
|
{
|
|
if (args.manager != m_RegisteredInteractionManager)
|
|
Debug.LogWarning($"An Interactable was unregistered from an unexpected {nameof(XRInteractionManager)}." +
|
|
$" {this} was expecting to communicate with \"{m_RegisteredInteractionManager}\" but was unregistered from \"{args.manager}\".", this);
|
|
|
|
unregistered?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// right before the Interactor first initiates hovering over an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is initiating the hover.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnHoverEntered(HoverEnterEventArgs)"/>
|
|
protected virtual void OnHoverEntering(HoverEnterEventArgs args)
|
|
{
|
|
if (m_CustomReticle != null)
|
|
AttachCustomReticle(args.interactorObject);
|
|
|
|
var added = m_InteractorsHovering.Add(args.interactorObject);
|
|
Debug.Assert(added, "An Interactable received a Hover Enter event for an Interactor that was already hovering over it.", this);
|
|
isHovered = true;
|
|
|
|
if (args.interactorObject is XRBaseInputInteractor variableSelectInteractor)
|
|
m_VariableSelectInteractors.Add(variableSelectInteractor);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interactor first initiates hovering over an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is initiating the hover.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnHoverExited(HoverExitEventArgs)"/>
|
|
protected virtual void OnHoverEntered(HoverEnterEventArgs args)
|
|
{
|
|
if (m_InteractorsHovering.Count == 1)
|
|
m_FirstHoverEntered?.Invoke(args);
|
|
|
|
m_HoverEntered?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// right before the Interactor ends hovering over an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is ending the hover.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnHoverExited(HoverExitEventArgs)"/>
|
|
protected virtual void OnHoverExiting(HoverExitEventArgs args)
|
|
{
|
|
if (m_CustomReticle != null)
|
|
RemoveCustomReticle(args.interactorObject);
|
|
|
|
var removed = m_InteractorsHovering.Remove(args.interactorObject);
|
|
Debug.Assert(removed, "An Interactable received a Hover Exit event for an Interactor that was not hovering over it.", this);
|
|
if (m_InteractorsHovering.Count == 0)
|
|
isHovered = false;
|
|
|
|
if (!IsSelected(args.interactorObject))
|
|
{
|
|
if (m_InteractionStrengths.Count > 0)
|
|
m_InteractionStrengths.Remove(args.interactorObject);
|
|
|
|
if (args.interactorObject is XRBaseInputInteractor variableSelectInteractor)
|
|
m_VariableSelectInteractors.Remove(variableSelectInteractor);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interactor ends hovering over an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is ending the hover.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnHoverEntered(HoverEnterEventArgs)"/>
|
|
protected virtual void OnHoverExited(HoverExitEventArgs args)
|
|
{
|
|
if (!isHovered)
|
|
m_LastHoverExited?.Invoke(args);
|
|
|
|
m_HoverExited?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method right
|
|
/// before the Interactor first initiates selection of an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is initiating the selection.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnSelectEntered(SelectEnterEventArgs)"/>
|
|
protected virtual void OnSelectEntering(SelectEnterEventArgs args)
|
|
{
|
|
var added = m_InteractorsSelecting.Add(args.interactorObject);
|
|
Debug.Assert(added, "An Interactable received a Select Enter event for an Interactor that was already selecting it.", this);
|
|
isSelected = true;
|
|
|
|
if (args.interactorObject is XRBaseInputInteractor variableSelectInteractor)
|
|
m_VariableSelectInteractors.Add(variableSelectInteractor);
|
|
|
|
if (m_InteractorsSelecting.Count == 1)
|
|
firstInteractorSelecting = args.interactorObject;
|
|
|
|
CaptureAttachPose(args.interactorObject);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interactor first initiates selection of an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is initiating the selection.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnSelectExited(SelectExitEventArgs)"/>
|
|
protected virtual void OnSelectEntered(SelectEnterEventArgs args)
|
|
{
|
|
if (m_InteractorsSelecting.Count == 1)
|
|
m_FirstSelectEntered?.Invoke(args);
|
|
|
|
m_SelectEntered?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// right before the Interactor ends selection of an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is ending the selection.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnSelectExited(SelectExitEventArgs)"/>
|
|
protected virtual void OnSelectExiting(SelectExitEventArgs args)
|
|
{
|
|
var removed = m_InteractorsSelecting.Remove(args.interactorObject);
|
|
Debug.Assert(removed, "An Interactable received a Select Exit event for an Interactor that was not selecting it.", this);
|
|
if (m_InteractorsSelecting.Count == 0)
|
|
isSelected = false;
|
|
|
|
if (!IsHovered(args.interactorObject))
|
|
{
|
|
if (m_InteractionStrengths.Count > 0)
|
|
m_InteractionStrengths.Remove(args.interactorObject);
|
|
|
|
if (args.interactorObject is XRBaseInputInteractor variableSelectInteractor)
|
|
m_VariableSelectInteractors.Remove(variableSelectInteractor);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interactor ends selection of an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is ending the selection.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnSelectEntered(SelectEnterEventArgs)"/>
|
|
protected virtual void OnSelectExited(SelectExitEventArgs args)
|
|
{
|
|
if (!isSelected)
|
|
m_LastSelectExited?.Invoke(args);
|
|
|
|
m_SelectExited?.Invoke(args);
|
|
|
|
// The dictionaries are pruned so that they don't infinitely grow in size as selections are made.
|
|
if (!isSelected)
|
|
{
|
|
firstInteractorSelecting = null;
|
|
m_AttachPoseOnSelect.Clear();
|
|
m_LocalAttachPoseOnSelect.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method right
|
|
/// before the Interaction group first gains focus of an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction group that is initiating focus.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnFocusEntered(FocusEnterEventArgs)"/>
|
|
protected virtual void OnFocusEntering(FocusEnterEventArgs args)
|
|
{
|
|
var added = m_InteractionGroupsFocusing.Add(args.interactionGroup);
|
|
Debug.Assert(added, "An Interactable received a Focus Enter event for an Interaction group that was already focusing it.", this);
|
|
isFocused = true;
|
|
|
|
if (m_InteractionGroupsFocusing.Count == 1)
|
|
firstInteractionGroupFocusing = args.interactionGroup;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interaction group first gains focus of an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction group that is initiating the focus.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnFocusExited(FocusExitEventArgs)"/>
|
|
protected virtual void OnFocusEntered(FocusEnterEventArgs args)
|
|
{
|
|
if (m_InteractionGroupsFocusing.Count == 1)
|
|
m_FirstFocusEntered?.Invoke(args);
|
|
|
|
m_FocusEntered?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// right before the Interaction group loses focus of an Interactable
|
|
/// in a first pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction group that is losing focus.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnFocusExited(FocusExitEventArgs)"/>
|
|
protected virtual void OnFocusExiting(FocusExitEventArgs args)
|
|
{
|
|
var removed = m_InteractionGroupsFocusing.Remove(args.interactionGroup);
|
|
Debug.Assert(removed, "An Interactable received a Focus Exit event for an Interaction group that did not have focus of it.", this);
|
|
if (m_InteractionGroupsFocusing.Count == 0)
|
|
isFocused = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method
|
|
/// when the Interaction group loses focus of an Interactable
|
|
/// in a second pass.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interaction group that is losing focus.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnFocusEntered(FocusEnterEventArgs)"/>
|
|
protected virtual void OnFocusExited(FocusExitEventArgs args)
|
|
{
|
|
if (!isFocused)
|
|
m_LastFocusExited?.Invoke(args);
|
|
|
|
m_FocusExited?.Invoke(args);
|
|
|
|
if (!isFocused)
|
|
firstInteractionGroupFocusing = null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="XRBaseInputInteractor"/> calls this method when the
|
|
/// Interactor begins an activation event on this Interactable.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is sending the activate event.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnDeactivated"/>
|
|
protected virtual void OnActivated(ActivateEventArgs args)
|
|
{
|
|
m_Activated?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <see cref="XRBaseInputInteractor"/> calls this method when the
|
|
/// Interactor ends an activation event on this Interactable.
|
|
/// </summary>
|
|
/// <param name="args">Event data containing the Interactor that is sending the deactivate event.</param>
|
|
/// <remarks>
|
|
/// <paramref name="args"/> is only valid during this method call, do not hold a reference to it.
|
|
/// </remarks>
|
|
/// <seealso cref="OnActivated"/>
|
|
protected virtual void OnDeactivated(DeactivateEventArgs args)
|
|
{
|
|
m_Deactivated?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The <see cref="XRInteractionManager"/> calls this method to signal to update the interaction strength.
|
|
/// </summary>
|
|
/// <param name="updatePhase">The update phase during which this method is called.</param>
|
|
/// <seealso cref="GetInteractionStrength"/>
|
|
/// <seealso cref="IXRInteractionStrengthInteractable.ProcessInteractionStrength"/>
|
|
protected virtual void ProcessInteractionStrength(XRInteractionUpdateOrder.UpdatePhase updatePhase)
|
|
{
|
|
var maxInteractionStrength = 0f;
|
|
|
|
using (s_ProcessInteractionStrengthMarker.Auto())
|
|
{
|
|
if (!isSelected && !isHovered)
|
|
{
|
|
// Optimize to avoid float equality check in Value setter if the value has already been cleared
|
|
// due to being not selected or hovered.
|
|
if (m_ClearedLargestInteractionStrength)
|
|
return;
|
|
|
|
m_LargestInteractionStrength.Value = 0f;
|
|
m_ClearedLargestInteractionStrength = true;
|
|
|
|
return;
|
|
}
|
|
|
|
m_ClearedLargestInteractionStrength = false;
|
|
|
|
var hasFilters = m_InteractionStrengthFilters.registeredSnapshot.Count > 0;
|
|
|
|
// Select is checked before Hover to allow process to only be called once per interactor hovering and selecting
|
|
// using the largest initial interaction strength.
|
|
if (isSelected)
|
|
{
|
|
for (int i = 0, count = m_InteractorsSelecting.Count; i < count; ++i)
|
|
{
|
|
var interactor = m_InteractorsSelecting[i];
|
|
if (interactor is XRBaseInputInteractor)
|
|
continue;
|
|
|
|
var interactionStrength = hasFilters
|
|
? ProcessInteractionStrengthFilters(interactor, k_InteractionStrengthSelect)
|
|
: k_InteractionStrengthSelect;
|
|
|
|
m_InteractionStrengths[interactor] = interactionStrength;
|
|
maxInteractionStrength = Mathf.Max(maxInteractionStrength, interactionStrength);
|
|
}
|
|
}
|
|
|
|
if (isHovered)
|
|
{
|
|
for (int i = 0, count = m_InteractorsHovering.Count; i < count; ++i)
|
|
{
|
|
var interactor = m_InteractorsHovering[i];
|
|
if (interactor is XRBaseInputInteractor || IsSelected(interactor))
|
|
continue;
|
|
|
|
var interactionStrength = hasFilters
|
|
? ProcessInteractionStrengthFilters(interactor, k_InteractionStrengthHover)
|
|
: k_InteractionStrengthHover;
|
|
|
|
m_InteractionStrengths[interactor] = interactionStrength;
|
|
maxInteractionStrength = Mathf.Max(maxInteractionStrength, interactionStrength);
|
|
}
|
|
}
|
|
|
|
for (int i = 0, count = m_VariableSelectInteractors.Count; i < count; ++i)
|
|
{
|
|
var interactor = m_VariableSelectInteractors[i];
|
|
|
|
var interactionStrength = hasFilters
|
|
? ProcessInteractionStrengthFilters(interactor, ReadInteractionStrength(interactor))
|
|
: ReadInteractionStrength(interactor);
|
|
|
|
m_InteractionStrengths[interactor] = interactionStrength;
|
|
maxInteractionStrength = Mathf.Max(maxInteractionStrength, interactionStrength);
|
|
}
|
|
}
|
|
|
|
using (s_ProcessInteractionStrengthEventMarker.Auto())
|
|
{
|
|
m_LargestInteractionStrength.Value = maxInteractionStrength;
|
|
}
|
|
}
|
|
|
|
float ReadInteractionStrength(XRBaseInputInteractor interactor)
|
|
{
|
|
// Use the Select input value as the initial interaction strength.
|
|
// For interactors that use motion controller input, this is typically the analog trigger or grip press amount.
|
|
// Fall back to the default values for selected and hovered interactors in the case when the interactor
|
|
// is misconfigured and is missing the input wrapper or component reference.
|
|
#pragma warning disable CS0618 // Type or member is obsolete -- Retained for backwards compatibility
|
|
if (!interactor.forceDeprecatedInput)
|
|
return interactor.selectInput.ReadValue();
|
|
|
|
if (interactor.xrController != null)
|
|
return interactor.xrController.selectInteractionState.value;
|
|
#pragma warning restore CS0618
|
|
|
|
return IsSelected(interactor) ? k_InteractionStrengthSelect : k_InteractionStrengthHover;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the processing value of the filters in <see cref="hoverFilters"/> for the given Interactor and this
|
|
/// Interactable.
|
|
/// </summary>
|
|
/// <param name="interactor">The Interactor to be validated by the hover filters.</param>
|
|
/// <returns>
|
|
/// Returns <see langword="true"/> if all processed filters also return <see langword="true"/>, or if
|
|
/// <see cref="hoverFilters"/> is empty. Otherwise, returns <see langword="false"/>.
|
|
/// </returns>
|
|
protected bool ProcessHoverFilters(IXRHoverInteractor interactor)
|
|
{
|
|
return XRFilterUtility.Process(m_HoverFilters, interactor, this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the processing value of the filters in <see cref="selectFilters"/> for the given Interactor and this
|
|
/// Interactable.
|
|
/// </summary>
|
|
/// <param name="interactor">The Interactor to be validated by the select filters.</param>
|
|
/// <returns>
|
|
/// Returns <see langword="true"/> if all processed filters also return <see langword="true"/>, or if
|
|
/// <see cref="selectFilters"/> is empty. Otherwise, returns <see langword="false"/>.
|
|
/// </returns>
|
|
protected bool ProcessSelectFilters(IXRSelectInteractor interactor)
|
|
{
|
|
return XRFilterUtility.Process(m_SelectFilters, interactor, this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the processing value of the interaction strength filters in <see cref="interactionStrengthFilters"/> for the given Interactor and this
|
|
/// Interactable.
|
|
/// </summary>
|
|
/// <param name="interactor">The Interactor to process by the interaction strength filters.</param>
|
|
/// <param name="interactionStrength">The interaction strength before processing.</param>
|
|
/// <returns>Returns the modified interaction strength that is the result of passing the interaction strength through each filter.</returns>
|
|
protected float ProcessInteractionStrengthFilters(IXRInteractor interactor, float interactionStrength)
|
|
{
|
|
return XRFilterUtility.Process(m_InteractionStrengthFilters, interactor, this, interactionStrength);
|
|
}
|
|
}
|
|
}
|