Home Technical Talk

Maxscript - Clone and reverse an entire arm animation

I have an animated character, and I want to clone and reverse the bone animations in such a way that the bones overlap the originals but are in reverse order.

So far I am working on a four bone system, where the bone chain comes down a bicep, forearm, hand, and finishes at a dummy in the hand with no animation. I then made a script which creates a similar bone chain which goes from the hand dummy, back up the arm chain in reverse order.

I then made a simple script which points the bones in the correct direction, but something is not quite right. I worked out that if a bone needs to point the other direction, then its base/pivot point needs to be the end point of the previous bone. So for instance, the pivot of newHand in relation to the oldHand is backward in such a way that the pivot is at the fingertips, rather than the wrist, with the target facing being the wrist, not the fingertips. The same was true for the entire arm structure.

In my calculations to determine which rotation I needed, I couldn't figure out how to get an exact inverse that I needed out of the rotational quat, or even the euler angle. It seems like I need 360-x 360-y 360-z and build a new quat from such a eulerangle. But that didn't work. So I used a vector system, in which I find the vector from the pivot to the target, create a matrix to give me such rotation and then apply it. That works to an extent, but the bone is often rotated 180 degrees on what looks like the z axis. I have read that using quats has this issue, but I found that using angleaxis rotation on all three parts of the eulerangle, separately, gives about the same error.

What I want is for the bones to not only point toward the correct target position, but also retain the Z-axis rotation which was present on the original bone.

I don't know if there is a technical term for what I am trying to do, so I don't even know how to research this. Inverse, reverse, and mirror used as search terms give me no results which fit my needs. It also seems like there should be a simple quat reversal function to do exactly this, giving me the perfect opposite of the angle I need in 720 space, but I have no clue what that is.

Below is the code I used so far:

fn getMostDistantVertFromOrigin obj = (
    local vD = 0
    local vSel = 0
    local v = 0
    for v = 1 to (getNumVerts obj) do (
        local vert = getVert obj v
        local d = distance vert obj.position
        if (d>vD) then (
            vD = d
            vSel = v
        )
    )
    vSel
)


fn makeChildBonesReverse obj depthToCopy = (
    if (depthToCopy<=0) then return undefined

    if (obj.parent!=undefined) then (
        local nonDummies = 0
        local child = obj.parent
        if ((classOf child)!= Dummy) then (
       
            nonDummies+=1
       
            local newBone = copy $Bone00
            newBone.position = obj.position
           
            --get vector from position to child position
            local p = child.position
            local v = normalize (p - obj.position)
            local r = (matrixFromNormal v) as eulerangles
           
            --rotate the new bone to point toward the child position
            rotate newBone (angleAxis r.x [1,0,0])
            rotate newBone (angleAxis r.y [0,1,0])
            rotate newBone (angleAxis r.z [0,0,1])
           
            --find the vert most distant from
            local vSel = getMostDistantVertFromOrigin newBone
            if (vSel!=0) then (
                setVert newbone vSel child.position
            )
                           
            --if this child has children, work on those
            local childBone = makeChildBonesReverse child (depthToCopy-1)
            if (childBone!=undefined) then (
                childBone.parent = newBone
            )
            return newBone
        )
       
        if (((classOf obj) != Dummy) and (nonDummies==0)) then (
           
            local newBone = copy $Box00
            newBone.position = obj.position
            return newBone
           
        )
       
    ) else (
        --put a knob on the position to make sure we know there
        --is a bone here, but that it has no children
       
        if ((classOf obj) != Dummy) then (
           
            local newBone = copy $Box00
            newBone.position = obj.position
            return newBone
           
        )       
    )   
)

fn getAncestor obj depth = (
    if (depth<1) then return undefined
    local r = obj
    for n = 1 to depth do (
        r = r.parent
    )
    return r
)

fn getFirstChild obj depth = (
    if (depth<1) then return undefined
    local r = obj
    for n = 1 to depth do (
        r = r.children[1]
    )
    return r
)

fn forAllKeysPointBoneAtObject obj target keySource = (
    --get the keys on keysource
    for key in keySource.rotation.controller.keys do (
    --for t = 9 to 8000 do (
        local t = key.time
       
        --first reset the rotation on the object
       
        --then animate it
        with animate on at time t (
            in coordsys world (
                local origP = obj.position
                 obj.position = [0,0,0]
                --obj.rotation = (quat 0 0 0 1)
                obj.position = origP
            )
            in coordsys obj (
                --point obj at target position
                local p1 = target.position
                local p2 = obj.position
                local v = normalize (p1-p2)
                local r = (matrixFromNormal v) as eulerangles
                obj.rotation = (quat 0 0 0 1)
                --rotate obj (angleAxis r.x [1,0,0])
                --rotate obj (angleAxis r.y [0,1,0])
                --rotate obj (angleAxis r.z [0,0,1])   
                rotate obj r           
               
                --local q1 = key.value -- rotation quat
                --local q2 = inverse q1
               
                --obj.rotation = q2
               
                --local e1=r
                --local e2=((e1 as quat) as eulerangles)
               
                --local e1=q1 as eulerangles
                --local e2=q2 as eulerangles
                --format "%: % %" t e1 e2
            )
        )
    )
)

fn bakeRotationIntoChain obj = (
    local r = in coordsys obj obj.rotation
    local child = obj.children[1]
    --zero rotation here
    in coordsys world (
        p = obj.position
        obj.position = [0,0,0]
        obj.rotation = quat 0 0 0 1
        obj.position = p
    )
    if (child!=undefined) do (
        --move the rotation down the chain
        in coordsys child (
            child.rotation+=r
        )
        --repeat for children in the chain
        bakeRotationIntoChain child
    )
)

fn reverseBoneKinetics obj depthToCopy = (
    --copy the structure backward
    local newBone = makeChildBonesReverse obj depthToCopy
   
    --parent the new structure to the selected object
    newBone.parent = obj
   
    --bake it
    bakeRotationIntoChain newBone
       
    --reverse the animation rotations
    --first build a table for bone to bone cloning
    local targetBones=#()
    local newBones=#()
    targetBones[1]=obj
    for n = 1 to depthToCopy do (
        targetBones[n+1]=getAncestor obj n
        newbones[n]=getFirstChild obj n
       
        --then process the bone pairs
        --point the current bone at its counterpart child using the keys system from the counterpart
        forAllKeysPointBoneAtObject newBones[n] targetBones[n+1] targetBones[n]
    )
)

reverseBoneKinetics $rhand 3 --hand forearm bicep


Replies

  • merricksdad
  • monster
    Options
    Offline / Send Message
    monster polycounter
    I'm not sure I'm understanding you.

    You want a clone of the arm with an inverted hierarchy and with the same animation?

    You could clone the arm, use constraints (position and orient) to the original arm, and then collapse the controllers with a key per frame. No script needed. (Unless you wanted to script that process).

  • merricksdad
    Options
    Offline / Send Message
    No I need to clone the arm, reverse the hierarchy, and have the new bones match up in position and rotation to the old bones, but facing the other direction and pivoting in the opposite position. For instance, I need a bone going from the hand to the elbow instead of the elbow to the hand, but the new bone must stay perfectly with the animation of the old bone, in both position and rotation. Same with elbow to shoulder, built from shoulder to elbow. I have the position down, as well as the point-to vector, but not the localized z-rotation. I'm still working on this because I don't understand fully the relationship between facing the new bone toward the joints of the old bones in relation to the local Z axis rotation. I can make them point the correct direction all I want, but when I wrap them into a skin, I get localized z-axis rotation on the new bones which does not match the z-axis rotation on the old bones.

    I know the request itself is superficially un-useful, but I'm making weapons for a game where you don't have access to previous bones backward from the hand node, and so I have to rely on local copies of the bones, except in reverse order. My goal is to create a hand-based object on my character which creates a skinned fire appearance all the way up to the shoulder. To do this, it needs to somehow have access to the forearm and bicep positions, as well as their localized z-rotations.

    I've tried simply copying the localized z-rotations from the "target" bone to the clone bone, but that doesn't seem to work as expected.

    If my grasp of 3D coordinate systems was just a bit better, I could probably get this in 2 seconds.

    I understand that the whole point-toward scripting I have above can be generally simplified into a two part process, using only x and y rotations returned from the matrixFromNormal call. One moving the facing up off the local x/y plane, and one moving the rotation around the local z-axis so that the facing vector is perfect. But the math I used sometimes gives a third number in the z-axis. Not all the time though, so I don't understand that part.

    I still think it is just as simple as copying the z-rotation from bone to bone after I get the facing correct, as long as I do that in the correct coordinate system. But something in my code is wrong, or I am completely mistaken.
  • PolyHertz
    Options
    Offline / Send Message
    PolyHertz polycount lvl 666
    Just an fyi; You can reverse the bone hierarchy via "reassign root" in bone tools.
Sign In or Register to comment.