12using System.Collections;
13using System.Collections.Generic;
28 [Tooltip(
"Whether or not this AnchorableBehaviour is actively attached to its anchor.")]
29 private bool _isAttached =
false;
35 if (_isAttached != value) {
37 if (_anchor !=
null) {
43 Debug.LogWarning(
"Tried to attach an anchorable behaviour, but it has no assigned anchor.", this.gameObject);
48 _isLockedToAnchor =
false;
49 _isRotationLockedToAnchor =
false;
54 _hasTargetPositionLastUpdate =
false;
55 _hasTargetRotationLastUpdate =
false;
58 if (_reactivateGravityOnDetach) {
62 _reactivateGravityOnDetach =
false;
69 [Tooltip(
"The current anchor of this AnchorableBehaviour.")]
70 [OnEditorChange(
"anchor"), SerializeField]
77 if (_anchor != value) {
79 if (_anchor !=
null) {
84 _isLockedToAnchor =
false;
85 _isRotationLockedToAnchor =
false;
87 _hasTargetPositionLastUpdate =
false;
88 _hasTargetRotationLastUpdate =
false;
90 if (_anchor !=
null) {
98 Debug.LogWarning(
"The '" + value.name +
"' anchor is not in " +
this.name +
"'s anchor group.",
this.gameObject);
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]
109 get {
return _anchorGroup; }
113 _anchorGroup = value;
116 Debug.LogWarning(this.name +
"'s anchor is not within its anchorGroup (setting it to null).", this.gameObject);
123 [Header(
"Attachment")]
125 [Tooltip(
"Anchors beyond this range are ignored as possible anchors for this object.")]
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)]
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 "
139 private float _motionlessRangeFraction = 0.40F;
140 [SerializeField, Disable]
141 private float _maxMotionlessRange;
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.")]
147 private float _maxAttachmentAngle = 60F;
149 private float _minAttachmentDotProduct;
151 [Tooltip(
"Always attach an anchor if there is one within this distance, regardless "
155 private float _alwaysAttachDistance = 0f;
159 [Tooltip(
"Should the object move instantly to the anchor position?")]
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)]
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)]
174 [Tooltip(
"How fast should the object move towards its target position? Higher values are faster.")]
175 [
DisableIf(
"lockToAnchor", isEqualTo:
true)]
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.")]
185 [Header(
"Interaction")]
187 [Tooltip(
"Additional features are enabled when this GameObject also has an InteractionBehaviour component.")]
190 [SerializeField, HideInInspector]
191 private bool _interactionBehaviourIsNull =
true;
193 [Tooltip(
"If the InteractionBehaviour is set, objects will automatically detach from their anchor when grasped.")]
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.")]
201 [OnEditorChange(
"tryAnchorNearestOnGraspEnd")]
202 private bool _tryAnchorNearestOnGraspEnd =
true;
205 return _tryAnchorNearestOnGraspEnd;
213 _tryAnchorNearestOnGraspEnd = value;
220 [Tooltip(
"Should the object pull away from its anchor and reach towards the user's hand when the user's hand is nearby?")]
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.")]
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.")]
231 private Anchor _preferredAnchor =
null;
278 private bool _isLockedToAnchor =
false;
279 private Vector3 _offsetTowardsHand = Vector3.zero;
280 private Vector3 _targetPositionLastUpdate = Vector3.zero;
281 private bool _hasTargetPositionLastUpdate =
false;
283 private bool _isRotationLockedToAnchor =
false;
284 private Quaternion _targetRotationLastUpdate = Quaternion.identity;
285 private bool _hasTargetRotationLastUpdate =
false;
288 refreshInteractionBehaviour();
289 refreshInspectorConveniences();
293 refreshInteractionBehaviour();
297 refreshInteractionBehaviour();
298 refreshInspectorConveniences();
303 if (_tryAnchorNearestOnGraspEnd) {
312 if (
anchor !=
null && _isAttached) {
318 private bool _reactivateGravityOnDetach =
false;
321 updateAttractionToHand();
329 _reactivateGravityOnDetach =
true;
332 updateAnchorAttachment();
334 updateAnchorAttachmentRotation();
339 if (_isLockedToAnchor) {
344 updateAnchorPreference();
353 endAnchorPreference();
363 endAnchorPreference();
366 private void refreshInspectorConveniences() {
367 _minAttachmentDotProduct = Mathf.Cos(_maxAttachmentAngle * Mathf.Deg2Rad);
371 private void refreshInteractionBehaviour() {
426 Anchor optimalAnchor =
null;
427 float optimalScore = 0F;
429 float testScore = 0F;
432 testScore = getAnchorScore(
anchor);
435 if (testScore == 0F)
continue;
437 if (testScore > optimalScore) {
438 optimalAnchor = testAnchor;
439 optimalScore = testScore;
443 return optimalAnchor;
447 private List<Anchor> _nearbyAnchorsBuffer =
new List<Anchor>();
458 bool requireAnchorActiveAndEnabled =
true) {
459 HashSet<Anchor> anchorsToCheck;
468 _nearbyAnchorsBuffer.
Clear();
469 foreach (var
anchor in anchorsToCheck) {
471 || (requireAnchorActiveAndEnabled && !
anchor.isActiveAndEnabled))
continue;
474 _nearbyAnchorsBuffer.Add(
anchor);
478 return _nearbyAnchorsBuffer;
492 bool requireAnchorHasSpace =
true,
493 bool requireAnchorActiveAndEnabled =
true) {
494 HashSet<Anchor> anchorsToCheck;
503 Anchor closestAnchor =
null;
504 float closestDistSqrd =
float.PositiveInfinity;
505 foreach (var testAnchor
in anchorsToCheck) {
506 if (requireAnchorHasSpace) {
508 || testAnchor.allowMultipleObjects;
509 if (!anchorHasSpace) {
514 if (requireAnchorActiveAndEnabled && !testAnchor.isActiveAndEnabled) {
519 float testDistanceSqrd = (testAnchor.transform.position - this.transform.position).sqrMagnitude;
520 if (testDistanceSqrd < closestDistSqrd) {
521 closestAnchor = testAnchor;
522 closestDistSqrd = testDistanceSqrd;
527 return closestAnchor;
570 this.interactionBehaviour.rigidbody.velocity,
571 anchor.transform.position,
574 _minAttachmentDotProduct,
575 _alwaysAttachDistance);
585 public static float GetAnchorScore(Vector3 anchObjPos, Vector3 anchObjVel, Vector3 anchorPos,
float maxDistance,
float nonDirectedMaxDistance,
float minAngleProduct,
586 float alwaysAttachDistance = 0f) {
588 float directedness = anchObjVel.magnitude.Map(0.20F, 1F, 0F, 1F);
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));
594 float distanceSqrd = (anchorPos - effPos).sqrMagnitude;
596 if (distanceSqrd > effMaxDistance * effMaxDistance) {
600 distanceScore = distanceSqrd.Map(0F, effMaxDistance * effMaxDistance, 1F, 0F);
604 float dotProduct = Vector3.Dot(anchObjVel.normalized, (anchorPos - effPos).normalized);
607 dotProduct = Mathf.Lerp(1F, dotProduct, directedness);
609 angleScore = dotProduct.Map(minAngleProduct, 1f, 0f, 1f);
610 angleScore *= angleScore;
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)),
616 angleScore = useAlwaysAttachDistanceAmount.Map(0f, 1f, angleScore, 1f);
618 return distanceScore * angleScore;
621 private void updateAttractionToHand() {
623 if (_offsetTowardsHand != Vector3.zero) {
624 _offsetTowardsHand = Vector3.Lerp(_offsetTowardsHand, Vector3.zero, 5F * Time.deltaTime);
630 float reachTargetAmount = 0F;
631 Vector3 towardsHand = Vector3.zero;
633 Vector3 hoverTarget = Vector3.zero;
640 else hoverTarget = hoveringController.
hoverPoint;
643 Vector3.Distance(hoverTarget,
anchor.transform.position)));
644 towardsHand = hoverTarget -
anchor.transform.position;
648 _offsetTowardsHand =
Vector3.Lerp(_offsetTowardsHand, targetOffsetTowardsHand, 5 * Time.deltaTime);
651 private void updateAnchorAttachment() {
658 finalPosition = this.transform.position;
665 finalPosition = targetPosition + _offsetTowardsHand;
668 _hasTargetPositionLastUpdate =
false;
671 if (_isLockedToAnchor) {
673 finalPosition = targetPosition + _offsetTowardsHand;
676 _hasTargetPositionLastUpdate =
false;
680 finalPosition -= _offsetTowardsHand;
684 if (_hasTargetPositionLastUpdate) {
685 finalPosition += (targetPosition - _targetPositionLastUpdate);
688 _targetPositionLastUpdate = targetPosition;
689 _hasTargetPositionLastUpdate =
true;
694 if (
Vector3.Distance(finalPosition, targetPosition) < 0.001F) {
695 _isLockedToAnchor =
true;
699 finalPosition += _offsetTowardsHand;
706 this.transform.position = finalPosition;
709 this.transform.position = finalPosition;
713 private void updateAnchorAttachmentRotation() {
720 finalRotation = this.transform.rotation;
727 finalRotation = targetRotation;
730 _hasTargetPositionLastUpdate =
false;
733 if (_isRotationLockedToAnchor) {
735 finalRotation = targetRotation;
738 _hasTargetRotationLastUpdate =
false;
743 if (_hasTargetRotationLastUpdate) {
744 finalRotation = (
Quaternion.Inverse(_targetRotationLastUpdate) * targetRotation) * finalRotation;
747 _targetRotationLastUpdate = targetRotation;
748 _hasTargetRotationLastUpdate =
true;
754 if (
Quaternion.Angle(targetRotation, finalRotation) < 2F) {
755 _isRotationLockedToAnchor =
true;
763 this.transform.rotation = finalRotation;
766 this.transform.rotation = finalRotation;
770 private void updateAnchorPreference() {
771 Anchor newPreferredAnchor;
776 newPreferredAnchor =
null;
779 if (_preferredAnchor != newPreferredAnchor) {
780 if (_preferredAnchor !=
null) {
784 _preferredAnchor = newPreferredAnchor;
786 if (_preferredAnchor !=
null) {
792 private void endAnchorPreference() {
793 if (_preferredAnchor !=
null) {
795 _preferredAnchor =
null;
799 private void detachAnchorOnGraspBegin() {
803 private void tryToAnchorOnGraspEnd() {
809 #region Unity Events (Internal)
811 #pragma warning disable 0649
813 private EnumEventTable _eventTable;
814 #pragma warning restore 0649
825 private void initUnityEvents() {
834 private void setupCallback(ref Action action,
EventType type) {
835 if (_eventTable.HasUnityEvent((
int)type)) {
836 action += () => _eventTable.Invoke((
int)type);
The Hand class reports the physical characteristics of a detected hand.
Vector PalmPosition
The center position of the palm.
void NotifyAnchorableObjectAdded(AnchorableBehaviour anchObj)
void NotifyAnchorableObjectRemoved(AnchorableBehaviour anchObj)
bool Contains(Anchor anchor)
void NotifyAttached(AnchorableBehaviour anchObj)
HashSet< AnchorableBehaviour > anchoredObjects
Gets the set of AnchorableBehaviours currently attached to this anchor.
static HashSet< Anchor > allAnchors
void NotifyAnchorPreference(AnchorableBehaviour anchObj)
bool allowMultipleObjects
void NotifyEndAnchorPreference(AnchorableBehaviour anchObj)
void NotifyDetached(AnchorableBehaviour anchObj)
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...
float anchorLerpCoeffPerSec
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,...
AnimationCurve attractionReachByDistance
bool IsWithinRange(Anchor anchor)
Returns whether the specified anchor is within attachment range of this Anchorable object.
InteractionBehaviour interactionBehaviour
bool tryAnchorNearestOnGraspEnd
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...
bool matchAnchorMotionWhileAttaching
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 ...