• Runtimes
  • Setting world position/rotation of bones

Hi. I'm using the c++ runtime, and have implemented a ragdoll system.
When the ragdoll is active, I want to set the bones position/rotation manually in spine, and it mostly works, except one scenario. When I have an X-scale of -1 (for flipping the animation) any bones that use Inherit_NoRotationOrReflection have incorrect rotations.

I've been trying to work through the code in updateWorldTransform to try and make a robust implementation but am starting to bang my head against a wall, and I'm wondering if there is an easier way of doing all this that I've missed. Is there a 'best-practice' method of setting all bones world transforms manually? I should note I also want to be able to turn off the ragdoll and let spine take over the animation again.

Thanks

Related Discussions
...

To set the world position and rotation of bones manually in the Spine C++ runtime, especially when dealing with scenarios like flipping animations (X-scale of -1) and bones with Inherit_NoRotationOrReflection, you need to follow a structured approach. Here are the steps you can take:

  1. Update Local Transforms: Modify the local transforms of the bones based on your ragdoll system. This includes setting the local position, rotation, and scale.

  2. Update World Transforms: After modifying the local transforms, call Skeleton::updateWorldTransform() to recalculate the world transforms of all bones. This ensures that the changes in local transforms are propagated correctly through the bone hierarchy.

  3. Handle Special Cases: For bones with Inherit_NoRotationOrReflection, you may need to manually adjust their world transforms after calling updateWorldTransform(). This can be done by calculating the correct world rotation and position based on the parent's world transform and the bone's local transform.

Here is a code snippet to illustrate the process:

// Assuming you have a pointer to the skeleton and the bone you want to modify
Skeleton* skeleton = ...;
Bone* bone = skeleton->findBone("mybone");

// Set local transforms based on your ragdoll system
bone->setX(localX);
bone->setY(localY);
bone->setRotation(localRotation);
bone->setScaleX(localScaleX);
bone->setScaleY(localScaleY);

// Update world transforms
skeleton->updateWorldTransform();

// Handle special cases for bones with Inherit_NoRotationOrReflection
if (bone->getInheritRotation() == Inherit_NoRotationOrReflection) {
    // Calculate the correct world rotation and position
    float parentWorldRotation = bone->getParent()->getWorldRotation();
    float correctedRotation = localRotation - parentWorldRotation;
    bone->setWorldRotation(correctedRotation);
    // Similarly, adjust world position if needed
}

// When turning off the ragdoll, let Spine take over the animation again
if (!ragdollActive) {
    // Apply animations and update world transforms
    state.apply(skeleton);
    skeleton->updateWorldTransform();
}

This approach ensures that the local transforms are correctly set, and the world transforms are recalculated properly, taking into account any special cases like Inherit_NoRotationOrReflection.

For more detailed information, you can refer to the spine-cpp Runtime Documentation and the Runtime Skeletons Guide.

6 Tage später

Thanks, I got this working, but not exactly how it is described.
There is no function setWorldRotation, and still end up with special cases when scaleX is negative. I'm doing this:

void	Skeleton::SetBoneWorldPosRot( Bone* bone, Vec2f pos, float rot )
{
	Vec2f local;
	bone->worldToParent( pos.x, pos.y, local.x, local.y );
	bone->setX( local.x );
	bone->setY( local.y );
	bone->setScaleX( 1.0f );
	bone->setScaleY( 1.0f );

	float localr = -RadToDeg(rot) + bone->getParent()->getWorldRotationX();	// is scaleX is positive
	if( mSkeleton->getScaleX()<0.0f )
	{
		// fix if scaleX is negative
		localr = -localr;
	}

	bone->setRotation( localr );

	mSkeleton->updateWorldTransform( Physics::Physics_None );	// ensures that the changes in local transforms are propagated correctly through the bone hierarchy
}

void	Skeleton::SetBoneWorldPosRotFixUp( Bone* bone, Vec2f pos, float rot )
{
	// part 2 of SetBoneWorldPosRot
	// this is to fix up special cases (like when ScaleX is -1 and NoRotationOrReflection is used)
	if( bone->getData().getInherit()==Inherit_NoRotationOrReflection )
	{
		// special case. fix up the rotation
		float toRotate = RadToDeg(rot) - bone->getWorldRotationX();
		bone->rotateWorld( toRotate );
		if( mSkeleton->getScaleX()<0.0f )
		{
			bone->setRotation( toRotate + bone->getRotation() );
		}
		else
		{
			bone->setRotation( -toRotate + bone->getRotation() );
		}
	}
}

// call SetBoneWorldPosRot on every bone
// then call SetBoneWorldPosRotFixUp on every bone

The conversion from spine to rigid bodies now works reliably. I've had a hell of a time trying to get it working in reverse (to return from ragdoll to animation) but I think I've finally got that working too.

I should add that to do it in reverse I'm having to modify the world values of each bone which leads to a bit of distortion, but I can't see any way of doing it by just modifying the local bone values that doesn't result in incorrect bone locations due to Inherit_NoRotationOrReflection

There is ambiguity in the rotation/scale components of a world transform. It can be a nightmare, and that's with normal transforms. It's worse with the inherit modes! Many times I have regretted they exist! Special cases like what you have are the way.

Is your issue with ambiguity when going world -> local? You can't know if a world transform was originally local scale -1,-1 or 180 rotation and scaleX -1.

When returning from ragdoll to spine, after several failed attempts, the version I ended up works like this:

  1. Take a copy of all the bone positions/rotations (world space) whilst still in ragdoll mode. (call it State A)
  2. Call skeleton->setToSetupPose()
  3. Resume calls to animState->update() and animState->apply()
  4. Each frame, after calling updateWorldTransform, take a copy of all the bone positions/rotations (call it State B)
  5. Interpolate between State A and State B, and call bone->setWorld and bone->rotateWorld as required.
  6. Continue each frame until the interpolation reaches 100%

The side effect I'm seeing is that the top of the head on 'hero-pro' that I'm using for testing becomes slightly warped. I haven't dug down into exactly why this is, but I can understand how it might be happening when operating in world space.

Previously I tried taking my two state copies of the bone positions/rotations in local space, and interpolating between them instead. This avoiding warping, but the legs that have Inherit_NoRotationOrReflection on them would always end up flipped in the wrong direction as soon as the interpolation began.

I was also confused that my attempts at modifying the local bone values seemed to permanently affect the animation. So even after completely turning off any interpolation, the anim would be all bent out of shape. I had assumed that animState->apply() overwrote all bone local values and so any previous modifications I made would be undone?
It is possible I was doing something completely wrong as my code was a mess after trying so many different approaches.

I'm a bit nervous as to what other cases I will encounter when my artists start experimenting. Have you considered at all providing reference code for this sort of thing? Most rigid body simulators support just position and rotation and so being able to convert between the two would seem like a very useful feature - even if there were some imperfections due to inherit modes.

I looked at the Unity Ragdoll examples, but I don't know Unity or C# so it was of limited help.

  • Nate und Harald haben auf diesen Beitrag geantwortet.

    daMuzza I had assumed that animState->apply() overwrote all bone local values and so any previous modifications I made would be undone?

    Yes, but AnimationState apply only affects keyed properties. Properties not keyed in the animations being applied are not changed. You can use Skeleton setBonesToSetupPose (or setToSetupPose) to reset all bones to a known state (the setup pose).

    daMuzza Previously I tried taking my two state copies of the bone positions/rotations in local space, and interpolating between them instead. This avoiding warping, but the legs that have Inherit_NoRotationOrReflection on them would always end up flipped in the wrong direction as soon as the interpolation began.

    This sounds very close to what you want, just with a special case for the inherit mode(s) you use. Keeping everything in local space avoids some nasty behavior, like when interpolating world rotations.

    daMuzza The side effect I'm seeing is that the top of the head on 'hero-pro' that I'm using for testing becomes slightly warped.

    Hmm, hard to say why. Maybe more inherit mode pain, if the head (or neck/etc) bone doesn't inherit scale.

    You might look at TransformConstraint. It has code to interpolate between local and world bone transform properties (the mix between the target bone and constrained bone(s)).

    Note world rotations are by definition 0-360, because it is simply the direction the bone is pointing. When interpolating two world rotations, the challenge is there are 2 solutions: clockwise and counterclockwise. You can choose the shortest direction, but that may become the longest direction over time, as the rag doll and animation rotations change. When that happens your shortest direction interpolation flips to the other side -- a very noticeable, jarring jump. AnimationState solves this by remembering the shortest direction of the first interpolation and sticking with it. If you can stay in local space, it's much better because you won't have this problem at all.

    I think you can do this in a solid way. I'm happy to add some utilities that would help, but I'm not sure what utilities would be useful in our generic runtimes, where there isn't a ragdoll physics system to integrate with. Maybe Harald has some input (on Monday) or can point to spine-unity code relevant to your use case.

    • Bearbeitet

    @daMuzza The spine-unity ragdoll components also just set local bone rotation values (see the code here) based on the respective parent bone's rotation and scale properties.

    The interesting part for you would be this method here. Basically it first determines the game-toolkit-side ragdoll transform local rotation and position (which is different from skeleton space),
    and then it applies the skeleton-side parent bone space difference of special cases which need to be addressed, like flipping positions or angles if the parent bone is flipped, etc. Then it applies these local values at the skeleton's bones.

    Unfortunately the spine-unity skeleton ragdoll example component code does not yet respect disabled inherit rotation and scale. What would need to be done is to then use the game-toolkit world rotation of a bone directly instead of calculating the local rotation in relation to it's parent, and doing the same when inherit scale is disabled. These local rotation and position bone values should then be applied as before to the bones. In case this does not work as expected with Inherit_NoRotationOrReflection, perhaps it is indeed due to the ambiguity problem pointed out by Nate earlier.

    daMuzza The side effect I'm seeing is that the top of the head on 'hero-pro' that I'm using for testing becomes slightly warped. I haven't dug down into exactly why this is, but I can understand how it might be happening when operating in world space.

    To address such an issue, it might be the easier solution to set any bone transform values in local space. If I misunderstood what you're actually doing, please do let me know.

    Thanks for the replies. Good news is that I have made progress.
    It turns out that IK Constraints were a large part of my problem. I disable them whilst the ragdoll is active, but the process of re-enabling them caused bones to move.
    skeleton->setToSetupPose() does bones->setToSetupPose() and ikConstraints->setToSetupPose(). I changed my code to only do the bones->setToSetupPose()
    I also gradually turn on IK constraints during the interpolation by calling setMix on them.
    I also embarrassingly had one place where I was updated bones out of order due to an unordered map, that was not helping!

    With these changes, I was able to interpolate in local space, with no special regard to Inherit_NoRotationOrReflection, and it is looking a lot better. No more distortion, and no bones suddenly swapping rotations.

    @daMuzza Very glad to hear you've made some good progress! Thanks for letting us know.