/****************************************************************************** * 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. * ******************************************************************************/ namespace Leap { using Leap.Unity; using System; using System.Collections.Generic; using UnityEngine; public static class TestHandFactory { #region Test Frame / Hand API public enum UnitType { LeapUnits, UnityUnits } /// /// Creates a test Frame that contains two Hands (by default). You can also /// optionally specify a TestHandPose to produce a frame with a different test pose. /// public static Frame MakeTestFrame(int frameId, bool includeLeftHand = true, bool includeRightHand = true, TestHandPose handPose = TestHandPose.HeadMountedA, UnitType unitType = UnitType.LeapUnits) { var testFrame = new Frame(frameId, 0, 120.0f, new List()); if (includeLeftHand) testFrame.Hands.Add(MakeTestHand(true, handPose, frameId, 10, unitType)); if (includeRightHand) testFrame.Hands.Add(MakeTestHand(false, handPose, frameId, 20, unitType)); return testFrame; } /// /// Returns a test Leap Hand object transformed by the leftHandTransform argument. /// If the Leap hand is a right hand, the position and rotation of the Hand will be /// mirrored along the X axis (so you can provide LeapTransform to construct both /// left and right hands. /// public static Hand MakeTestHand(bool isLeft, LeapTransform leftHandTransform, int frameId = 0, int handId = 0, UnitType unitType = UnitType.LeapUnits) { // Apply the appropriate mirroring if this is a right hand. if (!isLeft) { leftHandTransform.translation = new Vector(-leftHandTransform.translation.x, leftHandTransform.translation.y, leftHandTransform.translation.z); leftHandTransform.rotation = new LeapQuaternion(-leftHandTransform.rotation.x, leftHandTransform.rotation.y, leftHandTransform.rotation.z, -leftHandTransform.rotation.w); leftHandTransform.MirrorX(); } // Leap space is oriented differently than Unity space, so correct for this here. var hand = makeLeapSpaceTestHand(frameId, handId, isLeft) .Transform(leftHandTransform); var correctingQuaternion = Quaternion.Euler(90f, 0f, 180f); var correctingLeapQuaternion = new LeapQuaternion(correctingQuaternion.x, correctingQuaternion.y, correctingQuaternion.z, correctingQuaternion.w); var transformedHand = hand.Transform(new LeapTransform(Vector.Zero, correctingLeapQuaternion)); if (unitType == UnitType.UnityUnits) { transformedHand.TransformToUnityUnits(); } return transformedHand; } /// /// Returns a test Hand object. /// public static Hand MakeTestHand(bool isLeft, int frameId = 0, int handId = 0, UnitType unitType = UnitType.LeapUnits) { return MakeTestHand(isLeft, LeapTransform.Identity, frameId, handId, unitType); } /// /// Returns a test Leap Hand object in the argument TestHandPose. /// public static Hand MakeTestHand(bool isLeft, TestHandPose pose, int frameId = 0, int handId = 0, UnitType unitType = UnitType.LeapUnits) { return MakeTestHand(isLeft, GetTestPoseLeftHandTransform(pose), frameId, handId, unitType); } #endregion #region Test Hand Poses public enum TestHandPose { HeadMountedA, HeadMountedB, DesktopModeA, Screentop } public static LeapTransform GetTestPoseLeftHandTransform(TestHandPose pose) { LeapTransform transform = LeapTransform.Identity; switch (pose) { case TestHandPose.HeadMountedA: transform.rotation = angleAxis(180 * Constants.DEG_TO_RAD, Vector.Forward); transform.translation = new Vector(80f, 120f, 0f); break; case TestHandPose.HeadMountedB: transform.rotation = Quaternion.Euler(30F, -10F, -20F).ToLeapQuaternion(); transform.translation = new Vector(220f, 270f, 130f); break; case TestHandPose.DesktopModeA: transform.rotation = angleAxis(0f * Constants.DEG_TO_RAD, Vector.Forward) .Multiply(angleAxis(-90f * Constants.DEG_TO_RAD, Vector.Right)) .Multiply(angleAxis(180f * Constants.DEG_TO_RAD, Vector.Up)); transform.translation = new Vector(120f, 0f, -170f); break; case TestHandPose.Screentop: transform.rotation = angleAxis(0 * Constants.DEG_TO_RAD, Vector.Forward) .Multiply(angleAxis(140 * Constants.DEG_TO_RAD, Vector.Right)) .Multiply(angleAxis(0 * Constants.DEG_TO_RAD, Vector.Up)); transform.translation = new Vector(-120f, 20f, -380f); transform.scale = new Vector(1, 1, 1); break; } return transform; } #endregion #region Leap Space Hand Generation public static Vector PepperWristOffset = new Vector(-8.87f, -0.5f, 85.12f); private static Hand makeLeapSpaceTestHand(int frameId, int handId, bool isLeft) { List fingers = new List(5); fingers.Add(makeThumb(frameId, handId, isLeft)); fingers.Add(makeIndexFinger(frameId, handId, isLeft)); fingers.Add(makeMiddleFinger(frameId, handId, isLeft)); fingers.Add(makeRingFinger(frameId, handId, isLeft)); fingers.Add(makePinky(frameId, handId, isLeft)); Vector armWrist = new Vector(-7.05809944059f, 4.0f, 50.0f); Vector elbow = armWrist + 250f * Vector.Backward; // Adrian: The previous "armBasis" used "elbow" as a translation component. Arm arm = new Arm(elbow, armWrist, (elbow + armWrist) / 2, Vector.Forward, 250f, 41f, LeapQuaternion.Identity); Hand testHand = new Hand(frameId, handId, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 85f, isLeft, 0.0f, arm, fingers, new Vector(0, 0, 0), new Vector(0, 0, 0), new Vector(0, 0, 0), Vector.Down, LeapQuaternion.Identity, Vector.Forward, PepperWristOffset); // new Vector(-12.36385750984f, -6.5f, 81.0111342526f)); return testHand; } private static LeapQuaternion angleAxis(float angle, Vector axis) { if (!axis.MagnitudeSquared.NearlyEquals(1.0f)) { throw new ArgumentException("Axis must be a unit vector."); } float sineHalfAngle = Mathf.Sin(angle / 2.0f); LeapQuaternion q = new LeapQuaternion(sineHalfAngle * axis.x, sineHalfAngle * axis.y, sineHalfAngle * axis.z, Mathf.Cos(angle / 2.0f)); return q.Normalized; } private static LeapQuaternion rotationBetween(Vector fromDirection, Vector toDirection) { float m = Mathf.Sqrt(2.0f + 2.0f * fromDirection.Dot(toDirection)); Vector w = (1.0f / m) * fromDirection.Cross(toDirection); return new LeapQuaternion(w.x, w.y, w.z, 0.5f * m); } private static Finger makeThumb(int frameId, int handId, bool isLeft) { Vector position = new Vector(19.3382610281f, -6.0f, 53.168484654f); Vector forward = new Vector(0.636329113772f, -0.5f, -0.899787143982f); Vector up = new Vector(0.804793943718f, 0.447213915513f, 0.390264553767f); float[] jointLengths = { 0.0f, 46.22f, 31.57f, 21.67f }; return makeFinger(Finger.FingerType.TYPE_THUMB, position, forward, up, jointLengths, frameId, handId, handId + 0, isLeft); } private static Finger makeIndexFinger(int frameId, int handId, bool isLeft) { Vector position = new Vector(23.1812851873f, 2.0f, -23.1493459317f); Vector forward = new Vector(0.166044313785f, -0.14834045293f, -0.974897120667f); Vector up = new Vector(0.0249066470677f, 0.988936352868f, -0.1462345681f); float[] jointLengths = { 68.12f, 39.78f, 22.38f, 15.82f }; return makeFinger(Finger.FingerType.TYPE_INDEX, position, forward, up, jointLengths, frameId, handId, handId + 1, isLeft); } private static Finger makeMiddleFinger(int frameId, int handId, bool isLeft) { Vector position = new Vector(2.78877821918f, 4.0f, -23.252105626f); Vector forward = new Vector(0.0295207858556f, -0.148340452932f, -0.988495641481f); Vector up = new Vector(-0.145765270107f, 0.977715980076f, -0.151075968756f); float[] jointLengths = { 64.60f, 44.63f, 26.33f, 17.40f }; return makeFinger(Finger.FingerType.TYPE_MIDDLE, position, forward, up, jointLengths, frameId, handId, handId + 2, isLeft); } private static Finger makeRingFinger(int frameId, int handId, bool isLeft) { Vector position = new Vector(-17.447168266f, 4.0f, -17.2791440615f); Vector forward = new Vector(-0.121317937368f, -0.148340347175f, -0.981466810174f); Vector up = new Vector(-0.216910468316f, 0.968834928679f, -0.119619102602f); float[] jointLengths = { 58.00f, 41.37f, 25.65f, 17.30f }; return makeFinger(Finger.FingerType.TYPE_RING, position, forward, up, jointLengths, frameId, handId, handId + 3, isLeft); } private static Finger makePinky(int frameId, int handId, bool isLeft) { Vector position = new Vector(-35.3374394559f, 0.0f, -9.72871382551f); Vector forward = new Vector(-0.259328923438f, -0.105851224797f, -0.959970847306f); Vector up = new Vector(-0.353350220937f, 0.935459475557f, -0.00769356576168f); float[] jointLengths = { 53.69f, 32.74f, 18.11f, 15.96f }; return makeFinger(Finger.FingerType.TYPE_PINKY, position, forward, up, jointLengths, frameId, handId, handId + 4, isLeft); } private static Finger makeFinger(Finger.FingerType name, Vector position, Vector forward, Vector up, float[] jointLengths, int frameId, int handId, int fingerId, bool isLeft) { forward = forward.Normalized; up = up.Normalized; Bone[] bones = new Bone[5]; float proximalDistance = -jointLengths[0]; Bone metacarpal = makeBone(Bone.BoneType.TYPE_METACARPAL, position + forward * proximalDistance, jointLengths[0], 8f, forward, up, isLeft); proximalDistance += jointLengths[0]; bones[0] = metacarpal; Bone proximal = makeBone(Bone.BoneType.TYPE_PROXIMAL, position + forward * proximalDistance, jointLengths[1], 8f, forward, up, isLeft); proximalDistance += jointLengths[1]; bones[1] = proximal; Bone intermediate = makeBone(Bone.BoneType.TYPE_INTERMEDIATE, position + forward * proximalDistance, jointLengths[2], 8f, forward, up, isLeft); proximalDistance += jointLengths[2]; bones[2] = intermediate; Bone distal = makeBone(Bone.BoneType.TYPE_DISTAL, position + forward * proximalDistance, jointLengths[3], 8f, forward, up, isLeft); bones[3] = distal; return new Finger(frameId, handId, fingerId, 0.0f, distal.NextJoint, forward, 8f, jointLengths[1] + jointLengths[2] + jointLengths[3], true, name, bones[0], bones[1], bones[2], bones[3]); } private static Bone makeBone(Bone.BoneType name, Vector proximalPosition, float length, float width, Vector direction, Vector up, bool isLeft) { LeapQuaternion rotation = UnityEngine.Quaternion.LookRotation(-direction.ToVector3(), up.ToVector3()).ToLeapQuaternion(); return new Bone( proximalPosition, proximalPosition + direction * length, Vector.Lerp(proximalPosition, proximalPosition + direction * length, .5f), direction, length, width, name, rotation); } #endregion } // Note: The fact that this class needs to exist is ridiculous // TODO: Look into automatically returning things in Unity units? Would require changes // for everything that uses the TestHandFactory. public static class LeapTestProviderExtensions { public static readonly float MM_TO_M = 1e-3f; public static LeapTransform GetLeapTransform(Vector3 position, Quaternion rotation) { Vector scale = new Vector(MM_TO_M, MM_TO_M, MM_TO_M); // Leap units -> Unity units. LeapTransform transform = new LeapTransform(position.ToVector(), rotation.ToLeapQuaternion(), scale); transform.MirrorZ(); // Unity is left handed. return transform; } public static void TransformToUnityUnits(this Hand hand) { hand.Transform(GetLeapTransform(Vector3.zero, Quaternion.identity)); } } }