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.
262 lines
8.7 KiB
262 lines
8.7 KiB
|
1 month ago
|
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);
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|