Issue with World Space Tween Calculation

Hi everyone,
I’m currently trying to build a tween(blend) tool that works in world space.

My approach is like this:
If I have an object with keys on frame 5 and frame 10, and I want to create a tween on frame 7, I do the following:

  • I get the world matrix of the object at frame 5 and frame 10.

  • I also get the parent inverse matrix of the object at frame 7 (the frame where I want to apply the tween).

  • I decompose the world matrices from frame 5 and 10.

  • I lerp the translation and scale, and slerp the rotation.

  • Then I rebuild the matrix from the interpolated values.

  • I multiply this result by the parent inverse matrix.

  • Finally, I decompose the final matrix and apply those values to the object at frame 7.

The problem is that the values I get are always wrong, and I can’t figure out why.
So I’m not sure if my approach is incorrect, or if I’m missing something important.

If anyone is familiar with this and is willing to look at the code with me, I’d really appreciate the help.
Thanks a lot :folded_hands:

Some things to check:
When you rebuild the matrix from the interpolated values, does that matrix make sense?
Use the xform command and set the matrix on a dummy object with
cmds.xform(dummyObj, m=mymatrix, ws=True)

If that works, is your matrix multiplication order correct when applying the parent inverse matrix?
Again, I suggest using the xform command to set the matrix directly on a dummy object.

And I’d say go ahead and post the code here, that way you can help the next person that comes along with an issue like yours.

2 Likes

Actually, the first problem I ran into is that Maya is not giving me a correct world matrix when using
MDagPath().inclusiveMatrix().

Because of that, I ended up extracting the components manually and rebuilding the matrix myself. That works to some extent — the translation is correct and behaves as expected — but the rotation is still not right.

At the moment, the issue seems to be one of these two things:

  • Either the rotation values themselves are coming out wrong

  • Or the rotate order is not being handled correctly when rebuilding the matrix

Here’s a snippet from the code:
the main functions is cacheData() and tween()

Ok, so saying you’re doing this in a C++ plugin would have been good information to lead with.

Hmm… I’m seeing a lot of complication here that’s probably not required. Or if it is required for some reason, it needs to have a comment explaining why.

Your getParentInverseMatrixAt method directly reads the t/r/s plugs of the transform and builds the matrix of the node at a given time. That just feels really error prone to me. I feel like a better way would be to just get the .worldMatrix[0] and .parentInverseMatrix attributes from within your dg context. Unless this specifically doesn’t work?

MQuaternion does have a slerp function. I’m looking right at it in MQuaternion.h for Maya 2024. I think you have to call it with the namespace like thisMQuaternion::slerp()

And why the heck are you doing manual matrix multiplication in worldMatrixToLocalTransform? Just multiply them.

I’m curious if there’s a lot of AI help in this code?

yeah, I agree the code looks more complicated than it should be.

The main reason for this is that every time I getting incorrect results in my tests. For example, things like:

MDagPath().inclusiveMatrix()

reading worldMatrix / parentInverseMatrix directly

were consistently giving me values that didn’t match what I was seeing in the viewport

Because of that, I tried manual approach on purpose, just to validate

Even the local-to-world conversion kept producing wrong rotations in my tests (the rotation applied back to the object always behaved like world rotation), so for debugging I ended up multiplying the matrices myself to see exactly where things were going wrong.

So yeah — This is very much a debugging / validation phase,

And no — there isn’t a lot of AI help in this code. Most of it comes from me fighting Maya’s evaluation and trying to make sure the math itself is actually correct before optimizing or cleaning things up.

Appreciate you taking the time to look through it and call these things out — it’s genuinely helpful.

If I were doing this, I would be doing the initial tests in the python api. In my experience it cuts down on the feedback loop, and is easier to experiment with. Since you’re eventually going to go to C++, I’d suggest using the 1.0 api, since it makes the translation easier. Pretty much all of my “new idea” plugins were prototyped in python.
But I’m also a lot more comfortable with Python, so you do what’s comfortable for you.

Here’s how I was able to get the worldMatrix and parentInverseMatrix in Python

from maya import OpenMaya as om
import gc


def getMatsAtTime(obj: om.MObject, attrName: str, time: float):
    # Set up a context guard, and get the matrix data
    evalTime = om.MTime(time, om.MTime.kFilm)
    ctx = om.MDGContext(evalTime)
    
    # while this exists in memory, we're using the context
    guard = om.MDGContextGuard(ctx)

    fnNode = om.MFnDependencyNode(obj)
    worldMatrixPlug = fnNode.findPlug(attrName, True).elementByLogicalIndex(0)
    worldMatrixObj = worldMatrixPlug.asMObject()
    worldMatrixData = om.MFnMatrixData(worldMatrixObj)
    worldMatrix = worldMatrixData.matrix()
    worldMatrix = [worldMatrix(i, j) for i in range(4) for j in range(4)]
    
    del guard  # force cleaning up the guard.  Not needed in c++
    gc.collect()  # force cleaning up the guard.  Not needed in c++
    return worldMatrix


# Get the MObject of the node I care about
sel = om.MSelectionList()
sel.add('locator2')
dagPath = om.MDagPath()
sel.getDagPath(0, dagPath)
obj = dagPath.node()

# Get the matrices at the given time
wmat_0 = getMatsAtTime(obj, 'worldMatrix', 0.0)
pimat_5 = getMatsAtTime(obj, 'parentInverseMatrix',5.0)
wmat_10 = getMatsAtTime(obj, 'worldMatrix',10.0)

print(wmat_0[12:15])
print(pimat_5[12:15])
print(wmat_10[12:15])

[Edit] The first pass I did at this worked for a bit, then started giving erratic values. If I do it all in one function like this, it seems stable

1 Like