Tanoda
InteractionController.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
10using Leap.Unity.Space;
12using System;
13using System.Collections;
14using System.Collections.Generic;
15using UnityEngine;
18
19namespace Leap.Unity.Interaction {
20
21 [Serializable]
22 public class InteractionControllerSet : SerializableHashSet<InteractionController> { }
23
28 public enum IgnoreHoverMode { None, Left, Right, Both }
29
36
37 [DisallowMultipleComponent]
38 public abstract class InteractionController : MonoBehaviour,
40
41 #region Inspector
42
43 [Tooltip("The manager responsible for this interaction controller. Interaction "
44 + "controllers should be children of their interaction manager.")]
46
47 [Header("Interaction Types")]
48
49 [Tooltip("If disabled, this interaction controller will not be used to generate "
50 + "hover information or primary hover information. Warning: Primary hover "
51 + "data is required for Interaction Engine user interface components like "
52 + "InteractionButton and InteractionSlider to function, so this controller "
53 + "won't able to interact with UI components.")]
54 [SerializeField]
55 [OnEditorChange("hoverEnabled")]
56 private bool _hoverEnabled = true;
57 public bool hoverEnabled {
58 get { return _hoverEnabled; }
59 set {
60 _hoverEnabled = value;
61
62 if (!_hoverEnabled) {
64 }
65 }
66 }
67
68 [Tooltip("If disabled, this interaction controller will not collide with interaction "
69 + "objects and objects will not receive contact callbacks.")]
70 [SerializeField]
71 [OnEditorChange("contactEnabled")]
72 private bool _contactEnabled = true;
73 public bool contactEnabled {
74 get { return _contactEnabled; }
75 set {
76 _contactEnabled = value;
77
78 if (!_contactEnabled) {
79 disableContactBoneCollision();
80
82 }
83 else {
84 resetContactBonePose();
85
87 }
88 }
89 }
90
91 [Tooltip("If disabled, this interaction controller will not be able to grasp "
92 + "interaction objects.")]
93 [SerializeField]
94 [OnEditorChange("graspingEnabled")]
95 private bool _graspingEnabled = true;
96 public bool graspingEnabled {
97 get { return _graspingEnabled; }
98 set {
99 _graspingEnabled = value;
100
101 if (!_graspingEnabled) {
103 ReleaseGrasp();
104 }
105 }
106 }
107
108 #endregion
109
110 #region Public API
111
118 public abstract bool isTracked { get; }
119
124 public abstract bool isBeingMoved { get; }
125
130 public abstract bool isLeft { get; }
131
136 public bool isRight { get { return !isLeft; } }
137
141 public abstract Vector3 position { get; }
142
146 public abstract Quaternion rotation { get; }
147
151 public abstract Vector3 velocity { get; }
152
159 public abstract ControllerType controllerType { get; }
160
165 public abstract InteractionHand intHand { get; }
166
171 public float scale { get { return this.transform.lossyScale.x; } }
172
173 #endregion
174
175 #region Events
176
183 public Action<InteractionBehaviour> OnBeginPrimaryHoveringObject = (intObj) => { };
184
191 public Action<InteractionBehaviour> OnEndPrimaryHoveringObject = (intObj) => { };
192
196 public Action<InteractionBehaviour> OnStayPrimaryHoveringObject = (intObj) => { };
197
201 public Action OnGraspBegin = () => { };
202
206 public Action OnGraspStay = () => { };
207
211 public Action OnGraspEnd = () => { };
212
216 public Action<InteractionController> OnContactInitialized = (intCtrl) => { };
217
218 #endregion
219
220 #region Unity Events
221
222 protected virtual void Reset() {
223 if (manager == null) manager = GetComponentInParent<InteractionManager>();
224 }
225
226 protected virtual void OnEnable() {
227 if (_contactInitialized) {
229
230 resetContactBonePose();
231 }
232 }
233
234 protected virtual void Start() {
236 }
237
238 protected virtual void OnDisable() {
239 if (_contactInitialized) {
241 }
242 ReleaseGrasp();
243
247 }
248
249 #endregion
250
251 // A list of InteractionControllers for use as a temporary buffer.
252 private List<InteractionController> _controllerListBuffer = new List<InteractionController>();
253
260
261 if (hoverEnabled) fixedUpdateHovering();
262 if (contactEnabled) fixedUpdateContact();
263 if (graspingEnabled) fixedUpdateGrasping();
264 }
265
269
270 onObjectUnregistered(intObj);
271 }
272
281 protected abstract void onObjectUnregistered(IInteractionBehaviour intObj);
282
290 protected virtual void fixedUpdateController() { }
291
292 #region Hovering
293
299 public Func<IInteractionBehaviour, bool> customHoverActivityFilter = null;
300
301 // Hover Activity Filter
302 private Func<Collider, IInteractionBehaviour> hoverActivityFilter;
303 private IInteractionBehaviour hoverFilterFunc(Collider collider) {
304 Rigidbody rigidbody = collider.attachedRigidbody;
305 IInteractionBehaviour intObj = null;
306
307 bool objectValidForHover = rigidbody != null
308 && manager.interactionObjectBodies.TryGetValue(rigidbody, out intObj)
309 && !intObj.ShouldIgnoreHover(this)
311
312 if (objectValidForHover) return intObj;
313 else return null;
314 }
315
316 // Layer mask for the hover acitivity manager.
317 private Func<int> hoverLayerMaskAccessor;
318
319 // Hover Activity Manager
320 private ActivityManager<IInteractionBehaviour> _hoverActivityManager;
322 get {
323 if (_hoverActivityManager == null) {
324 if (hoverActivityFilter == null) hoverActivityFilter = hoverFilterFunc;
325 if (hoverLayerMaskAccessor == null) hoverLayerMaskAccessor = manager.GetInteractionLayerMask;
326
328 hoverActivityFilter);
329
330 _hoverActivityManager.activationLayerFunction = hoverLayerMaskAccessor;
331 }
332 return _hoverActivityManager;
333 }
334 }
335
339 private bool _primaryHoverLocked = false;
344 public bool primaryHoverLocked {
345 get { return _primaryHoverLocked; }
346 set { _primaryHoverLocked = value; }
347 }
348
356 _primaryHoveredObject = intObj;
357 _primaryHoverLocked = true;
358 }
359
365 public abstract Vector3 hoverPoint { get; }
366
367 private HashSet<IInteractionBehaviour> _hoveredObjects = new HashSet<IInteractionBehaviour>();
372 public ReadonlyHashSet<IInteractionBehaviour> hoveredObjects { get { return _hoveredObjects; } }
373
374 protected abstract List<Transform> _primaryHoverPoints { get; }
380
385 public bool isPrimaryHovering { get { return primaryHoveredObject != null; } }
386
387 private IInteractionBehaviour _primaryHoveredObject;
392 public IInteractionBehaviour primaryHoveredObject { get { return _primaryHoveredObject; } }
393
394 private float _primaryHoverDistance = float.PositiveInfinity;
399 public float primaryHoverDistance { get { return _primaryHoverDistance; } }
400
406 public Vector3 primaryHoveringPoint {
407 get {
408 return isPrimaryHovering ? _primaryHoverPoints[_primaryHoverPointIdx].position
409 : Vector3.zero;
410 }
411 }
412
417 public int primaryHoveringPointIndex { get { return _primaryHoverPointIdx; } }
418
420 private int _primaryHoverPointIdx = -1;
421 private List<IInteractionBehaviour> _perPointPrimaryHovered = new List<IInteractionBehaviour>();
422 private List<float> _perPointPrimaryHoverDistance = new List<float>();
423
424 private void fixedUpdateHovering() {
425 using (new ProfilerSample("Fixed Update InteractionController Hovering")) {
426 // Reset hover lock if the controller loses tracking.
428 _primaryHoverLocked = false;
429 }
430
431 // Update hover state.
433
434 Vector3? queryPosition = isTracked ? (Vector3?)hoverPoint : null;
435 hoverActivityManager.UpdateActivityQuery(queryPosition, LeapSpace.allEnabled);
436
437 // Add all returned objects as "hovered".
438 // Find closest objects per primary hover point to update primary hover state.
439 using (new ProfilerSample("Find Closest Objects for Primary Hover")) {
440 refreshHoverState(_hoverActivityManager.ActiveObjects);
441 }
442
443 // Refresh buffer information from the previous frame to be able to fire
444 // the appropriate hover state callbacks.
445 refreshHoverStateBuffers();
446 refreshPrimaryHoverStateBuffers();
447
448 // Support interactions in curved ("warped") space by "unwarping" hands into
449 // the curved space's physics (rectilinear) space.
452
453 if (space != null
454 && space.anchor != null
455 && space.anchor.space != null) {
456 unwarpColliders(primaryHoverPoints[_primaryHoverPointIdx], space);
457 }
458 }
459 }
460 }
461
488 protected abstract void unwarpColliders(Transform primaryHoverPoint,
489 ISpaceComponent warpedSpaceElement);
490
491 // Hover history, handled as part of the Interaction Manager's state-check calls.
492 private IInteractionBehaviour _primaryHoveredLastFrame = null;
493 private HashSet<IInteractionBehaviour> _hoveredLastFrame = new HashSet<IInteractionBehaviour>();
494
499 private void refreshHoverState(HashSet<IInteractionBehaviour> hoverCandidates) {
500 // Prepare data from last frame for hysteresis later on.
501 int primaryHoverPointIdxLastFrame = _primaryHoveredLastFrame != null ? _primaryHoverPointIdx : -1;
502
503 _hoveredObjects.Clear();
504
505 IInteractionBehaviour lockedPrimaryHoveredObject = null;
506 if (primaryHoverLocked) {
507 lockedPrimaryHoveredObject = _primaryHoveredObject;
508 }
509 _primaryHoveredObject = null;
510 _primaryHoverDistance = float.PositiveInfinity;
511 _primaryHoverPointIdx = -1;
512 _perPointPrimaryHovered.Clear();
513 _perPointPrimaryHoverDistance.Clear();
514 for (int i = 0; i < primaryHoverPoints.Count; i++) {
515 _perPointPrimaryHovered.Add(null);
516 _perPointPrimaryHoverDistance.Add(float.PositiveInfinity);
517 }
518
519 // We can only update hover information if there's tracked data.
520 if (!isTracked) return;
521
522 // Determine values to apply hysteresis to the primary hover state.
523 float maxNewPrimaryHoverDistance = float.PositiveInfinity;
524 if (_primaryHoveredLastFrame != null && primaryHoverPointIdxLastFrame != -1
525 && primaryHoverPoints[primaryHoverPointIdxLastFrame] != null) {
526
527 if (_contactBehaviours.ContainsKey(_primaryHoveredLastFrame)) {
528 // If we're actually touching the last primary hover, prevent the primary hover from changing at all.
529 maxNewPrimaryHoverDistance = 0F;
530 }
531 else {
532 float distanceToLastPrimaryHover = _primaryHoveredLastFrame.GetHoverDistance(
533 primaryHoverPoints[primaryHoverPointIdxLastFrame].position);
534 // Otherwise...
535 // The distance to a new object must be even closer than the current primary hover
536 // distance in order for that object to become the new primary hover.
537 maxNewPrimaryHoverDistance = distanceToLastPrimaryHover
538 * distanceToLastPrimaryHover.Map(0.009F, 0.018F, 0.4F, 0.95F);
539
540 }
541 }
542
543 foreach (IInteractionBehaviour behaviour in hoverCandidates) {
544 // All hover candidates automatically count as hovered.
545 _hoveredObjects.Add(behaviour);
546
547 // Some objects can ignore consideration for primary hover as an
548 // optimization, since it can require a lot of distance checks.
549 if (behaviour.ignorePrimaryHover) continue;
550
551 // Do further processing to determine the primary hover if primary hover isn't
552 // locked.
553 else if (!primaryHoverLocked) {
554 processPrimaryHover(behaviour, maxNewPrimaryHoverDistance);
555 }
556 }
557
558 // If the primary hover is locked, we need to process primary hover specifically
559 // for the locked object.
560 if (primaryHoverLocked && lockedPrimaryHoveredObject != null) {
561 processPrimaryHover(lockedPrimaryHoveredObject, float.PositiveInfinity);
562 }
563 }
564
565 private void processPrimaryHover(IInteractionBehaviour behaviour, float maxNewPrimaryHoverDistance) {
566 // Check against all positions currently registered as primary hover points,
567 // finding the closest one and updating hover data accordingly.
568 float shortestPointDistance = float.PositiveInfinity;
569 for (int i = 0; i < primaryHoverPoints.Count; i++) {
570 var primaryHoverPoint = primaryHoverPoints[i];
571
572 // It's possible to disable primary hover points to ignore them for hover
573 // consideration.
574 if (primaryHoverPoint == null) continue;
575 if (!primaryHoverPoint.gameObject.activeInHierarchy) continue;
576
577 // Skip non-index fingers for InteractionHands if they aren't extended.
578 if (intHand != null) {
579 if (!(intHand.leapHand).Fingers[i].IsExtended && i != 1) { continue; }
580 }
581
582 // Check primary hover for the primary hover point.
583 float behaviourDistance = GetHoverDistance(primaryHoverPoint.position, behaviour);
584 if (behaviourDistance < shortestPointDistance) {
585
586 // This is the closest behaviour to this primary hover point.
587 _perPointPrimaryHovered[i] = behaviour;
588 _perPointPrimaryHoverDistance[i] = behaviourDistance;
589 shortestPointDistance = behaviourDistance;
590
591 if (primaryHoverLocked) {
592 // If primary hover is locked, there's only one object to consider,
593 // and the current primary hover point is the closest one to the object
594 // so far, so update primary hover accordingly.
595 _primaryHoveredObject = _perPointPrimaryHovered[i]; // (redundant)
596 _primaryHoverDistance = _perPointPrimaryHoverDistance[i];
597 _primaryHoverPointIdx = i;
598 }
599 else if (shortestPointDistance < _primaryHoverDistance
600 && (behaviour == _primaryHoveredLastFrame || behaviourDistance < maxNewPrimaryHoverDistance)) {
601
602 // This is the closest behaviour to ANY primary hover point, and the
603 // distance is less than the hysteresis distance to transition away from
604 // the previous primary hovered object.
605 _primaryHoveredObject = _perPointPrimaryHovered[i];
606 _primaryHoverDistance = _perPointPrimaryHoverDistance[i];
607 _primaryHoverPointIdx = i;
608 }
609 }
610 }
611 }
612
619 public void ClearHoverTracking() {
620 _controllerListBuffer.Clear();
621 _controllerListBuffer.Add(this);
622
623 var tempObjs = Pool<HashSet<IInteractionBehaviour>>.Spawn();
624 try {
625 foreach (var intObj in hoveredObjects) {
626 tempObjs.Add(intObj);
627 }
628
629 foreach (var intObj in tempObjs) {
630 // Prevents normal hover state checking on the next frame from firing duplicate
631 // hover-end callbacks. If the object is still hovered (and the controller is
632 // still in an enabled state), the object WILL receive a hover begin callback
633 // on the next frame.
634 _hoveredObjects.Remove(intObj);
635 _hoveredLastFrame.Remove(intObj);
636
637 intObj.EndHover(_controllerListBuffer);
638 }
639 }
640 finally {
641 tempObjs.Clear();
642 Pool<HashSet<IInteractionBehaviour>>.Recycle(tempObjs);
643 }
644 }
645
654 if (!hoveredObjects.Contains(intObj)) return;
655
656 _hoveredObjects.Remove(intObj);
657 _hoveredLastFrame.Remove(intObj);
658
659 _controllerListBuffer.Clear();
660 _controllerListBuffer.Add(this);
661
662 intObj.EndHover(_controllerListBuffer);
663 }
664
665 #region Hover State Checks
666
667 private HashSet<IInteractionBehaviour> _hoverEndedBuffer = new HashSet<IInteractionBehaviour>();
668 private HashSet<IInteractionBehaviour> _hoverBeganBuffer = new HashSet<IInteractionBehaviour>();
669
670 private List<IInteractionBehaviour> _hoverRemovalCache = new List<IInteractionBehaviour>();
671 private void refreshHoverStateBuffers() {
672 _hoverBeganBuffer.Clear();
673 _hoverEndedBuffer.Clear();
674
675 var trackedBehaviours = _hoverActivityManager.ActiveObjects;
676 foreach (var hoverable in trackedBehaviours) {
677 bool inLastFrame = false, inCurFrame = false;
678 if (hoveredObjects.Contains(hoverable)) {
679 inCurFrame = true;
680 }
681 if (_hoveredLastFrame.Contains(hoverable)) {
682 inLastFrame = true;
683 }
684
685 if (inCurFrame && !inLastFrame) {
686 _hoverBeganBuffer.Add(hoverable);
687 _hoveredLastFrame.Add(hoverable);
688 }
689 if (!inCurFrame && inLastFrame) {
690 _hoverEndedBuffer.Add(hoverable);
691 _hoveredLastFrame.Remove(hoverable);
692 }
693 }
694
695 foreach (var hoverable in _hoveredLastFrame) {
696 if (!trackedBehaviours.Contains(hoverable)) {
697 _hoverEndedBuffer.Add(hoverable);
698 _hoverRemovalCache.Add(hoverable);
699 }
700 }
701 foreach (var hoverable in _hoverRemovalCache) {
702 _hoveredLastFrame.Remove(hoverable);
703 }
704 _hoverRemovalCache.Clear();
705 }
706
712 bool IInternalInteractionController.CheckHoverEnd(out HashSet<IInteractionBehaviour> hoverEndedObjects) {
713 // Hover checks via the activity manager are robust to destroyed or made-invalid objects,
714 // so no additional validity checking is required.
715 hoverEndedObjects = _hoverEndedBuffer;
716
717 return _hoverEndedBuffer.Count > 0;
718 }
719
725 bool IInternalInteractionController.CheckHoverBegin(out HashSet<IInteractionBehaviour> hoverBeganObjects) {
726 hoverBeganObjects = _hoverBeganBuffer;
727 return _hoverBeganBuffer.Count > 0;
728 }
729
735 bool IInternalInteractionController.CheckHoverStay(out HashSet<IInteractionBehaviour> hoveredObjects) {
736 hoveredObjects = _hoveredObjects;
737 return hoveredObjects.Count > 0;
738 }
739
740 #endregion
741
750 if (!isPrimaryHovering) return;
751
752 // This will cause the primary-hover-end check to return this object, and prevent
753 // a duplicate primary-hover-end call when refreshPrimaryHoverStateBuffers() is
754 // called on the next frame.
755 var formerlyPrimaryHoveredObj = _primaryHoveredObject;
756 _primaryHoveredObject = null;
757 _primaryHoveredLastFrame = null;
758
760
761 _controllerListBuffer.Clear();
762 _controllerListBuffer.Add(this);
763
764 formerlyPrimaryHoveredObj.EndPrimaryHover(_controllerListBuffer);
765 }
766
767 #region Primary Hover State Checks
768
769 private IInteractionBehaviour _primaryHoverEndedObject = null;
770 private IInteractionBehaviour _primaryHoverBeganObject = null;
771
772 private void refreshPrimaryHoverStateBuffers() {
773 if (primaryHoveredObject != _primaryHoveredLastFrame) {
774 if (_primaryHoveredLastFrame != null) _primaryHoverEndedObject = _primaryHoveredLastFrame;
775 else _primaryHoverEndedObject = null;
776
777 _primaryHoveredLastFrame = primaryHoveredObject;
778
779 if (_primaryHoveredLastFrame != null) _primaryHoverBeganObject = _primaryHoveredLastFrame;
780 else _primaryHoverBeganObject = null;
781 }
782 else {
783 _primaryHoverEndedObject = null;
784 _primaryHoverBeganObject = null;
785 }
786 }
787
793 bool IInternalInteractionController.CheckPrimaryHoverEnd(out IInteractionBehaviour primaryHoverEndedObject) {
794 primaryHoverEndedObject = _primaryHoverEndedObject;
795 bool primaryHoverEnded = primaryHoverEndedObject != null;
796
797 if (primaryHoverEnded && _primaryHoverEndedObject is InteractionBehaviour) {
798 OnEndPrimaryHoveringObject(_primaryHoverEndedObject as InteractionBehaviour);
799 }
800
801 return primaryHoverEnded;
802 }
803
809 bool IInternalInteractionController.CheckPrimaryHoverBegin(out IInteractionBehaviour primaryHoverBeganObject) {
810 primaryHoverBeganObject = _primaryHoverBeganObject;
811 bool primaryHoverBegan = primaryHoverBeganObject != null;
812
813 if (primaryHoverBegan && _primaryHoverBeganObject is InteractionBehaviour) {
814 OnBeginPrimaryHoveringObject(_primaryHoverBeganObject as InteractionBehaviour);
815 }
816
817 return primaryHoverBegan;
818 }
819
825 bool IInternalInteractionController.CheckPrimaryHoverStay(out IInteractionBehaviour primaryHoveredObject) {
826 primaryHoveredObject = _primaryHoveredObject;
827 bool primaryHoverStayed = primaryHoveredObject != null;
828
829 if (primaryHoverStayed && primaryHoveredObject is InteractionBehaviour) {
830 OnStayPrimaryHoveringObject(primaryHoveredObject as InteractionBehaviour);
831 }
832
833 return primaryHoverStayed;
834 }
835
836 #endregion
837
842 public static float GetHoverDistance(Vector3 hoverPoint, IInteractionBehaviour behaviour) {
843 if (behaviour.space != null) {
844 return behaviour.GetHoverDistance(TransformPoint(hoverPoint, behaviour.space));
845 }
846 else {
847 return behaviour.GetHoverDistance(hoverPoint);
848 }
849 }
850
854 public static Vector3 TransformPoint(Vector3 worldPoint, ISpaceComponent element) {
855 if (element.anchor != null && element.anchor.space != null) {
856 Vector3 localPos = element.anchor.space.transform.InverseTransformPoint(worldPoint);
857 return element.anchor.space.transform.TransformPoint(element.anchor.transformer.InverseTransformPoint(localPos));
858 }
859 else {
860 return worldPoint;
861 }
862 }
863
864 #endregion
865
866 #region Contact
867
872 public ReadonlyHashSet<IInteractionBehaviour> contactingObjects { get { return _contactBehavioursSet; } }
873
874 #region Contact Bones
875
876 protected const float DEAD_ZONE_FRACTION = 0.04F;
877
878 private float _softContactDislocationDistance = 0.03F;
880 get { return _softContactDislocationDistance; }
881 set { _softContactDislocationDistance = value; }
882 }
883
884 private static PhysicMaterial s_defaultContactBoneMaterial;
885 protected static PhysicMaterial defaultContactBoneMaterial {
886 get {
887 if (s_defaultContactBoneMaterial == null) {
888 initDefaultContactBoneMaterial();
889 }
890 return s_defaultContactBoneMaterial;
891 }
892 }
893
898 private static void initDefaultContactBoneMaterial() {
899 if (s_defaultContactBoneMaterial == null) {
900 s_defaultContactBoneMaterial = new PhysicMaterial();
901 }
902 s_defaultContactBoneMaterial.hideFlags = HideFlags.HideAndDontSave;
903 s_defaultContactBoneMaterial.bounceCombine = PhysicMaterialCombine.Minimum;
904 s_defaultContactBoneMaterial.bounciness = 0F;
905 }
906
907 private bool _contactInitialized = false;
908 protected bool _wasContactInitialized { get { return _contactInitialized; } }
909 public abstract ContactBone[] contactBones { get; }
910 protected abstract GameObject contactBoneParent { get; }
911 protected float lastObjectTouchedAdjustedMassMass = 0.2f;
912
913 private Vector3[] _boneTargetPositions;
914 private Quaternion[] _boneTargetRotations;
915
936 protected abstract bool initContact();
937
938 private void finishInitContact() {
940 contactBoneParent.transform.parent = manager.transform;
941
942 var comment = contactBoneParent.GetComponent<ContactBoneParent>();
943 if (comment == null) {
944 comment = contactBoneParent.AddComponent<ContactBoneParent>();
945 }
946 comment.controller = this;
947
948 foreach (var contactBone in contactBones) {
949 contactBone.rigidbody.maxAngularVelocity = 30F;
950
951 contactBone.gameObject.layer = manager.contactBoneLayer;
952 }
953
954 _boneTargetPositions = new Vector3[contactBones.Length];
955 _boneTargetRotations = new Quaternion[contactBones.Length];
956 }
957
958 private void fixedUpdateContact() {
959 // Make sure contact data is initialized.
960 if (!_contactInitialized) {
961 if (initContact()) {
962 finishInitContact();
963 _contactInitialized = true;
965 }
966 else {
967 return;
968 }
969 }
970
971 // Clear contact data if we lose tracking.
972 if (!isTracked && _contactBehaviours.Count > 0) {
973 _contactBehaviours.Clear();
974
975 // Also clear soft contact state if tracking is lost.
976 _softContactCollisions.Clear();
977 }
978
979 // Disable contact bone parent if we lose tracking.
980 if (!isTracked) {
981 contactBoneParent.SetActive(false);
982 return;
983 }
984 else {
985 if (!contactBoneParent.activeSelf) {
986 contactBoneParent.SetActive(true);
987 }
988 }
989
990 // Request and store target bone positions and rotations
991 // for use during the contact update.
992 using (new ProfilerSample("Update Contact Bone Targets")) {
993 for (int i = 0; i < contactBones.Length; i++) {
994 getColliderBoneTargetPositionRotation(i, out _boneTargetPositions[i],
995 out _boneTargetRotations[i]);
996 }
997 }
998
999 using (new ProfilerSample("Update Contact Bones")) {
1000 normalizeBoneMasses();
1001 for (int i = 0; i < contactBones.Length; i++) {
1002 updateContactBone(i, _boneTargetPositions[i], _boneTargetRotations[i]);
1003 }
1004 }
1005 using (new ProfilerSample("Update Soft Contact")) {
1006 fixedUpdateSoftContact();
1007 }
1008 using (new ProfilerSample("Update Contact Callbacks")) {
1009 fixedUpdateContactState();
1010 }
1011 }
1012
1021 protected abstract void getColliderBoneTargetPositionRotation(int contactBoneIndex,
1022 out Vector3 targetPosition,
1023 out Quaternion targetRotation);
1024
1025 private void normalizeBoneMasses() {
1026 //If any of the contact bones have contacted an object that the others have not
1027 //Propagate that change in mass to the rest of the bones in the hand
1028 float tempAdjustedMass = lastObjectTouchedAdjustedMassMass;
1029 for (int i = 0; i < contactBones.Length; i++) {
1030 if (contactBones[i]._lastObjectTouchedAdjustedMass != tempAdjustedMass) {
1031 tempAdjustedMass = contactBones[i]._lastObjectTouchedAdjustedMass;
1032 for (int j = 0; j < contactBones.Length; j++) {
1033 contactBones[j]._lastObjectTouchedAdjustedMass = tempAdjustedMass;
1034 }
1035 break;
1036 }
1037 }
1038 lastObjectTouchedAdjustedMassMass = tempAdjustedMass;
1039 }
1040
1041 private void updateContactBone(int contactBoneIndex, Vector3 targetPosition, Quaternion targetRotation) {
1042 ContactBone contactBone = contactBones[contactBoneIndex];
1043 Rigidbody body = contactBone.rigidbody;
1044
1045 // Infer ahead if the Interaction Manager has a moving frame of reference.
1046 //manager.TransformAheadByFixedUpdate(targetPosition, targetRotation, out targetPosition, out targetRotation);
1047
1048 // Set a fixed rotation for bones; otherwise most friction is lost
1049 // as any capsule or spherical bones will roll on contact.
1050 using (new ProfilerSample("updateContactBone: MoveRotation")) {
1051 body.MoveRotation(targetRotation);
1052 }
1053
1054 // Calculate how far off its target the contact bone is.
1055 float errorDistance = 0f;
1056 float errorFraction = 0f;
1057 using (new ProfilerSample("updateContactBone: errorDistance, errorFraction")) {
1058 Vector3 lastTargetPositionTransformedAhead = contactBone.lastTargetPosition;
1060 manager.TransformAheadByFixedUpdate(contactBone.lastTargetPosition, out lastTargetPositionTransformedAhead);
1061 }
1062 errorDistance = Vector3.Distance(lastTargetPositionTransformedAhead, body.position);
1063 errorFraction = errorDistance / contactBone.width;
1064 }
1065
1066 // Adjust the mass of the contact bone based on the mass of
1067 // the object it is currently touching.
1068 float speed = 0f;
1069 using (new ProfilerSample("updateContactBone: speed & massScale")) {
1070 speed = velocity.magnitude;
1071 float massScale = Mathf.Clamp(1.0F - (errorFraction * 2.0F), 0.1F, 1.0F)
1072 * Mathf.Clamp(speed * 10F, 1F, 10F);
1073 body.mass = massScale * contactBone._lastObjectTouchedAdjustedMass;
1074 }
1075
1076 // Potentially enable Soft Contact if our error is too large.
1077 using (new ProfilerSample("updateContactBone: maybe enable Soft Contact")) {
1078 if (!_softContactEnabled && errorDistance >= softContactDislocationDistance
1079 && speed < 1.5F
1080 /* && boneArrayIndex != NUM_FINGERS * BONES_PER_FINGER */) {
1082 }
1083 }
1084
1085 // Attempt to move the contact bone to its target position and rotation
1086 // by setting its target velocity and angular velocity. Include a "deadzone"
1087 // for position to avoid tiny vibrations.
1088 using (new ProfilerSample("updateContactBone: Move to target, with deadzone")) {
1089 float deadzone = Mathf.Min(DEAD_ZONE_FRACTION * contactBone.width, 0.01F * scale);
1090 Vector3 delta = (targetPosition - body.position);
1091 float deltaMag = delta.magnitude;
1092 if (deltaMag <= deadzone) {
1093 body.velocity = Vector3.zero;
1094 contactBone.lastTargetPosition = body.position;
1095 }
1096 else {
1097 delta *= (deltaMag - deadzone) / deltaMag;
1098 contactBone.lastTargetPosition = body.position + delta;
1099
1100 Vector3 targetVelocity = delta / Time.fixedDeltaTime;
1101 float targetVelocityMag = targetVelocity.magnitude;
1102 body.velocity = (targetVelocity / targetVelocityMag)
1103 * Mathf.Clamp(targetVelocityMag, 0F, 100F);
1104 }
1105 Quaternion deltaRot = targetRotation * Quaternion.Inverse(body.rotation);
1106 body.angularVelocity = PhysicsUtility.ToAngularVelocity(deltaRot, Time.fixedDeltaTime);
1107 }
1108 }
1109
1110 #endregion
1111
1112 #region Soft Contact
1113
1114 private bool _softContactEnabled = false;
1115 public bool softContactEnabled { get { return _softContactEnabled; } }
1116
1117 private bool _disableSoftContactEnqueued = false;
1118 private IEnumerator _delayedDisableSoftContactCoroutine;
1119
1120 private Collider[] _softContactColliderBuffer = new Collider[32];
1121
1122 private bool _notTrackedLastFrame = true;
1123
1124 private void fixedUpdateSoftContact() {
1125 if (!isTracked) {
1126 _notTrackedLastFrame = true;
1127 return;
1128 }
1129 else {
1130 // If the hand was just initialized, initialize with soft contact.
1131 if (_notTrackedLastFrame) {
1133 }
1134
1135 _notTrackedLastFrame = false;
1136 }
1137
1138 if (_softContactEnabled) {
1139 foreach (var contactBone in contactBones) {
1140#if UNITY_ANDROID
1141 //for(int i = 0; i < _softContactColliderBuffer.Length; i++) {
1142 // _softContactColliderBuffer[i] = null;
1143 //}
1144 // HACK: assume only using hands on android
1145 // TODO: This probably doesn't take into accoutn ignoreContact settings
1146 PhysicsUtility.generateSphereContacts(contactBone.rigidbody.position,
1147 0.02f * manager.SimulationScale,
1148 contactBone.rigidbody.velocity,
1152 ref _softContactColliderBuffer);
1153
1154 //for (int i = 0; i < _softContactColliderBuffer.Length; i++) {
1155 // if (_softContactColliderBuffer[i] != null) {
1156 // NotifySoftContactOverlap(contactBone, _softContactColliderBuffer[i]);
1157 // }
1158 //}
1159#else
1160 Collider contactBoneCollider = contactBone.collider;
1161 if (contactBoneCollider is SphereCollider) {
1162 var boneSphere = contactBoneCollider as SphereCollider;
1163
1164 int numCollisions = Physics.OverlapSphereNonAlloc(contactBone.transform.TransformPoint(boneSphere.center),
1165 contactBone.transform.lossyScale.x * boneSphere.radius,
1166 _softContactColliderBuffer,
1168 QueryTriggerInteraction.Ignore);
1169 for (int i = 0; i < numCollisions; i++) {
1170 //NotifySoftContactOverlap(contactBone, _softContactColliderBuffer[i]);
1171
1172 // If the rigidbody is null, the object may have been destroyed.
1173 if (_softContactColliderBuffer[i].attachedRigidbody == null) continue;
1174 IInteractionBehaviour intObj;
1175 if (manager.interactionObjectBodies.TryGetValue(_softContactColliderBuffer[i].attachedRigidbody, out intObj)) {
1176 // Skip soft contact if the object is ignoring contact.
1177 if (intObj.ignoreContact) continue;
1178 if (intObj.isGrasped) continue;
1179 }
1180
1181 PhysicsUtility.generateSphereContact(boneSphere, 0, _softContactColliderBuffer[i],
1184 }
1185 }
1186 else if (contactBoneCollider is CapsuleCollider) {
1187 var boneCapsule = contactBoneCollider as CapsuleCollider;
1188
1189 Vector3 point0, point1;
1190 boneCapsule.GetCapsulePoints(out point0, out point1);
1191
1192 int numCollisions = Physics.OverlapCapsuleNonAlloc(point0, point1,
1193 contactBone.transform.lossyScale.x * boneCapsule.radius,
1194 _softContactColliderBuffer,
1196 QueryTriggerInteraction.Ignore);
1197 for (int i = 0; i < numCollisions; i++) {
1198 //NotifySoftContactOverlap(contactBone, _softContactColliderBuffer[i]);
1199
1200 // If the rigidbody is null, the object may have been destroyed.
1201 if (_softContactColliderBuffer[i].attachedRigidbody == null) continue;
1202 IInteractionBehaviour intObj;
1203 if (manager.interactionObjectBodies.TryGetValue(_softContactColliderBuffer[i].attachedRigidbody, out intObj)) {
1204 // Skip soft contact if the object is ignoring contact.
1205 if (intObj.ignoreContact) continue;
1206 if (intObj.isGrasped) continue;
1207 }
1208
1209 PhysicsUtility.generateCapsuleContact(boneCapsule, 0,
1210 _softContactColliderBuffer[i],
1213 }
1214 }
1215 else {
1216 var boneBox = contactBoneCollider as BoxCollider;
1217
1218 if (boneBox == null) {
1219 Debug.LogError("Unsupported collider type in ContactBone. Supported "
1220 + "types are SphereCollider, CapsuleCollider, and "
1221 + "BoxCollider.", this);
1222 continue;
1223 }
1224
1225 int numCollisions = Physics.OverlapBoxNonAlloc(boneBox.transform.TransformPoint(boneBox.center),
1226 Vector3.Scale(boneBox.size * 0.5F, contactBone.transform.lossyScale),
1227 _softContactColliderBuffer,
1228 boneBox.transform.rotation,
1230 QueryTriggerInteraction.Ignore);
1231 for (int i = 0; i < numCollisions; i++) {
1232 //NotifySoftContactOverlap(contactBone, _softContactColliderBuffer[i]);
1233
1234 // If the rigidbody is null, the object may have been destroyed.
1235 if (_softContactColliderBuffer[i].attachedRigidbody == null) continue;
1236 IInteractionBehaviour intObj;
1237 if (manager.interactionObjectBodies.TryGetValue(_softContactColliderBuffer[i].attachedRigidbody, out intObj)) {
1238 // Skip soft contact if the object is ignoring contact.
1239 if (intObj.ignoreContact) continue;
1240 if (intObj.isGrasped) continue;
1241 }
1242
1243 PhysicsUtility.generateBoxContact(boneBox, 0, _softContactColliderBuffer[i],
1246 }
1247 }
1248#endif
1249 }
1250
1251 // TODO: Implement me to replace trigger colliders
1252 //FinishSoftContactOverlapChecks();
1253
1254 //for (int i = 0; i < contactBones.Length; i++) {
1255 // Vector3 bonePosition = _boneTargetPositions[i];
1256 // Quaternion boneRotation = _boneTargetRotations[i];
1257
1258 // Generate soft contact data based on spheres at each bonePosition
1259 // of radius softContactBoneRadius.
1260 // bool sphereIntersecting;
1261 // using (new ProfilerSample("Generate Soft Contacts")) {
1262 // sphereIntersecting = PhysicsUtility.generateSphereContacts(bonePosition,
1263 // _softContactBoneRadius,
1264 // (bonePosition - _bonePositionsLastFrame[i]) / Time.fixedDeltaTime,
1265 // 1 << manager.interactionLayer,
1266 // ref manager._softContacts,
1267 // ref manager._softContactOriginalVelocities,
1268 // ref _tempColliderArray);
1269 // }
1270
1271 // _bonePositionsLastFrame[i] = bonePosition;
1272
1273 // softlyContacting = sphereIntersecting ? true : softlyContacting;
1274 //}
1275
1276 if (_softContactCollisions.Count > 0) {
1277 _disableSoftContactEnqueued = false;
1278 if (_delayedDisableSoftContactCoroutine != null) {
1279 manager.StopCoroutine(_delayedDisableSoftContactCoroutine);
1280 }
1281 }
1282 else {
1283 // If there are no detected Contacts, exit soft contact mode.
1285 }
1286 }
1287 }
1288
1297 protected virtual void onPreEnableSoftContact() { }
1298
1307 protected virtual void onPostDisableSoftContact() { }
1308
1309 public void EnableSoftContact() {
1310 if (!isTracked) return;
1311 using (new ProfilerSample("Enable Soft Contact")) {
1312 _disableSoftContactEnqueued = false;
1313 if (!_softContactEnabled) {
1315
1316 _softContactEnabled = true;
1317
1318 if (_delayedDisableSoftContactCoroutine != null) {
1319 manager.StopCoroutine(_delayedDisableSoftContactCoroutine);
1320 }
1321
1322 if (contactBones != null) {
1323 for (int i = 0; i < contactBones.Length; i++) {
1324 if (contactBones[i].collider == null) continue;
1325
1326 disableContactBoneCollision();
1327 }
1328 }
1329 }
1330 }
1331 }
1332
1333 public void DisableSoftContact() {
1334 using (new ProfilerSample("Enqueue Disable Soft Contact")) {
1335 if (!_disableSoftContactEnqueued) {
1336 _delayedDisableSoftContactCoroutine = DelayedDisableSoftContact();
1337 manager.StartCoroutine(_delayedDisableSoftContactCoroutine);
1338 _disableSoftContactEnqueued = true;
1339 }
1340 }
1341 }
1342
1343 private IEnumerator DelayedDisableSoftContact() {
1344 yield return new WaitForSecondsRealtime(0.3f);
1345 if (_disableSoftContactEnqueued) {
1346 using (new ProfilerSample("Disable Soft Contact")) {
1347 _softContactEnabled = false;
1348 for (int i = 0; i < contactBones.Length; i++) {
1349 enableContactBoneCollision();
1350 }
1351
1353 }
1354 }
1355 }
1356
1357 #region Soft Contact Collision Tracking
1358 /*
1359 // TODO: Make this a thing so we aren't using triggers
1360 private void NotifySoftContactOverlap(ContactBone contactBone, Collider otherCollider) {
1361
1362 }
1363
1364 // TODO: Make this a thing so we aren't using triggers
1365 private void FinishSoftContactOverlapChecks() {
1366
1367 }*/
1368
1369 // TODO: Maintaining a reference to the interaction object doesn't appear to be
1370 // necessary here, so get rid of the Pair class as a small optimization
1371 private Dictionary<BoneIntObjPair, HashSet<Collider>> _softContactCollisions = new Dictionary<BoneIntObjPair, HashSet<Collider>>();
1372
1373 private struct BoneIntObjPair : IEquatable<BoneIntObjPair> {
1374 public ContactBone bone;
1375 public IInteractionBehaviour intObj;
1376
1377 public override bool Equals(object obj) {
1378 return obj is BoneIntObjPair && this == (BoneIntObjPair)obj;
1379 }
1380 public bool Equals(BoneIntObjPair other) {
1381 return this == other;
1382 }
1383 public static bool operator !=(BoneIntObjPair one, BoneIntObjPair other) {
1384 return !(one == other);
1385 }
1386 public static bool operator ==(BoneIntObjPair one, BoneIntObjPair other) {
1387 return one.bone == other.bone && one.intObj == other.intObj;
1388 }
1389 public override int GetHashCode() {
1390 return bone.GetHashCode() ^ intObj.GetHashCode();
1391 }
1392 }
1393
1395 IInteractionBehaviour intObj,
1396 Collider collider) {
1397 var pair = new BoneIntObjPair() { bone = bone, intObj = intObj };
1398
1399 if (!_softContactCollisions.ContainsKey(pair)) {
1400 _softContactCollisions[pair] = new HashSet<Collider>();
1401 }
1402 _softContactCollisions[pair].Add(collider);
1403 }
1404
1406 IInteractionBehaviour intObj,
1407 Collider collider) {
1408 var pair = new BoneIntObjPair() { bone = bone, intObj = intObj };
1409
1410 if (!_softContactCollisions.ContainsKey(pair)) {
1411 Debug.LogError("No collision set found for this pair of collisions; Exit method "
1412 + "was called without a prior, corresponding Enter method!", this);
1413 }
1414 _softContactCollisions[pair].Remove(collider);
1415
1416 if (_softContactCollisions[pair].Count == 0) {
1417 _softContactCollisions.Remove(pair);
1418 }
1419 }
1420
1421 #endregion
1422
1423 #endregion
1424
1425 #region Contact Callbacks
1426
1427 private HashSet<IInteractionBehaviour> _contactBehavioursSet = new HashSet<IInteractionBehaviour>();
1428
1429 private Dictionary<IInteractionBehaviour, int> _contactBehaviours = new Dictionary<IInteractionBehaviour, int>();
1430 private HashSet<IInteractionBehaviour> _contactBehavioursLastFrame = new HashSet<IInteractionBehaviour>();
1431 private List<IInteractionBehaviour> _contactBehaviourRemovalCache = new List<IInteractionBehaviour>();
1432
1433 private HashSet<IInteractionBehaviour> _contactEndedBuffer = new HashSet<IInteractionBehaviour>();
1434 private HashSet<IInteractionBehaviour> _contactBeganBuffer = new HashSet<IInteractionBehaviour>();
1435
1436 public void NotifyContactBoneCollisionEnter(ContactBone contactBone, IInteractionBehaviour interactionObj) {
1437 int count;
1438 if (_contactBehaviours.TryGetValue(interactionObj, out count)) {
1439 _contactBehaviours[interactionObj] = count + 1;
1440 }
1441 else {
1442 _contactBehaviours[interactionObj] = 1;
1443 _contactBehavioursSet.Add(interactionObj);
1444 }
1445 }
1446
1447 public void NotifyContactBoneCollisionStay(ContactBone contactBone, IInteractionBehaviour interactionObj) {
1448 // If Contact state is cleared manually or due to the controller being disabled,
1449 // it will be restored here.
1450
1451 int count;
1452 if (!_contactBehaviours.TryGetValue(interactionObj, out count)) {
1453 _contactBehaviours[interactionObj] = 1;
1454 _contactBehavioursSet.Add(interactionObj);
1455 }
1456 }
1457
1458 public void NotifyContactBoneCollisionExit(ContactBone contactBone, IInteractionBehaviour interactionObj) {
1459 if (interactionObj.ignoreContact) {
1460 if (_contactBehaviours.ContainsKey(interactionObj)) _contactBehaviours.Remove(interactionObj);
1461 return;
1462 }
1463
1464 // Sometimes when the controller is disabled and re-enabled, we might be missing the
1465 // key in the dictionary already.
1466 if (!_contactBehaviours.ContainsKey(interactionObj)) return;
1467
1468 int count = _contactBehaviours[interactionObj];
1469 if (count == 1) {
1470 _contactBehaviours.Remove(interactionObj);
1471 _contactBehavioursSet.Remove(interactionObj);
1472 }
1473 else {
1474 _contactBehaviours[interactionObj] = count - 1;
1475 }
1476 }
1477
1485 public void ClearContactTracking() {
1486 _controllerListBuffer.Clear();
1487 _controllerListBuffer.Add(this);
1488
1489 var tempObjs = Pool<HashSet<IInteractionBehaviour>>.Spawn();
1490 try {
1491 foreach (var intObj in contactingObjects) {
1492 tempObjs.Add(intObj);
1493 }
1494
1495 foreach (var intObj in tempObjs) {
1496 _contactBehavioursSet.Remove(intObj);
1497 _contactBehaviours.Remove(intObj);
1498 _contactBehavioursLastFrame.Remove(intObj);
1499
1500 intObj.EndContact(_controllerListBuffer);
1501 }
1502 }
1503 finally {
1504 Pool<HashSet<IInteractionBehaviour>>.Recycle(tempObjs);
1505 }
1506 }
1507
1516 if (!contactingObjects.Contains(intObj)) return;
1517
1518 _contactBehavioursSet.Remove(intObj);
1519 _contactBehaviours.Remove(intObj);
1520 _contactBehavioursLastFrame.Remove(intObj);
1521
1522 _controllerListBuffer.Clear();
1523 _controllerListBuffer.Add(this);
1524
1525 intObj.EndContact(_controllerListBuffer);
1526 }
1527
1532 private void fixedUpdateContactState() {
1533 _contactEndedBuffer.Clear();
1534 _contactBeganBuffer.Clear();
1535
1536 // Update contact ended state.
1537 _contactBehaviourRemovalCache.Clear();
1538 foreach (var interactionObj in _contactBehavioursLastFrame) {
1539 if (!_contactBehaviours.ContainsKey(interactionObj)
1540 || !contactBoneParent.activeInHierarchy
1541 /* || !contactEnabled TODO: Use properties to support disabling contact at runtime! */) {
1542 _contactEndedBuffer.Add(interactionObj);
1543 _contactBehaviourRemovalCache.Add(interactionObj);
1544 }
1545 }
1546 foreach (var interactionObj in _contactBehaviourRemovalCache) {
1547 _contactBehavioursLastFrame.Remove(interactionObj);
1548 }
1549
1550 // Update contact began state.
1551 if (contactBoneParent.activeInHierarchy /* && contactEnabled TODO: can this just be removed cleanly?*/) {
1552 foreach (var intObjCountPair in _contactBehaviours) {
1553 var interactionObj = intObjCountPair.Key;
1554 if (!_contactBehavioursLastFrame.Contains(interactionObj)) {
1555 _contactBeganBuffer.Add(interactionObj);
1556 _contactBehavioursLastFrame.Add(interactionObj);
1557 }
1558 }
1559 }
1560 }
1561
1562 private List<IInteractionBehaviour> _removeContactObjsBuffer = new List<IInteractionBehaviour>();
1568 bool IInternalInteractionController.CheckContactEnd(out HashSet<IInteractionBehaviour> contactEndedObjects) {
1569 // Ensure contact objects haven't been destroyed or set to ignore contact
1570 _removeContactObjsBuffer.Clear();
1571 foreach (var objTouchCountPair in _contactBehaviours) {
1572 if (objTouchCountPair.Key.gameObject == null
1573 || objTouchCountPair.Key.rigidbody == null
1574 || objTouchCountPair.Key.ignoreContact
1575 || !isTracked) {
1576 _removeContactObjsBuffer.Add(objTouchCountPair.Key);
1577 }
1578 }
1579
1580 // Clean out removed, invalid, or ignoring-contact objects
1581 foreach (var intObj in _removeContactObjsBuffer) {
1582 _contactBehaviours.Remove(intObj);
1583 _contactEndedBuffer.Add(intObj);
1584 }
1585
1586 contactEndedObjects = _contactEndedBuffer;
1587 return _contactEndedBuffer.Count > 0;
1588 }
1589
1595 bool IInternalInteractionController.CheckContactBegin(out HashSet<IInteractionBehaviour> contactBeganObjects) {
1596 contactBeganObjects = _contactBeganBuffer;
1597 return _contactBeganBuffer.Count > 0;
1598 }
1599
1600 private HashSet<IInteractionBehaviour> _contactedObjects = new HashSet<IInteractionBehaviour>();
1606 bool IInternalInteractionController.CheckContactStay(out HashSet<IInteractionBehaviour> contactedObjects) {
1607 _contactedObjects.Clear();
1608 foreach (var objCountPair in _contactBehaviours) {
1609 _contactedObjects.Add(objCountPair.Key);
1610 }
1611
1612 contactedObjects = _contactedObjects;
1613 return contactedObjects.Count > 0;
1614 }
1615
1616 #endregion
1617
1618 private void disableContactBoneCollision() {
1619 foreach (var contactBone in contactBones) {
1620 contactBone.collider.isTrigger = true;
1621 }
1622 }
1623
1624 private void enableContactBoneCollision() {
1625 foreach (var contactBone in contactBones) {
1626 contactBone.collider.isTrigger = false;
1627 }
1628 }
1629
1630 private void resetContactBonePose() {
1631 int index = 0;
1632 foreach (var contactBone in contactBones) {
1636
1637 contactBone.rigidbody.position = position;
1638 contactBone.rigidbody.rotation = rotation;
1639 contactBone.rigidbody.velocity = Vector3.zero;
1640 contactBone.rigidbody.angularVelocity = Vector3.zero;
1641 }
1642 }
1643
1644 #endregion
1645
1646 #region Grasping
1647
1649 public bool isGraspingObject { get { return _graspedObject != null; } }
1650
1652 public IInteractionBehaviour graspedObject { get { return _graspedObject; } }
1653
1655 public ReadonlyHashSet<IInteractionBehaviour> graspCandidates { get { return graspActivityManager.ActiveObjects; } }
1656
1665 public abstract List<Vector3> graspManipulatorPoints { get; }
1666
1672 public abstract Vector3 GetGraspPoint();
1673
1679 public bool TryGrasp(IInteractionBehaviour intObj) {
1680 if (checkShouldGraspAtemporal(intObj)) {
1681 _graspedObject = intObj;
1682 OnGraspBegin();
1683
1684 return true;
1685 }
1686
1687 return false;
1688 }
1689
1698 public virtual void SwapGrasp(IInteractionBehaviour replacement) {
1699 if (_graspedObject == null) {
1700 throw new InvalidOperationException("Cannot swap grasp if we are not currently grasping.");
1701 }
1702
1703 if (replacement == null) {
1704 throw new ArgumentNullException("The replacement object is null!");
1705 }
1706
1707 if (replacement.isGrasped && !replacement.allowMultiGrasp) {
1708 throw new InvalidOperationException("Cannot swap grasp if the replacement object is already grasped and does not support multi grasp.");
1709 }
1710
1711 //Notify the currently grasped object that it is being released
1712 _releasingControllersBuffer.Clear();
1713 _releasingControllersBuffer.Add(this);
1714 _graspedObject.EndGrasp(_releasingControllersBuffer);
1715 OnGraspEnd();
1716
1717 //Switch to the replacement object
1718 _graspedObject = replacement;
1719
1720 var tempControllers = Pool<List<InteractionController>>.Spawn();
1721 try {
1722 //Let the replacement object know that it is being grasped
1723 tempControllers.Add(this);
1724 replacement.BeginGrasp(tempControllers);
1725 OnGraspBegin();
1726 }
1727 finally {
1728 tempControllers.Clear();
1729 Pool<List<InteractionController>>.Recycle(tempControllers);
1730 }
1731 }
1732
1745 protected abstract bool checkShouldGraspAtemporal(IInteractionBehaviour intObj);
1746
1747 private Func<Collider, IInteractionBehaviour> graspActivityFilter;
1748 private IInteractionBehaviour graspFilterFunc(Collider collider) {
1749 Rigidbody body = collider.attachedRigidbody;
1750 IInteractionBehaviour intObj = null;
1751
1752 bool validForGrasping = body != null
1753 && manager.interactionObjectBodies.TryGetValue(body, out intObj)
1754 && !intObj.ignoreGrasping;
1755
1756 if (validForGrasping) return intObj;
1757
1758 return null;
1759 }
1760
1761 // Layer mask for the hover acitivity manager.
1762 private Func<int> graspLayerMaskAccessor;
1763
1764 // Grasp Activity Manager
1765 private ActivityManager<IInteractionBehaviour> _graspActivityManager;
1767 private ActivityManager<IInteractionBehaviour> graspActivityManager {
1768 get {
1769 if (_graspActivityManager == null) {
1770 if (graspActivityFilter == null) graspActivityFilter = graspFilterFunc;
1771 if (graspLayerMaskAccessor == null) graspLayerMaskAccessor = manager.GetInteractionLayerMask;
1772
1773 _graspActivityManager = new ActivityManager<IInteractionBehaviour>(1F, graspActivityFilter);
1774
1775 _graspActivityManager.activationLayerFunction = graspLayerMaskAccessor;
1776 }
1777 return _graspActivityManager;
1778 }
1779 }
1780
1781 private IInteractionBehaviour _graspedObject = null;
1782
1783 private void fixedUpdateGrasping() {
1784 using (new ProfilerSample("Fixed Update Controller Grasping")) {
1785 Vector3? graspPoint = isTracked ? (Vector3?)hoverPoint : null;
1786 graspActivityManager.UpdateActivityQuery(graspPoint, LeapSpace.allEnabled);
1787
1789 }
1790 }
1791
1800 protected abstract void fixedUpdateGraspingState();
1801
1807 protected virtual void onGraspedObjectForciblyReleased(IInteractionBehaviour objectToBeReleased) { }
1808
1813 protected abstract bool checkShouldGrasp(out IInteractionBehaviour objectToGrasp);
1814
1819 protected abstract bool checkShouldRelease(out IInteractionBehaviour objectToRelease);
1820
1821 private List<InteractionController> _releasingControllersBuffer = new List<InteractionController>();
1828 public bool ReleaseGrasp() {
1829 if (_graspedObject == null) {
1830 return false;
1831 }
1832 else {
1833 // Release this controller's grasp.
1834 _releasingControllersBuffer.Clear();
1835 _releasingControllersBuffer.Add(this);
1836
1837 // Calling things in the right order requires we remember the object we're
1838 // releasing.
1839 var tempGraspedObject = _graspedObject;
1840
1841 // Clear controller grasped object, and enable soft contact.
1842 OnGraspEnd();
1843 _graspedObject = null;
1845
1846 // Fire object's grasp-end callback.
1847 tempGraspedObject.EndGrasp(_releasingControllersBuffer);
1848
1849 // The grasped object was forcibly released; some controllers hook into this
1850 // by virtual method implementation.
1851 onGraspedObjectForciblyReleased(tempGraspedObject);
1852
1853 return true;
1854 }
1855 }
1856
1868 public static void ReleaseGrasps(IInteractionBehaviour graspedObj,
1870 var controllersBuffer = Pool<List<InteractionController>>.Spawn();
1871 try {
1872 foreach (var controller in controllers) {
1873 if (controller.graspedObject != graspedObj) {
1874 Debug.LogError("Argument intObj " + graspedObj.name + " is not held by "
1875 + "controller " + controller.name + "; skipping release for this "
1876 + "controller.");
1877 continue;
1878 }
1879
1880 controllersBuffer.Add(controller);
1881 }
1882
1883 // Enable soft contact on releasing controllers, and clear grasp state.
1884 // Note: controllersBuffer is iterated twice to preserve state modification order.
1885 // For reference order, see InteractionController.ReleaseGrasp() above.
1886 foreach (var controller in controllersBuffer) {
1887 // Fire grasp end callback for the controller.
1888 controller.OnGraspEnd();
1889
1890 // Clear grasped object state.
1891 controller._graspedObject = null;
1892
1893 // Avoid "popping" of released objects by enabling soft contact on releasing
1894 // controllers.
1895 controller.EnableSoftContact();
1896 }
1897
1898 // Evaluate object logic for being released by each controller.
1899 graspedObj.EndGrasp(controllersBuffer);
1900
1901 // Object was forcibly released, so fire virtual callbacks on each controller.
1902 foreach (var controller in controllersBuffer) {
1903 controller.onGraspedObjectForciblyReleased(graspedObj);
1904 }
1905 }
1906 finally {
1907 controllersBuffer.Clear();
1908 Pool<List<InteractionController>>.Recycle(controllersBuffer);
1909 }
1910 }
1911
1916 public bool ReleaseGrasp(out IInteractionBehaviour releasedObject) {
1917 releasedObject = _graspedObject;
1918
1919 if (ReleaseGrasp()) {
1920 // releasedObject will be non-null
1921 return true;
1922 }
1923
1924 // releasedObject will be null
1925 return false;
1926 }
1927
1932 public bool ReleaseObject(IInteractionBehaviour toRelease) {
1933 if (toRelease == null) return false;
1934
1935 if (_graspedObject == toRelease) {
1936 ReleaseGrasp();
1937 return true;
1938 }
1939 else {
1940 return false;
1941 }
1942 }
1943
1944 #region Grasp State Checking
1945
1951 releasedObject = null;
1952
1953 bool shouldReleaseObject = false;
1954
1955 // Check releasing against interaction state.
1956 if (_graspedObject == null) {
1957 return false;
1958 }
1959 else if (_graspedObject.ignoreGrasping) {
1960 onGraspedObjectForciblyReleased(_graspedObject);
1961
1962 releasedObject = _graspedObject;
1963 shouldReleaseObject = true;
1964 }
1965
1966 // Actually check whether the controller implementation will release its grasp.
1967 if (!shouldReleaseObject) shouldReleaseObject = checkShouldRelease(out releasedObject);
1968
1969 if (shouldReleaseObject) {
1970 OnGraspEnd();
1971 _graspedObject = null;
1972 EnableSoftContact(); // prevent objects popping out of the hand on release
1973 return true;
1974 }
1975
1976 return false;
1977 }
1978
1983 bool IInternalInteractionController.CheckGraspBegin(out IInteractionBehaviour newlyGraspedObject) {
1984 newlyGraspedObject = null;
1985
1986 // Check grasping against interaction state.
1987 if (_graspedObject != null) {
1988 // Can't grasp any object if we're already grasping one or
1989 // if grasping is disabled.
1990 return false;
1991 }
1992
1993 // Actually check whether the controller implementation will grasp.
1994 bool shouldGraspObject = checkShouldGrasp(out newlyGraspedObject);
1995 if (shouldGraspObject) {
1996 _graspedObject = newlyGraspedObject;
1997 OnGraspBegin();
1998
1999 return true;
2000 }
2001
2002 return false;
2003 }
2004
2010 bool IInternalInteractionController.CheckGraspHold(out IInteractionBehaviour graspedObject) {
2011 graspedObject = _graspedObject;
2012 if (graspedObject != null) OnGraspStay();
2013 return graspedObject != null;
2014 }
2015
2021 bool IInternalInteractionController.CheckSuspensionBegin(out IInteractionBehaviour suspendedObject) {
2022 suspendedObject = null;
2023
2024 if (_graspedObject != null && !isTracked && !_graspedObject.isSuspended) {
2025 suspendedObject = _graspedObject;
2026 }
2027
2028 return suspendedObject != null;
2029 }
2030
2036 bool IInternalInteractionController.CheckSuspensionEnd(out IInteractionBehaviour resumedObject) {
2037 resumedObject = null;
2038
2039 if (_graspedObject != null && isTracked && _graspedObject.isSuspended) {
2040 resumedObject = _graspedObject;
2041 }
2042
2043 return resumedObject != null;
2044 }
2045
2046 #endregion
2047
2048 #endregion
2049
2050 #region Gizmos
2051
2052 public static class GizmoColors {
2053
2054 public static Color ContactBone { get { return Color.green.WithAlpha(0.5F); } }
2055 public static Color SoftContactBone { get { return Color.white.WithAlpha(0.5F); } }
2056
2057 public static Color HoverPoint { get { return Color.yellow.WithAlpha(0.5F); } }
2058 public static Color PrimaryHoverPoint { get { return Color.Lerp(Color.red, Color.yellow, 0.5F).WithAlpha(0.5F); } }
2059
2060 public static Color GraspPoint { get { return Color.Lerp(Color.blue, Color.cyan, 0.3F).WithAlpha(0.5F); } }
2061 public static Color Graspable { get { return Color.cyan.WithAlpha(0.5F); } }
2062
2063 }
2064
2071 public virtual void OnDrawRuntimeGizmos(RuntimeGizmoDrawer drawer) {
2072 if (!this.isActiveAndEnabled) return;
2073
2074 if (contactBoneParent != null) {
2075 if (!softContactEnabled) {
2076 drawer.color = GizmoColors.ContactBone;
2077 }
2078 else {
2079 drawer.color = GizmoColors.SoftContactBone;
2080 }
2081
2082 drawer.DrawColliders(contactBoneParent, true, true, true);
2083 }
2084
2085 // Hover Point
2086 if (hoverEnabled) {
2087 drawHoverPoint(drawer, hoverPoint);
2088 }
2089
2090 // Primary Hover Points
2091 if (hoverEnabled) {
2092 foreach (var point in primaryHoverPoints) {
2093 if (point == null) continue;
2094 drawPrimaryHoverPoint(drawer, point.position);
2095 }
2096 }
2097 }
2098
2099 protected static void drawHoverPoint(RuntimeGizmoDrawer drawer, Vector3 pos) {
2100 drawer.color = GizmoColors.HoverPoint;
2101 drawer.DrawWireSphere(pos, 0.03F);
2102 }
2103
2104 protected static void drawPrimaryHoverPoint(RuntimeGizmoDrawer drawer, Vector3 pos) {
2105 drawer.color = GizmoColors.PrimaryHoverPoint;
2106 drawer.DrawWireSphere(pos, 0.015F);
2107 }
2108
2109 #endregion
2110
2111 }
2112
2113}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
The Hand class reports the physical characteristics of a detected hand.
Definition: Hand.cs:26
ActivityManager is a wrapper around PhysX sphere queries for arbitrary Unity objects....
Func< int > activationLayerFunction
This function, if set to a non-null value, overrides the activationLayerMask setting with the result ...
HashSet< T > ActiveObjects
Returns the currently "active" objects – objects that were within the latest sphere query.
Contact Bones store data for the colliders and rigidbodies in each bone of the contact-related repres...
Definition: ContactBone.cs:24
Rigidbody rigidbody
The Rigidbody of this ContactBone. This field must not be null for the ContactBone to work correctly.
Definition: ContactBone.cs:40
InteractionBehaviours are components that enable GameObjects to interact with interaction controllers...
void ClearHoverTracking()
Clears all hover tracking state and fires the hover-end callbacks immediately. If objects are still i...
void ClearPrimaryHoverTracking()
Clears primary hover tracking state for the current primary hovered object.
abstract void onObjectUnregistered(IInteractionBehaviour intObj)
This method is called by the InteractionController when it is notified by the InteractionManager that...
void LockPrimaryHover(InteractionBehaviour intObj)
Sets the argument interaction object to be the current primary hover of this interaction controller a...
abstract bool isBeingMoved
Gets whether the underlying object (Leap hand or a held controller) is currently being moved or being...
ActivityManager< IInteractionBehaviour > hoverActivityManager
Action OnGraspEnd
Called when the InteractionController releases an object.
virtual void SwapGrasp(IInteractionBehaviour replacement)
Seamlessly swap the currently grasped object for a replacement object. It will behave like the hand r...
abstract Vector3 velocity
Returns the current velocity of this controller.
bool ReleaseGrasp()
Releases the object this hand is holding and returns true if the hand was holding an object,...
ReadonlyHashSet< IInteractionBehaviour > hoveredObjects
Returns a set of all Interaction objects currently hovered by this InteractionController.
void NotifySoftContactCollisionExit(ContactBone bone, IInteractionBehaviour intObj, Collider collider)
bool TryGrasp(IInteractionBehaviour intObj)
Checks if the provided interaction object can be grasped by this interaction controller in its curren...
ReadonlyList< Transform > primaryHoverPoints
Gets the list of Transforms to consider against nearby objects to determine the closest object (prima...
bool ReleaseGrasp(out IInteractionBehaviour releasedObject)
As ReleaseGrasp(), but also outputs the released object into releasedObject if the hand successfully ...
IInteractionBehaviour primaryHoveredObject
Gets the InteractionBehaviour that is currently this InteractionController's primary hovered object,...
abstract bool isTracked
Gets whether the underlying object (Leap hand or a held controller) is currently in a tracked state....
abstract void fixedUpdateGraspingState()
Called every fixed frame if grasping is enabled in the Interaction Manager.
abstract bool initContact()
Called to initialize contact colliders. See remarks for implementation requirements.
void ClearContactTrackingForObject(IInteractionBehaviour intObj)
Clears contact state for the specified object and fires its ContactEnd callbacks immediately.
Action< InteractionBehaviour > OnEndPrimaryHoveringObject
Called when this InteractionController stops primarily hovering over an InteractionBehaviour....
Action< InteractionBehaviour > OnBeginPrimaryHoveringObject
Called when this InteractionController begins primarily hovering over an InteractionBehaviour....
abstract bool checkShouldGrasp(out IInteractionBehaviour objectToGrasp)
Returns whether this controller should grasp an object this fixed frame, and if so,...
void NotifyObjectUnregistered(IInteractionBehaviour intObj)
abstract List< Vector3 > graspManipulatorPoints
Gets the points of the controller to add to the calculation to determine how held objects should move...
abstract Quaternion rotation
Returns the current rotation of this controller.
static float GetHoverDistance(Vector3 hoverPoint, IInteractionBehaviour behaviour)
Returns the hover distance from the hoverPoint to the specified object, automatically accounting for ...
ReadonlyHashSet< IInteractionBehaviour > graspCandidates
Gets the set of objects currently considered graspable.
abstract Vector3 hoverPoint
Gets the current position to check against nearby objects for hovering. Position is only used if the ...
Action< InteractionController > OnContactInitialized
Called when contact data is initialized.
void NotifyContactBoneCollisionStay(ContactBone contactBone, IInteractionBehaviour interactionObj)
void NotifySoftContactCollisionEnter(ContactBone bone, IInteractionBehaviour intObj, Collider collider)
Func< IInteractionBehaviour, bool > customHoverActivityFilter
In addition to standard hover validity checks, you can set this filter property to further filter obj...
void NotifyContactBoneCollisionEnter(ContactBone contactBone, IInteractionBehaviour interactionObj)
static Vector3 TransformPoint(Vector3 worldPoint, ISpaceComponent element)
Applies the spatial warping of the provided ISpaceComponent to a world-space point.
Vector3 primaryHoveringPoint
Gets the position of the primary hovering point that is closest to its primary hovered object,...
static void drawHoverPoint(RuntimeGizmoDrawer drawer, Vector3 pos)
abstract ControllerType controllerType
Gets the type of controller this object represents underneath the InteractionController abstraction....
void ClearContactTracking()
Clears contact state for this controller and fires the appropriate ContactEnd callbacks on currently-...
virtual void onGraspedObjectForciblyReleased(IInteractionBehaviour objectToBeReleased)
Optionally override this method to perform logic just before a grasped object is released because it ...
abstract bool checkShouldGraspAtemporal(IInteractionBehaviour intObj)
Checks if the provided interaction object can be grasped by this interaction controller in its curren...
void NotifyContactBoneCollisionExit(ContactBone contactBone, IInteractionBehaviour interactionObj)
void ClearHoverTrackingForObject(IInteractionBehaviour intObj)
Clears the hover tracking state for an object and fires the hover-end callback for that object immedi...
abstract InteractionHand intHand
If this InteractionController's controllerType is ControllerType.Hand, this gets the InteractionHand,...
ReadonlyHashSet< IInteractionBehaviour > contactingObjects
Gets the set of interaction objects that are currently touching this interaction controller.
int primaryHoveringPointIndex
Gets the index in the primaryHoverPoints array of the primary hover point that is currently closest t...
float scale
Contact requires knowledge of the controller's scale. Non-uniformly scaled controllers are NOT suppor...
static void drawPrimaryHoverPoint(RuntimeGizmoDrawer drawer, Vector3 pos)
static void ReleaseGrasps(IInteractionBehaviour graspedObj, ReadonlyHashSet< InteractionController > controllers)
Helper static method for forcing multiple controllers to release their grasps on a single object simu...
abstract bool checkShouldRelease(out IInteractionBehaviour objectToRelease)
Returns whether this controller should release an object this fixed frame, and if so,...
Action OnGraspBegin
Called when the InteractionController begins grasping an object.
abstract Vector3 GetGraspPoint()
Returns approximately where the controller is grasping the currently grasped InteractionBehaviour....
bool isPrimaryHovering
Gets whether the InteractionController is currently primarily hovering over any interaction object.
virtual void fixedUpdateController()
Called just before the InteractionController proceeds with its usual FixedUpdate.
virtual void onPostDisableSoftContact()
Optionally override this method to perform logic just after soft contact is disabled for this control...
Action< InteractionBehaviour > OnStayPrimaryHoveringObject
Called every (fixed) frame this InteractionController is primarily hovering over an InteractionBehavi...
abstract bool isLeft
Gets whether the underlying object (Leap hand or a held controller) represents or is held by a left h...
virtual void onPreEnableSoftContact()
Optionally override this method to perform logic just before soft contact is enabled for this control...
abstract void getColliderBoneTargetPositionRotation(int contactBoneIndex, out Vector3 targetPosition, out Quaternion targetRotation)
If your controller features no moving colliders relative to itself, simply return the desired positio...
abstract Vector3 position
Returns the current position of this controller.
virtual void OnDrawRuntimeGizmos(RuntimeGizmoDrawer drawer)
By default, this method will draw all of the colliders found in the contactBoneParent hierarchy,...
Action OnGraspStay
Called while the InteractionController is grasping an object.
IInteractionBehaviour graspedObject
Gets the object the controller is currently grasping, or null if there is no such object.
abstract void unwarpColliders(Transform primaryHoverPoint, ISpaceComponent warpedSpaceElement)
Implementing this method is necessary to support curved spaces as rendered by a Leap Graphic Renderer...
bool isRight
Gets whether the underlying object (Leap hand or a held controller) represents or is held by a right ...
bool primaryHoverLocked
When set to true, locks the current primarily hovered object, even if the hand gets closer to a diffe...
bool ReleaseObject(IInteractionBehaviour toRelease)
Attempts to release this hand's object, but only if the argument object is the object currently grasp...
bool isGraspingObject
Gets whether the controller is currently grasping an object.
float primaryHoverDistance
Gets the distance from the closest primary hover point on this controller to its primarily hovered ob...
Hand leapHand
Gets the last tracked state of the Leap hand.
Dictionary< Rigidbody, PhysicsUtility.Velocities > _softContactOriginalVelocities
Stores data for implementing Soft Contact for interaction controllers.
float SimulationScale
A scale that can be used to appropriately transform distances that otherwise expect one Unity unit to...
float WorldHoverActivationRadius
Interaction objects further than this distance from a given controller's hover point will not be cons...
List< PhysicsUtility.SoftContact > _softContacts
Stores data for implementing Soft Contact for interaction controllers.
Dictionary< Rigidbody, IInteractionBehaviour > interactionObjectBodies
Maps a Rigidbody to its attached interaction object, if the Rigidbody is part of and interaction obje...
void TransformAheadByFixedUpdate(Vector3 position, Quaternion rotation, out Vector3 newPosition, out Quaternion newRotation)
Transforms a position and rotation ahead by one FixedUpdate based on the prior motion of the Interact...
int GetInteractionLayerMask()
Returns a layer mask containing all layers that might contain interaction objects.
static InteractionManager instance
Often, only one InteractionManager is necessary per Unity scene. This property will contain that Inte...
void DrawColliders(GameObject gameObject, bool useWireframe=true, bool traverseHierarchy=true, bool drawTriggers=false)
void DrawWireSphere(Pose pose, float radius, int numSegments=32)
Color color
Sets or gets the color for the gizmos that will be drawn next.
static List< LeapSpace > allEnabled
Definition: LeapSpace.cs:19
IInteractionBehaviour is the interface that defines all Interaction objects, specifying the minimum s...
void EndGrasp(List< InteractionController > endedGrasping)
void EndContact(List< InteractionController > endedContact)
float GetHoverDistance(Vector3 worldPosition)
void EndHover(List< InteractionController > endedHovering)
void BeginGrasp(List< InteractionController > beganGrasping)
bool CheckGraspEnd(out IInteractionBehaviour releasedObject)
Vector3 InverseTransformPoint(Vector3 localWarpedSpace)
Transform a point from warped space to rect space.
ControllerType
The Interaction Engine can be controlled by hands tracked by the Leap Motion Controller,...
IgnoreHoverMode
Specified on a per-object basis to allow Interaction objects to ignore hover for the left hand,...
A utility struct for ease of use when you want to wrap a piece of code in a Profiler....
A simple wrapper around HashSet to provide readonly access. Useful when you want to return a HashSet ...
A simple wrapper around List to provide readonly access. Useful when you want to return a list to som...
Definition: ReadonlyList.cs:20