11#if UNITY_2017_2_OR_NEWER
18using System.Collections.Generic;
26 [DisallowMultipleComponent]
31 [Header(
"Controller Configuration")]
33 [Tooltip(
"Read-only. InteractionXRControllers use Unity's built-in XRNode tracking "
34 +
"API to receive tracking data for XR controllers by default. If you add a "
35 +
"custom script to set this controller's trackingProvider to something "
36 +
"other than the DefaultXRNodeTrackingProvider, the change will be reflected "
37 +
"here. (Hint: Use the ExecuteInEditMode attribute.)")]
38 [Disable, SerializeField]
39 #pragma warning disable 0414
40 private string _trackingProviderType =
"DefaultXRNodeTrackingProvider";
41#pragma warning restore 0414
46 [Tooltip(
"If this string is not empty and does not match a controller in "
47 +
"Input.GetJoystickNames(), then this game object will disable itself.")]
49 [FormerlySerializedAs(
"_deviceString")]
50 private string _deviceJoystickTokens =
"oculus touch right";
53 #pragma warning disable 0649
54 [Tooltip(
"Which hand will hold this controller? This property cannot be changed "
59 #pragma warning restore 0649
61 [Tooltip(
"Whether to continuously poll attached joystick data for a joystick that "
62 +
"matches the device joystick tokens, using Input.GetJoystickNames(). This "
63 +
"call allocates garbage, so be wary of setting a low polling interval.")]
64 [SerializeField, OnEditorChange(
"pollConnection")]
65 private bool _pollConnection =
true;
76 get {
return _pollConnection; }
77 set { _pollConnection = value; }
79 [Tooltip(
"The period in seconds at which to check if the controller is connected "
80 +
"by calling Input.GetJoystickNames(). This call allocates garbage, so be "
81 +
"wary of setting a low polling interval.")]
83 [
DisableIf(
"_pollConnection", isEqualTo:
false)]
86 [Header(
"Hover Configuration")]
88 [Tooltip(
"This is the point used to determine the distance to objects for the "
89 +
"purposes of their 'hovered' state. Generally, it should be somewhere "
90 +
"between the tip of the controller and the controller's center of mass.")]
92 private Transform _hoverPoint;
94 [Tooltip(
"These points refine the hover point when determining distances to "
95 +
"interaction objects for evaluating which object should be the primary hover "
96 +
"of this interaction controller. An object's proximity to one of these "
97 +
"points is interpreted as the user's intention to interact specifically "
98 +
"with that object, and is important when building less accident-prone user "
99 +
"interfaces. For example, hands place their primary hover points on the "
100 +
"thumb, index finger, and middle finger by default. Controllers generally "
101 +
"should have a primary hover point at any tip of the controller you expect "
102 +
"users might use to hit a button. Warning: Each point costs distance checks "
103 +
"against nearby objects, so making this list large is costly!")]
107 [Header(
"Grasping Configuration")]
109 [Tooltip(
"The point around which to check objects eligible for being grasped. Only "
110 +
"objects with an InteractionBehaviour component with ignoreGrasping disabled "
111 +
"are eligible for grasping. Upon attempting to grasp with a controller, the "
112 +
"object closest to the grasp point is chosen for grasping.")]
117 [Tooltip(
"This string should match an Axis specified in Edit->Project Settings->"
118 +
"Input. This is the button to use to listen for grasping.")]
121 [Tooltip(
"The duration of time in seconds beyond initially pressing the grasp button "
122 +
"that the user can move the grasp point within range of a graspable "
123 +
"interaction object and still trigger a grasp. With a value of zero, objects "
124 +
"can only be grasped if they are already within the grasp distance of the "
128 [Header(
"Enable/Disable GameObjects with Tracking State")]
130 [Tooltip(
"These objects will be made active only while the controller is tracked. "
131 +
"For more fine-tuned behavior, we recommend implementing your own logic. "
132 +
"controller.isJoystickDetected and controller.isTracked are useful for this.")]
134 private List<GameObject> _enableObjectsOnlyWhenTracked;
143 if (_enableObjectsOnlyWhenTracked ==
null) {
144 _enableObjectsOnlyWhenTracked =
new List<GameObject>();
146 return _enableObjectsOnlyWhenTracked;
181 fixedUpdatePollConnection();
185 gameObject.SetActive(
true);
190 gameObject.SetActive(
false);
194 refreshContactBoneTargets();
199 #region Controller Connection Polling
201 private bool _isJoystickDetected =
false;
216 private float _pollTimer = 0f;
218 private void fixedUpdatePollConnection() {
219 if (_pollConnection && !_isJoystickDetected) {
220 _pollTimer += Time.fixedDeltaTime;
231 _isJoystickDetected =
true;
234 string[] joysticksConnected = Input.GetJoystickNames().Query()
235 .Select(s => s.ToLower()).ToArray();
237 .Split(
" ".ToCharArray());
238 bool matchesController = joysticksConnected.Query()
239 .Any(joystick => controllerSupportTokens.Query()
240 .All(token => joystick.Contains(token)));
242 _isJoystickDetected = matchesController;
248 #region Controller Tracking
250 private bool _hasTrackedPositionLastFrame =
false;
251 private Vector3 _trackedPositionLastFrame = Vector3.zero;
252 private Quaternion _trackedRotationLastFrame = Quaternion.identity;
257 if (_backingDefaultTrackingProvider ==
null) {
258 _backingDefaultTrackingProvider = _defaultTrackingProvider;
261 return _backingDefaultTrackingProvider;
264 if (_backingTrackingProvider !=
null) {
268 _backingTrackingProvider = value;
270 if (_backingTrackingProvider !=
null) {
279 if (_backingDefaultTrackingProvider ==
null) {
280 refreshDefaultTrackingProvider();
283 return _backingDefaultTrackingProvider;
286 _backingDefaultTrackingProvider = value;
290 private void refreshDefaultTrackingProvider() {
291 var defaultProvider = gameObject.GetComponent<DefaultXRNodeTrackingProvider>();
292 if (defaultProvider ==
null) {
293 defaultProvider = gameObject.AddComponent<DefaultXRNodeTrackingProvider>();
295 defaultProvider.xrNode = this.
xrNode;
297 _defaultTrackingProvider = defaultProvider;
300 private void refreshControllerTrackingData(Vector3
position, Quaternion
rotation) {
303 if (_hasTrackedPositionLastFrame) {
304 _trackedPositionLastFrame = this.transform.position;
305 _trackedRotationLastFrame = this.transform.rotation;
310 refreshContactBoneTargets();
312 if (!_hasTrackedPositionLastFrame) {
313 _hasTrackedPositionLastFrame =
true;
314 _trackedPositionLastFrame = this.transform.position;
315 _trackedRotationLastFrame = this.transform.rotation;
321 #region Movement Detection
323 private const float RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD = 00.07F;
324 private const float RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD_SQR
325 = RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD * RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD;
326 private const float RIG_LOCAL_ROTATION_SPEED_THRESHOLD = 10.00F;
327 private const float BEING_MOVED_TIMEOUT = 0.5F;
329 private float _lastTimeMoved = 0F;
330 private bool _isBeingMoved =
false;
332 var isMoving =
false;
333 var baseTransform = this.
manager.transform;
336 var baseLocalPos = baseTransform.InverseTransformPoint(
position);
337 var baseLocalPosLastFrame =baseTransform.InverseTransformPoint(
338 _trackedPositionLastFrame);
339 var baseLocalSqrSpeed = ((baseLocalPos - baseLocalPosLastFrame)
340 / Time.fixedDeltaTime).sqrMagnitude;
341 if (baseLocalSqrSpeed > RIG_LOCAL_MOVEMENT_SPEED_THRESHOLD_SQR) {
346 var baseLocalRot = baseTransform.InverseTransformRotation(
rotation);
347 var baseLocalRotLastFrame = baseTransform.InverseTransformRotation(
348 _trackedRotationLastFrame);
349 var baseLocalAngularSpeed =
Quaternion.Angle(baseLocalRot, baseLocalRotLastFrame)
350 / Time.fixedDeltaTime;
351 if (baseLocalAngularSpeed > RIG_LOCAL_ROTATION_SPEED_THRESHOLD) {
356 _lastTimeMoved = Time.fixedTime;
361 var timeSinceLastMoving = Time.fixedTime - _lastTimeMoved;
363 && timeSinceLastMoving < BEING_MOVED_TIMEOUT;
368 #region General InteractionController Implementation
390 return _isBeingMoved;
400 #if UNITY_2017_2_OR_NEWER
422 return this.transform.position;
431 return this.transform.rotation;
440 if (_hasTrackedPositionLastFrame) {
441 return (this.transform.position - _trackedPositionLastFrame) / Time.fixedDeltaTime;
473 #region Hover Implementation
479 get {
return _hoverPoint ==
null ? Vector3.zero : _hoverPoint.position; }
494 private Vector3 _pivotingPositionOffset = Vector3.zero;
495 private Vector3 _unwarpingPositionOffset = Vector3.zero;
496 private Quaternion _unwarpingRotationOffset = Quaternion.identity;
500 Vector3 unwarpedPosition;
501 Quaternion unwarpedRotation;
502 warpedSpaceElement.
anchor.
transformer.WorldSpaceUnwarp(primaryHoverPoint.position,
503 primaryHoverPoint.rotation,
504 out unwarpedPosition,
505 out unwarpedRotation);
510 _pivotingPositionOffset = -primaryHoverPoint.position;
511 _unwarpingPositionOffset = unwarpedPosition;
512 _unwarpingRotationOffset = unwarpedRotation * Quaternion.Inverse(primaryHoverPoint.rotation);
514 refreshContactBoneTargets(useUnwarpingData:
true);
519 #region Contact Implementation
521 private Vector3[] _contactBoneLocalPositions;
522 private Quaternion[] _contactBoneLocalRotations;
524 private Vector3[] _contactBoneTargetPositions;
525 private Quaternion[] _contactBoneTargetRotations;
529 get {
return _contactBones; }
532 private GameObject _contactBoneParent;
534 get {
return _contactBoneParent; }
540 if (_contactBoneParent ==
null) {
541 _contactBoneParent =
new GameObject(
"VR Controller Contact Bones "
542 + (
isLeft ?
"(Left)" :
"(Right"));
545 foreach (var contactBone
in _contactBones) {
546 contactBone.transform.parent = _contactBoneParent.transform;
552 private void refreshContactBoneTargets(
bool useUnwarpingData =
false) {
557 if (useUnwarpingData) {
558 moveControllerTransform(_pivotingPositionOffset, Quaternion.identity);
559 moveControllerTransform(_unwarpingPositionOffset, _unwarpingRotationOffset);
562 for (
int i = 0; i < _contactBones.Length; i++) {
563 _contactBoneTargetPositions[i]
564 = this.transform.TransformPoint(_contactBoneLocalPositions[i]);
565 _contactBoneTargetRotations[i]
566 = this.transform.TransformRotation(_contactBoneLocalRotations[i]);
570 if (useUnwarpingData) {
571 moveControllerTransform(-_unwarpingPositionOffset, Quaternion.Inverse(_unwarpingRotationOffset));
572 moveControllerTransform(-_pivotingPositionOffset, Quaternion.identity);
577 private void moveControllerTransform(Vector3 deltaPosition, Quaternion deltaRotation) {
578 this.transform.rotation = deltaRotation * this.transform.rotation;
579 this.transform.position = deltaPosition + this.transform.position;
582 private List<ContactBone> _contactBoneBuffer =
new List<ContactBone>();
583 private List<Collider> _colliderBuffer =
new List<Collider>();
584 private void initContactBones() {
585 _colliderBuffer.Clear();
586 _contactBoneBuffer.Clear();
589 Utils.FindColliders<Collider>(this.gameObject, _colliderBuffer,
590 includeInactiveObjects:
true);
592 foreach (var collider
in _colliderBuffer) {
593 if (collider.isTrigger)
continue;
595 ContactBone contactBone = collider.gameObject.AddComponent<ContactBone>();
596 Rigidbody body = collider.gameObject.GetComponent<Rigidbody>();
598 body = collider.gameObject.AddComponent<Rigidbody>();
601 body.freezeRotation =
true;
602 body.useGravity =
false;
603 body.collisionDetectionMode = CollisionDetectionMode.ContinuousDynamic;
606 contactBone.interactionController =
this;
607 contactBone.rigidbody = body;
608 contactBone.collider = collider;
610 _contactBoneBuffer.Add(contactBone);
613 int numBones = _colliderBuffer.Count;
614 _contactBones =
new ContactBone[numBones];
615 _contactBoneLocalPositions =
new Vector3[numBones];
616 _contactBoneLocalRotations =
new Quaternion[numBones];
617 _contactBoneTargetPositions =
new Vector3[numBones];
618 _contactBoneTargetRotations =
new Quaternion[numBones];
619 for (
int i = 0; i < numBones; i++) {
620 _contactBones[i] = _contactBoneBuffer[i];
622 _contactBoneLocalPositions[i]
623 = _contactBoneTargetPositions[i]
624 = this.transform.InverseTransformPoint(_contactBones[i].transform.position);
625 _contactBoneLocalRotations[i]
626 = _contactBoneTargetRotations[i]
627 = this.transform.InverseTransformRotation(_contactBones[i].transform.rotation);
632 out Vector3 targetPosition,
633 out Quaternion targetRotation) {
634 targetPosition = _contactBoneTargetPositions[contactBoneIndex];
635 targetRotation = _contactBoneTargetRotations[contactBoneIndex];
640 #region Grasping Implementation
655 private float _graspDepressedValue = 0.8F;
661 get {
return _graspDepressedValue; }
662 set { _graspDepressedValue = value; }
665 private float _graspReleasedValue = 0.7F;
672 get {
return _graspReleasedValue; }
673 set { _graspReleasedValue = value; }
676 private List<Vector3> _graspManipulatorPointsBuffer =
new List<Vector3>();
685 _graspManipulatorPointsBuffer.Clear();
686 _graspManipulatorPointsBuffer.Add(
hoverPoint);
687 _graspManipulatorPointsBuffer.Add(
hoverPoint + this.transform.rotation * Vector3.forward * 0.05F);
688 _graspManipulatorPointsBuffer.Add(
hoverPoint + this.transform.rotation * Vector3.right * 0.05F);
689 return _graspManipulatorPointsBuffer;
695 private bool _graspButtonLastFrame =
false;
696 private bool _graspButtonDown =
false;
697 private bool _graspButtonUp =
false;
698 private float _graspButtonDownSlopTimer = 0F;
699 private bool _inputWarningDisplayed =
false;
706 refreshClosestGraspableObject();
708 fixedUpdateGraspButtonState();
711 private void refreshClosestGraspableObject() {
712 _closestGraspableObject =
null;
714 float closestGraspableDistance =
float.PositiveInfinity;
718 _closestGraspableObject = intObj;
719 closestGraspableDistance = testDist;
724 private void fixedUpdateGraspButtonState(
bool ignoreTemporal =
false) {
725 _graspButtonDown =
false;
726 _graspButtonUp =
false;
728 bool graspButton = _graspButtonLastFrame;
730 if (!_graspButtonLastFrame) {
735 if (!_inputWarningDisplayed) {
736 Debug.LogWarning(
"VR CONTROLLER INPUT AXES ARE NOT SET UP. Go to your Input Manager " +
738 "Joystick Axis or disable this controller.",
this);
739 _inputWarningDisplayed =
true;
741 graspButton = Input.GetKey(
isLeft ? KeyCode.JoystickButton14: KeyCode.JoystickButton15);
750 _graspButtonDown =
true;
756 Debug.LogWarning(
"The graspReleasedValue should be less than or equal to the "
757 +
"graspDepressedValue!",
this);
765 if (!_inputWarningDisplayed) {
766 Debug.LogWarning(
"VR CONTROLLER INPUT AXES ARE NOT SET UP. Go to your Input Manager " +
768 "Joystick Axis or disable this controller.",
this);
769 _inputWarningDisplayed =
true;
771 graspButton = Input.GetKey(
isLeft ? KeyCode.JoystickButton14 : KeyCode.JoystickButton15);
780 _graspButtonUp =
true;
781 _graspButtonDownSlopTimer = 0F;
785 if (_graspButtonDownSlopTimer > 0F) {
786 _graspButtonDownSlopTimer -= Time.fixedDeltaTime;
789 _graspButtonLastFrame = graspButton;
794 && (_graspButtonDown || _graspButtonDownSlopTimer > 0F)
795 && _closestGraspableObject !=
null;
797 objectToGrasp =
null;
798 if (shouldGrasp) { objectToGrasp = _closestGraspableObject; }
810 && _graspButtonLastFrame
813 var tempControllers = Pool<List<InteractionController>>.Spawn();
818 tempControllers.Clear();
819 Pool<List<InteractionController>>.Recycle(tempControllers);
829 objectToRelease =
null;
832 return shouldRelease;
840 base.OnDrawRuntimeGizmos(drawer);
843 float graspAmount = 0F;
849 catch (ArgumentException) { }
852 drawer.color =
Color.Lerp(GizmoColors.GraspPoint,
Color.white, graspAmount);
856 if (_closestGraspableObject !=
null) {
857 drawer.color =
Color.Lerp(GizmoColors.Graspable,
Color.white, Mathf.Sin(Time.time * 2 * Mathf.PI * 2F));
Implements IVRControllerTrackingProvider using Unity.XR.InputTracking for XRNodes....
ReadonlyHashSet< IInteractionBehaviour > graspCandidates
Gets the set of objects currently considered graspable.
InteractionManager manager
bool _wasContactInitialized
IInteractionBehaviour graspedObject
Gets the object the controller is currently grasping, or null if there is no such object.
bool isGraspingObject
Gets whether the controller is currently grasping an object.
List< GameObject > enableObjectsOnlyWhenTracked
These objects will be made active only while the controller is tracked. For more fine-tuned behavior,...
new List< Transform > primaryHoverPoints
override Vector3 hoverPoint
Gets the center point used for hover distance checking.
override bool isBeingMoved
Gets whether or not the underlying controller is currently being moved in world space,...
override void OnDrawRuntimeGizmos(RuntimeGizmos.RuntimeGizmoDrawer drawer)
override bool checkShouldRelease(out IInteractionBehaviour objectToRelease)
Returns whether this controller should release an object this fixed frame, and if so,...
override bool checkShouldGraspAtemporal(IInteractionBehaviour intObj)
If the provided object is within range of this VR controller's grasp point and the grasp button is cu...
override InteractionHand intHand
This implementation of InteractionControllerBase does not represent a Leap hand, so it need not retur...
override List< Vector3 > graspManipulatorPoints
Gets a list returning this controller's hoverPoint. Because the InteractionVRController represents a ...
Func< float > graspAxisOverride
By default, InteractionVRController uses Input.GetAxis(graspButtonAxis) to determine the "depression"...
bool isUsingCustomTracking
override bool initContact()
Called to initialize contact colliders. See remarks for implementation requirements.
override 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...
override List< Transform > _primaryHoverPoints
Gets the list of points to be used to perform higher-fidelity "primary hover" checks....
override GameObject contactBoneParent
VRNode xrNode
Gets the XRNode associated with this XR controller. Note: If the tracking mode for this controller is...
void RefreshControllerConnection()
float pollConnectionInterval
bool pollConnection
Whether to continuously poll attached joystick data for a joystick that matches the device joystick t...
override ContactBone[] contactBones
float graspDepressedValue
The value between 0 and 1 past which the grasping axis value will cause an attempt to grasp a graspab...
override void fixedUpdateController()
Called just before the InteractionController proceeds with its usual FixedUpdate.
override bool isLeft
Gets whether the controller is a left-hand controller.
override Vector3 position
Gets the last-tracked position of the controller.
override bool checkShouldGrasp(out IInteractionBehaviour objectToGrasp)
Returns whether this controller should grasp an object this fixed frame, and if so,...
override ControllerType controllerType
Gets the type of controller this is. For InteractionVRController, the type is always ControllerType....
override void unwarpColliders(Transform primaryHoverPoint, ISpaceComponent warpedSpaceElement)
Implementing this method is necessary to support curved spaces as rendered by a Leap Graphic Renderer...
override void onObjectUnregistered(IInteractionBehaviour intObj)
InteractionVRController doesn't need to do anything when an object is unregistered.
override bool isTracked
Gets whether or not the underlying controller is currently tracked and any joystick token filtering h...
override void fixedUpdateGraspingState()
Called every fixed frame if grasping is enabled in the Interaction Manager.
bool isJoystickDetected
Whether the device joystick tokens matched an entry in Input.GetJoystickNames(). If pollConnection is...
string deviceJoystickTokens
virtual void OnValidate()
override Quaternion rotation
Gets the last-tracked rotation of the controller.
override Vector3 GetGraspPoint()
Returns approximately where the controller is grasping the currently grasped InteractionBehaviour....
override Vector3 velocity
Gets the current velocity of the controller.
IXRControllerTrackingProvider trackingProvider
float graspReleasedValue
If the grasping axis value passes the graspDepressedValue, it must then drop underneath this value in...
IInteractionBehaviour is the interface that defines all Interaction objects, specifying the minimum s...
float GetHoverDistance(Vector3 worldPosition)
void BeginGrasp(List< InteractionController > beganGrasping)
The interface for providing tracking data to an InteractionVRController.
Action< Vector3, Quaternion > OnTrackingDataUpdate
An event that is fired whenever new tracking data is available for this controller.
bool isTracked
Gets whether or not this provider is currently tracking the controller for which it provides data.
ControllerType
The Interaction Engine can be controlled by hands tracked by the Leap Motion Controller,...