0

I am trying to set the hand into one of a series of poses. I have created a script that captures a frame, serializes the left hand which is then stored as an xml for loading. I currently have the system setting the hand to the correct pose by calculating the offset between the palm position in the live data and that of the stored pose. The problem I am having is getting the hand to rotate.

The 'hand' parameter is the current hand from the live data and the 'pose' parameter is the hand that has been loaded from xml.

This is the method I have created:

public static Hand SetHandInPose(Hand hand, Hand pose)
{
    if(hand == null)
    {
        Debug.Log("Hand is null, so returning that");
        return hand;
    }
    if(pose == null)
    {
        Debug.Log("The loaded pose is null, so let's just return the original hand");
        return hand;
    }
    Hand h = pose;

    Quaternion handRotOffset = pose.Rotation.ToQuaternion() * Quaternion.Inverse(hand.Rotation.ToQuaternion());
    //Debug.Log("The rotational offset is: " + handRotOffset.eulerAngles);
    Vector offset = hand.PalmPosition - pose.PalmPosition;

    h.Rotation = hand.Rotation;//(h.Rotation.ToQuaternion() * handRotOffset).ToLeapQuaternion();
    h.PalmPosition += (offset.ToVector3()).ToVector();
    h.WristPosition += (offset.ToVector3()).ToVector();

    for(int f = 0; f< h.Fingers.Count; f++)
    {
        for(int i = 0; i < h.Fingers[f].bones.Length; i++)
        {
            //offset = hand.Fingers[f].bones[i].Center - pose.Fingers[f].bones[i].Center;     

            //if (h.Fingers[f].bones[i].Type == Bone.BoneType.TYPE_METACARPAL) continue;
            h.Fingers[f].bones[i].Center += (offset.ToVector3()).ToVector();
            h.Fingers[f].bones[i].NextJoint += (offset.ToVector3()).ToVector();
            h.Fingers[f].bones[i].PrevJoint += (offset.ToVector3()).ToVector();
            h.Fingers[f].bones[i].Rotation = hand.Fingers[f].bones[i].Rotation;
            //h.Fingers[f].bones[i].Rotation = (h.Fingers[f].bones[i].Rotation.ToQuaternion() * handRotOffset).ToLeapQuaternion();
            h.Fingers[f].bones[i].Direction = (h.Fingers[f].bones[i].NextJoint - h.Fingers[f].bones[i].PrevJoint);
            h.Fingers[f].bones[i].Center = (h.Fingers[f].bones[i].PrevJoint + h.Fingers[f].bones[i].NextJoint) / 2f;
        }
        h.Fingers[f].Direction = h.Fingers[f].GetBone(Bone.BoneType.TYPE_INTERMEDIATE).Direction;
    }
    return h;
}

Any suggestions/help would be much appreciated.

UPDATE:

Here's the new code based on the suggestion recieved.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Leap;
using Leap.Unity;

 public class MimicHandModelDriver : MonoBehaviour
 {

    public Chirality whichHand = Chirality.Left;

    public enum RotationMode { Inherit, Overwrite }
    public RotationMode rotationMode = RotationMode.Inherit;

    public MultiLeap_CapsuleHand handModelToDrive;
    private bool _handModelInitialized = false;
    private bool _handModelBegun = false;

    private Hand mimicHand = null;

    Hand sourceHand; //The current hand from the live data
    Hand poseHand; //Reference to the hand we load from XML that is in the pose we want

    public Hand SourceHand
    {
        set
        {
            sourceHand = value;
        }
    }

    public Hand PoseHand
    {
        set
        {
            poseHand = value;
        }
    }

    private void Update()
    {

        if (sourceHand != null)
        {
            // Copy data from the tracked hand into the mimic hand.
            if (mimicHand == null) { mimicHand = new Hand(); }

            mimicHand.CopyFrom(poseHand); //copy the stored pose in the mimic hand
            mimicHand.Arm.CopyFrom(poseHand.Arm); // copy the stored pose's arm into the mimic hand

            // Use the rotation from the live data
            var handRotation = sourceHand.Rotation.ToQuaternion();

            // Transform the copied hand so that it's centered on the current hands position and matches it's rotation.
            mimicHand.SetTransform(sourceHand.PalmPosition.ToVector3(), handRotation);
        }

        // Drive the attached HandModel.
        if (mimicHand != null && handModelToDrive != null)
        {
            // Initialize the handModel if it hasn't already been initialized.
            if (!_handModelInitialized)
            {
                handModelToDrive.SetLeapHand(mimicHand); //Prevents an error with null reference exception when creating the spheres from
                //the init hand call
                handModelToDrive.InitHand();
                _handModelInitialized = true;
            }

            // Set the HandModel's hand data.
            handModelToDrive.SetLeapHand(mimicHand);

            // "Begin" the HandModel to represent a 'newly tracked' hand.
            if (!_handModelBegun)
            {
                handModelToDrive.BeginHand();
                _handModelBegun = true;
            }

            Debug.Log("Updating the mimic hand");
            handModelToDrive.UpdateTheHand(); //This method contains the update code, with update code commented out
            //so i control when a hand is updated. I have used this throughout the rest of my project so i know this works. 
        }
    }

}
user7856951
  • 503
  • 1
  • 4
  • 15
  • It looks like this question attracted an answer, but then the question was substantially modified based on a new problem, some days later. I don't know how much that would invalidate the help you've already received below, but since you've posed your new problem in new questions, I think this one is better back how it was. Remember that if your question changes so much that the answers below are wrong or confusing, then they might get downvotes on that basis - try to always ensure the Q&A are kept in good order. – halfer Jan 31 '18 at 17:00
  • The answer given wasn't really applicable. – user7856951 Jan 31 '18 at 17:06
  • It happens. Nevertheless, if you modify this question to your current problem, you'll have three copies rather than two. I can't tell how much the original differs from your new situation, but in general I would not modify this even if the answer did not help, since it is still confusing for a question and an answer to disagree. I'd recommend focussing on your second question, since the +2 may help with new views. – halfer Jan 31 '18 at 17:12

1 Answers1

1

We've got some convenience/extension methods built into our UnityModules that make copying hand data around easier:

hand.Transform(LeapTransform transform) -- this will apply a transform to the Hand. hand.SetTransform(Vector3 position, Quaternion rotation) -- this will transform a Hand to center the PalmPosition on position and transform the whole hand to align with rotation. hand.CopyFrom(Hand other) -- this one will handle all the gorey details of copying the data (read: pose) from one Hand to another.

In this case, CopyFrom and SetTransform will get you the data you're looking for.

EDIT: So, the new aspect of your question involves using this data to drive a HandModel -- in this case, a CapsuleHand. For the current version of our Unity assets, the hand model pipeline is pretty shut-down. You're either using the standard Leap rig to drive real tracked hands, or you have a bit of a rough time.

Fortunately, you can just drive a HandModel manually, but you have to be careful to call the right methods in the right order. Check out this example script, which can drive a HandModel that it has a reference to. Important: because you're driving the HandModel independently of the normal Provider/HandPool/HandGroup pipeline, you need to create a new HandModel -- e.g. a new, duplicate CapsuleHand object -- that isn't in your Leap Rig and isn't being driven by that rig's HandPool. This script can then drive it:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Leap;
using Leap.Unity;

public class MimicHandModelDriver : MonoBehaviour {

  public Chirality whichHand = Chirality.Left;

  public enum RotationMode { Inherit, Overwrite }
  public RotationMode rotationMode = RotationMode.Inherit;

  public HandModelBase handModelToDrive;
  private bool _handModelInitialized = false;
  private bool _handModelBegun = false;

  [Header("Debug")]
  public bool drawEditorGizmos = false;

  private Hand mimicHand = null;

  private void Update() {
    // Get a hand from the standard tracking pipeline.
    var sourceHand = Hands.Get(whichHand);

    if (sourceHand != null) {
      // Copy data from the tracked hand into the mimic hand.
      if (mimicHand == null) { mimicHand = new Hand(); }
      mimicHand.CopyFrom(sourceHand);
      mimicHand.Arm.CopyFrom(sourceHand.Arm); // Capsule Hands like to have Arm data too.

      // Figure out what rotation to use for the mimic hand.
      var handRotation = this.transform.rotation;
      if (rotationMode == RotationMode.Inherit) {
        handRotation = mimicHand.Rotation.ToQuaternion();
      }

      // Transform the copied hand so that it's centered on this object's transform.
      mimicHand.SetTransform(this.transform.position, handRotation);
    }

    // Drive the attached HandModel.
    if (mimicHand != null && handModelToDrive != null) {
      // Initialize the handModel if it hasn't already been initialized.
      if (!_handModelInitialized) {
        handModelToDrive.InitHand();
        _handModelInitialized = true;
      }

      // Set the HandModel's hand data.
      handModelToDrive.SetLeapHand(mimicHand);

      // "Begin" the HandModel to represent a 'newly tracked' hand.
      if (!_handModelBegun) {
        handModelToDrive.BeginHand();
        _handModelBegun = true;
      }

      // Every Update, we call UpdateHand. It's necessary to call this every update
      // specifically for CapsuleHands, which uses manual GL calls to render rather than
      // a standard MeshRenderer.
      handModelToDrive.UpdateHand();
    }
  }

  // Draw some gizmos in case there's no HandModel attached.
  private void OnDrawGizmos() {
    if (!drawEditorGizmos) return;

    Gizmos.color = Color.red;

    if (mimicHand != null) {
      draw(mimicHand.PalmPosition.ToVector3());

      for (int f = 0; f < 5; f++) {
        for (int b = 0; b < 4; b++) {
          draw(mimicHand.Fingers[f].bones[b].NextJoint.ToVector3());
        }
      }
    }
  }

  private void draw(Vector3 pos) {
    Gizmos.DrawWireSphere(pos, 0.01f);
  }

}
vab
  • 177
  • 9
  • I have previously tried using the hand.transform and hand.setTransform but while it does put the hand in the correct position it causes the hand to spin like crazy. – user7856951 Jan 28 '18 at 12:18
  • Hi, are you still there ? – user7856951 Jan 30 '18 at 10:21
  • I am unable to reproduce the problem you are describing with SetTransform. I just re-tested the above script in a new project after importing the latest UnityModules Core Assets, found here: https://developer.leapmotion.com/unity -- rotation arguments passed in to SetTransform are absolute; I don't observe them accumulating on the hand. Are you running the latest version of the Leap Motion Core Assets? Have you confirmed that the rotation argument you are passing in to SetTransform() is in fact the rotation you want the hand to have? – vab Feb 01 '18 at 00:57
  • Alternatively, if you can create a simple example script that can reproduce the bug you're seeing with SetTransform, I can get you a fix for that. – vab Feb 01 '18 at 01:03
  • I have added code based on your example that shows what i want. It works with this gizmos example but for some reason when i use it with a capsule hand, it spins like crazy. – user7856951 Feb 01 '18 at 10:56
  • Gotcha. OK, I'll take a look. – vab Feb 01 '18 at 20:14
  • I've edited the response and the example to explicitly drive a CapsuleHand (or, theoretically, any other hand derived from `HandModelBase`). The HandModel system wasn't really designed to be spoofed in this way, but I'm able to see a duplicate CapsuleHand that doesn't accumulate any rotations when I use that example script. Make sure you use a duplicate CapsuleHand for the script to drive, that isn't also receiving hand data from a real Leap pipeline/rig. – vab Feb 02 '18 at 06:04
  • Hi, I have updated your mimicHandDriver example to do what i want, but the hand still spins like crazy. I have updated the comments to explain what each line is trying to do. – user7856951 Feb 05 '18 at 09:05
  • Hi omonobic, are you still there ? – user7856951 Feb 08 '18 at 10:26
  • Do you have a repo or a .zip of the project you can upload somewhere that I can use to reproduce the problem? I don't think the issue is in the latest script you have in your question, so the easiest way for me to figure out what's going on is to see the rotation happening in a project. – vab Feb 10 '18 at 18:25
  • Hi, i have discovered something interesting. My script works if i use the live hand data from "Hands.Get(...)" however, if i pass it a copy of the live hand data obtained in the same way, then the hand spins like crazy. A second issue i'm having is the bones of the capsule hand i am using are really thin. – user7856951 Feb 12 '18 at 15:34