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.
819 lines
33 KiB
819 lines
33 KiB
|
1 month ago
|
using System;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using UnityEngine;
|
||
|
|
using UnityEngine.Serialization;
|
||
|
|
|
||
|
|
namespace UDE_HAND_INTERACTION
|
||
|
|
{
|
||
|
|
public class FingerPressInteractor : InteractDetector<FingerPressInteractor, FingerPressInteractable>
|
||
|
|
{
|
||
|
|
private class SurfaceHitCache
|
||
|
|
{
|
||
|
|
private readonly struct HitInfo
|
||
|
|
{
|
||
|
|
public readonly bool IsValid;
|
||
|
|
public readonly SurfaceHit Hit;
|
||
|
|
|
||
|
|
public HitInfo(bool isValid, SurfaceHit hit)
|
||
|
|
{
|
||
|
|
IsValid = isValid;
|
||
|
|
Hit = hit;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private Dictionary<FingerPressInteractable, HitInfo> _surfacePatchHitCache;
|
||
|
|
private Dictionary<FingerPressInteractable, HitInfo> _backingSurfaceHitCache;
|
||
|
|
private Vector3 _origin;
|
||
|
|
|
||
|
|
public bool GetPatchHit(FingerPressInteractable interactable, out SurfaceHit hit)
|
||
|
|
{
|
||
|
|
if (!_surfacePatchHitCache.ContainsKey(interactable))
|
||
|
|
{
|
||
|
|
bool isValid = interactable.SurfacePatch
|
||
|
|
.ClosestSurfacePoint(_origin, out SurfaceHit patchHit);
|
||
|
|
HitInfo info = new HitInfo(isValid, patchHit);
|
||
|
|
_surfacePatchHitCache.Add(interactable, info);
|
||
|
|
}
|
||
|
|
|
||
|
|
hit = _surfacePatchHitCache[interactable].Hit;
|
||
|
|
return _surfacePatchHitCache[interactable].IsValid;
|
||
|
|
}
|
||
|
|
|
||
|
|
public bool GetBackingHit(FingerPressInteractable interactable, out SurfaceHit hit)
|
||
|
|
{
|
||
|
|
if (!_backingSurfaceHitCache.ContainsKey(interactable))
|
||
|
|
{
|
||
|
|
bool isValid = interactable.SurfacePatch.BackingSurface
|
||
|
|
.ClosestSurfacePoint(_origin, out SurfaceHit backingHit);
|
||
|
|
HitInfo info = new HitInfo(isValid, backingHit);
|
||
|
|
_backingSurfaceHitCache.Add(interactable, info);
|
||
|
|
}
|
||
|
|
|
||
|
|
hit = _backingSurfaceHitCache[interactable].Hit;
|
||
|
|
return _backingSurfaceHitCache[interactable].IsValid;
|
||
|
|
}
|
||
|
|
|
||
|
|
public SurfaceHitCache()
|
||
|
|
{
|
||
|
|
_surfacePatchHitCache = new Dictionary<FingerPressInteractable, HitInfo>();
|
||
|
|
_backingSurfaceHitCache = new Dictionary<FingerPressInteractable, HitInfo>();
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Reset(Vector3 origin)
|
||
|
|
{
|
||
|
|
_origin = origin;
|
||
|
|
_surfacePatchHitCache.Clear();
|
||
|
|
_backingSurfaceHitCache.Clear();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[SerializeField]
|
||
|
|
private Transform TipTransform;
|
||
|
|
|
||
|
|
//public Vector3 _localPositionOffset;
|
||
|
|
|
||
|
|
[SerializeField]
|
||
|
|
private float _radius = 0.005f;
|
||
|
|
|
||
|
|
private float _touchReleaseThreshold = 0.002f;
|
||
|
|
|
||
|
|
[FormerlySerializedAs("_zThreshold")]
|
||
|
|
private float _equalDistanceThreshold = 0.001f;
|
||
|
|
|
||
|
|
public Vector3 ClosestPoint { get; private set; }
|
||
|
|
|
||
|
|
public Vector3 TouchPoint { get; private set; }
|
||
|
|
public Vector3 TouchNormal { get; private set; }
|
||
|
|
|
||
|
|
public float Radius => _radius;
|
||
|
|
|
||
|
|
public Vector3 Origin { get; private set; }
|
||
|
|
|
||
|
|
private Vector3 _previousFingerPressOrigin;
|
||
|
|
|
||
|
|
private FingerPressInteractable _previousCandidate = null;
|
||
|
|
private FingerPressInteractable _hitInteractable = null;
|
||
|
|
private FingerPressInteractable _recoilInteractable = null;
|
||
|
|
|
||
|
|
private Vector3 _previousSurfacePointLocal;
|
||
|
|
private Vector3 _firstTouchPointLocal;
|
||
|
|
private Vector3 _targetTouchPointLocal;
|
||
|
|
private Vector3 _easeTouchPointLocal;
|
||
|
|
|
||
|
|
private bool _isRecoiled;
|
||
|
|
private bool _isDragging;
|
||
|
|
private ProgressCurve _dragEaseCurve;
|
||
|
|
private ProgressCurve _pinningResyncCurve;
|
||
|
|
private Vector3 _dragCompareSurfacePointLocal;
|
||
|
|
private float _maxDistanceFromFirstTouchPoint;
|
||
|
|
|
||
|
|
private float _recoilVelocityExpansion;
|
||
|
|
private float _selectMaxDepth;
|
||
|
|
private float _reEnterDepth;
|
||
|
|
private float _lastUpdateTime;
|
||
|
|
|
||
|
|
private Func<float> _timeProvider;
|
||
|
|
|
||
|
|
private bool _isPassedSurface;
|
||
|
|
public bool IsPassedSurface
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
return _isPassedSurface;
|
||
|
|
}
|
||
|
|
set
|
||
|
|
{
|
||
|
|
bool previousValue = _isPassedSurface;
|
||
|
|
_isPassedSurface = value;
|
||
|
|
if (value != previousValue)
|
||
|
|
{
|
||
|
|
WhenPassedSurfaceChanged.Invoke(value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public Action<bool> WhenPassedSurfaceChanged = delegate { };
|
||
|
|
private SurfaceHitCache _hitCache;
|
||
|
|
|
||
|
|
private Dictionary<FingerPressInteractable, Matrix4x4> _previousSurfaceTransformMap;
|
||
|
|
private float _previousDragCurveProgress;
|
||
|
|
private float _previousPinningCurveProgress;
|
||
|
|
|
||
|
|
protected override void Awake()
|
||
|
|
{
|
||
|
|
base.Awake();
|
||
|
|
_timeProvider = () => Time.time;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void Start()
|
||
|
|
{
|
||
|
|
base.Start();
|
||
|
|
this.AssertField(TipTransform, nameof(TipTransform));
|
||
|
|
this.AssertField(_timeProvider, nameof(_timeProvider));
|
||
|
|
_dragEaseCurve = new ProgressCurve();
|
||
|
|
_pinningResyncCurve = new ProgressCurve();
|
||
|
|
_hitCache = new SurfaceHitCache();
|
||
|
|
_previousSurfaceTransformMap = new Dictionary<FingerPressInteractable, Matrix4x4>();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void DoPreprocess()
|
||
|
|
{
|
||
|
|
base.DoPreprocess();
|
||
|
|
_previousFingerPressOrigin = Origin;
|
||
|
|
Origin = TipTransform.position;
|
||
|
|
_hitCache.Reset(Origin);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void DoPostprocess()
|
||
|
|
{
|
||
|
|
base.DoPostprocess();
|
||
|
|
var interactables = FingerPressInteractable.Registry.List(this);
|
||
|
|
foreach (FingerPressInteractable interactable in interactables)
|
||
|
|
{
|
||
|
|
_previousSurfaceTransformMap[interactable] = interactable.SurfacePatch.BackingSurface.Transform.worldToLocalMatrix;
|
||
|
|
}
|
||
|
|
_lastUpdateTime = _timeProvider();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override bool ComputeShouldSelect()
|
||
|
|
{
|
||
|
|
if (_recoilInteractable != null)
|
||
|
|
{
|
||
|
|
float depth = ComputeFingerPressDepth(_recoilInteractable, Origin);
|
||
|
|
_reEnterDepth = Mathf.Min(depth + _recoilInteractable.RecoilAssist.ReEnterDistance, _reEnterDepth);
|
||
|
|
_hitInteractable = depth > _reEnterDepth ? _recoilInteractable : null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return _hitInteractable != null;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override bool ComputeShouldUnselect()
|
||
|
|
{
|
||
|
|
return _hitInteractable == null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool GetBackingHit(FingerPressInteractable interactable, out SurfaceHit hit)
|
||
|
|
{
|
||
|
|
return _hitCache.GetBackingHit(interactable, out hit);
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool GetPatchHit(FingerPressInteractable interactable, out SurfaceHit hit)
|
||
|
|
{
|
||
|
|
return _hitCache.GetPatchHit(interactable, out hit);
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool InteractableInRange(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
if (!_previousSurfaceTransformMap.ContainsKey(interactable))
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 previousLocalFingerPressOrigin = _previousSurfaceTransformMap[interactable].MultiplyPoint(_previousFingerPressOrigin);
|
||
|
|
Vector3 adjustedFingerPressOrigin = interactable.SurfacePatch.BackingSurface.Transform.TransformPoint(previousLocalFingerPressOrigin);
|
||
|
|
|
||
|
|
float hoverDistance = interactable == Interactable ? Mathf.Max(interactable.ExitHoverTangent, interactable.ExitHoverNormal) : Mathf.Max(interactable.EnterHoverTangent, interactable.EnterHoverNormal);
|
||
|
|
|
||
|
|
float moveDistance = Vector3.Distance(Origin, adjustedFingerPressOrigin);
|
||
|
|
float maxDistance = moveDistance + Radius + hoverDistance + _equalDistanceThreshold + interactable.CloseDistanceThreshold;
|
||
|
|
|
||
|
|
return interactable.SurfacePatch.ClosestSurfacePoint(Origin, out _, maxDistance);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void DoHoverUpdate()
|
||
|
|
{
|
||
|
|
if (_interactable != null && GetBackingHit(_interactable, out SurfaceHit backingHit))
|
||
|
|
{
|
||
|
|
TouchPoint = backingHit.Point;
|
||
|
|
TouchNormal = backingHit.Normal;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_recoilInteractable != null)
|
||
|
|
{
|
||
|
|
bool withinSurface = SurfaceUpdate(_recoilInteractable);
|
||
|
|
if (!withinSurface)
|
||
|
|
{
|
||
|
|
_isRecoiled = false;
|
||
|
|
_recoilInteractable = null;
|
||
|
|
_recoilVelocityExpansion = 0;
|
||
|
|
IsPassedSurface = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ShouldCancel(_recoilInteractable))
|
||
|
|
{
|
||
|
|
GenerateDetectorEvent(DetectorEventType.Cancel, _recoilInteractable);
|
||
|
|
_previousFingerPressOrigin = Origin;
|
||
|
|
_previousCandidate = null;
|
||
|
|
_hitInteractable = null;
|
||
|
|
_recoilInteractable = null;
|
||
|
|
_recoilVelocityExpansion = 0;
|
||
|
|
IsPassedSurface = false;
|
||
|
|
_isRecoiled = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override FingerPressInteractable ComputeCandidate()
|
||
|
|
{
|
||
|
|
if (_recoilInteractable != null)
|
||
|
|
{
|
||
|
|
return _recoilInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (_hitInteractable != null)
|
||
|
|
{
|
||
|
|
return _hitInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
FingerPressInteractable closestInteractable = ComputeSelectCandidate();
|
||
|
|
if (closestInteractable != null)
|
||
|
|
{
|
||
|
|
_hitInteractable = closestInteractable;
|
||
|
|
_previousCandidate = closestInteractable;
|
||
|
|
return _hitInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
closestInteractable = ComputeHoverCandidate();
|
||
|
|
_previousCandidate = closestInteractable;
|
||
|
|
|
||
|
|
return closestInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
private FingerPressInteractable ComputeSelectCandidate()
|
||
|
|
{
|
||
|
|
FingerPressInteractable closestInteractable = null;
|
||
|
|
float closestNormalDistance = float.MaxValue;
|
||
|
|
float closestTangentDistance = float.MaxValue;
|
||
|
|
|
||
|
|
var interactables = FingerPressInteractable.Registry.List(this);
|
||
|
|
|
||
|
|
foreach (FingerPressInteractable interactable in interactables)
|
||
|
|
{
|
||
|
|
if (!InteractableInRange(interactable) || !GetBackingHit(interactable, out SurfaceHit backingHit) || !GetPatchHit(interactable, out SurfaceHit patchHit))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Matrix4x4 previousSurfaceMatrix = _previousSurfaceTransformMap.ContainsKey(interactable) ? _previousSurfaceTransformMap[interactable] : interactable.SurfacePatch.BackingSurface.Transform.worldToLocalMatrix;
|
||
|
|
Vector3 localFingerPressOrigin = previousSurfaceMatrix.MultiplyPoint(_previousFingerPressOrigin);
|
||
|
|
Vector3 adjustedFingerPressOrigin = interactable.SurfacePatch.BackingSurface.Transform.TransformPoint(localFingerPressOrigin);
|
||
|
|
|
||
|
|
if (!PassesEnterHoverDistanceCheck(adjustedFingerPressOrigin, interactable))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 moveDirection = Origin - adjustedFingerPressOrigin;
|
||
|
|
float magnitude = moveDirection.magnitude;
|
||
|
|
if (magnitude == 0f)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
moveDirection /= magnitude;
|
||
|
|
Ray ray = new(adjustedFingerPressOrigin, moveDirection);
|
||
|
|
|
||
|
|
Vector3 closestSurfaceNormal = backingHit.Normal;
|
||
|
|
|
||
|
|
if (Vector3.Dot(moveDirection, closestSurfaceNormal) < 0f)
|
||
|
|
{
|
||
|
|
bool hit = interactable.SurfacePatch.BackingSurface.Raycast(ray, out SurfaceHit surfaceHit);
|
||
|
|
hit = hit && surfaceHit.Distance <= magnitude;
|
||
|
|
|
||
|
|
if (!hit)
|
||
|
|
{
|
||
|
|
float distance = ComputeDistanceAbove(interactable, Origin);
|
||
|
|
if (distance <= 0)
|
||
|
|
{
|
||
|
|
Vector3 closestSurfacePointToOrigin = backingHit.Point;
|
||
|
|
hit = true;
|
||
|
|
surfaceHit = new SurfaceHit()
|
||
|
|
{
|
||
|
|
Point = closestSurfacePointToOrigin,
|
||
|
|
Normal = backingHit.Normal,
|
||
|
|
Distance = distance
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (hit)
|
||
|
|
{
|
||
|
|
float tangentDistance = ComputeTangentDistance(interactable, surfaceHit.Point);
|
||
|
|
|
||
|
|
if (tangentDistance > (interactable != _previousCandidate ? interactable.EnterHoverTangent : interactable.ExitHoverTangent))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
float normalDistance = Vector3.Dot(adjustedFingerPressOrigin - surfaceHit.Point, surfaceHit.Normal);
|
||
|
|
bool normalDistanceEqual = Mathf.Abs(normalDistance - closestNormalDistance) < _equalDistanceThreshold;
|
||
|
|
|
||
|
|
if (normalDistance > closestNormalDistance + interactable.CloseDistanceThreshold)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (closestInteractable == null || normalDistance < closestNormalDistance - closestInteractable.CloseDistanceThreshold)
|
||
|
|
{
|
||
|
|
closestNormalDistance = normalDistance;
|
||
|
|
closestTangentDistance = tangentDistance;
|
||
|
|
closestInteractable = interactable;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tangentDistance < closestTangentDistance)
|
||
|
|
{
|
||
|
|
closestNormalDistance = normalDistance;
|
||
|
|
closestTangentDistance = tangentDistance;
|
||
|
|
closestInteractable = interactable;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (closestInteractable != null)
|
||
|
|
{
|
||
|
|
GetBackingHit(closestInteractable, out SurfaceHit backingHitClosest);
|
||
|
|
GetPatchHit(closestInteractable, out SurfaceHit patchHitClosest);
|
||
|
|
|
||
|
|
ClosestPoint = patchHitClosest.Point;
|
||
|
|
TouchPoint = backingHitClosest.Point;
|
||
|
|
TouchNormal = backingHitClosest.Normal;
|
||
|
|
|
||
|
|
foreach (FingerPressInteractable interactable in interactables)
|
||
|
|
{
|
||
|
|
if (interactable == closestInteractable)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!InteractableInRange(interactable) || !GetBackingHit(interactable, out SurfaceHit backingHit) || !GetPatchHit(interactable, out SurfaceHit patchHit))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Matrix4x4 previousSurfaceMatrix = _previousSurfaceTransformMap.ContainsKey(interactable) ? _previousSurfaceTransformMap[interactable] : interactable.SurfacePatch.BackingSurface.Transform.worldToLocalMatrix;
|
||
|
|
|
||
|
|
Vector3 localFingerPressOrigin = previousSurfaceMatrix.MultiplyPoint(_previousFingerPressOrigin);
|
||
|
|
Vector3 adjustedFingerPressOrigin = interactable.SurfacePatch.BackingSurface.Transform.TransformPoint(localFingerPressOrigin);
|
||
|
|
|
||
|
|
if (!PassesEnterHoverDistanceCheck(adjustedFingerPressOrigin, interactable))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 backingToTouchPoint = TouchPoint - backingHit.Point;
|
||
|
|
float normalDistance = Vector3.Dot(backingToTouchPoint, backingHit.Normal);
|
||
|
|
|
||
|
|
if (normalDistance <= 0 || normalDistance > interactable.CloseDistanceThreshold)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
float tangentDistance = ComputeTangentDistance(interactable, TouchPoint);
|
||
|
|
|
||
|
|
if (tangentDistance > interactable.EnterHoverTangent || tangentDistance > closestTangentDistance)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return closestInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
private bool PassesEnterHoverDistanceCheck(Vector3 position, FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
if (interactable == _previousCandidate)
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
float distanceThreshold = 0f;
|
||
|
|
if (interactable.MinThresholds.Enabled)
|
||
|
|
{
|
||
|
|
distanceThreshold = Mathf.Min(interactable.MinThresholds.MinNormal, MinFingerPressDepth(interactable));
|
||
|
|
}
|
||
|
|
|
||
|
|
return ComputeDistanceAbove(interactable, position) > distanceThreshold;
|
||
|
|
}
|
||
|
|
|
||
|
|
public float MinFingerPressDepth(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
float minDepth = interactable.ExitHoverNormal;
|
||
|
|
foreach (FingerPressInteractor fingerPressInteractor in interactable.Interactors)
|
||
|
|
{
|
||
|
|
float normalDistance = ComputeFingerPressDepth(interactable, fingerPressInteractor.Origin);
|
||
|
|
minDepth = Mathf.Min(normalDistance, minDepth);
|
||
|
|
}
|
||
|
|
|
||
|
|
return minDepth;
|
||
|
|
}
|
||
|
|
|
||
|
|
private FingerPressInteractable ComputeHoverCandidate()
|
||
|
|
{
|
||
|
|
FingerPressInteractable closestInteractable = null;
|
||
|
|
float closestNormalDistance = float.MaxValue;
|
||
|
|
float closestTangentDistance = float.MaxValue;
|
||
|
|
|
||
|
|
var interactables = FingerPressInteractable.Registry.List(this);
|
||
|
|
|
||
|
|
foreach (FingerPressInteractable interactable in interactables)
|
||
|
|
{
|
||
|
|
if (!InteractableInRange(interactable) || !GetBackingHit(interactable, out SurfaceHit backingHit) || !GetPatchHit(interactable, out SurfaceHit patchHit))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!PassesEnterHoverDistanceCheck(Origin, interactable) && !PassesEnterHoverDistanceCheck(_previousFingerPressOrigin, interactable))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 closestSurfacePoint = backingHit.Point;
|
||
|
|
Vector3 closestSurfaceNormal = backingHit.Normal;
|
||
|
|
|
||
|
|
Vector3 surfaceToPoint = Origin - closestSurfacePoint;
|
||
|
|
float magnitude = surfaceToPoint.magnitude;
|
||
|
|
if (magnitude != 0f)
|
||
|
|
{
|
||
|
|
if (Vector3.Dot(surfaceToPoint, closestSurfaceNormal) > 0f)
|
||
|
|
{
|
||
|
|
float normalDistance = ComputeDistanceAbove(interactable, Origin);
|
||
|
|
if (normalDistance > (_previousCandidate != interactable ? interactable.EnterHoverNormal : interactable.ExitHoverNormal))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
float tangentDistance = ComputeTangentDistance(interactable, Origin);
|
||
|
|
if (tangentDistance > (_previousCandidate != interactable ? interactable.EnterHoverTangent : interactable.ExitHoverTangent))
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool normalDistanceEqual = Mathf.Abs(normalDistance - closestNormalDistance) < _equalDistanceThreshold;
|
||
|
|
|
||
|
|
if (normalDistance > closestNormalDistance + interactable.CloseDistanceThreshold)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (closestInteractable == null || normalDistance < closestNormalDistance - closestInteractable.CloseDistanceThreshold)
|
||
|
|
{
|
||
|
|
closestInteractable = interactable;
|
||
|
|
closestNormalDistance = normalDistance;
|
||
|
|
closestTangentDistance = tangentDistance;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (tangentDistance < closestTangentDistance)
|
||
|
|
{
|
||
|
|
closestInteractable = interactable;
|
||
|
|
closestNormalDistance = normalDistance;
|
||
|
|
closestTangentDistance = tangentDistance;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (closestInteractable != null)
|
||
|
|
{
|
||
|
|
GetBackingHit(closestInteractable, out SurfaceHit backingHitClosest);
|
||
|
|
GetPatchHit(closestInteractable, out SurfaceHit patchHitClosest);
|
||
|
|
|
||
|
|
ClosestPoint = patchHitClosest.Point;
|
||
|
|
TouchPoint = backingHitClosest.Point;
|
||
|
|
TouchNormal = backingHitClosest.Normal;
|
||
|
|
}
|
||
|
|
|
||
|
|
return closestInteractable;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void InteractableSelected(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
if (interactable != null && GetBackingHit(interactable, out SurfaceHit backingHit))
|
||
|
|
{
|
||
|
|
_previousSurfacePointLocal =
|
||
|
|
_firstTouchPointLocal =
|
||
|
|
_easeTouchPointLocal =
|
||
|
|
_targetTouchPointLocal =
|
||
|
|
interactable.SurfacePatch.BackingSurface.Transform.InverseTransformPoint(TouchPoint);
|
||
|
|
|
||
|
|
Vector3 lateralComparePoint = backingHit.Point;
|
||
|
|
_dragCompareSurfacePointLocal = interactable.SurfacePatch.BackingSurface.Transform.InverseTransformPoint(lateralComparePoint);
|
||
|
|
_dragEaseCurve.Copy(interactable.DragThresholds.DragEaseCurve);
|
||
|
|
_pinningResyncCurve.Copy(interactable.PositionPinning.ResyncCurve);
|
||
|
|
_isDragging = false;
|
||
|
|
_isRecoiled = false;
|
||
|
|
|
||
|
|
_maxDistanceFromFirstTouchPoint = 0;
|
||
|
|
_selectMaxDepth = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
IsPassedSurface = true;
|
||
|
|
base.InteractableSelected(interactable);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void HandleDisabled()
|
||
|
|
{
|
||
|
|
_hitInteractable = null;
|
||
|
|
base.HandleDisabled();
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override Pose ComputeDetectorPose()
|
||
|
|
{
|
||
|
|
if (Interactable == null)
|
||
|
|
{
|
||
|
|
return Pose.identity;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!Interactable.ClosestBackingSurfaceHit(TouchPoint, out SurfaceHit hit))
|
||
|
|
{
|
||
|
|
return Pose.identity;
|
||
|
|
}
|
||
|
|
|
||
|
|
return new Pose(TouchPoint, Quaternion.LookRotation(hit.Normal));
|
||
|
|
}
|
||
|
|
|
||
|
|
private float ComputeDistanceAbove(FingerPressInteractable interactable, Vector3 point)
|
||
|
|
{
|
||
|
|
return SurfaceUtils.ComputeDistanceAbove(interactable.SurfacePatch, point, _radius);
|
||
|
|
}
|
||
|
|
|
||
|
|
private float ComputeFingerPressDepth(FingerPressInteractable interactable, Vector3 point)
|
||
|
|
{
|
||
|
|
return SurfaceUtils.ComputeDepth(interactable.SurfacePatch, point, _radius);
|
||
|
|
}
|
||
|
|
|
||
|
|
private float ComputeTangentDistance(FingerPressInteractable interactable, Vector3 point)
|
||
|
|
{
|
||
|
|
return SurfaceUtils.ComputeTangentDistance(interactable.SurfacePatch, point, _radius);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual bool SurfaceUpdate(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
if (interactable == null)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!GetBackingHit(interactable, out SurfaceHit backingHit))
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ComputeDistanceAbove(interactable, Origin) > _touchReleaseThreshold)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool wasRecoiled = _isRecoiled;
|
||
|
|
_isRecoiled = _hitInteractable == null && _recoilInteractable != null;
|
||
|
|
|
||
|
|
Vector3 closestSurfacePoint = backingHit.Point;
|
||
|
|
|
||
|
|
Vector3 positionOnSurfaceLocal = interactable.SurfacePatch.BackingSurface.Transform.InverseTransformPoint(closestSurfacePoint);
|
||
|
|
|
||
|
|
if (interactable.DragThresholds.Enabled)
|
||
|
|
{
|
||
|
|
float worldDepthDelta = Mathf.Abs(ComputeFingerPressDepth(interactable, Origin) - ComputeFingerPressDepth(interactable, _previousFingerPressOrigin));
|
||
|
|
Vector3 positionDeltaLocal = positionOnSurfaceLocal - _previousSurfacePointLocal;
|
||
|
|
Vector3 positionDeltaWorld = interactable.SurfacePatch.BackingSurface.Transform.TransformVector(positionDeltaLocal);
|
||
|
|
|
||
|
|
bool isZMotion = worldDepthDelta > positionDeltaWorld.magnitude && worldDepthDelta > interactable.DragThresholds.DragNormal;
|
||
|
|
|
||
|
|
if (isZMotion)
|
||
|
|
{
|
||
|
|
_dragCompareSurfacePointLocal = positionOnSurfaceLocal;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!_isDragging)
|
||
|
|
{
|
||
|
|
if (!isZMotion)
|
||
|
|
{
|
||
|
|
Vector3 surfaceDeltaLocal = positionOnSurfaceLocal - _dragCompareSurfacePointLocal;
|
||
|
|
Vector3 surfaceDeltaWorld = interactable.SurfacePatch.BackingSurface.Transform.TransformVector(surfaceDeltaLocal);
|
||
|
|
if (surfaceDeltaWorld.magnitude > interactable.DragThresholds.DragTangent)
|
||
|
|
{
|
||
|
|
_isDragging = true;
|
||
|
|
_dragEaseCurve.Start();
|
||
|
|
_previousDragCurveProgress = 0;
|
||
|
|
_targetTouchPointLocal = positionOnSurfaceLocal;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (isZMotion)
|
||
|
|
{
|
||
|
|
_isDragging = false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_targetTouchPointLocal = positionOnSurfaceLocal;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_targetTouchPointLocal = positionOnSurfaceLocal;
|
||
|
|
}
|
||
|
|
|
||
|
|
Vector3 pinnedTouchPointLocal = _targetTouchPointLocal;
|
||
|
|
if (interactable.PositionPinning.Enabled)
|
||
|
|
{
|
||
|
|
if (!_isRecoiled)
|
||
|
|
{
|
||
|
|
Vector3 deltaFromCaptureLocal = pinnedTouchPointLocal - _firstTouchPointLocal;
|
||
|
|
Vector3 deltaFromCaptureWorld = interactable.SurfacePatch.BackingSurface.Transform.TransformVector(deltaFromCaptureLocal);
|
||
|
|
_maxDistanceFromFirstTouchPoint = Mathf.Max(deltaFromCaptureWorld.magnitude, _maxDistanceFromFirstTouchPoint);
|
||
|
|
|
||
|
|
float deltaAsPercent = 1;
|
||
|
|
if (interactable.PositionPinning.MaxPinDistance != 0f)
|
||
|
|
{
|
||
|
|
deltaAsPercent = Mathf.Clamp01(_maxDistanceFromFirstTouchPoint / interactable.PositionPinning.MaxPinDistance);
|
||
|
|
deltaAsPercent = interactable.PositionPinning.PinningEaseCurve.Evaluate(deltaAsPercent);
|
||
|
|
}
|
||
|
|
|
||
|
|
pinnedTouchPointLocal = _firstTouchPointLocal + deltaFromCaptureLocal * deltaAsPercent;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (!wasRecoiled)
|
||
|
|
{
|
||
|
|
_pinningResyncCurve.Start();
|
||
|
|
_previousPinningCurveProgress = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
float pinningCurveProgress = _pinningResyncCurve.Progress();
|
||
|
|
if (pinningCurveProgress != 1f)
|
||
|
|
{
|
||
|
|
float deltaProgress = pinningCurveProgress - _previousPinningCurveProgress;
|
||
|
|
Vector3 delta = pinnedTouchPointLocal - _easeTouchPointLocal;
|
||
|
|
pinnedTouchPointLocal = _easeTouchPointLocal + deltaProgress / (1f - _previousPinningCurveProgress) * delta;
|
||
|
|
_previousPinningCurveProgress = pinningCurveProgress;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
float dragCurveProgress = _dragEaseCurve.Progress();
|
||
|
|
if (dragCurveProgress != 1f)
|
||
|
|
{
|
||
|
|
float deltaProgress = dragCurveProgress - _previousDragCurveProgress;
|
||
|
|
Vector3 delta = pinnedTouchPointLocal - _easeTouchPointLocal;
|
||
|
|
_easeTouchPointLocal += deltaProgress / (1f - _previousDragCurveProgress) * delta;
|
||
|
|
_previousDragCurveProgress = dragCurveProgress;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
_easeTouchPointLocal = pinnedTouchPointLocal;
|
||
|
|
}
|
||
|
|
|
||
|
|
TouchPoint = interactable.SurfacePatch.BackingSurface.Transform.TransformPoint(_easeTouchPointLocal);
|
||
|
|
interactable.ClosestBackingSurfaceHit(TouchPoint, out SurfaceHit hit);
|
||
|
|
TouchNormal = hit.Normal;
|
||
|
|
|
||
|
|
_previousSurfacePointLocal = positionOnSurfaceLocal;
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual bool ShouldCancel(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
return (interactable.CancelSelectNormal > 0.0f && ComputeFingerPressDepth(interactable, Origin) > interactable.CancelSelectNormal) ||
|
||
|
|
(interactable.CancelSelectTangent > 0.0f && ComputeTangentDistance(interactable, Origin) > interactable.CancelSelectTangent);
|
||
|
|
}
|
||
|
|
|
||
|
|
protected virtual bool ShouldRecoil(FingerPressInteractable interactable)
|
||
|
|
{
|
||
|
|
if (!interactable.RecoilAssist.Enabled)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
float depth = ComputeFingerPressDepth(interactable, Origin);
|
||
|
|
float deltaTime = _timeProvider() - _lastUpdateTime;
|
||
|
|
float recoilExitDistance = interactable.RecoilAssist.ExitDistance;
|
||
|
|
|
||
|
|
if (interactable.RecoilAssist.UseVelocityExpansion)
|
||
|
|
{
|
||
|
|
Vector3 frameDeltaWorld = Origin - _previousFingerPressOrigin;
|
||
|
|
float normalVelocity = Mathf.Max(0, Vector3.Dot(frameDeltaWorld, -TouchNormal));
|
||
|
|
normalVelocity = deltaTime > 0 ? normalVelocity / deltaTime : 0f;
|
||
|
|
float adjustment = Mathf.InverseLerp( interactable.RecoilAssist.VelocityExpansionMinSpeed, interactable.RecoilAssist.VelocityExpansionMaxSpeed, normalVelocity);
|
||
|
|
float targetRecoilVelocityExpansion = Mathf.Clamp01(adjustment) * interactable.RecoilAssist.VelocityExpansionDistance;
|
||
|
|
|
||
|
|
if (targetRecoilVelocityExpansion > _recoilVelocityExpansion)
|
||
|
|
{
|
||
|
|
_recoilVelocityExpansion = targetRecoilVelocityExpansion;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
float decayRate = interactable.RecoilAssist.VelocityExpansionDecayRate * deltaTime;
|
||
|
|
_recoilVelocityExpansion = Math.Max(targetRecoilVelocityExpansion, _recoilVelocityExpansion - decayRate);
|
||
|
|
}
|
||
|
|
|
||
|
|
recoilExitDistance += _recoilVelocityExpansion;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (depth > _selectMaxDepth)
|
||
|
|
{
|
||
|
|
_selectMaxDepth = depth;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (interactable.RecoilAssist.UseDynamicDecay)
|
||
|
|
{
|
||
|
|
Vector3 frameDeltaWorld = Origin - _previousFingerPressOrigin;
|
||
|
|
Vector3 normalDeltaWorld = Vector3.Project(frameDeltaWorld, TouchNormal);
|
||
|
|
float normalRatio = frameDeltaWorld.sqrMagnitude > 0.0000001f ? normalDeltaWorld.magnitude / frameDeltaWorld.magnitude : 1f;
|
||
|
|
float decayFactor = interactable.RecoilAssist.DynamicDecayCurve.Evaluate(normalRatio);
|
||
|
|
_selectMaxDepth = Mathf.Lerp(_selectMaxDepth, depth, decayFactor * deltaTime);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (depth < _selectMaxDepth - recoilExitDistance)
|
||
|
|
{
|
||
|
|
_reEnterDepth = depth + interactable.RecoilAssist.ReEnterDistance;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
protected override void DoSelectUpdate()
|
||
|
|
{
|
||
|
|
bool withinSurface = SurfaceUpdate(_selectedInteractable);
|
||
|
|
if (!withinSurface)
|
||
|
|
{
|
||
|
|
_hitInteractable = null;
|
||
|
|
IsPassedSurface = _recoilInteractable != null;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ShouldCancel(_selectedInteractable))
|
||
|
|
{
|
||
|
|
GenerateDetectorEvent(DetectorEventType.Cancel, _selectedInteractable);
|
||
|
|
_previousFingerPressOrigin = Origin;
|
||
|
|
_previousCandidate = null;
|
||
|
|
_hitInteractable = null;
|
||
|
|
_recoilInteractable = null;
|
||
|
|
_recoilVelocityExpansion = 0;
|
||
|
|
IsPassedSurface = false;
|
||
|
|
_isRecoiled = false;
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ShouldRecoil(_selectedInteractable))
|
||
|
|
{
|
||
|
|
_hitInteractable = null;
|
||
|
|
_recoilInteractable = _selectedInteractable;
|
||
|
|
_selectMaxDepth = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|