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.
818 lines
33 KiB
818 lines
33 KiB
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; |
|
} |
|
} |
|
} |
|
}
|
|
|