PokeInteractor.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using UnityEngine.Assertions;
  4. namespace Rokid.UXR.Interaction
  5. {
  6. /// <summary>
  7. /// Defines a near-poke interaction that is driven by a near-distance
  8. /// proximity computation and a raycast between the position
  9. /// recorded across two frames against a target surface.
  10. /// 定义由近距离接近计算和跨两帧记录的位置与目标表面之间的光线投射驱动的近戳交互。
  11. /// </summary>
  12. public class PokeInteractor : PointerInteractor<PokeInteractor, PokeInteractable>
  13. {
  14. [SerializeField]
  15. private HandType hand = HandType.None;
  16. [SerializeField]
  17. // 戳原点跟踪提供的变换
  18. [Tooltip("The poke origin tracks the provided transform.")]
  19. private Transform _pointTransform;
  20. [SerializeField, Optional]
  21. private Transform _surfaceHitPoint;
  22. [SerializeField]
  23. // (米,世界)定位在原点的球体的半径
  24. [Tooltip("(Meters, World) The radius of the sphere positioned at the origin.")]
  25. private float _radius = 0.005f;
  26. [SerializeField]
  27. // (米, 世界) 当 poke 原点超过此值时,将触发 poke unselect
  28. [Tooltip("(Meters, World) A poke unselect fires when the poke origin surpasses this " +
  29. "distance above a surface.")]
  30. private float _touchReleaseThreshold = 0.002f;
  31. // [FormerlySerializedAs("_zThreshold")]
  32. [SerializeField]
  33. //(米,世界)到表面的距离低于该阈值
  34. [Tooltip("(Meters, World) The threshold below which distances to a surface " +
  35. "are treated as equal for the purposes of ranking.")]
  36. private float _equalDistanceThreshold = 0.001f;
  37. private Vector3 ClosestPoint;
  38. public Vector3 TouchPoint;
  39. public Vector3 TouchNormal { get; private set; }
  40. [HideInInspector]
  41. public Vector3 InteractorButtonUpPosition;
  42. public float Radius => _radius;
  43. public Vector3 Origin { get; private set; }
  44. private Vector3 _previousPokeOrigin;
  45. /// <summary>
  46. /// 前一个候选者
  47. /// </summary>
  48. private PokeInteractable _previousCandidate = null;
  49. /// <summary>
  50. /// 点击可交互
  51. /// </summary>
  52. private PokeInteractable _hitInteractable = null;
  53. private Vector3 _previousSurfacePointLocal;
  54. private Vector3 _firstTouchPointLocal;
  55. private Vector3 _targetTouchPointLocal;
  56. private Vector3 _easeTouchPointLocal;
  57. private bool _isDragging;
  58. private ProgressCurve _dragEaseCurve;
  59. private Vector3 _dragCompareSurfacePointLocal;
  60. private float _maxDistanceFromFirstTouchPoint;
  61. private Dictionary<PokeInteractable, Matrix4x4> _previousSurfaceTransformMap;
  62. private float _previousProgress;
  63. protected override void Start()
  64. {
  65. base.Start();
  66. Assert.IsNotNull(_pointTransform);
  67. _dragEaseCurve = new ProgressCurve();
  68. _previousSurfaceTransformMap = new Dictionary<PokeInteractable, Matrix4x4>();
  69. }
  70. protected override void DoPreprocess()
  71. {
  72. base.DoPreprocess();
  73. _previousPokeOrigin = Origin;
  74. Origin = _pointTransform.position;
  75. }
  76. protected override void DoPostprocess()
  77. {
  78. base.DoPostprocess();
  79. var interactables = PokeInteractable.Registry.List(this);
  80. foreach (PokeInteractable interactable in interactables)
  81. {
  82. _previousSurfaceTransformMap[interactable] =
  83. interactable.Surface.Transform.worldToLocalMatrix;
  84. }
  85. }
  86. protected override bool ComputeShouldSelect()
  87. {
  88. return _hitInteractable != null;
  89. }
  90. protected override bool ComputeShouldUnselect()
  91. {
  92. return _hitInteractable == null;
  93. }
  94. protected override void DoHoverUpdate()
  95. {
  96. if (_interactable != null)
  97. {
  98. TouchPoint = _interactable.ComputeClosestPoint(Origin);
  99. TouchNormal = _interactable.ClosestSurfaceNormal(TouchPoint);
  100. }
  101. }
  102. public HandType GetHandType()
  103. {
  104. return hand;
  105. }
  106. protected override PokeInteractable ComputeCandidate()
  107. {
  108. if (_hitInteractable != null)
  109. {
  110. return _hitInteractable;
  111. }
  112. // First, see if we trigger a press on any interactable
  113. PokeInteractable closestInteractable = ComputeSelectCandidate();
  114. if (closestInteractable != null)
  115. {
  116. // We have found an active hit target, so we return it
  117. _hitInteractable = closestInteractable;
  118. _previousCandidate = closestInteractable;
  119. return _hitInteractable;
  120. }
  121. // Otherwise we have no active interactable, so we do a proximity-only check for
  122. // closest hovered interactable (above the surface)
  123. // 计算获取最优的Hover可交互物体
  124. closestInteractable = ComputeBestHoverInteractable();
  125. _previousCandidate = closestInteractable;
  126. return closestInteractable;
  127. }
  128. private PokeInteractable ComputeSelectCandidate()
  129. {
  130. PokeInteractable closestInteractable = null;
  131. float closestDist = float.MaxValue;
  132. float minNormalProject = float.MaxValue;
  133. var interactables = PokeInteractable.Registry.List(this);
  134. // Check the surface first as a movement through this will
  135. // automatically put us in a "active" state. We expect the raycast
  136. // to happen only in one direction
  137. foreach (PokeInteractable interactable in interactables)
  138. {
  139. Matrix4x4 previousSurfaceMatrix =
  140. _previousSurfaceTransformMap.ContainsKey(interactable)
  141. ? _previousSurfaceTransformMap[interactable]
  142. : interactable.Surface.Transform.worldToLocalMatrix;
  143. Vector3 localPokeOrigin = previousSurfaceMatrix.MultiplyPoint(_previousPokeOrigin);
  144. Vector3 adjustedPokeOrigin =
  145. interactable.Surface.Transform.TransformPoint(localPokeOrigin);
  146. if (!PassesEnterHoverDistanceCheck(adjustedPokeOrigin, interactable))
  147. {
  148. continue;
  149. }
  150. Vector3 moveDirection = Origin - adjustedPokeOrigin;
  151. float magnitude = moveDirection.magnitude;
  152. if (magnitude == 0f)
  153. {
  154. return null;
  155. }
  156. moveDirection /= magnitude;
  157. Ray ray = new Ray(adjustedPokeOrigin, moveDirection);
  158. //TODO Poke 碰撞计算的关键逻辑
  159. UnityEngine.Debug.DrawLine(adjustedPokeOrigin, adjustedPokeOrigin + moveDirection * 10f, Color.green);
  160. Vector3 closestSurfaceNormal = interactable.ClosestSurfaceNormal(Origin);
  161. // First check that we are moving towards the surface by checking
  162. // the direction of our position delta with the forward direction of the surface normal.
  163. // This is to not allow presses from "behind" the surface.
  164. // Check if we are moving toward the surface
  165. if (Vector3.Dot(moveDirection, closestSurfaceNormal) < 0f)
  166. {
  167. // Then do a raycast against the surface
  168. bool hit = interactable.Surface.Raycast(ray, out SurfaceHit surfaceHit);
  169. hit = hit && surfaceHit.Distance <= magnitude;
  170. if (!hit)
  171. {
  172. // We may still be touching the surface within our radius
  173. float distance = ComputeDistanceAbove(interactable, Origin);
  174. if (distance <= 0)
  175. {
  176. Vector3 closestSurfacePointToOrigin = interactable.ClosestSurfacePoint(Origin);
  177. hit = true;
  178. surfaceHit = new SurfaceHit()
  179. {
  180. Point = closestSurfacePointToOrigin,
  181. Normal = interactable.ClosestSurfaceNormal(Origin),
  182. Distance = distance
  183. };
  184. }
  185. }
  186. if (hit)
  187. {
  188. if (_surfaceHitPoint)
  189. {
  190. _surfaceHitPoint.position = surfaceHit.Point;
  191. }
  192. // Check if our collision lies outside of the optional volume mask
  193. if (interactable.VolumeMask != null &&
  194. !Collisions.IsPointWithinCollider(surfaceHit.Point, interactable.VolumeMask))
  195. {
  196. continue;
  197. }
  198. float distanceFromEdge =
  199. ComputeDistanceFrom(interactable, surfaceHit.Point);
  200. // Check if our collision lies outside of the max distance in the proximityfield
  201. if (distanceFromEdge > 0.01f)
  202. {
  203. continue;
  204. }
  205. // We collided against the surface and now we must rank this
  206. // interactable versus others that also pass this test this frame.
  207. // First we rank by normal distance traveled,
  208. // and secondly by closer proximity
  209. float normalProjection = Vector3.Dot(adjustedPokeOrigin - surfaceHit.Point, surfaceHit.Normal);
  210. bool normalDistanceEqual = Mathf.Abs(normalProjection - minNormalProject) < _equalDistanceThreshold;
  211. bool checkEdgeDistance = !normalDistanceEqual ||
  212. interactable.TiebreakerScore ==
  213. closestInteractable.TiebreakerScore;
  214. // Check if the point is either closer along the normal or
  215. // the normal delta with the best point so far is within the zThreshold and
  216. // is closer to the surface intersection point
  217. if ((!normalDistanceEqual && normalProjection < minNormalProject) ||
  218. (normalDistanceEqual && interactable.TiebreakerScore > closestInteractable.TiebreakerScore) ||
  219. (checkEdgeDistance && distanceFromEdge < closestDist))
  220. {
  221. minNormalProject = normalProjection;
  222. closestDist = distanceFromEdge;
  223. closestInteractable = interactable;
  224. }
  225. }
  226. }
  227. }
  228. if (closestInteractable != null)
  229. {
  230. ClosestPoint = closestInteractable.ComputeClosestPoint(Origin);
  231. TouchPoint = ClosestPoint;
  232. TouchNormal = closestInteractable.ClosestSurfaceNormal(TouchPoint);
  233. }
  234. else
  235. {
  236. TouchPoint = ClosestPoint = Vector3.zero;
  237. }
  238. return closestInteractable;
  239. }
  240. private bool PassesEnterHoverDistanceCheck(Vector3 position, PokeInteractable interactable)
  241. {
  242. if (interactable == _previousCandidate)
  243. {
  244. return true;
  245. }
  246. return ComputeDistanceAbove(interactable, position) > interactable.EnterHoverDistance;
  247. }
  248. private PokeInteractable ComputeBestHoverInteractable()
  249. {
  250. PokeInteractable closestInteractable = null;
  251. float closestDistance = float.MaxValue;
  252. var interactables = PokeInteractable.Registry.List(this);
  253. int interactableCount = 0;
  254. // We check that we're above the surface first as we don't
  255. // care about hovers that originate below the surface
  256. foreach (PokeInteractable interactable in interactables)
  257. {
  258. interactableCount++;
  259. // Hover if between EnterHover and MaxDistance
  260. // Or if above EnterHover last frame and within MaxDistance this frame:
  261. // eg. if EnterHover and MaxDistance are the same, still want to hover in one frame
  262. if (!PassesEnterHoverDistanceCheck(Origin, interactable) &&
  263. !PassesEnterHoverDistanceCheck(_previousPokeOrigin, interactable))
  264. {
  265. continue;
  266. }
  267. Vector3 closestSurfacePoint = interactable.ClosestSurfacePoint(Origin);
  268. Vector3 closestSurfaceNormal = interactable.ClosestSurfaceNormal(Origin);
  269. Vector3 surfaceToPoint = Origin - closestSurfacePoint;
  270. float magnitude = surfaceToPoint.magnitude;
  271. if (magnitude != 0f)
  272. {
  273. // Check if our position is above the surface
  274. if (Vector3.Dot(surfaceToPoint, closestSurfaceNormal) > 0f)
  275. {
  276. // Check if our position lies outside of the optional volume mask
  277. if (interactable.VolumeMask != null &&
  278. !Collisions.IsPointWithinCollider(Origin, interactable.VolumeMask))
  279. {
  280. continue;
  281. }
  282. // We're above the surface so now we must rank this
  283. // interactable versus others that also pass this test this frame
  284. // but may be at a closer proximity.
  285. float distanceFromSurfacePoint = ComputeDistanceFrom(interactable, Origin);
  286. if (distanceFromSurfacePoint > interactable.MaxDistance)
  287. {
  288. continue;
  289. }
  290. Ray ray = new Ray(Origin, interactable.ClosestSurfacePoint(Origin) - Origin);
  291. Debug.DrawLine(Origin, interactable.ClosestSurfacePoint(Origin), Color.yellow);
  292. bool hit = interactable.ColliderSurface.Raycast(ray, out SurfaceHit testHit, 1000);
  293. if (!hit)
  294. {
  295. // RKLog.Info("====Interaction==== filter use ray ");
  296. continue;
  297. }
  298. if (distanceFromSurfacePoint < closestDistance ||
  299. Mathf.Abs(distanceFromSurfacePoint - closestDistance) < _equalDistanceThreshold
  300. && interactable.TiebreakerScore > closestInteractable.TiebreakerScore)
  301. {
  302. closestDistance = distanceFromSurfacePoint;
  303. closestInteractable = interactable;
  304. }
  305. }
  306. }
  307. }
  308. RKLog.Debug("====Interaction==== interactables.Count :" + interactableCount);
  309. if (closestInteractable != null)
  310. {
  311. ClosestPoint = closestInteractable.ComputeClosestPoint(Origin);
  312. TouchPoint = ClosestPoint;
  313. TouchNormal = closestInteractable.ClosestSurfaceNormal(TouchPoint);
  314. }
  315. return closestInteractable;
  316. }
  317. protected override void InteractableSelected(PokeInteractable interactable)
  318. {
  319. if (interactable != null)
  320. {
  321. _previousSurfacePointLocal =
  322. _firstTouchPointLocal =
  323. _easeTouchPointLocal =
  324. _targetTouchPointLocal =
  325. interactable.Surface.Transform.InverseTransformPoint(TouchPoint);
  326. Vector3 lateralComparePoint = interactable.ClosestSurfacePoint(Origin);
  327. _dragCompareSurfacePointLocal = interactable.Surface.Transform.InverseTransformPoint(lateralComparePoint);
  328. _dragEaseCurve.Copy(interactable.DragThresholding.DragEaseCurve);
  329. _isDragging = false;
  330. _maxDistanceFromFirstTouchPoint = 0;
  331. }
  332. base.InteractableSelected(interactable);
  333. }
  334. protected override void HandleDisabled()
  335. {
  336. _hitInteractable = null;
  337. base.HandleDisabled();
  338. }
  339. protected override Pose ComputePointerPose()
  340. {
  341. if (Interactable == null)
  342. {
  343. return Pose.identity;
  344. }
  345. return new Pose(
  346. TouchPoint,
  347. Quaternion.LookRotation(Interactable.ClosestSurfaceNormal(TouchPoint))
  348. );
  349. }
  350. // The distance above a surface along the closest normal.
  351. // Returns 0 for where the sphere touches the surface along the normal.
  352. private float ComputeDistanceAbove(PokeInteractable interactable, Vector3 point)
  353. {
  354. //TODO Key Point 计算Hover的距离
  355. Vector3 closestSurfacePoint = interactable.ClosestSurfacePoint(point);
  356. Vector3 closestSurfaceNormal = interactable.ClosestSurfaceNormal(point);
  357. Vector3 surfaceToPoint = point - closestSurfacePoint;
  358. return Vector3.Dot(surfaceToPoint, closestSurfaceNormal) - _radius;
  359. }
  360. // The distance below a surface along the closest normal. Always positive.
  361. private float ComputeDepth(PokeInteractable interactable, Vector3 point)
  362. {
  363. return Mathf.Max(0f, -ComputeDistanceAbove(interactable, point));
  364. }
  365. // The distance from the closest point as computed by the proximity field and surface.
  366. // Returns the distance to the point without taking into account the surface normal.
  367. private float ComputeDistanceFrom(PokeInteractable interactable, Vector3 point)
  368. {
  369. Vector3 closestSurfacePoint = interactable.ComputeClosestPoint(point);
  370. Vector3 surfaceToPoint = point - closestSurfacePoint;
  371. return surfaceToPoint.magnitude - _radius;
  372. }
  373. protected override void DoSelectUpdate()
  374. {
  375. PokeInteractable interactable = _selectedInteractable;
  376. if (interactable == null)
  377. {
  378. _hitInteractable = null;
  379. return;
  380. }
  381. // Unselect if the interactor is above the surface by at least _touchReleaseThreshold
  382. if (ComputeDistanceAbove(interactable, Origin) > _touchReleaseThreshold)
  383. {
  384. _hitInteractable = null;
  385. return;
  386. }
  387. Vector3 closestSurfacePointWorld = interactable.ClosestSurfacePoint(Origin);
  388. Vector3 positionOnSurfaceLocal =
  389. interactable.Surface.Transform.InverseTransformPoint(closestSurfacePointWorld);
  390. if (interactable.DragThresholding.Enabled)
  391. {
  392. float worldDepthDelta = Mathf.Abs(ComputeDepth(interactable, Origin) -
  393. ComputeDepth(interactable, _previousPokeOrigin));
  394. Vector3 positionDeltaLocal = positionOnSurfaceLocal - _previousSurfacePointLocal;
  395. Vector3 positionDeltaWorld =
  396. interactable.Surface.Transform.TransformVector(positionDeltaLocal);
  397. bool isZMotion = worldDepthDelta > positionDeltaWorld.magnitude &&
  398. worldDepthDelta > interactable.DragThresholding.ZThreshold;
  399. if (isZMotion)
  400. {
  401. _dragCompareSurfacePointLocal = positionOnSurfaceLocal;
  402. }
  403. if (!_isDragging)
  404. {
  405. if (!isZMotion)
  406. {
  407. Vector3 surfaceDeltaLocal =
  408. positionOnSurfaceLocal - _dragCompareSurfacePointLocal;
  409. Vector3 surfaceDeltaWorld =
  410. interactable.Surface.Transform.TransformVector(surfaceDeltaLocal);
  411. if (surfaceDeltaWorld.magnitude >
  412. interactable.DragThresholding.SurfaceThreshold)
  413. {
  414. _isDragging = true;
  415. _dragEaseCurve.Start();
  416. _previousProgress = 0;
  417. _targetTouchPointLocal = positionOnSurfaceLocal;
  418. }
  419. }
  420. }
  421. else
  422. {
  423. if (isZMotion)
  424. {
  425. _isDragging = false;
  426. }
  427. else
  428. {
  429. _targetTouchPointLocal = positionOnSurfaceLocal;
  430. }
  431. }
  432. }
  433. else
  434. {
  435. _targetTouchPointLocal = positionOnSurfaceLocal;
  436. }
  437. Vector3 pinnedTouchPointLocal = _targetTouchPointLocal;
  438. if (SelectedInteractable.PositionPinning.Enabled)
  439. {
  440. Vector3 deltaFromCaptureLocal = pinnedTouchPointLocal - _firstTouchPointLocal;
  441. Vector3 deltaFromCaptureWorld =
  442. interactable.Surface.Transform.TransformVector(deltaFromCaptureLocal);
  443. _maxDistanceFromFirstTouchPoint = Mathf.Max(deltaFromCaptureWorld.magnitude, _maxDistanceFromFirstTouchPoint);
  444. float deltaAsPercent = 1;
  445. if (SelectedInteractable.PositionPinning.MaxPinDistance != 0f)
  446. {
  447. deltaAsPercent = Mathf.Clamp01(_maxDistanceFromFirstTouchPoint / SelectedInteractable.PositionPinning.MaxPinDistance);
  448. }
  449. pinnedTouchPointLocal = _firstTouchPointLocal + deltaFromCaptureLocal * deltaAsPercent;
  450. }
  451. float progress = _dragEaseCurve.Progress();
  452. if (progress != 1f)
  453. {
  454. float deltaProgress = progress - _previousProgress;
  455. Vector3 delta = pinnedTouchPointLocal - _easeTouchPointLocal;
  456. _easeTouchPointLocal += deltaProgress / (1f - _previousProgress) * delta;
  457. _previousProgress = progress;
  458. }
  459. else
  460. {
  461. _easeTouchPointLocal = pinnedTouchPointLocal;
  462. }
  463. TouchPoint =
  464. interactable.Surface.Transform.TransformPoint(_easeTouchPointLocal);
  465. TouchNormal = interactable.ClosestSurfaceNormal(TouchPoint);
  466. _previousSurfacePointLocal = positionOnSurfaceLocal;
  467. if (interactable.ReleaseDistance > 0.0f)
  468. {
  469. if (ComputeDistanceFrom(interactable, Origin) > interactable.ReleaseDistance)
  470. {
  471. GeneratePointerEvent(PointerEventType.Cancel, interactable, idIndex);
  472. _previousPokeOrigin = Origin;
  473. _previousCandidate = null;
  474. _hitInteractable = null;
  475. }
  476. }
  477. }
  478. }
  479. }