Muscle values to bone rotation

I am currently exploring uncharted territories of Mecanim. My goal is to implement my own IK solver via Mecanim muscle / DoF values, but getting and setting poses via a HumanPoseHandler is rather slow if done many times a frame. Thus, I’d like to simulate the behaviour of Mecanim on a lightweight, virtual skeleton instead. But don’t let me bore you with my reasons here and cut to the chase…

I’d like to know precisely how Mecanim computes a bone’s rotation from the corresponding muscle / DoF values (which are usually in the [-1,1] range)?

With some insight into the C# reference (especially here), I have worked out this code snippet that works under some circumstances:

``````public Quaternion BoneRotation(Avatar avatar, int bone, float x, float y, float z) {
var mx = HumanTrait.MuscleFromBone(bone, 0);
var my = HumanTrait.MuscleFromBone(bone, 1);
var mz = HumanTrait.MuscleFromBone(bone, 2);

var preQ = avatar.GetPreRotation(bone); // exposed via reflection
var sign = avatar.GetLimitSign(bone); // exposed via reflection

var r = Quaternion.Euler(
(mx >= 0) ? (MuscleAngle(mx, x) * sign.x) : 0.0f,
(my >= 0) ? (MuscleAngle(my, y) * sign.y) : 0.0f,
(mz >= 0) ? (MuscleAngle(mz, z) * sign.z) : 0.0f);

return parentRotation * preQ * r;
}
``````

Calculating the individual DoF angles (MuscleAngle) from the muscle values is easy once you got the limits from the avatar settings. However, the calculated result is correct only if at most one DoF receives a non-zero value (e.g., x=0,y=0,z=1). As soon as multiple DoF angles are set (e.g., x=0,y=1,z=1), my result is wrong and while it appears that some post transformation is missing, for the hell of it, I cannot figure out what that would be.

So how does Mecanim combine the single DoF rotations into the bone’s final rotation?

Could be that it uses a different rotation order.

Instead of a single Quaternion.Euler call, try doing a separate AngleAxis call for each axis and multiplying them in different orders.

I tried all 6 now and even with the axes pre-rotated based on the parent rotation, no success there.

I’m afraid I currently do not know why your calculation is sometimes off, but still would like to tune in to the muscle space to rotation conversion discussion.
In particular, I wonder how you retrieved joint rotation limit data. In my project, the minimum and maximum rotation are manually computed right after instantiating the Avatar and before further changing its muscle values, like in this example:

``````Animator animator = GetComponent<Animator>();
HumanPoseHandler humanPoseHandler = new HumanPoseHandler(GetComponent<Animator>().avatar, transform);
int leftEyeInOutMuscleIndex = HumanTrait.MuscleFromBone((int)HumanBodyBones.LeftEye, 1);
Transform leftEye = animator.GetBoneTransform(HumanBodyBones.LeftEye);
Quaternion leftEyeTaredRotation = leftEye.rotation;
HumanPose currentPose = new HumanPose();
humanPoseHandler.GetHumanPose(ref currentPose);
HumanPose destinationPose = currentPose;

// Out Limit
destinationPose.muscles[leftEyeInOutMuscleIndex] = -1f;
_humanPoseHandler.SetHumanPose(ref destinationPose);
Quaternion leftEyeMaxOutRotation = leftEye.rotation;
float leftEyeMaxOutLimit = -Quaternion.Angle(leftEyeTaredRotation, leftEyeMaxOutRotation);

// In Limit
destinationPose.muscles[leftEyeInOutMuscleIndex] = 1f;
humanPoseHandler.SetHumanPose(ref destinationPose);
Quaternion leftEyeMaxInRotation = leftEye.rotation;
float leftEyeMaxInLimit = Quaternion.Angle(leftEyeTaredRotation, leftEyeMaxInRotation);
``````

Did you resolve that issue with reflection as well?

Well I do know why - it’s because the axes rotations somehow depend on each other, much like @Kybernetik suggested. But it’s not a simple re-ordering of the rotations, and I cannot figure out how exactly they transform each other. Good opportunity to bump this.

Nope, but that’s a cool idea for sure!

Right now, I am using a copy of the original avatar data - via the asset’s ModelImporter, you can retrieve a HumanDescription, which also contains the joint limits. However, for distribution, the data needs to be copied at edit time and serialized to another object, because the ModelImporter is Editor-only. That’s quite a bloated workflow and I might just prefer yours!

There’s no way to get the limits directly from the avatar as far as I know, which should be changed IMO. The data has to be there, it’s just not exposed to C# in any way (probably because Mecanim was never meant to be used in this immediate way).

1 Like

I wonder if an extra internal processing step is taking place between frames by Mecanim that is messing up your calculations.

Maybe @Mecanim-Dev has some insight on what causes this and the correct way to approach it?

Has anyone made any progress on this?

It is possible (although kind of ugly) to retrieve the limits directly from the avatar using serialized properties.

It definitely isn’t the problem, but I’m guessing you should probably also be multiplying by postQ in your final result.

I have figured it out. It’s using swing-twist degrees. @pdinklag

``````void SetMuscle(HumanBodyBone humanBodyBone, Vector3 muscle) {
var sign = avatar.GetLimitSign(humanBodyBone);
var preQ = avatar.GetPreRotation(humanBodyBone);
var postQ = avatar.GetPostRotation(humanBodyBone);
var scale = new Vector3( // TODO: use GetMuscleDefaultMin if muscle is negative
HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 0)),
HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 1)),
HumanTrait.GetMuscleDefaultMax(HumanTrait.MuscleFromBone(humanBodyBone, 2)));
var angle = Vector3.Scale(sign, Vector3.Scale(scale, muscle));
animator.GetBoneTransform(humanBodyBone).localRotation =
preQ
* Quaternion.AngleAxis(new Vector3(0, angle.y, angle.z).magnitude, new Vector3(0, angle.y, angle.z).normalized)
* Quaternion.AngleAxis(angle.x, new Vector3(1,0,0))
* Quaternion.Inverse(postQ);
}
``````
2 Likes

Hi. Need to resurrect this old thread, but code provided by @lox9973 is not fully correct. It can be easly observed by comparing Unity’s default humanoid avatar muscle editor window manipulation and manipulation by skeleton bones by this script. Rotation around X axis is correct, but ZY combination are become wrong when approaching to limit muscle values. So question is follows: how to make quaternion that rotates bone using muscle values (euler angles) exactly like Unity does?

Before that, anyone could tell me that what postQ and preQ exactly mean?