diff Orchestland/Assets/LeapMotion/Scripts/Utils/GrabbingHand.cs @ 1:f7675884f2a1

Add Orchestland project
author Daiki OYAKAWA <e135764@ie.u-ryukyu.ac.jp>
date Fri, 17 Jul 2015 23:09:20 +0900
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Orchestland/Assets/LeapMotion/Scripts/Utils/GrabbingHand.cs	Fri Jul 17 23:09:20 2015 +0900
@@ -0,0 +1,338 @@
+/******************************************************************************\
+* Copyright (C) Leap Motion, Inc. 2011-2014.                                   *
+* Leap Motion proprietary. Licensed under Apache 2.0                           *
+* Available at http://www.apache.org/licenses/LICENSE-2.0.html                 *
+\******************************************************************************/
+
+using UnityEngine;
+using System.Collections;
+using Leap;
+
+// Leap Motion hand script that detects pinches and grabs the closest rigidbody.
+public class GrabbingHand : MonoBehaviour {
+
+  public enum PinchState {
+    kPinched,
+    kReleased,
+    kReleasing
+  }
+
+  // Layers that we can grab.
+  public LayerMask grabbableLayers = ~0;
+
+  // Ratio of the length of the proximal bone of the thumb that will trigger a pinch.
+  public float grabTriggerDistance = 0.7f;
+
+  // Ratio of the length of the proximal bone of the thumb that will trigger a release.
+  public float releaseTriggerDistance = 1.2f;
+
+  // Maximum distance of an object that we can grab when pinching.
+  public float grabObjectDistance = 2.0f;
+
+  // If the object gets far from the pinch we'll break the bond.
+  public float releaseBreakDistance = 0.3f;
+
+  // Curve of the trailing off of strength as you release the object.
+  public AnimationCurve releaseStrengthCurve;
+
+  // Filtering the rotation of grabbed object.
+  public float rotationFiltering = 0.4f;
+
+  // Filtering the movement of grabbed object.
+  public float positionFiltering = 0.4f;
+
+  // Minimum tracking confidence of the hand that will cause a change of state.
+  public float minConfidence = 0.1f;
+
+  // Clamps the movement of the grabbed object.
+  public Vector3 maxMovement = new Vector3(Mathf.Infinity, Mathf.Infinity, Mathf.Infinity);
+  public Vector3 minMovement = new Vector3(-Mathf.Infinity, -Mathf.Infinity, -Mathf.Infinity);
+
+  protected PinchState pinch_state_;
+  protected Collider active_object_;
+
+  protected float last_max_angular_velocity_;
+  protected Quaternion rotation_from_palm_;
+
+  protected Vector3 current_pinch_position_;
+  protected Vector3 filtered_pinch_position_;
+  protected Vector3 object_pinch_offset_;
+  protected Quaternion palm_rotation_;
+
+  void Start() {
+    pinch_state_ = PinchState.kReleased;
+    active_object_ = null;
+    last_max_angular_velocity_ = 0.0f;
+    rotation_from_palm_ = Quaternion.identity;
+    current_pinch_position_ = Vector3.zero;
+    filtered_pinch_position_ = Vector3.zero;
+    object_pinch_offset_ = Vector3.zero;
+    palm_rotation_ = Quaternion.identity;
+  }
+
+  void OnDestroy() {
+    OnRelease();
+  }
+
+  // Finds the closest grabbable object within range of the pinch.
+  protected Collider FindClosestGrabbableObject(Vector3 pinch_position) {
+    Collider closest = null;
+    float closest_sqr_distance = grabObjectDistance * grabObjectDistance;
+    Collider[] close_things =
+        Physics.OverlapSphere(pinch_position, grabObjectDistance, grabbableLayers);
+
+    for (int j = 0; j < close_things.Length; ++j) {
+      float sqr_distance = (pinch_position - close_things[j].transform.position).sqrMagnitude;
+
+      if (close_things[j].GetComponent<Rigidbody>() != null && sqr_distance < closest_sqr_distance &&
+          !close_things[j].transform.IsChildOf(transform) &&
+          close_things[j].tag != "NotGrabbable") {
+
+        GrabbableObject grabbable = close_things[j].GetComponent<GrabbableObject>();
+        if (grabbable == null || !grabbable.IsGrabbed()) {
+          closest = close_things[j];
+          closest_sqr_distance = sqr_distance;
+        }
+      }
+    }
+
+    return closest;
+  }
+
+  // Notify grabbable objects when they are ready to grab :)
+  protected void Hover() {
+    Collider hover = FindClosestGrabbableObject(current_pinch_position_);
+
+    if (hover != active_object_ && active_object_ != null) {
+      GrabbableObject old_grabbable = active_object_.GetComponent<GrabbableObject>();
+
+      if (old_grabbable != null)
+        old_grabbable.OnStopHover();
+    }
+
+    if (hover != null) {
+      GrabbableObject new_grabbable = hover.GetComponent<GrabbableObject>();
+
+      if (new_grabbable != null)
+        new_grabbable.OnStartHover();
+    }
+
+    active_object_ = hover;
+  }
+
+  protected void StartPinch() {
+    // Only pinch if we're hovering over an object.
+    if (active_object_ == null)
+      return;
+
+    HandModel hand_model = GetComponent<HandModel>();
+    Leap.Utils.IgnoreCollisions(gameObject, active_object_.gameObject, true);
+    GrabbableObject grabbable = active_object_.GetComponent<GrabbableObject>();
+
+    // Setup initial position and rotation conditions.
+    palm_rotation_ = hand_model.GetPalmRotation();
+    object_pinch_offset_ = Vector3.zero;
+
+    // If we don't center the object, find the closest point in the collider for our grab point.
+    if (grabbable == null || !grabbable.centerGrabbedObject) {
+      Vector3 delta_position = active_object_.transform.position - current_pinch_position_;
+
+      Ray pinch_ray = new Ray(current_pinch_position_, delta_position);
+      RaycastHit pinch_hit;
+
+      // If we raycast hits the object, we're outside the collider so grab the hit point.
+      // If not, we're inside the collider so just use the pinch position.
+      if (active_object_.Raycast(pinch_ray, out pinch_hit, grabObjectDistance))
+        object_pinch_offset_ = active_object_.transform.position - pinch_hit.point;
+      else
+        object_pinch_offset_ = active_object_.transform.position - current_pinch_position_;
+    }
+
+    filtered_pinch_position_ = active_object_.transform.position - object_pinch_offset_;
+    object_pinch_offset_ = Quaternion.Inverse(active_object_.transform.rotation) *
+                           object_pinch_offset_;
+    rotation_from_palm_ = Quaternion.Inverse(palm_rotation_) * active_object_.transform.rotation;
+
+    // If we can rotate the object quickly, increase max angular velocity for now.
+    if (grabbable == null || grabbable.rotateQuickly) {
+      last_max_angular_velocity_ = active_object_.GetComponent<Rigidbody>().maxAngularVelocity;
+      active_object_.GetComponent<Rigidbody>().maxAngularVelocity = Mathf.Infinity;
+    }
+
+    if (grabbable != null) {
+      // Notify grabbable object that it was grabbed.
+      grabbable.OnGrab();
+
+      if (grabbable.useAxisAlignment) {
+        // If this option is enabled we only want to align the object axis with the palm axis
+        // so we'll cancel out any rotation about the aligned axis.
+        Vector3 palm_vector = grabbable.rightHandAxis;
+        if (hand_model.GetLeapHand().IsLeft)
+          palm_vector = Vector3.Scale(palm_vector, new Vector3(-1, 1, 1));
+
+        Vector3 axis_in_palm = rotation_from_palm_ * grabbable.objectAxis;
+        Quaternion axis_correction = Quaternion.FromToRotation(axis_in_palm, palm_vector);
+        if (Vector3.Dot(axis_in_palm, palm_vector) < 0)
+          axis_correction = Quaternion.FromToRotation(axis_in_palm, -palm_vector);
+          
+        rotation_from_palm_ = axis_correction * rotation_from_palm_;
+      }
+    }
+  }
+
+  protected void OnRelease() {
+    if (active_object_ != null) {
+      // Notify the grabbable object that is was released.
+      GrabbableObject grabbable = active_object_.GetComponent<GrabbableObject>();
+      if (grabbable != null)
+        grabbable.OnRelease();
+
+      if (grabbable == null || grabbable.rotateQuickly)
+        active_object_.GetComponent<Rigidbody>().maxAngularVelocity = last_max_angular_velocity_;
+
+      Leap.Utils.IgnoreCollisions(gameObject, active_object_.gameObject, false);
+    }
+    active_object_ = null;
+
+    Hover();
+  }
+
+  protected PinchState GetNewPinchState() {
+    HandModel hand_model = GetComponent<HandModel>();
+    Hand leap_hand = hand_model.GetLeapHand();
+
+    Vector leap_thumb_tip = leap_hand.Fingers[0].TipPosition;
+    float closest_distance = Mathf.Infinity;
+
+    // Check thumb tip distance to joints on all other fingers.
+    // If it's close enough, you're pinching.
+    for (int i = 1; i < HandModel.NUM_FINGERS; ++i) {
+      Finger finger = leap_hand.Fingers[i];
+
+      for (int j = 0; j < FingerModel.NUM_BONES; ++j) {
+        Vector leap_joint_position = finger.Bone((Bone.BoneType)j).NextJoint;
+
+        float thumb_tip_distance = leap_joint_position.DistanceTo(leap_thumb_tip);
+        closest_distance = Mathf.Min(closest_distance, thumb_tip_distance);
+      }
+    }
+
+    // Scale trigger distance by thumb proximal bone length.
+    float proximal_length = leap_hand.Fingers[0].Bone(Bone.BoneType.TYPE_PROXIMAL).Length;
+    float trigger_distance = proximal_length * grabTriggerDistance;
+    float release_distance = proximal_length * releaseTriggerDistance;
+
+    if (closest_distance <= trigger_distance)
+      return PinchState.kPinched;
+    if (closest_distance <= release_distance && pinch_state_ != PinchState.kReleased &&
+        !ObjectReleaseBreak(current_pinch_position_))
+      return PinchState.kReleasing;
+    return PinchState.kReleased;
+  }
+
+  protected void UpdatePinchPosition() {
+    HandModel hand_model = GetComponent<HandModel>();
+    current_pinch_position_ = 0.5f * (hand_model.fingers[0].GetTipPosition() + 
+                                      hand_model.fingers[1].GetTipPosition());
+
+    Vector3 delta_pinch = current_pinch_position_ - filtered_pinch_position_;
+    filtered_pinch_position_ += (1.0f - positionFiltering) * delta_pinch;
+  }
+
+  protected void UpdatePalmRotation() {
+    HandModel hand_model = GetComponent<HandModel>();
+    palm_rotation_ = Quaternion.Slerp(palm_rotation_, hand_model.GetPalmRotation(),
+                                      1.0f - rotationFiltering);
+  }
+
+  protected bool ObjectReleaseBreak(Vector3 pinch_position) {
+    if (active_object_ == null)
+      return true;
+
+    Vector3 delta_position = pinch_position - active_object_.transform.position;
+    return delta_position.magnitude > releaseBreakDistance;
+  }
+
+  // If we're in a pinch state, just move the object to the right spot using velocities.
+  protected void ContinueHardPinch() {
+    Quaternion target_rotation = palm_rotation_ * rotation_from_palm_;
+
+    Vector3 target_position = filtered_pinch_position_ + target_rotation * object_pinch_offset_;
+    target_position.x = Mathf.Clamp(target_position.x, minMovement.x, maxMovement.x);
+    target_position.y = Mathf.Clamp(target_position.y, minMovement.y, maxMovement.y);
+    target_position.z = Mathf.Clamp(target_position.z, minMovement.z, maxMovement.z);
+    Vector3 velocity = (target_position - active_object_.transform.position) / Time.deltaTime;
+    active_object_.GetComponent<Rigidbody>().velocity = velocity;
+
+    Quaternion delta_rotation = target_rotation *
+                                Quaternion.Inverse(active_object_.transform.rotation);
+
+    float angle = 0.0f;
+    Vector3 axis = Vector3.zero;
+    delta_rotation.ToAngleAxis(out angle, out axis);
+
+    if (angle >= 180) {
+      angle = 360 - angle;
+      axis = -axis;
+    }
+    if (angle != 0)
+      active_object_.GetComponent<Rigidbody>().angularVelocity = angle * axis;
+  }
+
+  // If we are releasing the object only apply a weaker force to the object
+  // like it's sliding through your fingers.
+  protected void ContinueSoftPinch() {
+    Quaternion target_rotation = palm_rotation_ * rotation_from_palm_;
+
+    Vector3 target_position = filtered_pinch_position_ + target_rotation * object_pinch_offset_;
+    Vector3 delta_position = target_position - active_object_.transform.position;
+
+    float strength = (releaseBreakDistance - delta_position.magnitude) / releaseBreakDistance;
+    strength = releaseStrengthCurve.Evaluate(strength);
+    active_object_.GetComponent<Rigidbody>().AddForce(delta_position.normalized * strength * positionFiltering,
+                                      ForceMode.Acceleration);
+
+    Quaternion delta_rotation = target_rotation *
+                                Quaternion.Inverse(active_object_.transform.rotation);
+
+    float angle = 0.0f;
+    Vector3 axis = Vector3.zero;
+    delta_rotation.ToAngleAxis(out angle, out axis);
+
+    active_object_.GetComponent<Rigidbody>().AddTorque(strength * rotationFiltering * angle * axis,
+                                       ForceMode.Acceleration);
+  }
+
+  void FixedUpdate() {
+    UpdatePalmRotation();
+    UpdatePinchPosition();
+    HandModel hand_model = GetComponent<HandModel>();
+    Hand leap_hand = hand_model.GetLeapHand();
+
+    if (leap_hand == null)
+      return;
+
+    PinchState new_pinch_state = GetNewPinchState();
+    if (pinch_state_ == PinchState.kPinched) {
+      if (new_pinch_state == PinchState.kReleased)
+        OnRelease();
+      else if (active_object_ != null)
+        ContinueHardPinch();
+    }
+    else if (pinch_state_ == PinchState.kReleasing) {
+      if (new_pinch_state == PinchState.kReleased)
+        OnRelease();
+      else if (new_pinch_state == PinchState.kPinched)
+        StartPinch();
+      else if (active_object_ != null)
+        ContinueSoftPinch();
+    }
+    else {
+      if (new_pinch_state == PinchState.kPinched)
+        StartPinch();
+      else
+        Hover();
+    }
+    pinch_state_ = new_pinch_state;
+  }
+}