Tanoda
AnchorableBehaviour.cs
Go to the documentation of this file.
1/******************************************************************************
2 * Copyright (C) Ultraleap, Inc. 2011-2020. *
3 * *
4 * Use subject to the terms of the Apache License 2.0 available at *
5 * http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
6 * between Ultraleap and you, your company or other organization. *
7 ******************************************************************************/
8
11using System;
12using System.Collections;
13using System.Collections.Generic;
14using UnityEngine;
16
17namespace Leap.Unity.Interaction {
18
24 public class AnchorableBehaviour : MonoBehaviour {
25
26 [Disable]
27 [SerializeField]
28 [Tooltip("Whether or not this AnchorableBehaviour is actively attached to its anchor.")]
29 private bool _isAttached = false;
30 public bool isAttached {
31 get {
32 return _isAttached;
33 }
34 set {
35 if (_isAttached != value) {
36 if (value == true) {
37 if (_anchor != null) {
38 _isAttached = value;
39 _anchor.NotifyAttached(this);
40 OnAttachedToAnchor.Invoke();
41 }
42 else {
43 Debug.LogWarning("Tried to attach an anchorable behaviour, but it has no assigned anchor.", this.gameObject);
44 }
45 }
46 else {
47 _isAttached = false;
48 _isLockedToAnchor = false;
49 _isRotationLockedToAnchor = false;
50
51 OnDetachedFromAnchor.Invoke();
52 _anchor.NotifyDetached(this);
53
54 _hasTargetPositionLastUpdate = false;
55 _hasTargetRotationLastUpdate = false;
56
57 // TODO: A more robust gravity fix.
58 if (_reactivateGravityOnDetach) {
59 if (interactionBehaviour != null) {
60 interactionBehaviour.rigidbody.useGravity = true;
61 }
62 _reactivateGravityOnDetach = false;
63 }
64 }
65 }
66 }
67 }
68
69 [Tooltip("The current anchor of this AnchorableBehaviour.")]
70 [OnEditorChange("anchor"), SerializeField]
71 private Anchor _anchor;
72 public Anchor anchor {
73 get {
74 return _anchor;
75 }
76 set {
77 if (_anchor != value) {
78 if (IsValidAnchor(value)) {
79 if (_anchor != null) {
80 OnDetachedFromAnchor.Invoke();
81 _anchor.NotifyDetached(this);
82 }
83
84 _isLockedToAnchor = false;
85 _isRotationLockedToAnchor = false;
86 _anchor = value;
87 _hasTargetPositionLastUpdate = false;
88 _hasTargetRotationLastUpdate = false;
89
90 if (_anchor != null) {
91 isAttached = true;
92 }
93 else {
94 isAttached = false;
95 }
96 }
97 else {
98 Debug.LogWarning("The '" + value.name + "' anchor is not in " + this.name + "'s anchor group.", this.gameObject);
99 }
100 }
101 }
102 }
103
104 [Tooltip("The anchor group for this AnchorableBehaviour. If set to null, all Anchors "
105 + "will be valid anchors for this object.")]
106 [OnEditorChange("anchorGroup"), SerializeField]
107 private AnchorGroup _anchorGroup;
109 get { return _anchorGroup; }
110 set {
111 if (_anchorGroup != null) _anchorGroup.NotifyAnchorableObjectRemoved(this);
112
113 _anchorGroup = value;
114 if (anchor != null && !_anchorGroup.Contains(anchor)) {
115 anchor = null;
116 Debug.LogWarning(this.name + "'s anchor is not within its anchorGroup (setting it to null).", this.gameObject);
117 }
118
119 if (_anchorGroup != null) _anchorGroup.NotifyAnchorableObjectAdded(this);
120 }
121 }
122
123 [Header("Attachment")]
124
125 [Tooltip("Anchors beyond this range are ignored as possible anchors for this object.")]
126 public float maxAnchorRange = 0.3F;
127
128 [Tooltip("Only allowed when an InteractionBehaviour is attached to this object. If enabled, this "
129 + "object's Attach() method or its variants will weigh its velocity towards an anchor along "
130 + "with its proximity when seeking an anchor to attach to.")]
131 [DisableIf("_interactionBehaviourIsNull", true)]
132 public bool useTrajectory = true;
133
134 [Tooltip("The fraction of the maximum anchor range to use as the effective max range when "
135 + "useTrajectory is enabled, but the object attempts to find an anchor without any "
136 + "velocity.")]
137 [SerializeField]
138 [Range(0.01F, 1F)]
139 private float _motionlessRangeFraction = 0.40F;
140 [SerializeField, Disable]
141 private float _maxMotionlessRange;
142
143 [Tooltip("The maximum angle this object's trajectory can be away from an anchor to consider it as "
144 + "an anchor to attach to.")]
145 [SerializeField]
146 [Range(20F, 90F)]
147 private float _maxAttachmentAngle = 60F;
149 private float _minAttachmentDotProduct;
150
151 [Tooltip("Always attach an anchor if there is one within this distance, regardless "
152 + "of trajectory.")]
153 [SerializeField]
154 [MinValue(0f)]
155 private float _alwaysAttachDistance = 0f;
156
157 [Header("Motion")]
158
159 [Tooltip("Should the object move instantly to the anchor position?")]
160 public bool lockToAnchor = false;
161
162 [Tooltip("Should the object move smoothly towards the anchor at first, but lock to it once it reaches the anchor? "
163 + "Note: Disabling the AnchorableBehaviour will stop the object from moving towards its anchor, and will "
164 + "'release' it from the anchor, so that on re-enable the object will smoothly move to the anchor again.")]
165 [DisableIf("lockToAnchor", isEqualTo: true)]
166 public bool lockWhenAttached = true;
167
168 [Tooltip("While this object is moving smoothly towards its anchor, should it also inherit the motion of the "
169 + "anchor itself if the anchor is not stationary? Otherwise, the anchor might be able to run away from this "
170 + "AnchorableBehaviour and prevent it from actually getting to the anchor.")]
171 [DisableIf("lockToAnchor", isEqualTo: true)]
173
174 [Tooltip("How fast should the object move towards its target position? Higher values are faster.")]
175 [DisableIf("lockToAnchor", isEqualTo: true)]
176 [Range(0, 100F)]
177 public float anchorLerpCoeffPerSec = 20F;
178
179 [Header("Rotation")]
180
181 [Tooltip("Should the object also rotate to match its anchor's rotation? If checked, motion settings applied "
182 + "to how the anchor translates will also apply to how it rotates.")]
183 public bool anchorRotation = false;
184
185 [Header("Interaction")]
186
187 [Tooltip("Additional features are enabled when this GameObject also has an InteractionBehaviour component.")]
188 [Disable]
190 [SerializeField, HideInInspector]
191 private bool _interactionBehaviourIsNull = true;
192
193 [Tooltip("If the InteractionBehaviour is set, objects will automatically detach from their anchor when grasped.")]
194 [Disable]
195 public bool detachWhenGrasped = true;
196
197 [Tooltip("Should the AnchorableBehaviour automatically try to anchor itself when a grasp ends? If useTrajectory is enabled, "
198 + "this object will automatically attempt to attach to the nearest valid anchor that is in the direction of its trajectory, "
199 + "otherwise it will simply attempt to attach to its nearest valid anchor.")]
200 [SerializeField]
201 [OnEditorChange("tryAnchorNearestOnGraspEnd")]
202 private bool _tryAnchorNearestOnGraspEnd = true;
204 get {
205 return _tryAnchorNearestOnGraspEnd;
206 }
207 set {
208 if (interactionBehaviour != null) {
209 // Prevent duplicate subscription.
210 interactionBehaviour.OnGraspEnd -= tryToAnchorOnGraspEnd;
211 }
212
213 _tryAnchorNearestOnGraspEnd = value;
214 if (interactionBehaviour != null && _tryAnchorNearestOnGraspEnd) {
215 interactionBehaviour.OnGraspEnd += tryToAnchorOnGraspEnd;
216 }
217 }
218 }
219
220 [Tooltip("Should the object pull away from its anchor and reach towards the user's hand when the user's hand is nearby?")]
221 public bool isAttractedByHand = false;
222
223 [Tooltip("If the object is attracted to hands, how far should the object be allowed to pull away from its anchor "
224 + "towards a nearby InteractionHand? Value is in Unity distance units, WORLD space.")]
225 public float maxAttractionReach = 0.1F;
226
227 [Tooltip("This curve converts the distance of the hand (X axis) to the desired attraction reach distance for the object (Y axis). "
228 + "The evaluated value is clamped between 0 and 1, and then scaled by maxAttractionReach.")]
229 public AnimationCurve attractionReachByDistance;
230
231 private Anchor _preferredAnchor = null;
237 public Anchor preferredAnchor { get { return _preferredAnchor; } }
238
239 #region Events
240
244 public Action OnAttachedToAnchor = () => { };
245
249 public Action OnLockedToAnchor = () => { };
250
254 public Action OnDetachedFromAnchor = () => { };
255
259 public Action WhileAttachedToAnchor = () => { };
260
264 public Action WhileLockedToAnchor = () => { };
265
274 public Action OnPostTryAnchorOnGraspEnd = () => { };
275
276 #endregion
277
278 private bool _isLockedToAnchor = false;
279 private Vector3 _offsetTowardsHand = Vector3.zero;
280 private Vector3 _targetPositionLastUpdate = Vector3.zero;
281 private bool _hasTargetPositionLastUpdate = false;
282
283 private bool _isRotationLockedToAnchor = false;
284 private Quaternion _targetRotationLastUpdate = Quaternion.identity;
285 private bool _hasTargetRotationLastUpdate = false;
286
287 void OnValidate() {
288 refreshInteractionBehaviour();
289 refreshInspectorConveniences();
290 }
291
292 void Reset() {
293 refreshInteractionBehaviour();
294 }
295
296 void Awake() {
297 refreshInteractionBehaviour();
298 refreshInspectorConveniences();
299
300 if (interactionBehaviour != null) {
301 interactionBehaviour.OnGraspBegin += detachAnchorOnGraspBegin;
302
303 if (_tryAnchorNearestOnGraspEnd) {
304 interactionBehaviour.OnGraspEnd += tryToAnchorOnGraspEnd;
305 }
306 }
307
308 initUnityEvents();
309 }
310
311 void Start() {
312 if (anchor != null && _isAttached) {
315 }
316 }
317
318 private bool _reactivateGravityOnDetach = false;
319
320 void Update() {
321 updateAttractionToHand();
322
323 if (anchor != null && isAttached) {
324 if (interactionBehaviour != null && interactionBehaviour.rigidbody.useGravity) {
325 // TODO: This is a temporary fix for gravity to be fixed in a future IE PR.
326 // The proper solution involves switching the behaviour to FixedUpdate and more
327 // intelligently communicating with the attached InteractionBehaviour.
328 interactionBehaviour.rigidbody.useGravity = false;
329 _reactivateGravityOnDetach = true;
330 }
331
332 updateAnchorAttachment();
333 if (anchorRotation) {
334 updateAnchorAttachmentRotation();
335 }
336
337 WhileAttachedToAnchor.Invoke();
338
339 if (_isLockedToAnchor) {
340 WhileLockedToAnchor.Invoke();
341 }
342 }
343
344 updateAnchorPreference();
345 }
346
347 void OnDisable() {
348 if (!this.enabled) {
349 Detach();
350 }
351
352 // Make sure we don't leave dangling anchor-preference state.
353 endAnchorPreference();
354 }
355
356 void OnDestroy() {
357 if (interactionBehaviour != null) {
358 interactionBehaviour.OnGraspBegin -= detachAnchorOnGraspBegin;
359 interactionBehaviour.OnGraspEnd -= tryToAnchorOnGraspEnd;
360 }
361
362 // Make sure we don't leave dangling anchor-preference state.
363 endAnchorPreference();
364 }
365
366 private void refreshInspectorConveniences() {
367 _minAttachmentDotProduct = Mathf.Cos(_maxAttachmentAngle * Mathf.Deg2Rad);
368 _maxMotionlessRange = maxAnchorRange * _motionlessRangeFraction;
369 }
370
371 private void refreshInteractionBehaviour() {
372 interactionBehaviour = GetComponent<InteractionBehaviour>();
373 _interactionBehaviourIsNull = interactionBehaviour == null;
374
375 detachWhenGrasped = !_interactionBehaviourIsNull;
376 if (_interactionBehaviourIsNull) useTrajectory = false;
377 }
378
383 public void Detach() {
384 isAttached = false;
385 }
386
393 if (this.anchorGroup != null) {
394 return this.anchorGroup.Contains(anchor);
395 }
396 else {
397 return true;
398 }
399 }
400
405 return (this.transform.position - anchor.transform.position).sqrMagnitude < maxAnchorRange * maxAnchorRange;
406 }
407
420 if (!useTrajectory) {
421 // Simply try to attach to the nearest valid anchor.
422 return GetNearestValidAnchor();
423 }
424 else {
425 // Pick the nearby valid anchor with the highest score, based on proximity and trajectory.
426 Anchor optimalAnchor = null;
427 float optimalScore = 0F;
428 Anchor testAnchor = null;
429 float testScore = 0F;
430 foreach (var anchor in GetNearbyValidAnchors()) {
431 testAnchor = anchor;
432 testScore = getAnchorScore(anchor);
433
434 // Scores of 0 mark ineligible anchors.
435 if (testScore == 0F) continue;
436
437 if (testScore > optimalScore) {
438 optimalAnchor = testAnchor;
439 optimalScore = testScore;
440 }
441 }
442
443 return optimalAnchor;
444 }
445 }
446
447 private List<Anchor> _nearbyAnchorsBuffer = new List<Anchor>();
457 public List<Anchor> GetNearbyValidAnchors(bool requireAnchorHasSpace = true,
458 bool requireAnchorActiveAndEnabled = true) {
459 HashSet<Anchor> anchorsToCheck;
460
461 if (this.anchorGroup == null) {
462 anchorsToCheck = Anchor.allAnchors;
463 }
464 else {
465 anchorsToCheck = this.anchorGroup.anchors;
466 }
467
468 _nearbyAnchorsBuffer.Clear();
469 foreach (var anchor in anchorsToCheck) {
470 if ((requireAnchorHasSpace && (!anchor.allowMultipleObjects && anchor.anchoredObjects.Count != 0))
471 || (requireAnchorActiveAndEnabled && !anchor.isActiveAndEnabled)) continue;
472
473 if ((anchor.transform.position - this.transform.position).sqrMagnitude <= maxAnchorRange * maxAnchorRange) {
474 _nearbyAnchorsBuffer.Add(anchor);
475 }
476 }
477
478 return _nearbyAnchorsBuffer;
479 }
480
491 public Anchor GetNearestValidAnchor(bool requireWithinRange = true,
492 bool requireAnchorHasSpace = true,
493 bool requireAnchorActiveAndEnabled = true) {
494 HashSet<Anchor> anchorsToCheck;
495
496 if (this.anchorGroup == null) {
497 anchorsToCheck = Anchor.allAnchors;
498 }
499 else {
500 anchorsToCheck = this.anchorGroup.anchors;
501 }
502
503 Anchor closestAnchor = null;
504 float closestDistSqrd = float.PositiveInfinity;
505 foreach (var testAnchor in anchorsToCheck) {
506 if (requireAnchorHasSpace) {
507 bool anchorHasSpace = testAnchor.anchoredObjects.Count == 0
508 || testAnchor.allowMultipleObjects;
509 if (!anchorHasSpace) {
510 // Skip the anchor for consideration.
511 continue;
512 }
513 }
514 if (requireAnchorActiveAndEnabled && !testAnchor.isActiveAndEnabled) {
515 // Skip the anchor for consideration.
516 continue;
517 }
518
519 float testDistanceSqrd = (testAnchor.transform.position - this.transform.position).sqrMagnitude;
520 if (testDistanceSqrd < closestDistSqrd) {
521 closestAnchor = testAnchor;
522 closestDistSqrd = testDistanceSqrd;
523 }
524 }
525
526 if (!requireWithinRange || closestDistSqrd < maxAnchorRange * maxAnchorRange) {
527 return closestAnchor;
528 }
529 else {
530 return null;
531 }
532 }
533
539 public bool TryAttach(bool ignoreRange = false) {
540 if (anchor != null && (ignoreRange || IsWithinRange(anchor))) {
541 isAttached = true;
542 return true;
543 }
544 else {
545 return false;
546 }
547 }
548
556
557 if (preferredAnchor != null) {
558 _preferredAnchor = preferredAnchor;
560 isAttached = true;
561 return true;
562 }
563
564 return false;
565 }
566
568 private float getAnchorScore(Anchor anchor) {
569 return GetAnchorScore(this.interactionBehaviour.rigidbody.position,
570 this.interactionBehaviour.rigidbody.velocity,
571 anchor.transform.position,
573 _maxMotionlessRange,
574 _minAttachmentDotProduct,
575 _alwaysAttachDistance);
576 }
577
585 public static float GetAnchorScore(Vector3 anchObjPos, Vector3 anchObjVel, Vector3 anchorPos, float maxDistance, float nonDirectedMaxDistance, float minAngleProduct,
586 float alwaysAttachDistance = 0f) {
587 // Calculated a "directedness" heuristic for determining whether the user is throwing or releasing without directed motion.
588 float directedness = anchObjVel.magnitude.Map(0.20F, 1F, 0F, 1F);
589
590 float effMaxDistance = directedness.Map(0F, 1F, nonDirectedMaxDistance, maxDistance);
591 Vector3 effPos = Utils.Map(Mathf.Sqrt(Mathf.Sqrt(directedness)), 0f, 1f,
592 anchObjPos, (anchObjPos - anchObjVel.normalized * effMaxDistance * 0.30f));
593
594 float distanceSqrd = (anchorPos - effPos).sqrMagnitude;
595 float distanceScore;
596 if (distanceSqrd > effMaxDistance * effMaxDistance) {
597 distanceScore = 0F;
598 }
599 else {
600 distanceScore = distanceSqrd.Map(0F, effMaxDistance * effMaxDistance, 1F, 0F);
601 }
602
603 float angleScore;
604 float dotProduct = Vector3.Dot(anchObjVel.normalized, (anchorPos - effPos).normalized);
605
606 // Angular score only factors in based on how directed the motion of the object is.
607 dotProduct = Mathf.Lerp(1F, dotProduct, directedness);
608
609 angleScore = dotProduct.Map(minAngleProduct, 1f, 0f, 1f);
610 angleScore *= angleScore;
611
612 // Support an "always-attach distance" within which only distanceScore matters
613 float semiDistanceSqrd = (anchorPos - Vector3.Lerp(anchObjPos, effPos, 0.5f)).sqrMagnitude;
614 float useAlwaysAttachDistanceAmount = semiDistanceSqrd.Map(0f, Mathf.Max(0.0001f, (0.25f * alwaysAttachDistance * alwaysAttachDistance)),
615 1f, 0f);
616 angleScore = useAlwaysAttachDistanceAmount.Map(0f, 1f, angleScore, 1f);
617
618 return distanceScore * angleScore;
619 }
620
621 private void updateAttractionToHand() {
622 if (interactionBehaviour == null || anchor == null || !isAttractedByHand) {
623 if (_offsetTowardsHand != Vector3.zero) {
624 _offsetTowardsHand = Vector3.Lerp(_offsetTowardsHand, Vector3.zero, 5F * Time.deltaTime);
625 }
626
627 return;
628 }
629
630 float reachTargetAmount = 0F;
631 Vector3 towardsHand = Vector3.zero;
633 Vector3 hoverTarget = Vector3.zero;
634
636 if (hoveringController is InteractionHand) {
638 hoverTarget = hoveringHand.PalmPosition.ToVector3();
639 }
640 else hoverTarget = hoveringController.hoverPoint;
641
642 reachTargetAmount = Mathf.Clamp01(attractionReachByDistance.Evaluate(
643 Vector3.Distance(hoverTarget, anchor.transform.position)));
644 towardsHand = hoverTarget - anchor.transform.position;
645 }
646
647 Vector3 targetOffsetTowardsHand = towardsHand * maxAttractionReach * reachTargetAmount;
648 _offsetTowardsHand = Vector3.Lerp(_offsetTowardsHand, targetOffsetTowardsHand, 5 * Time.deltaTime);
649 }
650
651 private void updateAnchorAttachment() {
652 // Initialize position.
653 Vector3 finalPosition;
654 if (interactionBehaviour != null) {
655 finalPosition = interactionBehaviour.rigidbody.position;
656 }
657 else {
658 finalPosition = this.transform.position;
659 }
660
661 // Update position based on anchor state.
662 Vector3 targetPosition = anchor.transform.position;
663 if (lockToAnchor) {
664 // In this state, we are simply locked directly to the anchor.
665 finalPosition = targetPosition + _offsetTowardsHand;
666
667 // Reset anchor position storage; it can't be updated from this state.
668 _hasTargetPositionLastUpdate = false;
669 }
670 else if (lockWhenAttached) {
671 if (_isLockedToAnchor) {
672 // In this state, we are already attached to the anchor.
673 finalPosition = targetPosition + _offsetTowardsHand;
674
675 // Reset anchor position storage; it can't be updated from this state.
676 _hasTargetPositionLastUpdate = false;
677 }
678 else {
679 // Undo any "reach towards hand" offset.
680 finalPosition -= _offsetTowardsHand;
681
682 // If desired, automatically correct for the anchor itself moving while attempting to return to it.
683 if (matchAnchorMotionWhileAttaching && this.transform.parent != anchor.transform) {
684 if (_hasTargetPositionLastUpdate) {
685 finalPosition += (targetPosition - _targetPositionLastUpdate);
686 }
687
688 _targetPositionLastUpdate = targetPosition;
689 _hasTargetPositionLastUpdate = true;
690 }
691
692 // Lerp towards the anchor.
693 finalPosition = Vector3.Lerp(finalPosition, targetPosition, anchorLerpCoeffPerSec * Time.deltaTime);
694 if (Vector3.Distance(finalPosition, targetPosition) < 0.001F) {
695 _isLockedToAnchor = true;
696 }
697
698 // Redo any "reach toward hand" offset.
699 finalPosition += _offsetTowardsHand;
700 }
701 }
702
703 // Set final position.
704 if (interactionBehaviour != null) {
705 interactionBehaviour.rigidbody.position = finalPosition;
706 this.transform.position = finalPosition;
707 }
708 else {
709 this.transform.position = finalPosition;
710 }
711 }
712
713 private void updateAnchorAttachmentRotation() {
714 // Initialize rotation.
715 Quaternion finalRotation;
716 if (interactionBehaviour != null) {
717 finalRotation = interactionBehaviour.rigidbody.rotation;
718 }
719 else {
720 finalRotation = this.transform.rotation;
721 }
722
723 // Update rotation based on anchor state.
724 Quaternion targetRotation = anchor.transform.rotation;
725 if (lockToAnchor) {
726 // In this state, we are simply locked directly to the anchor.
727 finalRotation = targetRotation;
728
729 // Reset anchor rotation storage; it can't be updated from this state.
730 _hasTargetPositionLastUpdate = false;
731 }
732 else if (lockWhenAttached) {
733 if (_isRotationLockedToAnchor) {
734 // In this state, we are already attached to the anchor.
735 finalRotation = targetRotation;
736
737 // Reset anchor rotation storage; it can't be updated from this state.
738 _hasTargetRotationLastUpdate = false;
739 }
740 else {
741 // If desired, automatically correct for the anchor itself moving while attempting to return to it.
742 if (matchAnchorMotionWhileAttaching && this.transform.parent != anchor.transform) {
743 if (_hasTargetRotationLastUpdate) {
744 finalRotation = (Quaternion.Inverse(_targetRotationLastUpdate) * targetRotation) * finalRotation;
745 }
746
747 _targetRotationLastUpdate = targetRotation;
748 _hasTargetRotationLastUpdate = true;
749 }
750
751 // Slerp towards the anchor rotation.
752 finalRotation = Quaternion.Slerp(finalRotation, targetRotation, anchorLerpCoeffPerSec * 0.8F * Time.deltaTime);
753
754 if (Quaternion.Angle(targetRotation, finalRotation) < 2F) {
755 _isRotationLockedToAnchor = true;
756 }
757 }
758 }
759
760 // Set final rotation.
761 if (interactionBehaviour != null) {
762 interactionBehaviour.rigidbody.rotation = finalRotation;
763 this.transform.rotation = finalRotation;
764 }
765 else {
766 this.transform.rotation = finalRotation;
767 }
768 }
769
770 private void updateAnchorPreference() {
771 Anchor newPreferredAnchor;
772 if (!isAttached) {
773 newPreferredAnchor = FindPreferredAnchor();
774 }
775 else {
776 newPreferredAnchor = null;
777 }
778
779 if (_preferredAnchor != newPreferredAnchor) {
780 if (_preferredAnchor != null) {
781 _preferredAnchor.NotifyEndAnchorPreference(this);
782 }
783
784 _preferredAnchor = newPreferredAnchor;
785
786 if (_preferredAnchor != null) {
787 _preferredAnchor.NotifyAnchorPreference(this);
788 }
789 }
790 }
791
792 private void endAnchorPreference() {
793 if (_preferredAnchor != null) {
794 _preferredAnchor.NotifyEndAnchorPreference(this);
795 _preferredAnchor = null;
796 }
797 }
798
799 private void detachAnchorOnGraspBegin() {
800 Detach();
801 }
802
803 private void tryToAnchorOnGraspEnd() {
805
807 }
808
809 #region Unity Events (Internal)
810
811 #pragma warning disable 0649
812 [SerializeField]
813 private EnumEventTable _eventTable;
814 #pragma warning restore 0649
815
816 public enum EventType {
817 OnAttachedToAnchor = 100,
818 OnLockedToAnchor = 105,
823 }
824
825 private void initUnityEvents() {
826 setupCallback(ref OnAttachedToAnchor, EventType.OnAttachedToAnchor);
827 setupCallback(ref OnLockedToAnchor, EventType.OnLockedToAnchor);
828 setupCallback(ref OnDetachedFromAnchor, EventType.OnDetachedFromAnchor);
829 setupCallback(ref WhileAttachedToAnchor, EventType.WhileAttachedToAnchor);
830 setupCallback(ref WhileLockedToAnchor, EventType.WhileLockedToAnchor);
831 setupCallback(ref OnPostTryAnchorOnGraspEnd, EventType.OnPostTryAnchorOnGraspEnd);
832 }
833
834 private void setupCallback(ref Action action, EventType type) {
835 if (_eventTable.HasUnityEvent((int)type)) {
836 action += () => _eventTable.Invoke((int)type);
837 }
838 }
839
840 #endregion
841
842 }
843
844}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
The Hand class reports the physical characteristics of a detected hand.
Definition: Hand.cs:26
Vector PalmPosition
The center position of the palm.
Definition: Hand.cs:165
void NotifyAnchorableObjectAdded(AnchorableBehaviour anchObj)
Definition: AnchorGroup.cs:69
void NotifyAnchorableObjectRemoved(AnchorableBehaviour anchObj)
Definition: AnchorGroup.cs:73
bool Contains(Anchor anchor)
Definition: AnchorGroup.cs:45
void NotifyAttached(AnchorableBehaviour anchObj)
Definition: Anchor.cs:129
HashSet< AnchorableBehaviour > anchoredObjects
Gets the set of AnchorableBehaviours currently attached to this anchor.
Definition: Anchor.cs:50
static HashSet< Anchor > allAnchors
Definition: Anchor.cs:22
void NotifyAnchorPreference(AnchorableBehaviour anchObj)
Definition: Anchor.cs:150
void NotifyEndAnchorPreference(AnchorableBehaviour anchObj)
Definition: Anchor.cs:158
void NotifyDetached(AnchorableBehaviour anchObj)
Definition: Anchor.cs:137
AnchorableBehaviours mix well with InteractionBehaviours you'd like to be able to pick up and place i...
Action OnLockedToAnchor
Called when this AnchorableBehaviour locks to an Anchor.
Anchor FindPreferredAnchor()
Attempts to find and return the best anchor for this anchorable object to attach to based on its curr...
Action OnPostTryAnchorOnGraspEnd
Called just after this anchorable behaviour's InteractionBehaviour OnObjectGraspEnd for this anchor....
Anchor GetNearestValidAnchor(bool requireWithinRange=true, bool requireAnchorHasSpace=true, bool requireAnchorActiveAndEnabled=true)
Returns the nearest valid anchor to this Anchorable object. If this anchorable object has its anchorG...
bool TryAttachToNearestAnchor()
Attempts to find and attach this anchorable object to the nearest valid anchor, or the most optimal n...
Action OnDetachedFromAnchor
Called when this AnchorableBehaviour detaches from an Anchor.
Action OnAttachedToAnchor
Called when this AnchorableBehaviour attaches to an Anchor.
static float GetAnchorScore(Vector3 anchObjPos, Vector3 anchObjVel, Vector3 anchorPos, float maxDistance, float nonDirectedMaxDistance, float minAngleProduct, float alwaysAttachDistance=0f)
Calculates and returns a score from 0 (non-valid anchor) to 1 (ideal anchor) based on the argument co...
List< Anchor > GetNearbyValidAnchors(bool requireAnchorHasSpace=true, bool requireAnchorActiveAndEnabled=true)
Returns all anchors within the max anchor range of this anchorable object. If this anchorable object ...
bool IsValidAnchor(Anchor anchor)
Returns whether the argument anchor is an acceptable anchor for this anchorable object; that is,...
bool IsWithinRange(Anchor anchor)
Returns whether the specified anchor is within attachment range of this Anchorable object.
Action WhileAttachedToAnchor
Called during every Update() in which this AnchorableBehaviour is attached to an Anchor.
Anchor preferredAnchor
Gets the anchor this AnchorableBehaviour would most prefer to attach to. This value is refreshed ever...
bool TryAttach(bool ignoreRange=false)
Attempts to attach to this Anchorable object's currently specified anchor. The attempt may fail if th...
Action WhileLockedToAnchor
Called during every Update() in which this AnchorableBehaviour is locked to an Anchor.
void Detach()
Detaches this Anchorable object from its anchor. The anchor reference remains unchanged....
InteractionBehaviours are components that enable GameObjects to interact with interaction controllers...
Action OnGraspBegin
Called when the object becomes grasped, if it was not already held by any interaction controllers on ...
Rigidbody rigidbody
The Rigidbody associated with this interaction object.
Action OnGraspEnd
Called when the object is no longer grasped by any interaction controllers.
bool isHovered
Gets whether any interaction controller is nearby.
Hand closestHoveringHand
Gets the closest Leap hand to this object, or null if no hand is nearby.
InteractionController closestHoveringController
Gets the closest interaction controller to this object, or null if no controller is nearby....
abstract Vector3 hoverPoint
Gets the current position to check against nearby objects for hovering. Position is only used if the ...