Tanoda
InteractionButton.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 System;
11using UnityEngine;
13using UnityEngine.Serialization;
14
15namespace Leap.Unity.Interaction {
16
22
23 #region Inspector
24
25 [Header("UI Control")]
26
27 [Tooltip("When set to false, this UI control will not be functional. Use this instead "
28 + "of disabling the component itself when you want to disable the user's "
29 + "ability to affect this UI control while keeping the GameObject active and, "
30 + "for example, rendering, and able to receive primaryHover state.")]
31 [SerializeField, FormerlySerializedAs("controlEnabled")]
32 private bool _controlEnabled = true;
33 public bool controlEnabled {
34 get { return _controlEnabled; }
35 set { _controlEnabled = value; }
36 }
37
38 [Header("Motion Configuration")]
39
42 public enum StartingPositionMode { Depressed, Relaxed }
43
47 [Tooltip("The minimum and maximum heights the button can exist at.")]
48 public Vector2 minMaxHeight = new Vector2(0f, 0.02f);
49
54 [Tooltip("The height that this button rests at; this value is a lerp in between the min and max height.")]
55 [Range(0f, 1f)]
56 public float restingHeight = 0.5f;
57
61 [Range(0, 1)]
62 [SerializeField]
63 private float _springForce = 0.1f;
64 public float springForce {
65 get {
66 return _springForce;
67 }
68 set {
69 _springForce = value;
70 }
71 }
72
73 #endregion
74
75 #region Events
76
77 [SerializeField]
78 [FormerlySerializedAs("OnPress")]
79 private UnityEvent _OnPress = new UnityEvent();
80
81 [SerializeField]
82 [FormerlySerializedAs("OnUnpress")]
83 private UnityEvent _OnUnpress = new UnityEvent();
84
85 public Action OnPress = () => { };
86 public Action OnUnpress = () => { };
87
88 #endregion
89
90 #region State
91
92 protected bool _isPressed = false;
94 public bool isPressed { get { return _isPressed; } }
95 [Obsolete("Deprecated. Use isPressed instead.", false)]
96 public bool isDepressed { get { return _isPressed; } }
97
98 protected bool _pressedThisFrame = false;
102 public bool pressedThisFrame { get { return _pressedThisFrame; } }
103 [Obsolete("Deprecated. Use pressedThisFrame instead.", false)]
104 public bool depressedThisFrame { get { return _pressedThisFrame; } }
105
106 protected bool _unpressedThisFrame = false;
110 public bool unpressedThisFrame { get { return _unpressedThisFrame; } }
111 [Obsolete("Deprecated. Use unpressedThisFrame instead.", false)]
112 public bool unDepressedThisFrame { get { return _unpressedThisFrame; } }
113
114 private float _pressedAmount = 0F;
121 public float pressedAmount { get { return _pressedAmount; } }
122 [Obsolete("Deprecated. Use pressedAmount instead.", false)]
123 public float depressedAmount { get { return _pressedAmount; } }
124
125
129 protected Vector3 initialLocalPosition;
130
135 protected Vector3 localPhysicsPosition;
136
141 protected Vector3 physicsPosition = Vector3.zero;
142
147 public virtual Vector3 RelaxedLocalPosition {
148 get {
150 + Vector3.back * Mathf.Lerp(minMaxHeight.x, minMaxHeight.y, restingHeight);
151 }
152 }
153
154 private Rigidbody _lastDepressor;
155 private Vector3 _localDepressorPosition;
156 private Vector3 _physicsVelocity = Vector3.zero;
157 private bool _physicsOccurred;
158 private bool _initialIgnoreGrasping = false;
159 private Quaternion _initialLocalRotation;
160 private InteractionController _lockedInteractingController = null;
161
162 #endregion
163
164 #region Unity Events
165
166 void Reset() {
169
171
172 rigidbody = GetComponent<Rigidbody>();
173 if (rigidbody != null) {
174 rigidbody.useGravity = false;
175 }
176 }
177
178 protected override void OnDisable() {
179 if (isPressed) {
180 _unpressedThisFrame = true;
181 OnUnpress();
182
183 if (_lockedInteractingController != null) {
184 _lockedInteractingController.primaryHoverLocked = false;
185 }
186 }
187
188 base.OnDisable();
189 }
190
191 protected override void Start() {
192 if(transform == transform.root) {
193 Debug.LogError("This button has no parent! Please ensure that it is parented to something!", this);
194 enabled = false;
195 }
196
197 // Initialize Positions
198 initialLocalPosition = transform.localPosition;
200 initialLocalPosition = transform.localPosition
201 + Vector3.forward * Mathf.Lerp(minMaxHeight.x, minMaxHeight.y, restingHeight);
202 }
203 transform.localPosition = initialLocalPosition
204 + Vector3.back * Mathf.Lerp(minMaxHeight.x, minMaxHeight.y, restingHeight);
205
206 localPhysicsPosition = transform.localPosition;
207 physicsPosition = transform.position;
208 rigidbody.position = physicsPosition;
209 _initialIgnoreGrasping = ignoreGrasping;
210 _initialLocalRotation = transform.localRotation;
211
212 //Add a custom grasp controller
215
216 OnPress += _OnPress.Invoke;
217 OnUnpress += _OnUnpress.Invoke;
218
219 base.Start();
220 }
221
222 protected virtual void FixedUpdate() {
223 if (!_physicsOccurred) {
224 _physicsOccurred = true;
225
226 if (!isGrasped && !rigidbody.IsSleeping()) {
227
228 float localPhysicsDisplacementPercentage
229 = Mathf.InverseLerp(minMaxHeight.x, minMaxHeight.y,
231
232 // Sleep the rigidbody if it's not really moving.
233 if (rigidbody.position == physicsPosition
234 && _physicsVelocity == Vector3.zero
235 && Mathf.Abs(localPhysicsDisplacementPercentage - restingHeight) < 0.01F) {
236 rigidbody.Sleep();
237 } else {
238 // Otherwise reset the body's position to where it was last time PhysX
239 // looked at it.
240 if (_physicsVelocity.ContainsNaN()) {
241 _physicsVelocity = Vector3.zero;
242 }
243
244 rigidbody.position = physicsPosition;
245 rigidbody.velocity = _physicsVelocity;
246 }
247 }
248 }
249 }
250
251 private const float FRICTION_COEFFICIENT = 30F;
252 private const float DRAG_COEFFICIENT = 60F;
253 protected virtual void Update() {
254
255 // Reset our convenience state variables.
256 _pressedThisFrame = false;
257 _unpressedThisFrame = false;
258
259 // Disable collision on this button if it is not the primary hover.
260 ignoreGrasping = _initialIgnoreGrasping ? true : !isPrimaryHovered && !isGrasped;
262
263 // Enforce local rotation (if button is child of non-kinematic rigidbody,
264 // this is necessary).
265 transform.localRotation = _initialLocalRotation;
266
267 // Apply physical corrections only if PhysX has modified our positions.
268 if (_physicsOccurred) {
269 _physicsOccurred = false;
270
271 // Record and enforce the sliding state from the previous frame.
272 if (this.primaryHoverDistance < 0.005f || isGrasped) {
275 transform.parent.InverseTransformPoint(rigidbody.position)
277 } else {
278 Vector2 localSlidePosition = new Vector2(localPhysicsPosition.x,
280
282 = transform.parent.InverseTransformPoint(this.transform.position);
283
284 localPhysicsPosition = new Vector3(localSlidePosition.x,
285 localSlidePosition.y,
287 }
288
289 // Calculate the physical kinematics of the button in local space
290 Vector3 localPhysicsVelocity = transform.parent.InverseTransformVector(rigidbody.velocity);
291 if (isPressed && isPrimaryHovered && _lastDepressor != null) {
292 Vector3 curLocalDepressorPos = transform.parent.InverseTransformPoint(_lastDepressor.position);
293 Vector3 origLocalDepressorPos = transform.parent.InverseTransformPoint(transform.TransformPoint(_localDepressorPosition));
294 localPhysicsVelocity = Vector3.back * 0.05f;
295 localPhysicsPosition = constrainDepressedLocalPosition(curLocalDepressorPos - origLocalDepressorPos);
296 }
297 else if (isGrasped) {
298 // Do nothing!
299 }
300 else {
301 Vector3 originalLocalVelocity = localPhysicsVelocity;
302
303 // Spring force
304 localPhysicsVelocity +=
305 Mathf.Clamp(_springForce * 10000F
307 - Mathf.Lerp(minMaxHeight.x, minMaxHeight.y, restingHeight)
309 -100f / transform.parent.lossyScale.x,
310 100f / transform.parent.lossyScale.x)
311 * Time.fixedDeltaTime * Vector3.forward;
312
313 // Friction & Drag
314 float velMag = originalLocalVelocity.magnitude;
315 var frictionDragVelocityChangeAmt = 0f;
316 if (velMag > 0F) {
317 // Friction force
318 var frictionForceAmt = velMag * FRICTION_COEFFICIENT;
319 frictionDragVelocityChangeAmt
320 += Time.fixedDeltaTime * transform.parent.lossyScale.x * frictionForceAmt;
321
322 // Drag force
323 float velSqrMag = velMag * velMag;
324 var dragForceAmt = velSqrMag * DRAG_COEFFICIENT;
325 frictionDragVelocityChangeAmt
326 += Time.fixedDeltaTime * transform.parent.lossyScale.x * dragForceAmt;
327
328 // Apply velocity change, but don't let friction or drag let velocity
329 // magnitude cross zero.
330 var newVelMag = Mathf.Max(0, velMag - frictionDragVelocityChangeAmt);
331 localPhysicsVelocity = localPhysicsVelocity / velMag * newVelMag;
332 }
333
334 }
335
336 // Transform the local physics back into world space
337 physicsPosition = transform.parent.TransformPoint(localPhysicsPosition);
338 _physicsVelocity = transform.parent.TransformVector(localPhysicsVelocity);
339
340 // Calculate the Depression State of the Button from its Physical Position
341 // Set its Graphical Position to be Constrained Physically
342 bool oldDepressed = isPressed;
343
344 // Normalized depression amount.
345 _pressedAmount = localPhysicsPosition.z.Map(initialLocalPosition.z - minMaxHeight.x,
347 1F, 0F);
348
349 // If the button is depressed past its limit...
352 if ((isPrimaryHovered && _lastDepressor != null) || isGrasped) {
353 _isPressed = true;
354 }
355 else {
357 _physicsVelocity = _physicsVelocity * 0.1f;
358 _isPressed = false;
359 _lastDepressor = null;
360 }
361 // Else if the button is extended past its limit...
362 }
365 physicsPosition = transform.position;
366 _isPressed = false;
367 _lastDepressor = null;
368 }
369 else {
370 // Else, just make the physical and graphical motion of the button match
371 transform.localPosition = localPhysicsPosition;
372
373 // Allow some hysteresis before setting isDepressed to false.
374 if (!isPressed
376 _isPressed = false;
377 _lastDepressor = null;
378 }
379 }
380
381 // If our depression state has changed since last time...
382 if (isPressed && !oldDepressed) {
384 _lockedInteractingController = primaryHoveringController;
385
386 OnPress();
387 _pressedThisFrame = true;
388
389 } else if (!isPressed && oldDepressed) {
390 _unpressedThisFrame = true;
391 OnUnpress();
392
393 if (!(isGrasped && graspingController == _lockedInteractingController)) {
394 _lockedInteractingController.primaryHoverLocked = false;
395 }
396
397 _lastDepressor = null;
398 }
399 }
400 }
401
407 protected virtual Vector3 constrainDepressedLocalPosition(Vector3 localPosition) {
408 // Buttons are only allowed to move along their Z axis.
409 return new Vector3(
412 localPhysicsPosition.z + localPosition.z);
413 }
414
415 protected virtual void onGraspBegin() {
417 _lockedInteractingController = primaryHoveringController;
418 }
419
420 protected virtual void onGraspEnd() {
423 _physicsVelocity = _physicsVelocity * 0.1f;
424 }
425
426 if (_lockedInteractingController != null && !isPressed) {
427 _lockedInteractingController.primaryHoverLocked = false;
428 _lockedInteractingController = null;
429 }
430 }
431
432 protected virtual void OnCollisionEnter(Collision collision) { trySetDepressor(collision.collider); }
433 protected virtual void OnCollisionStay(Collision collision) { trySetDepressor(collision.collider); }
434
435 // during Soft Contact, controller colliders are triggers
436 protected virtual void OnTriggerEnter(Collider collider) { trySetDepressor(collider); }
437 protected virtual void OnTriggerStay(Collider collider) { trySetDepressor(collider); }
438
439 // Try grabbing the offset between the fingertip and this object...
440 private void trySetDepressor(Collider collider) {
441 if (collider.attachedRigidbody != null && _lastDepressor == null && (localPhysicsPosition.z > initialLocalPosition.z - minMaxHeight.x)
442 && (manager.contactBoneBodies.ContainsKey(collider.attachedRigidbody)
443 && !this.ShouldIgnoreHover(manager.contactBoneBodies[collider.attachedRigidbody].interactionController))) {
444 _lastDepressor = collider.attachedRigidbody;
445 _localDepressorPosition = transform.InverseTransformPoint(collider.attachedRigidbody.position);
446 }
447 }
448
449 #endregion
450
451 #region Gizmos
452
453 protected virtual void OnDrawGizmosSelected() {
454 if (transform.parent != null) {
455 Gizmos.matrix = transform.parent.localToWorldMatrix;
456 Vector2 heights = minMaxHeight;
457 Vector3 originPosition = Application.isPlaying ? initialLocalPosition : transform.localPosition;
458 if (!Application.isPlaying && startingPositionMode == StartingPositionMode.Relaxed) {
459 originPosition = transform.localPosition + Vector3.forward * Mathf.Lerp(minMaxHeight.x, minMaxHeight.y, restingHeight);
460 }
461
462 Gizmos.color = Color.red;
463 Gizmos.DrawLine(originPosition + (Vector3.back * heights.x), originPosition + (Vector3.back * heights.y));
464 Gizmos.color = Color.green;
465 Gizmos.DrawLine(originPosition + (Vector3.back * heights.x), originPosition + (Vector3.back * Mathf.Lerp(heights.x, heights.y, restingHeight)));
466 }
467 }
468
469 #endregion
470
471 #region Public Methods
472
478 public void SetMinHeight(float minHeight) {
479 minMaxHeight = new Vector2(Mathf.Min(minMaxHeight.y, minHeight), minMaxHeight.y);
480 }
481 [Obsolete("Deprecated. Use SetMinHeight instead.", false)]
482 public void setMinHeight(float minHeight) {
483 SetMinHeight(minHeight);
484 }
485
491 public void SetMaxHeight(float maxHeight) {
492 minMaxHeight = new Vector2(minMaxHeight.x, Mathf.Max(minMaxHeight.x, maxHeight));
493 }
494 [Obsolete("Deprecated. Use SetMaxHeight instead.", false)]
495 public void setMaxHeight(float maxHeight) {
496 SetMaxHeight(maxHeight);
497 }
498
499 #endregion
500
501 }
502}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
UnityEngine.Color Color
Definition: TestScript.cs:32
InteractionBehaviours are components that enable GameObjects to interact with interaction controllers...
InteractionController primaryHoveringController
Gets the closest primary hovering interaction controller for this object, if it has one....
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.
bool isPrimaryHovered
Gets whether this object is the primary hover for any interaction controller.
InteractionController graspingController
Gets the controller currently grasping this object. Warning: If allowMultigrasp is enabled on this ob...
Action OnGraspEnd
Called when the object is no longer grasped by any interaction controllers.
bool isGrasped
Gets whether this object is grasped by any interaction controller.
float primaryHoverDistance
Gets the distance to the primary hover point whose controller is primarily hovering over this object....
A physics-enabled button. Activated by physically pressing the button, with events for press and unpr...
virtual void OnTriggerEnter(Collider collider)
Vector3 physicsPosition
The physical position of this element in world space; may diverge from the graphical position.
bool isPressed
Gets whether the button is currently held down.
Vector2 minMaxHeight
The minimum and maximum heights the button can exist at.
virtual void OnTriggerStay(Collider collider)
virtual Vector3 RelaxedLocalPosition
Returns the local position of this button when it is able to relax into its target position.
Vector3 localPhysicsPosition
The physical position of this element in local space; may diverge from the graphical position.
void SetMaxHeight(float maxHeight)
Sets the maximum height (y component) of the minMaxHeight property. The maximum height can't be set s...
virtual Vector3 constrainDepressedLocalPosition(Vector3 localPosition)
Clamps the input local-space position to the bounds allowed by this UI element, without clamping alon...
bool unpressedThisFrame
Gets whether the button was unpressed this frame.
Vector3 initialLocalPosition
The initial position of this element in local space, stored on Start().
virtual void OnCollisionEnter(Collision collision)
float restingHeight
The height that this button rests at; this value is a lerp in between the min and max height.
virtual void OnCollisionStay(Collision collision)
bool pressedThisFrame
Gets whether the button was pressed during this Update frame.
void SetMinHeight(float minHeight)
Sets the minimum height (x component) of the minMaxHeight property. The minimum height can't be set l...
float pressedAmount
Gets a normalized value between 0 and 1 based on how depressed the button currently is relative to it...
void LockPrimaryHover(InteractionBehaviour intObj)
Sets the argument interaction object to be the current primary hover of this interaction controller a...
bool primaryHoverLocked
When set to true, locks the current primarily hovered object, even if the hand gets closer to a diffe...
Dictionary< Rigidbody, ContactBone > contactBoneBodies
Maps a Rigidbody to its attached ContactBone, if the Rigidbody is part of an interaction controller.