/* * 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; using System.Collections.Generic; using System.Linq; namespace Oculus.Interaction.Surfaces { /// /// The primary flat surface used by the Interaction SDK, this is an constructed by /// "clipping" a down to a subsection of itself using one or more /// s. /// /// /// ClippedPlaneSurfaces apply the logical AND of all their contained s to the /// underlying . Because s are axis-aligned in the /// space of the plane being clipped, this means all ClippedPlaneSurfaces will be rectangular subsections /// of the XY plane of the transform of the they clip. They are not necessarily /// centered on this transform, however. /// public class ClippedPlaneSurface : MonoBehaviour, IClippedSurface { private static readonly Bounds InfiniteBounds = new Bounds(Vector3.zero, Vector3.one * float.PositiveInfinity); private static readonly Bounds PlaneBounds = new Bounds(Vector3.zero, new Vector3(float.PositiveInfinity, float.PositiveInfinity, Vector3.kEpsilon)); [Tooltip("The Plane Surface to be clipped.")] [SerializeField] private PlaneSurface _planeSurface; [Tooltip("The clippers that will be used to clip the Plane Surface.")] [SerializeField, Interface(typeof(IBoundsClipper))] private List _clippers = new List(); private List Clippers { get; set; } /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public ISurface BackingSurface => _planeSurface; /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public Transform Transform => _planeSurface.Transform; /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public IReadOnlyList GetClippers() { if (Clippers != null) { return Clippers; } else { return _clippers.ConvertAll(clipper => clipper as IBoundsClipper); } } protected virtual void Awake() { Clippers = _clippers.ConvertAll(clipper => clipper as IBoundsClipper); } protected virtual void Start() { this.AssertField(_planeSurface, nameof(_planeSurface)); this.AssertCollectionItems(Clippers, nameof(Clippers)); } /// /// Clip a provided Bounds using the ClippedPlaneSurface's s. /// Comparable to applying the logical AND of the input with all /// the clippers returned by . /// /// The Bounds to clip /// The clipped result /// True if resulting bounds are contain any space, false if the clipped bounds have no volume public bool ClipBounds(in Bounds bounds, out Bounds clipped) { clipped = bounds; IReadOnlyList clippers = GetClippers(); for (int i = 0; i < clippers.Count; i++) { IBoundsClipper clipper = clippers[i]; if (clipper == null || !clipper.GetLocalBounds(Transform, out Bounds clipTo)) { continue; } if (!clipped.Clip(clipTo, out clipped)) { return false; } } return true; } private Vector3 ClampPoint(in Vector3 point, in Bounds bounds) { Vector3 min = bounds.min; Vector3 max = bounds.max; Vector3 localPoint = Transform.InverseTransformPoint(point); Vector3 clamped = new Vector3( Mathf.Clamp(localPoint.x, min.x, max.x), Mathf.Clamp(localPoint.y, min.y, max.y), Mathf.Clamp(localPoint.z, min.z, max.z)); return Transform.TransformPoint(clamped); } /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public bool ClosestSurfacePoint(in Vector3 point, out SurfaceHit hit, float maxDistance = 0) { if (_planeSurface.ClosestSurfacePoint(point, out hit, maxDistance) && ClipBounds(PlaneBounds, out Bounds clippedPlane)) { hit.Point = ClampPoint(hit.Point, clippedPlane); hit.Distance = Vector3.Distance(point, hit.Point); return maxDistance <= 0 || hit.Distance <= maxDistance; } return false; } /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public bool Raycast(in Ray ray, out SurfaceHit hit, float maxDistance = 0) { return BackingSurface.Raycast(ray, out hit, maxDistance) && ClipBounds(InfiniteBounds, out Bounds clipBounds) && clipBounds.size != Vector3.zero && clipBounds.Contains(Transform.InverseTransformPoint(hit.Point)); } #region Inject /// /// Injects all required dependencies for a dynamically instantiated ClippedPlaneSurface; effectively wraps /// and . /// This method exists to support Interaction SDK's dependency injection pattern and is not needed for typical /// Unity Editor-based usage. /// public void InjectAllClippedPlaneSurface( PlaneSurface planeSurface, IEnumerable clippers) { InjectPlaneSurface(planeSurface); InjectClippers(clippers); } /// /// Sets the underlying for a dynamically instantiated ClippedPlaneSurface. This method /// exists to support Interaction SDK's dependency injection pattern and is not needed for typical Unity /// Editor-based usage. /// public void InjectPlaneSurface(PlaneSurface planeSurface) { _planeSurface = planeSurface; } /// /// Sets the s for a dynamically instantiated ClippedPlaneSurface. This method exists /// to support Interaction SDK's dependency injection pattern and is not needed for typical Unity Editor-based usage. /// public void InjectClippers(IEnumerable clippers) { _clippers = new List( clippers.Select(c => c as UnityEngine.Object)); Clippers = clippers.ToList(); } #endregion } }