/*
* 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
}
}