Panda3D
softNodeTree.cxx
1 // Filename: softNodeTree.cxx
2 // Created by: masad (26Sep03)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 ////////////////////////////////////////////////////////////////////
16 // Includes
17 ////////////////////////////////////////////////////////////////////
18 
19 #include "softNodeTree.h"
20 #include "softEggGroupUserData.h"
21 #include "config_softegg.h"
22 #include "eggGroup.h"
23 #include "eggTable.h"
24 #include "eggXfmSAnim.h"
25 #include "eggData.h"
26 #include "softToEggConverter.h"
27 #include "dcast.h"
28 
29 #include <SAA.h>
30 
31 ////////////////////////////////////////////////////////////////////
32 // Function: SoftNodeTree::Constructor
33 // Access: Public
34 // Description:
35 ////////////////////////////////////////////////////////////////////
36 SoftNodeTree::
37 SoftNodeTree() {
38  _root = new SoftNodeDesc(NULL, "----root");
39  _root->fullname = "----root";
40  _fps = 0.0;
41  _use_prefix = 0;
42  _search_prefix = NULL;
43  _egg_data = (EggData *)NULL;
44  _egg_root = (EggGroupNode *)NULL;
45  _skeleton_node = (EggGroupNode *)NULL;
46 }
47 ////////////////////////////////////////////////////////////////////
48 // Function: GetName
49 // Access: Public
50 // Description: Given an element, return a copy of the element's
51 // name WITHOUT prefix.
52 ////////////////////////////////////////////////////////////////////
53 char *SoftNodeTree::
54 GetName( SAA_Scene *scene, SAA_Elem *element ) {
55  int nameLen;
56  char *name;
57 
58  // get the name
59  SAA_elementGetNameLength( scene, element, &nameLen );
60  name = new char[++nameLen];
61  SAA_elementGetName( scene, element, nameLen, name );
62 
63  return name;
64 }
65 
66 ////////////////////////////////////////////////////////////////////
67 // Function: GetFullName
68 // Access: Public
69 // Description: Given an element, return a copy of the element's
70 // name complete with prefix.
71 ////////////////////////////////////////////////////////////////////
72 char *SoftNodeTree::
73 GetFullName( SAA_Scene *scene, SAA_Elem *element )
74 {
75  int nameLen, prefixLen;
76  char *name, *prefix;
77 
78  // get the name length
79  SAA_elementGetNameLength( scene, element, &nameLen );
80  // get the prefix length
81  SAA_elementGetPrefixLength( scene, element, &prefixLen );
82  // allocate the array to hold name
83  name = new char[++nameLen];
84  // allocate the array to hold prefix and length + hyphen
85  prefix = new char[++prefixLen + nameLen + 4];
86  // get the name
87  SAA_elementGetName( scene, element, nameLen, name );
88  // get the prefix
89  SAA_elementGetPrefix( scene, element, prefixLen, prefix );
90  // add 'em together
91  strcat(prefix, "-");
92  strcat(prefix, name);
93 
94  // return string
95  return prefix;
96 }
97 
98 ////////////////////////////////////////////////////////////////////
99 // Function: GetModelNoteInfo
100 // Access: Public
101 // Description: Given an element, return a string containing the
102 // contents of its MODEL NOTE entry
103 ////////////////////////////////////////////////////////////////////
104 char *SoftNodeTree::
105 GetModelNoteInfo( SAA_Scene *scene, SAA_Elem *model ) {
106  int size;
107  char *modelNote = NULL;
108  SAA_Boolean bigEndian;
109 
110  SAA_elementGetUserDataSize( scene, model, "MNOT", &size );
111 
112  if ( size != 0 ) {
113  // allocate modelNote string
114  modelNote = new char[size + 1];
115 
116  // get ModelNote data from this model
117  SAA_elementGetUserData( scene, model, "MNOT", size,
118  &bigEndian, (void *)modelNote );
119 
120  //strip off newline, if present
121  char *eol = (char *)memchr( modelNote, '\n', size );
122  if ( eol != NULL)
123  *eol = '\0';
124  else
125  modelNote[size] = '\0';
126 
127  softegg_cat.spam() << "\nmodelNote = " << modelNote << endl;
128  }
129 
130  return modelNote;
131 }
132 
133 ////////////////////////////////////////////////////////////////////
134 // Function: GetRootName
135 // Access: Public
136 // Description: Given a string, return a copy of the string up to
137 // the first occurence of '-'.
138 ////////////////////////////////////////////////////////////////////
139 char *SoftNodeTree::
140 GetRootName( const char *name ) {
141  const char *hyphen;
142  char *root;
143  int len;
144 
145  hyphen = strchr( name, '-' );
146  len = hyphen-name;
147 
148  if ( (hyphen != NULL) && len ) {
149  root = new char[len+1];
150  strncpy( root, name, len );
151  root[len] = '\0';
152  }
153  else {
154  root = new char[strlen(name)+1];
155  strcpy( root, name );
156  }
157  return( root );
158 }
159 
160 ////////////////////////////////////////////////////////////////////
161 // Function: SoftNodeTree::build_complete_hierarchy
162 // Access: Public
163 // Description: Walks through the complete Soft hierarchy and builds
164 // up the corresponding tree.
165 ////////////////////////////////////////////////////////////////////
166 bool SoftNodeTree::
167 build_complete_hierarchy(SAA_Scene &scene, SAA_Database &database) {
168  SI_Error status;
169  SoftNodeDesc *node;
170 
171  // Get the entire Soft scene.
172  int numModels;
173  SAA_Elem *models;
174 
175  SAA_sceneGetNbModels( &scene, &numModels );
176  softegg_cat.spam() << "Scene has " << numModels << " model(s)...\n";
177 
178  // This while loop walks through the entire Soft hierarchy, one
179  // node at a time.
180  bool all_ok = true;
181  if ( numModels ) {
182  // allocate array of models
183  models = (SAA_Elem *) new SAA_Elem[numModels];
184  if ( models != NULL ) {
185  if ((status = SAA_sceneGetModels( &scene, numModels, models )) != SI_SUCCESS) {
186  return false;
187  }
188  for ( int i = 0; i < numModels; i++ ) {
189  int level;
190  status = SAA_elementGetHierarchyLevel( &scene, &models[i], &level );
191  softegg_cat.spam() << "model[" << i << "]" << endl;
192  softegg_cat.spam() << " level " << level << endl;
193  softegg_cat.spam() << " status is " << status << "\n";
194 
195  node = build_node(&scene, &models[i]);
196  if (!level && node)
197  node->set_parent(_root);
198  }
199  }
200  }
201 
202  softegg_cat.spam() << "jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj\n";
203 
204  // check the nodes that are junk for animation/artist control purposes
205  _root->check_junk(false);
206 
207  softegg_cat.spam() << "jpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjpjp\n";
208 
209  // check the nodes that are parent of ancestors of a joint
210  _root->check_joint_parent();
211 
212  softegg_cat.spam() << "pppppppppppppppppppppppppppppppppppppppppppppppppppppppp\n";
213 
214  // check the nodes that are pseudo joints
215  _root->check_pseudo_joints(false);
216 
217  softegg_cat.spam() << "========================================================\n";
218 
219  // find _parentJoint for each node
220  _root->set_parentJoint(&scene, NULL);
221 
222  return all_ok;
223 }
224 #if 0
225 ////////////////////////////////////////////////////////////////////
226 // Function: SoftNodeTree::build_selected_hierarchy
227 // Access: Public
228 // Description: Walks through the selected subset of the Soft
229 // hierarchy (or the complete hierarchy, if nothing is
230 // selected) and builds up the corresponding tree.
231 ////////////////////////////////////////////////////////////////////
232 bool SoftNodeTree::
233 build_selected_hierarchy(char *scene_name) {
234  MStatus status;
235 
236  MItDag dag_iterator(MItDag::kDepthFirst, MFn::kTransform, &status);
237  if (!status) {
238  status.perror("MItDag constructor");
239  return false;
240  }
241 
242  // Get only the selected geometry.
243  MSelectionList selection;
244  status = MGlobal::getActiveSelectionList(selection);
245  if (!status) {
246  status.perror("MGlobal::getActiveSelectionList");
247  return false;
248  }
249 
250  // Get the selected geometry only if the selection is nonempty;
251  // otherwise, get the whole scene anyway.
252  if (selection.isEmpty()) {
253  softegg_cat.info()
254  << "Selection list is empty.\n";
255  return build_complete_hierarchy();
256  }
257 
258  bool all_ok = true;
259  unsigned int length = selection.length();
260  for (unsigned int i = 0; i < length; i++) {
261  MDagPath root_path;
262  status = selection.getDagPath(i, root_path);
263  if (!status) {
264  status.perror("MSelectionList::getDagPath");
265  } else {
266  // Now traverse through the selected dag path and all nested
267  // dag paths.
268  dag_iterator.reset(root_path);
269  while (!dag_iterator.isDone()) {
270  MDagPath dag_path;
271  status = dag_iterator.getPath(dag_path);
272  if (!status) {
273  status.perror("MItDag::getPath");
274  } else {
275  build_node(dag_path);
276  }
277 
278  dag_iterator.next();
279  }
280  }
281  }
282 
283  if (all_ok) {
284  _root->check_pseudo_joints(false);
285  }
286 
287  return all_ok;
288 }
289 #endif
290 ////////////////////////////////////////////////////////////////////
291 // Function: SoftNodeTree::get_num_nodes
292 // Access: Public
293 // Description: Returns the total number of nodes in the hierarchy,
294 // not counting the root node.
295 ////////////////////////////////////////////////////////////////////
296 int SoftNodeTree::
297 get_num_nodes() const {
298  return _nodes.size();
299 }
300 
301 ////////////////////////////////////////////////////////////////////
302 // Function: SoftNodeTree::get_node
303 // Access: Public
304 // Description: Returns the nth node in the hierarchy, in an
305 // arbitrary ordering.
306 ////////////////////////////////////////////////////////////////////
308 get_node(int n) const {
309  nassertr(n >= 0 && n < (int)_nodes.size(), NULL);
310  return _nodes[n];
311 }
312 
313 ////////////////////////////////////////////////////////////////////
314 // Function: SoftNodeTree::get_node
315 // Access: Public
316 // Description: Returns the node named 'name' in the hierarchy, in
317 // an arbitrary ordering.
318 ////////////////////////////////////////////////////////////////////
320 get_node(string name) const {
321  NodesByName::const_iterator ni = _nodes_by_name.find(name);
322  if (ni != _nodes_by_name.end())
323  return (*ni).second;
324  return NULL;
325 }
326 
327 ////////////////////////////////////////////////////////////////////
328 // Function: SoftNodeTree::clear_egg
329 // Access: Public
330 // Description: Removes all of the references to generated egg
331 // structures from the tree, and prepares the tree for
332 // generating new egg structures.
333 ////////////////////////////////////////////////////////////////////
334 void SoftNodeTree::
335 clear_egg(EggData *egg_data, EggGroupNode *egg_root,
336  EggGroupNode *skeleton_node) {
337  _root->clear_egg();
338  _egg_data = egg_data;
339  _egg_root = egg_root;
340  _skeleton_node = skeleton_node;
341 }
342 
343 ////////////////////////////////////////////////////////////////////
344 // Function: SoftNodeTree::get_egg_group
345 // Access: Public
346 // Description: Returns the EggGroupNode corresponding to the group
347 // or joint for the indicated node. Creates the group
348 // node if it has not already been created.
349 ////////////////////////////////////////////////////////////////////
352  nassertr(_egg_root != (EggGroupNode *)NULL, NULL);
353 
354  // lets print some relationship
355  softegg_cat.spam() << " group " << node_desc->get_name() << "(" << node_desc->_egg_group << ")";
356  if (node_desc->_parent)
357  softegg_cat.spam() << " parent " << node_desc->_parent->get_name() << "(" << node_desc->_parent << ")";
358  else
359  softegg_cat.spam() << " parent " << node_desc->_parent;
360  softegg_cat.spam() << endl;
361 
362  if (node_desc->_egg_group == (EggGroup *)NULL) {
363  // We need to make a new group node.
364  EggGroup *egg_group;
365 
366  egg_group = new EggGroup(node_desc->get_name());
367  if (node_desc->is_joint()) {
368  egg_group->set_group_type(EggGroup::GT_joint);
369  }
370 
371  if (stec.flatten || (!node_desc->_parentJoint || node_desc->_parentJoint == _root)) {
372  // The parent is the root.
373  softegg_cat.spam() << "came hereeeee\n";
374  _egg_root->add_child(egg_group);
375  } else {
376  // The parent is another node.
377  EggGroup *parent_egg_group = get_egg_group(node_desc->_parentJoint);
378  parent_egg_group->add_child(egg_group);
379  }
380 
381  node_desc->_egg_group = egg_group;
382  }
383 
384  return node_desc->_egg_group;
385 }
386 
387 ////////////////////////////////////////////////////////////////////
388 // Function: SoftNodeTree::get_egg_table
389 // Access: Public
390 // Description: Returns the EggTable corresponding to the joint
391 // for the indicated node. Creates the table node if it
392 // has not already been created.
393 ////////////////////////////////////////////////////////////////////
396  nassertr(_skeleton_node != (EggGroupNode *)NULL, NULL);
397  nassertr(node_desc->is_joint(), NULL);
398 
399  // lets print some relationship
400  softegg_cat.spam() << " group " << node_desc->get_name() << "(" << node_desc->_egg_group << ")";
401  if (node_desc->_parent)
402  softegg_cat.spam() << " parent " << node_desc->_parent->get_name() << "(" << node_desc->_parent << ")";
403  else
404  softegg_cat.spam() << " parent " << node_desc->_parent;
405  softegg_cat.spam() << endl;
406 
407  if (node_desc->_egg_table == (EggTable *)NULL) {
408  softegg_cat.spam() << "creating a new table\n";
409  // We need to make a new table node.
410  // nassertr(node_desc->_parent != (SoftNodeDesc *)NULL, NULL);
411 
412  EggTable *egg_table = new EggTable(node_desc->get_name());
413  node_desc->_anim = new EggXfmSAnim("xform", _egg_data->get_coordinate_system());
414  node_desc->_anim->set_fps(_fps);
415  egg_table->add_child(node_desc->_anim);
416 
417  if (stec.flatten || (!node_desc->_parentJoint || node_desc->_parentJoint == _root)) {
418  // if (!node_desc->_parent->is_joint()) {
419  // The parent is not a joint; put it at the top.
420  _skeleton_node->add_child(egg_table);
421  } else {
422  // The parent is another joint.
423  EggTable *parent_egg_table = get_egg_table(node_desc->_parentJoint);
424  parent_egg_table->add_child(egg_table);
425  }
426 
427  node_desc->_egg_table = egg_table;
428  }
429 
430  return node_desc->_egg_table;
431 }
432 
433 ////////////////////////////////////////////////////////////////////
434 // Function: SoftNodeTree::get_egg_anim
435 // Access: Public
436 // Description: Returns the anim table corresponding to the joint
437 // for the indicated node. Creates the table node if it
438 // has not already been created.
439 ////////////////////////////////////////////////////////////////////
442  get_egg_table(node_desc);
443  return node_desc->_anim;
444 }
445 
446 ////////////////////////////////////////////////////////////////////
447 // Function: SoftNodeTree::handle_null
448 // Access: Public
449 // Description: Sets joint information for MNILL node
450 ////////////////////////////////////////////////////////////////////
451 void SoftNodeTree::
452 handle_null(SAA_Scene *scene, SoftNodeDesc *node_desc, const char *node_name) {
453  const char *name = node_name;
454  SAA_AlgorithmType algo;
455  SAA_Elem *model = node_desc->get_model();
456 
457  SAA_modelGetAlgorithm( scene, model, &algo );
458  softegg_cat.spam() << " null algorithm: " << algo << endl;
459 
460  if ( algo == SAA_ALG_INV_KIN ) {
461  // MakeJoint( &scene, lastJoint, lastAnim, model, name );
462  node_desc->set_joint();
463  softegg_cat.spam() << " encountered IK root: " << name << endl;
464  }
465  else if ( algo == SAA_ALG_INV_KIN_LEAF ) {
466  // MakeJoint( &scene, lastJoint, lastAnim, model, name );
467  node_desc->set_joint();
468  softegg_cat.spam() << " encountered IK leaf: " << name << endl;
469  }
470  else if ( algo == SAA_ALG_STANDARD ) {
471  SAA_Boolean isSkeleton = FALSE;
472  softegg_cat.spam() << " encountered Standard null: " << name << endl;
473 
474  SAA_modelIsSkeleton( scene, model, &isSkeleton );
475 
476  // check to see if this NULL is used as a skeleton
477  // or is animated via constraint only ( these nodes are
478  // tagged by the animator with the keyword "joint"
479  // somewhere in the nodes name)
480  if ( isSkeleton || (strstr( name, "joint" ) != NULL) ) {
481  // MakeJoint( &scene, lastJoint, lastAnim, model, name );
482  node_desc->set_joint();
483  softegg_cat.spam() << " animating Standard null!!!\n";
484  softegg_cat.spam() << "isSkeleton: " << isSkeleton << endl;
485  }
486  }
487  else
488  softegg_cat.spam() << " encountered some other NULL: " << algo << endl;
489 }
490 
491 ////////////////////////////////////////////////////////////////////
492 // Function: SoftNodeTree::build_node
493 // Access: Public
494 // Description: Returns a pointer to the node corresponding to the
495 // indicated dag_path object, creating it first if
496 // necessary.
497 ////////////////////////////////////////////////////////////////////
499 build_node(SAA_Scene *scene, SAA_Elem *model) {
500  char *name, *fullname;
501  string node_name;
502  int numChildren;
503  int thisChild;
504  SAA_Elem *children;
505  SAA_ModelType type;
506  SAA_Boolean isSkeleton = FALSE;
507 
508  fullname = GetFullName(scene, model);
509  if (_use_prefix)
510  name = fullname;
511  else
512  name = GetName(scene, model);
513 
514  node_name = name;
515 
516  SoftNodeDesc *node_desc = r_build_node(NULL, node_name);
517 
518  node_desc->fullname = fullname;
519  node_desc->set_model(model);
520  SAA_modelIsSkeleton( scene, model, &isSkeleton );
521 
522  // find out what type of node we're dealing with
523  SAA_modelGetType( scene, node_desc->get_model(), &type );
524 
525  if (type == SAA_MJNT || isSkeleton || (strstr(node_desc->get_name().c_str(), "joint") != NULL))
526  node_desc->set_joint();
527 
528  // treat the MNILL differently, because it needs to detect and set some joints
529  if (type == SAA_MNILL)
530  handle_null(scene, node_desc, name);
531 
532  if (node_desc->is_joint())
533  softegg_cat.spam() << "type: " << type << " isSkeleton: " << isSkeleton << endl;
534 
535  // get to the children
536  SAA_modelGetNbChildren( scene, model, &numChildren );
537  softegg_cat.spam() << " Model " << node_name << " children: " << numChildren << endl;
538 
539  if ( numChildren ) {
540  children = new SAA_Elem[numChildren];
541  SAA_modelGetChildren( scene, model, numChildren, children );
542  if (!children)
543  softegg_cat.info() << "Not enough Memory for children...\n";
544 
545  for ( thisChild = 0; thisChild < numChildren; thisChild++ ) {
546  fullname = GetFullName(scene, &children[thisChild]);
547  if (_use_prefix)
548  node_name = fullname;
549  else
550  node_name = GetName(scene, &children[thisChild]);
551 
552  softegg_cat.spam() << " building child " << thisChild << "...";
553 
554  SoftNodeDesc *node_child = r_build_node(node_desc, node_name);
555 
556  node_child->fullname = fullname;
557  node_child->set_model(&children[thisChild]);
558  SAA_modelIsSkeleton( scene, &children[thisChild], &isSkeleton );
559 
560  // find out what type of node we're dealing with
561  SAA_modelGetType( scene, node_child->get_model(), &type );
562 
563  if (type == SAA_MJNT || isSkeleton || (strstr(node_child->get_name().c_str(), "joint") != NULL))
564  node_child->set_joint();
565 
566  // treat the MNILL differently, because it needs to detect and set some joints
567  if (type == SAA_MNILL)
568  handle_null(scene, node_child, node_name.c_str());
569 
570  if (node_child->is_joint())
571  softegg_cat.spam() << "type: " << type << " isSkeleton: " << isSkeleton << endl;
572  }
573  }
574  return node_desc;
575 }
576 
577 ////////////////////////////////////////////////////////////////////
578 // Function: SoftNodeTree::r_build_node
579 // Access: Private
580 // Description: The recursive implementation of build_node().
581 ////////////////////////////////////////////////////////////////////
582 SoftNodeDesc *SoftNodeTree::
583 r_build_node(SoftNodeDesc *parent_node, const string &name) {
584  SoftNodeDesc *node_desc;
585 
586  // If we have already encountered this pathname, return the
587  // corresponding SoftNodeDesc immediately.
588  NodesByName::const_iterator ni = _nodes_by_name.find(name);
589  if (ni != _nodes_by_name.end()) {
590  softegg_cat.spam() << " already built node " << (*ni).first;
591  node_desc = (*ni).second;
592  node_desc->set_parent(parent_node);
593  return node_desc;
594  }
595 
596  // Otherwise, we have to create it. Do this recursively, so we
597  // create each node along the path.
598  node_desc = new SoftNodeDesc(parent_node, name);
599 
600  softegg_cat.spam() << " node name : " << name << endl;
601  _nodes.push_back(node_desc);
602 
603  _nodes_by_name.insert(NodesByName::value_type(name, node_desc));
604 
605  return node_desc;
606 }
607 
608 //
609 //
610 //
EggTable * get_egg_table(SoftNodeDesc *node_desc)
Returns the EggTable corresponding to the joint for the indicated node.
A base class for nodes in the hierarchy that are not leaf nodes.
Definition: eggGroupNode.h:51
char * GetModelNoteInfo(SAA_Scene *, SAA_Elem *)
Given an element, return a string containing the contents of its MODEL NOTE entry.
char * GetName(SAA_Scene *scene, SAA_Elem *element)
Given an element, return a copy of the element&#39;s name WITHOUT prefix.
SoftNodeDesc * get_node(int n) const
Returns the nth node in the hierarchy, in an arbitrary ordering.
CoordinateSystem get_coordinate_system() const
Returns the coordinate system in which the egg file is defined.
Definition: eggData.I:111
SAA_Elem * get_model() const
Returns the SAA_Elem * associated with this node.
This is the primary interface into all the egg data, and the root of the egg file structure...
Definition: eggData.h:41
int get_num_nodes() const
Returns the total number of nodes in the hierarchy, not counting the root node.
char * GetFullName(SAA_Scene *scene, SAA_Elem *element)
Given an element, return a copy of the element&#39;s name complete with prefix.
void clear_egg(EggData *egg_data, EggGroupNode *egg_root, EggGroupNode *skeleton_node)
Removes all of the references to generated egg structures from the tree, and prepares the tree for ge...
The main glue of the egg hierarchy, this corresponds to the <Group>, <Instance>, and <Joint> type nod...
Definition: eggGroup.h:36
char * GetRootName(const char *)
Given a string, return a copy of the string up to the first occurence of &#39;-&#39;.
EggGroup * get_egg_group(SoftNodeDesc *node_desc)
Returns the EggGroupNode corresponding to the group or joint for the indicated node.
bool build_complete_hierarchy(SAA_Scene &scene, SAA_Database &database)
Walks through the complete Soft hierarchy and builds up the corresponding tree.
This corresponds to an <Xfm$Anim_S$> entry, which is a collection of up to nine <S$Anim> entries that...
Definition: eggXfmSAnim.h:33
Describes a single instance of a node aka element in the Soft scene graph, relating it to the corresp...
Definition: softNodeDesc.h:46
void set_joint()
sets the _joint_type to JT_joint
This corresponds to a.
Definition: eggTable.h:31
EggNode * add_child(EggNode *node)
Adds the indicated child to the group and returns it.
void set_parent(SoftNodeDesc *parent)
Sometimes, parent is not known at node creation As soon as it is known, set the parent.
void handle_null(SAA_Scene *scene, SoftNodeDesc *node_desc, const char *node_name)
Sets joint information for MNILL node.
SoftNodeDesc * build_node(SAA_Scene *scene, SAA_Elem *model)
Returns a pointer to the node corresponding to the indicated dag_path object, creating it first if ne...
EggXfmSAnim * get_egg_anim(SoftNodeDesc *node_desc)
Returns the anim table corresponding to the joint for the indicated node.
void set_model(SAA_Elem *model)
Indicates an associated between the SoftNodeDesc and some SAA_Elem instance.
bool is_joint() const
Returns true if the node should be treated as a joint by the converter.