VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Samples~/visionOS/Scripts/Input Bridge/VisionOSFarCaster.cs
2025-07-29 13:45:50 +03:00

187 lines
6.5 KiB
C#

using System.Collections.Generic;
using Unity.Collections;
using UnityEngine.XR.Interaction.Toolkit.Interactors.Casters;
using UnityEngine.XR.Interaction.Toolkit.UI;
namespace UnityEngine.XR.Interaction.Toolkit.Samples.VisionOS
{
/// <summary>
/// The visionOS Far Caster serves as a means routing the spatial touch interaction collider to the Near-Far Interactor.
/// This class was mainly tested for Bounded and Unbounded mixed reality modes. It may also work in VR mode, but some additional testing and iteration may be required.
/// </summary>
public class VisionOSFarCaster : InteractionCasterBase, ICurveInteractionCaster, IUIModelUpdater
{
[Header("Vision Pro Settings")]
[SerializeField]
SpatialTouchInputReader m_SpatialTouchInputReader;
[Header("Filtering Settings")]
[SerializeField]
[Tooltip("Layer mask used for limiting sphere cast and sphere overlap targets.")]
LayerMask m_PhysicsLayerMask = -1;
/// <summary>
/// Gets or sets layer mask used for limiting sphere cast and sphere overlap targets.
/// </summary>
public LayerMask physicsLayerMask
{
get => m_PhysicsLayerMask;
set => m_PhysicsLayerMask = value;
}
[SerializeField]
QueryTriggerInteraction m_PhysicsTriggerInteraction = QueryTriggerInteraction.Ignore;
/// <summary>
/// Determines whether the cast sphere overlap will hit triggers.
/// </summary>
public QueryTriggerInteraction physicsTriggerInteraction
{
get => m_PhysicsTriggerInteraction;
set => m_PhysicsTriggerInteraction = value;
}
bool hasActiveTouch => m_HasTouchReader && m_SpatialTouchInputReader.hasActiveTouch.Value;
bool m_HasTouchReader;
NativeArray<Vector3> m_SamplePoints;
NativeArray<Vector3> m_EmptySamplePoints;
public NativeArray<Vector3> samplePoints => hasActiveTouch ? m_SamplePoints : m_EmptySamplePoints;
public Vector3 lastSamplePoint => hasActiveTouch && m_SamplePoints.Length > 1 ? m_SamplePoints[^1] : Vector3.zero;
/// <inheritdoc />
protected override bool InitializeCaster()
{
if (m_HasTouchReader && isActiveAndEnabled)
isInitialized = true;
return isInitialized;
}
/// <inheritdoc />
protected override void Awake()
{
base.Awake();
m_HasTouchReader = m_SpatialTouchInputReader != null;
m_SamplePoints = new NativeArray<Vector3>(2, Allocator.Persistent);
m_EmptySamplePoints = new NativeArray<Vector3>(1, Allocator.Persistent);
}
void OnEnable()
{
UpdateSamplePoints(Vector3.zero, Vector3.zero);
}
/// <inheritdoc />
protected override void OnDestroy()
{
base.OnDestroy();
if (m_SamplePoints.IsCreated)
m_SamplePoints.Dispose();
if (m_EmptySamplePoints.IsCreated)
m_EmptySamplePoints.Dispose();
}
/// <inheritdoc />
protected override void UpdateInternalData()
{
base.UpdateInternalData();
#if POLYSPATIAL_1_1_OR_NEWER
if (!m_SpatialTouchInputReader.TryGetPointerState(out var state))
return;
if (state.targetObject != null)
{
Vector3 castOriginPoint = effectiveCastOrigin.position;
Vector3 sampleEndPoint = castOriginPoint + (state.interactionPosition - castOriginPoint).normalized * 10f;
UpdateSamplePoints(sampleEndPoint, castOriginPoint);
}
else
{
var originPosition = effectiveCastOrigin.position;
UpdateSamplePoints(originPosition + state.startInteractionRayDirection * 10f, originPosition);
}
#endif
}
/// <inheritdoc />
public bool TryGetColliderTargets(XRInteractionManager interactionManager, List<Collider> colliders, List<RaycastHit> raycastHits)
{
#if POLYSPATIAL_1_1_OR_NEWER
if (!base.TryGetColliderTargets(interactionManager, colliders))
return false;
colliders.Clear();
raycastHits.Clear();
if (!m_SpatialTouchInputReader.TryGetPointerState(out var state))
return false;
if (state.targetObject == null)
return false;
if (state.targetObject.TryGetComponent(out Collider targetCollider) && IsColliderValid(targetCollider))
{
colliders.Add(targetCollider);
raycastHits.Add(new RaycastHit
{
point = targetCollider.ClosestPoint(state.interactionPosition),
});
return true;
}
#endif
return false;
}
bool IsColliderValid(Collider targetCollider)
{
bool layerMatch = (m_PhysicsLayerMask & (1 << targetCollider.gameObject.layer)) != 0;
bool shouldIgnoreFromTrigger = m_PhysicsTriggerInteraction == QueryTriggerInteraction.Ignore && targetCollider.isTrigger;
return layerMatch && !shouldIgnoreFromTrigger;
}
void UpdateSamplePoints(Vector3 hitPoint, Vector3 rayOrigin)
{
m_SamplePoints[0] = rayOrigin;
m_SamplePoints[1] = hitPoint;
}
/// <inheritdoc />
public bool UpdateUIModel(ref TrackedDeviceModel uiModel, bool isSelectActive, in Vector2 scrollDelta)
{
if (!isInitialized)
return false;
var sampleOrigin = effectiveCastOrigin;
uiModel.position = sampleOrigin.position;
uiModel.orientation = sampleOrigin.rotation;
uiModel.select = isSelectActive;
uiModel.scrollDelta = scrollDelta;
uiModel.raycastLayerMask = m_PhysicsLayerMask;
uiModel.interactionType = UIInteractionType.Ray;
var raycastPoints = uiModel.raycastPoints;
raycastPoints.Clear();
UpdateInternalData();
var numPoints = m_SamplePoints.Length;
if (numPoints <= 0)
{
return false;
}
if (raycastPoints.Capacity < numPoints)
raycastPoints.Capacity = numPoints;
for (var i = 0; i < numPoints; ++i)
{
raycastPoints.Add(m_SamplePoints[i]);
}
return true;
}
}
}