/* * Copyright (c) Meta Platforms, Inc. and affiliates. * All rights reserved. * * Licensed under the Oculus SDK License Agreement (the "License"); * you may not use the Oculus SDK except in compliance with the License, * which is provided at the time of installation or download, or which * otherwise accompanies this software in either electronic or hard copy form. * * You may obtain a copy of the License at * * https://developer.oculus.com/licenses/oculussdk/ * * Unless required by applicable law or agreed to in writing, the Oculus SDK * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using UnityEngine; namespace Oculus.Interaction { [System.Serializable] public struct DistantPointDetectorFrustums { /// /// The main frustum, items inside this frustum will be selected. /// [SerializeField] private ConicalFrustum _selectionFrustum; /// /// Selected items within this optional frustum will keep being selected. /// It should be wider than the selection frustum. /// [SerializeField, Optional] private ConicalFrustum _deselectionFrustum; /// /// When provided, items need to be also inside this frustum as well as the /// selection one to be selected. /// e.g. Selection frustum coming out from the hand, and aid frustum from the /// head so items need to be pointed at with the gaze and hand. /// [SerializeField, Optional] private ConicalFrustum _aidFrustum; /// /// Blends the scores between the Selection and Aid frustums. /// At 0 items centered in the selection frustum are given preference, at one /// items centered at the aid frustum are given preference. /// [SerializeField] [Range(0f, 1f)] private float _aidBlending; public ConicalFrustum SelectionFrustum => _selectionFrustum; public ConicalFrustum DeselectionFrustum => _deselectionFrustum; public ConicalFrustum AidFrustum => _aidFrustum; public float AidBlending => _aidBlending; public DistantPointDetectorFrustums(ConicalFrustum selection, ConicalFrustum deselection, ConicalFrustum aid, float blend) { _selectionFrustum = selection; _deselectionFrustum = deselection; _aidFrustum = aid; _aidBlending = blend; } } /// /// This class contains the logic for finding the best candidate among a series of colliders. /// It uses up to three conical frustums from DistantPointDetectorFrustums for selection and deselection. /// public class DistantPointDetector { private DistantPointDetectorFrustums _frustums; public DistantPointDetector(DistantPointDetectorFrustums frustums) { _frustums = frustums; } public bool ComputeIsPointing(Collider[] colliders, bool isSelecting, out float bestScore, out Vector3 bestHitPoint) { ConicalFrustum searchFrustrum = (isSelecting || _frustums.DeselectionFrustum == null) ? _frustums.SelectionFrustum : _frustums.DeselectionFrustum; bestHitPoint = Vector3.zero; bestScore = float.NegativeInfinity; bool anyHit = false; foreach (Collider collider in colliders) { float score = 0f; if (!searchFrustrum.HitsCollider(collider, out score, out Vector3 hitPoint)) { continue; } if (_frustums.AidFrustum != null) { if (!_frustums.AidFrustum.HitsCollider(collider, out float headScore, out Vector3 headPosition)) { continue; } score = score * (1f - _frustums.AidBlending) + headScore * _frustums.AidBlending; } if (score > bestScore) { bestHitPoint = hitPoint; bestScore = score; anyHit = true; } } return anyHit; } public bool IsPointingWithoutAid(Collider[] colliders, out Vector3 bestHitPoint) { if (_frustums.AidFrustum == null) { bestHitPoint = Vector3.zero; return false; } return !IsPointingAtColliders(colliders, _frustums.AidFrustum, out bestHitPoint) && IsWithinDeselectionRange(colliders); } public bool IsWithinDeselectionRange(Collider[] colliders) { return IsPointingAtColliders(colliders, _frustums.DeselectionFrustum) || IsPointingAtColliders(colliders, _frustums.SelectionFrustum); } private bool IsPointingAtColliders(Collider[] colliders, ConicalFrustum frustum) { if (frustum == null) { return false; } foreach (Collider collider in colliders) { if (frustum.HitsCollider(collider, out float score, out Vector3 point)) { return true; } } return false; } private bool IsPointingAtColliders(Collider[] colliders, ConicalFrustum frustum, out Vector3 bestHitPoint) { bestHitPoint = Vector3.zero; float bestScore = float.NegativeInfinity; bool isPointing = false; if (frustum == null) { return false; } foreach (Collider collider in colliders) { if (frustum.HitsCollider(collider, out float score, out Vector3 point)) { isPointing = true; if (score > bestScore) { bestScore = score; bestHitPoint = point; } } } return isPointing; } } }