/****************************************************************************** * Copyright (C) Ultraleap, Inc. 2011-2021. * * * * Use subject to the terms of the Apache License 2.0 available at * * http://www.apache.org/licenses/LICENSE-2.0, or another agreement * * between Ultraleap and you, your company or other organization. * ******************************************************************************/ using UnityEngine; namespace Leap.Unity { /// Ensure this script is on your player object and /// set to execute before the LeapXRServiceProvider public class LeapXRPinchLocomotion : MonoBehaviour { [Tooltip("Your Leap Hand Provider. Ensure the Pinch Locomotion script " + "is set to execute before this provider in the 'Script Execution Order'")] public LeapXRServiceProvider provider; [Range(0.00f, 50f)] public float pinchThreshold = 25f; [Range(0.0f, 0.2f)] public float momentum = 0.125f; public bool horizontalRotation = true; public bool enableScaling = true; bool isLeftPinching, isRightPinching; Vector3 rootA = Vector3.zero, rootB = Vector3.one; // The stationary world-space anchors Vector3 curA = Vector3.zero, curB = Vector3.one; // The dynamic world-space pinch points void Update() { Hand left = HandUtils.GetHand(provider.CurrentFrame, Chirality.Left); Hand right = HandUtils.GetHand(provider.CurrentFrame, Chirality.Right); bool leftPinching = left != null && left.PinchDistance < pinchThreshold; bool rightPinching = right != null && right.PinchDistance < pinchThreshold; if (leftPinching && rightPinching) { // Set Points when Both Pinched curA = left.GetPinchPosition(); curB = right.GetPinchPosition(); if (!isLeftPinching || !isRightPinching) { rootA = curA; rootB = curB; } } else if (leftPinching) { // Set Points when Left Pinched oneHandedPinchMove(left, isLeftPinching, isRightPinching, ref rootA, ref curA, rootB, ref curB); } else if (rightPinching) { // Set Points when Right Pinched oneHandedPinchMove(right, isRightPinching, isLeftPinching, ref rootB, ref curB, rootA, ref curA); } else { // Apply Momentum to Dynamic Points when Unpinched curA = Vector3.Lerp(curA, rootA, momentum); curB = Vector3.Lerp(curB, rootB, momentum); } isLeftPinching = leftPinching; isRightPinching = rightPinching; // Transform the root so the (dynamic) cur points match the (stationary) root points Vector3 pivot = ((rootA + rootB) / 2); Vector3 translation = pivot - ((curA + curB) / 2); Quaternion rotation = Quaternion.FromToRotation(Vector3.Scale(new Vector3(1f, horizontalRotation ? 0 : 1f, 1f), curB - curA), Vector3.Scale(new Vector3(1f, horizontalRotation ? 0 : 1f, 1f), rootB - rootA)); float scale = (rootA - rootB).magnitude / (curA - curB).magnitude; // Apply Translation transform.root.position += translation; if (rootA != rootB) { // Apply Rotation Pose curTrans = new Pose(transform.root.position, transform.root.rotation); curTrans = curTrans.Pivot(rotation, pivot); transform.root.position = curTrans.position; transform.root.rotation = curTrans.rotation; // Apply Scale about Pivot if (!float.IsNaN(scale) && enableScaling) { transform.root.position = ((transform.root.position - pivot) * scale) + pivot; transform.root.localScale *= scale; } } provider.RetransformFrames(); } /// Cheat Variable for one-handed momentum Vector3 residualMomentum = Vector3.zero; /// Ambidextrous function for handling one-handed pinch movement with momentum. void oneHandedPinchMove(Hand thisHand, bool thisIsPinching, bool otherIsPinching, ref Vector3 thisRoot, ref Vector3 thisCur, Vector3 otherRoot, ref Vector3 otherCur) { thisCur = thisHand.GetPinchPosition(); if (!thisIsPinching || otherIsPinching) { residualMomentum = otherCur - otherRoot; thisRoot = thisCur; } else { otherCur = (otherRoot + (thisCur - thisRoot)) + residualMomentum; } residualMomentum *= 1f - momentum; } } }