SOLVED Joint Orientation Math Challenge

I’ve run into a math challenge working on an animation re-targeting script. This Python script copies the bone rotational properties from one character rig to another (like the retarget tag) but also performs additional modifications and clean-up.

I'm using Cinema 4D R21.

Up to now, both characters were always rigged based on an “A-Pose”, but now the source characters can be based on a “T-Pose”. Simply adding 45 deg to the shoulder works fine, but every bone to the fingers center of rotation is now off by 45 deg. I’m struggling to find the math or hopefully a Cinema 4D Python functions that will enables me to calculate from the single axis rotation from the source rig, to the “multiple axis” rotations required for the target. This would be similar math used by Cinema 4D when rotation an object in “World Space”.

Example, if I have the source character in the T-Pose position and rotate the forearm “forward” 45 deg in the “H” axis only, the target character I first add 45 deg to the shoulder to the “B” axis making it in the T-Pose, then I need to apply multiple axis values close to (H = 35.264, P = -30, B = 9.736) to get the same orientation of the source character.

Source Character
Capture2.JPG

Target Character
Capture1.JPG

Any ideas? Bruce Kingsley

hi,

Sorry for the late reply.
You need to use matrices as i said.
You need to retrieve the matrix corresponding to the rotation. Which can be done checking the matrix before and after the rotation and doing a difference.

Once you have this matrix, you want to set the offset to zero : c4d.Vector(0) to only retain the rotation.

You now have to "translate" that rotation to the second object rotation. For that you need to multiply that rotation matrix with the second object matrix. Just like you would if you add a local position and want its global position. (see matrix manual)

The new matrix can be applied to the new object.

from typing import Optional
import c4d
import math
doc: c4d.documents.BaseDocument  # The active document
op: Optional[c4d.BaseObject]  # The active object, None if unselected

def main() -> None:
    doc.StartUndo()

    b = doc.SearchObject('rForearmBend')
    # define a matrix rotation around the Y axis of -65°
    # this matrix should be replaced by the rotation matrix found on the other join
    m = c4d.utils.MatrixRotY(c4d.utils.DegToRad(65))

    # we just want the rotation so we can reset the offset
    m.off = c4d.Vector(0)

    bmg = b.GetMg()

    newMatrix = m * bmg # operation order is important.
    # as we just want to keep the rotation, we "reset" the position with the 
    # previous one.
    newMatrix.off = bmg.off
    # To be sure the scale remain to 1 we normalize the matrix
    newMatrix.Normalize()

    doc.AddUndo(c4d.UNDOTYPE_CHANGE, b)
    b.SetMg(newMatrix)

    doc.EndUndo()
    c4d.EventAdd()


if __name__ == '__main__':
    main()

Cheers,
Manuel

Hi,

can you provide some code sample and a simplified scene file? Changing the rotation of the shoulder should have no effect on the fingers' rotation. They should still point in the same direction of the forearms (if that was the case).

I don't know how you are changing the rotation of your joints, but you should do it using matrices. We have a fairly good manual for that in our python documentation

Cheers,
Manuel

@m_magalhaes
Hi Manuel,

Attached is two characters, a source character rigged in a T-Pose and target character rigged in a A-Pose.
I animated the joints to show the challenge. I first rotate the shoulder of the target character shoulder so it's in the T-Pose like the source character. Then I rotate both characters forearm 66 deg in the H axis. You can see the poses don't match due to target orientation of the axis when rigged being different.

As far as my code, I'm doing pretty much the same as the Retarget tag except I'm reading the animation tracks of the source character and reading the vector values, and creating new animation track for the target character, which works fine if the both characters were originally rig using the same pose. But in this case, I'm adding 45 degs to the target character shoulder values which makes all child joints orientation of the axis different. So using the vector values are no longer correct.

T-Pose_A-Pose.c4d

hi,

Sorry for the late reply.
You need to use matrices as i said.
You need to retrieve the matrix corresponding to the rotation. Which can be done checking the matrix before and after the rotation and doing a difference.

Once you have this matrix, you want to set the offset to zero : c4d.Vector(0) to only retain the rotation.

You now have to "translate" that rotation to the second object rotation. For that you need to multiply that rotation matrix with the second object matrix. Just like you would if you add a local position and want its global position. (see matrix manual)

The new matrix can be applied to the new object.

from typing import Optional
import c4d
import math
doc: c4d.documents.BaseDocument  # The active document
op: Optional[c4d.BaseObject]  # The active object, None if unselected

def main() -> None:
    doc.StartUndo()

    b = doc.SearchObject('rForearmBend')
    # define a matrix rotation around the Y axis of -65°
    # this matrix should be replaced by the rotation matrix found on the other join
    m = c4d.utils.MatrixRotY(c4d.utils.DegToRad(65))

    # we just want the rotation so we can reset the offset
    m.off = c4d.Vector(0)

    bmg = b.GetMg()

    newMatrix = m * bmg # operation order is important.
    # as we just want to keep the rotation, we "reset" the position with the 
    # previous one.
    newMatrix.off = bmg.off
    # To be sure the scale remain to 1 we normalize the matrix
    newMatrix.Normalize()

    doc.AddUndo(c4d.UNDOTYPE_CHANGE, b)
    b.SetMg(newMatrix)

    doc.EndUndo()
    c4d.EventAdd()


if __name__ == '__main__':
    main()

Cheers,
Manuel

@m_magalhaes
Hi Manuel,

Thanks for the same code, I try it this weekend.

I've spent the last couple of days studying Matrixes and playing with code. During my tests, I noticed the GetMg() function returned the rotation created by the parent shoulder joint being changed to 45 deg, where I can see why the order of operation is important. I found a bunch of sample code rotating objects with points, but noting on a joint that has no points.

Thanks again. Bruce.

@m_magalhaes

Hi Manuel,

It looks like the code does what I need, though I'm still working to understand why it works. Below is a code snip of how I combined all of the joints three rotation values from the source joint and transferred it to the target joint as well as obtaining the new target rotation vector to set animation keyframes. Does this look correct?

 # Rotation values from source joint (for code testing only).
sourceYValue = 90
sourceXValue = -90
sourceZValue = 0

# Define a matrix rotation around the axis of the source joint rotation values
mX = c4d.utils.MatrixRotX(c4d.utils.DegToRad(sourceXValue))
mY = c4d.utils.MatrixRotY(c4d.utils.DegToRad(sourceYValue))
mZ = c4d.utils.MatrixRotZ(c4d.utils.DegToRad(sourceZValue))

# Combine all source joint rotation maxtrixs into one.
mTotalTargetRelitiveTransforms = mX * mY * mZ

# We just want the rotation so we can reset the offset
mTotalTargetRelitiveTransforms.off = c4d.Vector(0, 0, 0)

# Get the target joint orginal matrix rotations.
mTargetOrginalTransforms = targetJoint.GetMg()

# Combine target joint orginal matrix with new relitive rotation matrix.
newTargetMatrix = mTotalTargetRelitiveTransforms * mTargetOrginalTransforms 

# As we just want to keep the rotation, we "reset" the position with the
# previous one.
newTargetMatrix.off = mTargetOrginalTransforms.off

# To be sure the scale remain to 1 we normalize the matrix
newTargetMatrix.Normalize()

# Set the target joint new rotations.
doc.AddUndo(c4d.UNDOTYPE_CHANGE, targetJoint)
targetJoint.SetMg(newTargetMatrix)

# Use this vector to set animation keyframes.
vNewAnimationValues = targetJoint.GetRelRot()

Hi,
This seems correct and quiet the same i wrote. But it's hard for us to take the time to see if a code is working or not. This would need some test and we don't have really the time to do that.

Here we are changing the coordinates system. The rotation you retrieve from character A can be seen as a Local Rotation. If you want to go from local to global you need to multiply by the matrix of your object. That's why we multiply with the matrix of the joint of the character B. Cause we need to get that local rotation to a global rotation to apply that new rotation to the object itself.

I'm sure you can find on internet some resources that will explain how it works mathematically.

Cheers,
Manuel

@m_magalhaes

Hi Manuel,

Sorry for not making my last post clear, I wasn't requesting you to test my code as is it seems to work from my tests. I just wanted to see if I was combining the matrixes together correctly. Thanks for the help, consider this issue "Solved".

hi,

ho, sorry, so yes that is the correct way to combine matrix.
ps : I marked my thread as the right answer.

Cheers,
Manuel