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