/* * 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.Surfaces { /// /// A representation of an infinite plane which can serve as a surface for interactions. Rarely used on /// its own, but frequently used as an underlying component of s. /// /// /// While it is uncommon for a PlaneSurface to be used on its own (because few interactable elements can /// be considered infinite planes), it is quite common for multiple different s /// to exist on the same plane: consider multiple buttons on a single interactable UI. In such a scenario, many /// s can be backed by the same PlaneSurface, ensuring that they always remain /// coplanar regardless of edits to the underlying surface. /// public class PlaneSurface : MonoBehaviour, ISurface, IBounds { /// /// Used for interaction with flat surfaces, and acts in much the same way as Unity’s Plane. /// public enum NormalFacing { /// /// Normal faces along the transform's negative Z axis /// Backward, /// /// Normal faces along the transform's positive Z axis /// Forward, } /// /// The direction the normal faces. If Forward, the normal faces positive Z. If Backward, the normal faces negative Z. /// [Tooltip("The normal facing of the surface. Hits will be " + "registered either on the front or back of the plane " + "depending on this value.")] [SerializeField] private NormalFacing _facing = NormalFacing.Backward; /// /// Determines whether raycasts will hit both sides of the plane. Note that the raycast hit normal will respect Facing regardless of this setting. /// [SerializeField, Tooltip("Raycasts hit either side of plane, but hit normal " + "will still respect plane facing.")] private bool _doubleSided = false; /// /// Gets or sets the direction ( or ) for /// this plane. /// public NormalFacing Facing { get => _facing; set => _facing = value; } /// /// Determines whether this plane is double-sided. If true, this plane can be interacted with from either side; /// otherwise, the plane is only considered to exist if perceived from the direction of its normal. /// public bool DoubleSided { get => _doubleSided; set => _doubleSided = value; } /// /// Returns either the forward direction or the backward direction depending on whether is /// or , respectively. /// public Vector3 Normal { get { return _facing == NormalFacing.Forward ? transform.forward : -transform.forward; } } /// /// 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) { hit = new SurfaceHit(); GetPlaneParameters(out Vector3 planeNormal, out float planeDistance); float hitDistance = Vector3.Dot(planeNormal, point) + planeDistance; float absDistance = Mathf.Abs(hitDistance); if (maxDistance > 0 && absDistance > maxDistance) { return false; } hit.Point = point - planeNormal * hitDistance; hit.Distance = absDistance; hit.Normal = planeNormal.normalized; return true; } /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public Transform Transform => transform; /// /// Implementation of ; for details, please refer to /// the related documentation provided for that property. /// public Bounds Bounds { get { Vector3 normal = Normal; Vector3 size = new Vector3( Mathf.Abs(normal.x) == 1f ? float.Epsilon : float.PositiveInfinity, Mathf.Abs(normal.y) == 1f ? float.Epsilon : float.PositiveInfinity, Mathf.Abs(normal.z) == 1f ? float.Epsilon : float.PositiveInfinity); return new Bounds(transform.position, size); } } /// /// 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) { hit = new SurfaceHit(); GetPlaneParameters(out Vector3 planeNormal, out float planeDistance); float originDistance = Vector3.Dot(planeNormal, ray.origin) + planeDistance; if (!_doubleSided && originDistance <= 0) { return false; } if (Raycast(ray, out float hitDistance)) { if (maxDistance > 0 && hitDistance > maxDistance) { return false; } hit.Point = ray.GetPoint(hitDistance); hit.Distance = hitDistance; hit.Normal = planeNormal.normalized; return true; } return false; bool Raycast(in Ray ray, out float enter) { float num = Vector3.Dot(ray.direction, planeNormal); if (Mathf.Approximately(num, 0f)) { enter = 0f; return false; } enter = -originDistance / num; return enter > 0f; } } private static Vector3 _forward = Vector3.forward; private static Vector3 _back = Vector3.back; private void GetPlaneParameters(out Vector3 planeNormal, out float planeDistance) { transform.GetPositionAndRotation(out Vector3 position, out Quaternion rotation); planeNormal = rotation * (_facing == NormalFacing.Forward ? _forward : _back); planeDistance = -Vector3.Dot(planeNormal, position); } /// /// Gets a Unity Plane representation of the mathematical plane represented by this PlaneSurface. /// public Plane GetPlane() { return new Plane(Normal, transform.position); } #region Inject /// /// Injects all required dependencies for a dynamically instantiated PlaneSurface; 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 InjectAllPlaneSurface(NormalFacing facing, bool doubleSided) { InjectNormalFacing(facing); InjectDoubleSided(doubleSided); } /// /// Sets the value for a dynamically instantiated PlaneSurface. This method exists /// to support Interaction SDK's dependency injection pattern and is not needed for typical Unity Editor-based usage. /// public void InjectNormalFacing(NormalFacing facing) { _facing = facing; } /// /// Sets whether a dynamically instantiated PlaneSurface should be interactable from both sides. This method exists /// to support Interaction SDK's dependency injection pattern and is not needed for typical Unity Editor-based usage. /// public void InjectDoubleSided(bool doubleSided) { _doubleSided = doubleSided; } #endregion } }