Hi I’m experiencing a weird behaviour while animating sibling characters, ie characters derived from the same model.
Here is what happens.
I have one character char0 which is animated via a prebaked animation, then I instantiate a second character char1 of the same model, char1 will be animated through control_joints with command coming from the network.
What I observe is that char0 animation is generating char1 sporadic movements,
ie char1 is shaking (synchronously with char0) even if there is no action on control-joints coming from remote controller!!!
Sketch code:
// character 0 (self-animated)
NodePath char0 = window->load_model(framework.get_models(), "char0.egg");
char0.reparent_to(render);
char0.set_pos(0, 0, 0);
window->load_model(char0,"char0_anim.egg"); // load walk animation
AnimControlCollection char0_anims;
auto_bind(char0.node(), char0_anims, 0);
char0_anims.loop("char0_walk",true); // char0_walk is the name of the animation.
window->loop_animations(char0); // launch char 0 animation
// Now set a "twin" character whose control-joints are animated by a remote animator
//
// character 1 (same model as character 0!!!!)
NodePath char1 = window->load_model(framework.get_models(), "char0.egg");
char1.reparent_to(render);
char1.set_pos( 10, 0, 0);
make_all_control_joints_accessibles(char1);
// remote updater for character 1
PT(AsyncTask) update_task = new GenericAsyncTask("updateChars", &updateChars, (void*) NULL);
taskMgr->add(update_task);
...
AsyncTask::DoneStatus updateChars(GenericAsyncTask* task, void* data) {
if (update_message_to_char1_from_remote) update_control_joints_char1();
return AsyncTask::DS_cont;
}
No, that’s really weird! There’s just barely a possibility that something inside Panda could have gotten its wires crossed because it’s the same model file, but if so, it’s the first time I’ve heard of this happening. Could you verify this possibility by making another copy of char0.egg, say to char0a.egg, and loading this copy file for char1?
(1) if I merely duplicate char0.egg into char0a.egg and load it for char1, I get exactly the same phenomenon
Having said so, one has to notice that within char0.egg and char0a.egg, the group is still named char0
Hmm, but this is impossible. Once you’ve loaded a separate model file, there’s nothing within Panda that could automatically conflate it with any other model, even if some of the names within it are the same as names within that other model. It’s a completely different structure, nothing is shared.
The only thing that might conflate models like this is something that traverses the scene graph, looking for matching node names. Like auto_bind(), for instance, if you ever ran an auto_bind() on a node that contained both models (such as render). But auto_bind() is the only function I can think of within Panda that does this sort of thing.
Can you reproduce this phenomenon in a much simpler application?
Ah, I found it. The problem is not auto_bind(), per se, but your call to the unfortunately named WindowFramework::loop_animations(), which is actually implemented as:
////////////////////////////////////////////////////////////////////
// Function: WindowFramework::loop_animations
// Access: Public
// Description: Looks for characters and their matching animation
// files in the scene graph; binds and loops any
// matching animations found.
////////////////////////////////////////////////////////////////////
void WindowFramework::
loop_animations(int hierarchy_match_flags) {
// If we happened to load up both a character file and its matching
// animation file, attempt to bind them together now and start the
// animations looping.
auto_bind(get_render().node(), _anim_controls, hierarchy_match_flags);
_anim_controls.loop_all(true);
}
So you see that loop_animations() automatically performs another auto_bind(), and this time it searches from render. So it binds up all of your animations together in this call.
Instead of calling window->loop_animations(Joe), you should be calling simply Joe_anims.loop_all(true).
Once a joint is selected as “control_joint”, it is no more animated by a pre-baked animation that may be running on the character, although the others joints are, right?
The question is therefore: assume I have a character on which I set full control by selecting all its joints as control_joint. This character is primarely intended to be animated via a remote connection.
(1) I want anyway to keep the possibility to have this character entering into self-animated mode, say if it is not getting a remote drive control for more than one minute the pre-baked animation should play, but obviously still allowing the remote control to be effective.
How to do this in a smart and efficient way?
(2) same question as (1) and adding the possibility to “merge” both remote and pre-baked animations at the same time?
(1) The easiest and cleanest thing to do is to make two copies of the model, and apply the control_joint operation to only one of them. When you want to let him run by himself, swap in the other model. (Another alternative is to call release_joint() to undo the effects of control_joint(), but going back and forth like this is time-consuming and clumsy.)
(2) I don’t know what you mean by “merge”. Some kind of blending of the two animations at the controlled joints? Or maybe you mean a half-body animation, where some of the model follows a scripted animation, and other parts of it are controlled? Depending on your needs, you might be able to do combinations of control_joint() and release_joint(), or different copies of the model standing by, or you could even interpose extra joints at all the control locations and control_joint() only one of them, the one that has no animation on it, and let the other one continue to play whatever animation you prepare.
Well, I thought for a while about duplicating and swap between the two copies…
The issue is that this creates some difficulties due to the fact that the character is involved in Panda collision detection (pusher,…) and moreover some Bullet nodes are attached to some of its joints such as hands or feets.
So the idea I have in mind is to work on the character PartBundle and while iterating through the joint structure, each time a character_joint is encountered, create a new joint node with an identity transform, attach it to the father of the current joint node, and re-attach the current joint node to the newly created node, and so forth all along the bone structure.
When done, all new joint nodes would be used as control-nodes, the other nodes being driven by the baked animation.
The point is that I’m not so sure how safe it is to temper with the structure, and the right way to attach/detach such nodes in memory.
An alternative would be to do the same thing in the egg file, but I’d rather not to alter the original egg file.
You can use egg-optchar to easily and safely introduce new joints into the egg file (without permanently modifying the original egg file, of course–you’ll produce a different egg file as output).
This is how we solved the problem once of allowing customizable avatars, with certain joints being controlled by sliders (to control e.g. shoulder breadth, leg length, and so on), and other joints continuing to play back scripted animations.
ok, here follows a short Python sketch of what I had in mind.
def DuplicateJointHierarchy(actor, part, parentNode = None, level = 0):
if isinstance(part, CharacterJoint):
if parentNode:
#create new characterJoint attached to parentNode
new_joint = parentNode.attachNewNode() or parentNode.makeCharacterJoint(factoryParams??) ??
#name it
new_joint.setName(part.getName()+'N')
#have it being an identity rotation matrix and no translation
# new_joint.addLocalTransform(identity)??
#detach part from parentNode and reparent it to new_joint
part.reparenTo(new_joint)
for child in part.getChildren():
DuplicateJointHierarchy(actor, child, part, level + 1)
DuplicateJointHierarchy(actor, actor.getPartBundle('modelRoot'))
Then all joint nodes named xxN could be used as control.
Would this approach work?
If yes what’s the correct way to attach/detach intermediate nodes with identity transform?
(in other words: how to properly insert joint procedurally?)
There’s not really a good way to introduce a joint procedurally. It’s technically possible, but it’s really difficult because of the way all of the vertices are tied to their existing joints and the transforms they hold. That’s why I recommended using egg-optchar to do this in a pre-process.
I suppose you could write a simple Python script to process the joint list and generate the appropriate egg-optchar command, so you wouldn’t have to generate the list of joints to add by hand.
Indeed the ‘on-line’ procedural task seems complicated so I’ll go with a Python script to automate the egg-optchar command and have a dual egg file generated.
Note that it is also important to run egg-optchar on all of the anim egg files in the same step. That way, egg-optchar can ensure that all of the joint hierarchies match across all of the egg files.
Hi again,
I’m not so sure I fully get the meaning of your last comment.
So, to summarise:
(a) I start with john.egg and john_anim.egg
(b) Through a recursive exploration of john’s bone structure via a Python script, I generate the egg-optchar command
To illustrate assume john'skeleton hierarchy is A->B->C
At this step I have
egg-optchar john.egg -o john_new.egg -new A1,A -p B,A1 -new B1,B -p C,B1 -keep all
with A1,B1 being identity transform
giving the new skeleton A->A1->B->B1->C
The problem is that you have structurally changed john’s skeleton hierarchy, and unless you change john_anim in exactly the same way, you will find that you are unable to load and play the animation.
But not to worry, this is what egg-optchar is good at. Instead of the command you show, do this slight variant:
Thank you, I implemented it this way, this works fine!
I have a couple of additional questions for refinement:
(1) at the time I switch from baked john_anim to direct drive of control_joints, the joint nodes that were driven by john_anim keep their transform values on the bone path. So I either need to take these transforms into account for the local control_nodes transform, or (better) I have to reset the former animated joints to identity transform (well only the rotation part!). I can do it via a dummy animation that sets animated joints transform to identity…
(2) conversely, when I switch from control_joint drive to john_anim drive, I have to reset the controlled joints transform to identity which I can do directly in c++ code since the nodes are accessible as ‘control_joints’.
So the question is: in order to harmonize the processing and simplify coding is there a quick and smart way to force procedurally all joints (controlled or not) to identity rotation (ie for instance through recursively visiting each joint node)?
PS. additional minor questions to clarify missing information in Panda3D doc:
what’s the meaning of PartGroup::HMF_ok_part_extra | PartGroup::HMF_ok_anim_extra | PartGroup::HMF_ok_wrong_root_name in auto_bind() ?
in c++, I suppose the ‘name’ of the animation is the name of the bundle in the animation file and that this name can be changed to whatever name, right?