Advertisement

Correcting for bone length when doing animation remapping

Started by December 16, 2020 06:15 AM
22 comments, last by taylor7 4 years ago

h3ro said:
it will probably improve the animation a bit,..

no, it should by a lot, if not then you've got something else wrong;

also what is the meaning of this?

h3ro said:
Position = CurrentSkeletonBone.Pos + (OtherAnimBone.Pos - OtherSkeletonBone.Pos);

maybe u need to share more of your code… (if u can)

The animation was already pretty good. As you can see in the first gif, the animation from the small skeleton is mapped pretty well onto the big skeleton. There are some artifacts though, like the feet not standing still, like they should. Changing to your code improved the feet position about 50%, but there are still sliding going on.

As I have mentioned, I think this is because the change in size is causing the rotations to travel longer, in lack of a better description. If I want the foot to be fixed, and the parent proportions change, I need to update same of the rotations as well for the foot to be able to stand still? Or am I completely wrong here?

I will clean up the code a bit and post it tomorrow.

The position calculation is how I calculated the translation part of the bone animation before.

Thanks for all the help so far!

Advertisement

h3ro said:
If I want the foot to be fixed, and the parent proportions change, I need to update same of the rotations as well for the foot to be able to stand still?

correct, i did mention a few posts earlier that u may have to adjust some rotations, and u can do so by adjusting the angles of rotation as u showed or earlier, OR if your bone rotations are relative to parent, you can try this in their local spaces:

// pseudo, right-to-left multiplication

new.rotation = inverse(target_bone.rotation) * source_bone.rotation;

where

source_bone.rotation is the quaternion in the source skel bind pose when no animation is applied to the source skel

target_bone.rotation is the quaternion in the target skel bind pose when no animation is applied to the target skel

new.rotation is the quaternion that cancels orientation differences in bind poses when no animation is applied to any of them

once u get this, then u animate as usual to get animated.rotation from your system:

animated.rotation = … get it from your animation calculation in the current time t

then u can apply animated.rotation to new.rotation to get the final rotation:

final.rotation = animated.rotation * new.rotation

right, depending on your maths, u may have to invert source for target …

remember another thing is you can create a quat from angle and axis:

// pseudo 
quat q(ratio * angle, Axis(a,b,c));

there u go, not sure what system you got, but this is plenty to play with…

you're not too far from it;

see how it goes ?

I completely forgot to take the difference in animation between the two skeletons into account!

Adding this, it is starting to look better. It is looking almost perfect now, but there is still some sliding. I have tried playing around with different multiplication order and values for the relative animation, but this is the best I could do. If you have time, could you take a look at the code to see if there is something I am missing? The framework is MonoGame/Xna. The transform in the animation completely replaces the bone transform (so not relative to bone). Each bone is in local space, so I later do a “to worldspace” transform.

//Source = The skeleteon I am mapping to
//Other = The animation I am trying to transfer onto Source

for (int frameIndex = 0; frameIndex < otherAnimationClip.DynamicFrames.Count(); frameIndex++)
{
	for (int boneIndex = 0; boneIndex < sourceSkeleton.BoneCount; boneIndex++)
	{
		var rotation = Quaternion.Identity;
		var position = Vector3.Zero;

		var sourceBoneLength = GetBoneLength(sourceSkeleton, boneIndex);
		var boneToGetAnimDataFrom = GetMappedBone(settings.BoneSettings, boneIndex);

		if (HasValidMapping(boneToGetAnimDataFrom))
		{
			var mappedBondeIndex = boneToGetAnimDataFrom.MappedBone.BoneIndex;
			var otherBoneLength = GetBoneLength(otherSkeleton, mappedBondeIndex);

			if (HasAnimationData(mappedBondeIndex, otherAnimationClip))
			{
				GetSkeletonTransform(sourceSkeleton, boneIndex, out Quaternion sourceSkeletonRotation, out Vector3 sourceSkeletonPosition);
				GetSkeletonTransform(otherSkeleton, mappedBondeIndex, out Quaternion otherSkeletonRotation, out Vector3 otherSkeletonPosition);
				GetAnimationTransform(otherAnimationClip ,frameIndex, otherSkeleton, mappedBondeIndex, out Quaternion otherAnimationRotation, out Vector3 otherAnimationPosition);

				var ratio = sourceBoneLength / otherBoneLength;
				if (float.IsNaN(ratio))
					ratio = 1;

				var rotationDiff = sourceSkeletonRotation * Quaternion.Inverse(otherSkeletonRotation);
				position = otherAnimationPosition * (ratio);
				rotation = otherAnimationRotation * rotationDiff;
			}
			else
				// This case never triggers in the test case, so code removed
		}
		else
			// This case only trigger for unsignificant bones, like the eyes, so code removed.

		rotation.Normalize();
		outputAnimationFile.DynamicFrames[frameIndex].Rotation.Add(rotation);
		outputAnimationFile.DynamicFrames[frameIndex].Position.Add(position);
	}
}

This is how it currently look. You can see both the source and “other” here, the source is the high skeleton while other is the short one.

Old:

New:

Thanks for all the input and help!

ok the naming of your variables is not quite right but ok…

ok a few things to try:

  • it should be this:
var rotationDiff = otherSkeletonRotation * Quaternion.Inverse(sourceSkeletonRotation);
  • also, what are the return values of HasAnimationData(mappedBondeIndex,… ) for these 2 bones 1 & 2?:

From looking at the gif video, the return values for these 2 bones should be FALSE; they are not animated; so the same bones 3 & 4 on the “other” skel should also not be animated; is that what u got?

When doing:

var rotationDiff = otherSkeletonRotation * Quaternion.Inverse(sourceSkeletonRotation);

The animation looks a lot worse. As you can see there the clapping part of the animation now overshots badly. The feet are also floating so it does not change that.

Edit: I have been so focused on the feet. When using the code for rotationDiff, the hands are not touching, so that is also no good.

The animation format is a bit strange, the feet bones are considered to be animated, as they have a base animation applied to them which makes them differ from the skeletons bind pose. If I add a breakpoint to the else for HasAnimationData, that is never triggered.
I know the naming is not great, settled on that before learning the normal jargon for this. Will update it when things are working.

Advertisement

h3ro said:
The animation looks a lot worse.

hmm odd… that should have worked…, because the point of that inverse rotation mult is to “undo” the target's rotation (which u call source). ok, well, go back to your version of it, but bear in mind that it is not the right way round; your version “undoes” the rotational value that we want to keep - maybe that's the source of the problem, u might have to review your anim format ….

h3ro said:
If I add a breakpoint to the else for HasAnimationData, that is never triggered.

ah yes of course u left a comment about it…-lol-

h3ro said:
…s they have a base animation applied to them which makes them differ from the skeletons bind pose

well then if all your bones are animated then you don't need to call HasAnimationData at all as they will always have some anim applied to them; HasAnimationData would better be used like so:

// pseudo
HasAnimationData(bone index, frame index) // true if bone is animated in the given frame , false otherwise 

try this and see if it gives better result, because these 2 bones 1 & 2 definitely don't look animated (their position values ain't changing), so the equivalent ones 3 & 4 should also not move;

also out of interest, what are ratio values for 1 & 2 ? try and force them to 1 and see what happens, something like this:

// psuedo
if ( mappedBondeIndex == 1 or 2 ) // <--- use the correct values 
	position = otherAnimationPosition
else
	position = otherAnimationPosition * ratio

You may need to try the same for the rotation as well, but start with the position first…

see how it goes ?

The animation format is a bit strange. Unfortunately its not up to me. The idea is that you have a static frame and a set of dynamic frames. The static frame contains bone transforms which are only changed once and says the same for all keyframes. Imagine a guy sitting at a table. The knee joint never moves so that goes in the static frame. The hand used for eating has a lot of dynamic frames. Some animations dont have static or dynamic frames for a given bone, so then the bone bind position is used. If there is animation for a bone, it is has animation for each frame, so I just need the bone index. Its a strange format, but not up to me. Bone 1 and 2 gets its animated position from the static frame in this case.

I assume you meant the ratio between 1 and 3, which is 0.820255458. I tried overlaying the skeletons, but its hard to tell. They are pretty close in size so 0.82 makes sense I guess. (the fully red skeleton is the smaller one)

Changing the ratio to 1 for the given bone did not make a huge difference. Maybe a bit worse.

ddlox said:
go back to your version of it,

My version has issues as well. In my version there are not enough animation rotation. The animation is of a guy clapping, and with my version the hands to actually reach each other

If I use yours and remove the position scaling, then all the rotations looks pretty good. The hands line up (ish) and the feet dont float much at all. The proportions ever very wrong though. Not sure if this is useful info or not

ok understood…(comments on format)

yes i meant 1 & 3, an orthographic projection from the side could show better, but granted 0.82 looks reasonable…

have u tried:

//pseudo
rotation = (otherAnimationRotation * ratio) * rotationDiff

or u may have to get the actual angle out first:

//pseudo
otherAnimationRotation = create_quat(otherAnimationRotation.getAngleInRadians() * ratio, otherAnimationRotation.getAxis())
// then
rotation = otherAnimationRotation * rotationDiff

Good idea, but it did not really behave as expected:

var ratio = sourceBoneLength / otherBoneLength;
if (float.IsNaN(ratio))
	ratio = 1;

otherAnimatedRotation.ToAxisAngle(out Vector3 axis, out float angle);
otherAnimatedRotation = Quaternion.CreateFromAxisAngle(axis, angle* ratio);

var skeletonRotationDifference = otherSkeletonRotation * Quaternion.Inverse(sourceSkeletonRotation); 
position = otherAnimatedPosition * ratio;
rotation = otherAnimatedRotation * skeletonRotationDifference;

I did some sanity checks. CreateFromAxisAngle with the same angle as input creates the same animation as before. I also tried setting the ratio to one for the root bone, which stops the skeleton from rotating like you see above, but that causes multiple issues in child bones.

This topic is closed to new replies.

Advertisement