[SOLVED] Weird Character animation interferences!?

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;
}						

Any explanation??

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?

David

ok here is the status :

(1) if I merely duplicate char0.egg into char0a.egg and load it for char1, I get exactly the same phenomenon :frowning:
Having said so, one has to notice that within char0.egg and char0a.egg, the group is still named char0

<Group> char0 {
  <Dart> { 1 }
  <Group> groundPlane_transform {

(2) :bulb: I tend to suspect a name overlapping issue when the structures are loaded for animation, so if now within char0a.egg I change the group to char0a

<Group> char0a {
  <Dart> { 1 }
  <Group> groundPlane_transform {

The phenomenon disappears! :slight_smile:

Bottom line: I tend to think there is some structure name cross-talk…

BTW: note too that in the animation egg char0_walk.egg the bundle refers to char0…

<Table> {
  <Bundle> char0 {
    <Table> "<skeleton>" {

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?

David

Ok David, I think I get it!
Here is a self contained app that demonstrates it.

The phenomenon only happens if Joe is created after BigJack. In the other case it doesn’t.

// jean-claude's test bed for inter-animation interference
// Assume playing with two siblings of our well known Ralph: Joe & BigJack!

#define JOE_MODEL		"models/ralph.egg"
#define JOE_WALK		"models/ralph-walk.egg"
#define controlJointNam	"Skeleton"

#include "load_prc_file.h"
#include "pandaFramework.h"
#include "pandaSystem.h"

#include "character.h"
#include "auto_bind.h"
#include "animControlCollection.h"

void set_config() {
	load_prc_file_data("", "support-threads 1");
	load_prc_file_data("", "use-movietexture 1");
	load_prc_file_data("", "sync_video 0");
	load_prc_file_data("", "framebuffer-multisample 1");
	load_prc_file_data("", "basic-shaders-only 0");
	load_prc_file_data("", "model-path	C:/Panda_Models/models");
}

int main(int argc, char *argv[]) {
	set_config();
	
	// Create an instance of the PandaFramework class
	PandaFramework framework;
	framework.open_framework(argc, argv);
	framework.set_window_title("Joe & BigJack");
	WindowFramework *window = framework.open_window();

	// Check whether the window was opened right
	if (window == (WindowFramework *)NULL) {
		std::cout << "Could not load the window!\n";
		framework.close_framework();
		return 0;
	}

	window->enable_keyboard();
	window->setup_trackball();
	NodePath render = window->get_render();

	// Add BigJack
	NodePath BigJack = window->load_model(framework.get_models(), JOE_MODEL);
	BigJack.reparent_to(render); // Reparent the model to render
	BigJack.set_scale(1); 
	BigJack.set_pos(0,4,0); 

	NodePath BigJackChNP = BigJack.find("**/+Character"); // find node character definition 
	PT(Character) BigJackCH = DCAST(Character, BigJackChNP.node()); // pointer to character

	PT(CharacterJointBundle) Bundle = BigJackCH->get_bundle(0); // pointer to character joint bundle

	NodePath root_control = BigJack.attach_new_node("root_control"); // add new node "root" to be used to control BigJack_2
	PT(CharacterJoint) BigJack_char_joint;
	if (Bundle->control_joint(controlJointNam, root_control.node()) ) { // get control through Joint (only 1 for test)
		BigJack_char_joint = DCAST(CharacterJoint, Bundle->find_child(controlJointNam));   
	} else std::cout << "node: Skeleton not found in bundle tree \n";

	root_control.set_mat(BigJack_char_joint->get_default_value());	// matrice reference (@pose initiale)

	// Create the main character: Joe
	NodePath Joe = window->load_model(framework.get_models(),JOE_MODEL);
	Joe.set_scale(0.5);
	Joe.reparent_to(render);
	Joe.set_pos(0,0,0);

	// Load Joe's animation
	window->load_model(Joe,JOE_WALK);
	AnimControlCollection Joe_anims;
	auto_bind(Joe.node(), Joe_anims, 0);
	window->loop_animations(Joe);

	// Main loop:
	Thread *current_thread = Thread::get_current_thread();
	while (framework.do_frame(current_thread));

	framework.close_framework();
	return (0);
}

In other words it seems that the autobind is binding the animations to both Joe and BigJack.

If autobind is called prior to the creation of BigJack, then everything is ok…

Feedback?

Sorry, I haven’t had a chance to investigate yet. I’ll get to it today. :slight_smile:

David

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).

David

Thanks a lot David!
This unexpected and well hidden “auto_bind” has really been a nightmare :exclamation:

On a related subject:

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?

Thanks for your hints.

Jean-Claude

(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.

David

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.

David

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?)

Thanks

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.

David

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.

Thanks again for your support.

Jean-Claude

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.

David

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

© from this point on I should be able to control nodes A1,B1 while still having john’s anim running on A, B, C

(d) the remote control of the structure through A1 & B1 must anyway take into consideration the transforms at joint nodes A & B

Is this correct? What else should I do on john_anim.egg file?

What am I missing?

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:

egg-optchar -d new john.egg john_anim.egg -new A1,A -p B,A1 -new B1,B -p C,B1 -keepall 

Now you can load new/john.egg and apply new/john_anim.egg to animate him.

David

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?