global coordinates of a joint

so I know that if I call .getPos() on one of my controlled joints, it simply returns the local coordinates of the joint

if I make an expose joint node on that joint, and call .getPos() does it return the global coordinates of that joint?

in my little test it seems to do so; however I’m not completely convinced

is there another way to get the global coordinates of a joint?

you should be able to call getPos with respect to any nodepath. If you want to get the coordinates from render, you can call getPos(render)

update:
Calling getPos(render) on a joint that starts a chain that you have created in Maya, as I have, will give you the world coordinates of that joint. However, this does not give the correct world coordinates for any joint further down in the chain; this has to do with how Maya sets the transform attributes of joints in a chain - something I am not entirely clear on. Just thought someone might find this info useful

-Max

what do you mean by ‘chain’? If you mean a joint lower in the hierarchy, it should still work. This was my test:

import direct.directbase.DirectStart
from direct.actor.Actor import Actor
from direct.task import Task


base.disableMouse()
base.cam.setPos(0, -25, 5)

panda = Actor('panda', {'walk': 'panda-walk'})
panda.reparentTo(render)

#expose a joint lower in the hierarchy
#obtained the joint name from 'egg-optchar -ls panda.egg' in command prompt
joint = panda.exposeJoint(None, 'modelRoot', 'jnt2_1_1')

smiley = loader.loadModel('smiley')
smiley.reparentTo(render)

def posTask(task):
    #place the smiley at the joint every frame
    pos = joint.getPos(render)
    smiley.setPos(pos)
    #remove this next line if you dont want to spam your output
    print pos
    
    return Task.cont

#animate the panda and run the task
panda.loop('walk')
taskMgr.add(posTask, 'posTask')

run()

I’m sorry, I should clarify what I was doing.

When I was calling joint.getPos(render) ‘joint’ in that case it was a controlJoint, not an exposeJoint. With an exposeJoint, yes, this does work. I don’t believe I ever actually tried calling exposedJoint.getPos(render), I only tried controlledJoint.getPos(render) which as far as I can tell, only gives world coords for the highest joint in a hierarchy, or joint chain, as I’ve been thinking of them lately (stupid IK).

Thanks for the help!

That’s right–controlJoints are a little different than regular exposed joints, since they are given in local coordinates.

If you want, you can make the controlJoints get a proper global coordinate from render by parenting them to their parent joint. This means you need to expose the parent joint.

parent = actor.exposeJoint(None, "modelRoot", "joint_parent")
child = parent.attachNewNode("joint_child")
actor.controlJoint(child, "modelRoot", "joint_child")

In case you have a controlled joint parented to another controlled joint, you stack them all up, with the exposed joint at the top:

grandparent = actor.exposeJoint(None, "modelRoot", joint_grandparent")
parent = grandparent.attachNewNode("joint_parent")
actor.controlJoint(parent, "modelRoot", "joint_parent")
child = parent.attachNewNode("joint_child")
actor.controlJoint(child, "modelRoot", "joint_child")

This works because if the first parameter of controlJoint or exposeJoint is not None, then it indicates the NodePath that you have already created and attached to the right place to represent the node. (Normally, if the first parameter is None, it indicates that exposeJoint/controlJoint should create a new NodePath and return it.)

David

Thank you very much David! This solved a particularly puzzling problem I was having with my procedural character animation.

I’m posting to this (very) old thread to add one bit of information that might be useful to others. In some applications, it may be important to initialize the node created by controlJoint() to the initial transform specified for the joint in the model file, even in cases where one needs to be able to access its global transform.

For example, my application of this is for a custom physics-based procedural simulation of a character’s hair. The joints belonging to hair chains in the character model are controlled procedurally, while all other joints are controlled by prerecorded animations. The intent is that the hair will respond in a physically reasonable manner, regardless of the combination of the animation playing (that may include motion for the head) and the state of motion (rotation + translational acceleration) of the character.

To keep things simple, the simulation code knows nothing about orientation, computing only cartesian positions of nodes (in the coordinate system of the character’s head) connected to each other by rigid rods, interfaced by linear “ball-joint” bending springs. This information is copied into the controlled joints at each frame, by walking the hair chains and using lookAt(). The local roll of each joint is kept locked to its original value from the model file, since in the absence of torsion the hair chains should not twist.

For initialization, the simulation needs the original positions of the joints in the hair chains with respect to the character’s head. Panda can do the coordinate transformations easily, provided that one can get the global transform of a controlled joint.

Thus, we modify the approach slightly:

grandparent = actor.exposeJoint(None, "modelRoot", "joint_grandparent")
parent = actor.controlJoint(None, "modelRoot", "joint_parent")
parent.reparentTo(grandparent)
child = actor.controlJoint(None, "modelRoot", "joint_child")
child.reparentTo(parent)

We let controlJoint() create the new node and apply its default initialization in the usual way (copying the initial transformation from the joint in the model file), instead of creating the new node ourselves. Then we reparent the created node as suggested in this thread, in order to be able to read its global transform. At least in Panda3D 1.8.1, this does what is intended.

This does have the side effect that one cannot have an exposed joint further down the controlled joint chain. If that is attempted, the net transform read from the downstream exposed joint will be incorrect. (My interpretation is that this is because Panda expects the NodePaths related to controlled joints are not parented to anything.)

As for why one would need to do that - for example, in my particular application, each hair chain has a terminator joint whose only purpose is to indicate the initial position of the endpoint of the last hair segment in the chain. The terminator affects no vertices in the model, is never animated, and there is no need to really control it procedurally. However, its initial position must be read to initialize the simulation correctly.

This particular problem was solved by using controlJoint() also on the terminator joint, and calling releaseJoint() on it after the initialization is done.