You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
261 lines
8.7 KiB
261 lines
8.7 KiB
using NaughtyAttributes; |
|
using System.Collections.Generic; |
|
using UnityEngine; |
|
|
|
namespace UDE_HAND_INTERACTION |
|
{ |
|
public class InteractableSurface : MonoBehaviour, ISurface, IBounds, IClippedSurface<IBoundsClipper> |
|
{ |
|
public enum NormalFacing |
|
{ |
|
Backward, |
|
Forward, |
|
} |
|
|
|
public Transform Transform => transform; |
|
|
|
[SerializeField] |
|
private NormalFacing _facing = NormalFacing.Backward; |
|
|
|
[SerializeField] |
|
private bool _doubleSided = false; |
|
|
|
private InteractableSurface _interactableSurface; |
|
public ISurface BackingSurface => _interactableSurface; |
|
|
|
public enum SurfaceType |
|
{ |
|
Square, |
|
Circle, |
|
Infinity |
|
} |
|
|
|
public SurfaceType surfaceType = SurfaceType.Square; |
|
[HideInInspector] |
|
public float _radius = 0.1f; |
|
|
|
[HideInInspector] |
|
public Vector3 _position = Vector3.zero; |
|
[HideInInspector] |
|
public float _width = 0.1f; |
|
[HideInInspector] |
|
public float _height = 0.1f; |
|
|
|
public Vector3 Position |
|
{ |
|
get => _position; |
|
set => _position = value; |
|
} |
|
|
|
public Vector3 Size |
|
{ |
|
get => new(_width, _height, 0); |
|
set { _width = value[0]; _height = value[1]; } |
|
} |
|
|
|
|
|
[HideInInspector, SerializeField, Interface(typeof(IBoundsClipper))] |
|
private List<Object> _clippers; |
|
|
|
public bool GetLocalBounds(Transform localTo, out Bounds bounds) |
|
{ |
|
Vector3 localPos = localTo.InverseTransformPoint(transform.TransformPoint(Position)); |
|
Vector3 localSize = localTo.InverseTransformVector(transform.TransformVector(Size)); |
|
bounds = new Bounds(localPos, localSize); |
|
return isActiveAndEnabled; |
|
} |
|
|
|
public IEnumerable<IBoundsClipper> GetClippers() |
|
{ |
|
foreach (var clipper in _clippers) |
|
{ |
|
yield return clipper as IBoundsClipper; |
|
} |
|
} |
|
|
|
public bool ClipBounds(in Bounds bounds, out Bounds clipped) |
|
{ |
|
clipped = bounds; |
|
|
|
foreach (var clipperMono in _clippers) |
|
{ |
|
IBoundsClipper clipper = clipperMono as IBoundsClipper; |
|
if (clipper == null || !clipper.GetLocalBounds(transform, out Bounds clipTo)) |
|
{ |
|
continue; |
|
} |
|
|
|
if (!clipped.Clip(clipTo, out clipped)) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
protected virtual void Start() |
|
{ |
|
_interactableSurface = GetComponent<InteractableSurface>(); |
|
_clippers.Add(gameObject); |
|
} |
|
|
|
public Vector3 Normal |
|
{ |
|
get |
|
{ |
|
return _facing == NormalFacing.Forward ? transform.forward : -transform.forward; |
|
} |
|
} |
|
|
|
private bool IsPointAboveSurface(Vector3 point) |
|
{ |
|
Plane plane = GetPlane(); |
|
return plane.GetSide(point); |
|
} |
|
|
|
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(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); |
|
} |
|
|
|
public bool ClosestSurfacePoint(in Vector3 point, out SurfaceHit hit, float maxDistance) |
|
{ |
|
hit = new SurfaceHit(); |
|
Plane plane = GetPlane(); |
|
|
|
if(surfaceType == SurfaceType.Circle) |
|
{ |
|
Vector3 vectorFromPlane = point - Transform.position; |
|
Vector3 planeNormal = Normal; |
|
Vector3 projectedPoint = Vector3.ProjectOnPlane(vectorFromPlane, planeNormal); |
|
|
|
float distanceFromCenterSqr = projectedPoint.sqrMagnitude; |
|
float worldRadius = Transform.lossyScale.x * _radius; |
|
if (distanceFromCenterSqr > worldRadius * worldRadius) |
|
{ |
|
projectedPoint = worldRadius * projectedPoint.normalized; |
|
} |
|
Vector3 closestPoint = projectedPoint + Transform.position; |
|
|
|
hit.Point = closestPoint; |
|
hit.Normal = plane.normal; |
|
hit.Distance = Vector3.Distance(point, closestPoint); |
|
if (Vector3.Distance(point, transform.position) > _radius) return false; |
|
return maxDistance <= 0 || hit.Distance <= maxDistance; |
|
} |
|
else if(surfaceType == SurfaceType.Square) |
|
{ |
|
bool result = true; |
|
float hitDistance = plane.GetDistanceToPoint(point); |
|
if (maxDistance > 0 && Mathf.Abs(hitDistance) > maxDistance) |
|
{ |
|
result = false; |
|
} |
|
hit.Point = plane.ClosestPointOnPlane(point); |
|
hit.Distance = IsPointAboveSurface(point) ? hitDistance : -hitDistance; |
|
hit.Normal = plane.normal; |
|
|
|
if (result && GetLocalBounds(transform, out Bounds clippedPlane)) |
|
{ |
|
hit.Point = ClampPoint(hit.Point, clippedPlane); |
|
hit.Distance = Vector3.Distance(point, hit.Point); |
|
float VaildMaxDist = Mathf.Sqrt(Mathf.Pow(_width, 2) + Mathf.Pow(_height, 2)) * 0.5f; |
|
if (Vector3.Distance(point, transform.position) > VaildMaxDist) return false; |
|
return maxDistance <= 0 || hit.Distance <= maxDistance; |
|
} |
|
return false; |
|
} |
|
else |
|
{ |
|
float hitDistance = plane.GetDistanceToPoint(point); |
|
if (maxDistance > 0 && Mathf.Abs(hitDistance) > maxDistance) |
|
{ |
|
return false; |
|
} |
|
|
|
hit.Point = plane.ClosestPointOnPlane(point); |
|
hit.Distance = IsPointAboveSurface(point) ? hitDistance : -hitDistance; |
|
hit.Normal = plane.normal; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
public Bounds Bounds |
|
{ |
|
get |
|
{ |
|
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); |
|
} |
|
} |
|
|
|
public bool Raycast(in Ray ray, out SurfaceHit hit, float maxDistance) |
|
{ |
|
hit = new SurfaceHit(); |
|
Plane plane = GetPlane(); |
|
|
|
if (!_doubleSided && !IsPointAboveSurface(ray.origin)) |
|
{ |
|
return false; |
|
} |
|
|
|
if (plane.Raycast(ray, out float hitDistance)) |
|
{ |
|
if (maxDistance > 0 && hitDistance > maxDistance) |
|
{ |
|
return false; |
|
} |
|
|
|
if (surfaceType == SurfaceType.Circle) |
|
{ |
|
Vector3 hitPointWorld = ray.GetPoint(hitDistance); |
|
Vector3 hitPointLocal = Transform.InverseTransformPoint(hitPointWorld); |
|
|
|
if (Mathf.Abs(hitPointLocal.x) > _radius || Mathf.Abs(hitPointLocal.y) > _radius) |
|
{ |
|
return false; |
|
} |
|
|
|
hit.Point = hitPointWorld; |
|
hit.Normal = plane.normal; |
|
hit.Distance = hitDistance; |
|
} |
|
else if(surfaceType == SurfaceType.Square) |
|
{ |
|
|
|
hit.Point = ray.GetPoint(hitDistance); |
|
hit.Normal = plane.normal; |
|
hit.Distance = hitDistance; |
|
|
|
Bounds InfiniteBounds = new Bounds(Vector3.zero, Vector3.one * float.PositiveInfinity); |
|
return ClipBounds(InfiniteBounds, out Bounds clipBounds) && |
|
clipBounds.size != Vector3.zero && |
|
clipBounds.Contains(Transform.InverseTransformPoint(hit.Point)); |
|
} |
|
else |
|
{ |
|
hit.Point = ray.GetPoint(hitDistance); |
|
hit.Normal = plane.normal; |
|
hit.Distance = hitDistance; |
|
} |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
public Plane GetPlane() |
|
{ |
|
return new Plane(Normal, transform.position); |
|
} |
|
|
|
} |
|
} |
|
|
|
|