Unity Udexreal开发插件包
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

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);
}
}
}