浏览代码

further progress on egg-optchar

David Rose 22 年之前
父节点
当前提交
986f3f8bdc

+ 3 - 3
pandatool/src/egg-optchar/Sources.pp

@@ -7,12 +7,12 @@
   dtoolutil:c dtoolbase:c dconfig:c dtoolconfig:m dtool:m pystub
 #define UNIX_SYS_LIBS m
 
-#begin test_bin_target
-  #define TARGET egg-optchar
+#begin bin_target
+  #define TARGET egg-optchar-new
 
   #define SOURCES \
     config_egg_optchar.cxx config_egg_optchar.h \
     eggOptchar.cxx eggOptchar.h \
     eggOptcharUserData.I eggOptcharUserData.cxx eggOptcharUserData.h
 
-#end test_bin_target
+#end bin_target

+ 338 - 16
pandatool/src/egg-optchar/eggOptchar.cxx

@@ -17,16 +17,16 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "eggOptchar.h"
-
 #include "eggOptcharUserData.h"
+
 #include "dcast.h"
 #include "eggJointData.h"
 #include "eggSliderData.h"
 #include "eggCharacterCollection.h"
 #include "eggCharacterData.h"
-#include "eggJointPointer.h"
-#include "eggTable.h"
-#include "compose_matrix.h"
+#include "eggBackPointer.h"
+#include "string_utils.h"
+#include "pset.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: EggOptchar::Constructor
@@ -49,6 +49,38 @@ EggOptchar() {
     ("ls", "", 0,
      "List the joint hierarchy instead of performing any operations.",
      &EggOptchar::dispatch_none, &_list_hierarchy);
+
+  add_option
+    ("lp", "", 0,
+     "List the existing joint hierarchy as a series of -p joint,parent "
+     "commands, suitable for pasting into an egg-optchar command line.",
+     &EggOptchar::dispatch_none, &_list_hierarchy_p);
+
+  add_option
+    ("keep", "joint", 0,
+     "Keep the named joint (or slider) in the character, even if it does "
+     "not appear to be needed by the animation.",
+     &EggOptchar::dispatch_vector_string, NULL, &_keep_components);
+
+  add_option
+    ("expose", "joint", 0,
+     "Expose the named joint by flagging it with a DCS attribute, so "
+     "it can be found in the scene graph when the character is loaded, and "
+     "objects can be parented to it.  This implies -keep.",
+     &EggOptchar::dispatch_vector_string, NULL, &_expose_components);
+
+  add_option
+    ("keepall", "", 0,
+     "Keep all joints and sliders in the character.",
+     &EggOptchar::dispatch_none, &_keep_all);
+  
+  add_option
+    ("p", "joint,parent", 0,
+     "Moves the named joint under the named parent joint.  Use "
+     "\"-p joint,\" to reparent a joint to the root.  The joint transform "
+     "is recomputed appropriately under its new parent so that the animation "
+     "is not affected (the effect is similar to NodePath::wrt_reparent_to).",
+     &EggOptchar::dispatch_vector_string_pair, NULL, &_reparent_joints);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -58,13 +90,22 @@ EggOptchar() {
 ////////////////////////////////////////////////////////////////////
 void EggOptchar::
 run() {
-  int num_characters = _collection->get_num_characters();
+  // We have to apply the user-specified reparent requests first,
+  // before we even analyze the joints.  This is because reparenting
+  // the joints may change their properties.
+  if (apply_user_reparents()) {
+    cerr << "Reparenting hierarchy.\n";
+    // So we'll have to call do_reparent() twice.  It seems wasteful,
+    // but it really is necessary, and it's not that bad.
+    do_reparent();
+  }
 
+  int num_characters = _collection->get_num_characters();
   int ci;
+
+  // Now we can analyze the joints for their properties.
   for (ci = 0; ci < num_characters; ci++) {
     EggCharacterData *char_data = _collection->get_character(ci);
-
-    nout << "Processing " << char_data->get_name() << "\n";
     analyze_joints(char_data->get_root_joint());
     analyze_sliders(char_data);
   }
@@ -75,15 +116,27 @@ run() {
       nout << "Character: " << char_data->get_name() << "\n";
       list_joints(char_data->get_root_joint(), 0);
       list_scalars(char_data);
+      nout << char_data->get_num_joints() << " joints.\n";
     }
 
-  } else {
-    // Now, trigger the actual rebuilding of all the joint data.
+  } else if (_list_hierarchy_p) {
     for (ci = 0; ci < num_characters; ci++) {
       EggCharacterData *char_data = _collection->get_character(ci);
-      char_data->get_root_joint()->do_rebuild();
+      nout << "Character: " << char_data->get_name() << "\n";
+      list_joints_p(char_data->get_root_joint());
+      // A newline to cout is needed after the above call.
+      cout << "\n";
+      nout << char_data->get_num_joints() << " joints.\n";
     }
-    
+
+  } else {
+    // The meat of the program: determine which joints are to be
+    // removed, and then actually remove them.
+    determine_removed_components();
+    if (remove_joints()) {
+      do_reparent();
+    }
+
     write_eggs();
   }
 }
@@ -105,6 +158,236 @@ handle_args(ProgramBase::Args &args) {
   return EggCharacterFilter::handle_args(args);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: ProgramBase::dispatch_vector_string_pair
+//       Access: Protected, Static
+//  Description: Standard dispatch function for an option that takes
+//               a pair of string parameters.  The data pointer is to
+//               StringPairs vector; the pair will be pushed onto the
+//               end of the vector.
+////////////////////////////////////////////////////////////////////
+bool EggOptchar::
+dispatch_vector_string_pair(const string &opt, const string &arg, void *var) {
+  StringPairs *ip = (StringPairs *)var;
+
+  vector_string words;
+  tokenize(arg, words, ",");
+
+  if (words.size() == 2) {
+    StringPair sp;
+    sp._a = words[0];
+    sp._b = words[1];
+    ip->push_back(sp);
+
+  } else {
+    nout << "-" << opt
+         << " requires a pair of strings separated by a comma.\n";
+    return false;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::determine_removed_components
+//       Access: Private
+//  Description: Flag all joints and sliders that should be removed
+//               for optimization purposes.
+////////////////////////////////////////////////////////////////////
+void EggOptchar::
+determine_removed_components() {
+  typedef pset<string> Names;
+  Names keep_names;
+  Names expose_names;
+  Names names_used;
+
+  vector_string::const_iterator si;
+  for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
+    keep_names.insert(*si);
+  }
+  for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
+    keep_names.insert(*si);
+    expose_names.insert(*si);
+  }
+
+  // We always keep the root joint, which has no name.
+  keep_names.insert("");
+
+  int num_characters = _collection->get_num_characters();
+  for (int ci = 0; ci < num_characters; ci++) {
+    EggCharacterData *char_data = _collection->get_character(ci);
+    int num_components = char_data->get_num_components();
+    for (int i = 0; i < num_components; i++) {
+      EggComponentData *comp_data = char_data->get_component(i);
+      EggOptcharUserData *user_data = 
+        DCAST(EggOptcharUserData, comp_data->get_user_data());
+
+      const string &name = comp_data->get_name();
+      if (_keep_all || keep_names.find(name) != keep_names.end()) {
+        // Keep this component.
+        names_used.insert(name);
+
+        if (expose_names.find(name) != expose_names.end()) {
+          // In fact, expose it.
+          user_data->_flags |= EggOptcharUserData::F_expose;
+        }
+
+      } else {
+        // Remove this component if it's unanimated or empty.
+        if ((user_data->_flags & (EggOptcharUserData::F_static | EggOptcharUserData::F_empty)) != 0) {
+          user_data->_flags |= EggOptcharUserData::F_remove;
+        }
+      }
+    }
+  }
+
+  // Go back and tell the user about component names we didn't use,
+  // just to be helpful.
+  for (si = _keep_components.begin(); si != _keep_components.end(); ++si) {
+    const string &name = (*si);
+    if (names_used.find(name) == names_used.end()) {
+      nout << "No such joint: " << name << "\n";
+    }
+  }
+  for (si = _expose_components.begin(); si != _expose_components.end(); ++si) {
+    const string &name = (*si);
+    if (names_used.find(name) == names_used.end()) {
+      nout << "No such joint: " << name << "\n";
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::remove_joints
+//       Access: Private
+//  Description: Effects the actual removal of joints flagged for
+//               removal by reparenting the hierarchy appropriately.
+//               Returns true if anything is done, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggOptchar::
+remove_joints() {
+  bool did_anything = false;
+  int num_characters = _collection->get_num_characters();
+  for (int ci = 0; ci < num_characters; ci++) {
+    EggCharacterData *char_data = _collection->get_character(ci);
+    int num_joints = char_data->get_num_joints();
+    
+    int num_static = 0;
+    int num_empty = 0;
+    int num_identity = 0;
+    int num_kept = 0;
+    
+    for (int i = 0; i < num_joints; i++) {
+      EggJointData *joint_data = char_data->get_joint(i);
+      EggOptcharUserData *user_data = 
+        DCAST(EggOptcharUserData, joint_data->get_user_data());
+      
+      EggJointData *best_parent = find_best_parent(joint_data->get_parent());
+      
+      if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
+        // This joint will be removed, so reparent it to nothing.
+        joint_data->reparent_to((EggJointData *)NULL);
+        
+        // Move the vertices associated with this joint into its
+        // parent.
+        joint_data->move_vertices_to(best_parent);
+        
+        // Determine what kind of node it is we're removing, for the
+        // user's information.
+        if ((user_data->_flags & EggOptcharUserData::F_identity) != 0) {
+          num_identity++;
+        } else if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
+          num_static++;
+        } else if ((user_data->_flags & EggOptcharUserData::F_empty) != 0) {
+          num_empty++;
+        }
+        did_anything = true;
+
+      } else {
+        // This joint will be preserved, but maybe its parent will
+        // change.
+        joint_data->reparent_to(best_parent);
+        num_kept++;
+      }
+    }
+
+    if (num_joints == num_kept) {
+      nout << char_data->get_name() << ": keeping " << num_joints
+           << " joints.\n";
+    } else {
+      nout << char_data->get_name() << ": of " << num_joints 
+           << " joints, removing " << num_identity << " identity, "
+           << num_static << " static, and " << num_empty
+           << " empty joints, leaving " << num_kept << ".\n";
+    }
+  }
+
+  return did_anything;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::find_best_parent
+//       Access: Private
+//  Description: Searches for this first joint at this level or above
+//               that is not scheduled to be removed.  This is the
+//               joint that the first child of this joint should be
+//               reparented to.
+////////////////////////////////////////////////////////////////////
+EggJointData *EggOptchar::
+find_best_parent(EggJointData *joint_data) const {
+  EggOptcharUserData *user_data = 
+    DCAST(EggOptcharUserData, joint_data->get_user_data());
+
+  if ((user_data->_flags & EggOptcharUserData::F_remove) != 0) {
+    // Keep going.
+    nassertr(joint_data->get_parent() != (EggJointData *)NULL, NULL);
+    return find_best_parent(joint_data->get_parent());
+  }
+
+  // This is the one!
+  return joint_data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::apply_user_reparents
+//       Access: Private
+//  Description: Reparents all the joints that the user suggested on
+//               the command line.  Returns true if any operations
+//               were performed, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool EggOptchar::
+apply_user_reparents() {
+  bool did_anything = false;
+  int num_characters = _collection->get_num_characters();
+
+  StringPairs::const_iterator spi;
+  for (spi = _reparent_joints.begin(); spi != _reparent_joints.end(); ++spi) {
+    const StringPair &p = (*spi);
+
+    for (int ci = 0; ci < num_characters; ci++) {
+      EggCharacterData *char_data = _collection->get_character(ci);
+      EggJointData *node_a = char_data->find_joint(p._a);
+      EggJointData *node_b = char_data->get_root_joint();
+      if (!p._b.empty()) {
+        node_b = char_data->find_joint(p._b);
+      }
+
+      if (node_a == (EggJointData *)NULL) {
+        nout << "No joint named " << p._a << " in " << char_data->get_name()
+             << ".\n";
+      } else if (node_b == (EggJointData *)NULL) {
+        nout << "No joint named " << p._b << " in " << char_data->get_name()
+             << ".\n";
+      } else {
+        node_a->reparent_to(node_b);
+        did_anything = true;
+      }
+    }
+  }
+
+  return did_anything;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggOptchar::analyze_joints
 //       Access: Private
@@ -263,6 +546,29 @@ list_joints(EggJointData *joint_data, int indent_level) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::list_joints_p
+//       Access: Private
+//  Description: Outputs a list of the joint hierarchy as a series of
+//               -p joint,parent commands.
+////////////////////////////////////////////////////////////////////
+void EggOptchar::
+list_joints_p(EggJointData *joint_data) {
+  // As above, don't list the root joint.
+
+  int num_children = joint_data->get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    EggJointData *child_data = joint_data->get_child(i);
+    // We send output to cout instead of nout to avoid the
+    // word-wrapping, and also to allow the user to redirect this
+    // easily to a file.
+    cout << " -p " << child_data->get_name() << "," 
+         << joint_data->get_name();
+
+    list_joints_p(child_data);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggOptchar::list_scalars
 //       Access: Private
@@ -284,20 +590,36 @@ list_scalars(EggCharacterData *char_data) {
 ////////////////////////////////////////////////////////////////////
 void EggOptchar::
 describe_component(EggComponentData *comp_data, int indent_level) {
-  indent(nout, indent_level)
+  // We use cout instead of nout so the user can easily redirect this
+  // to a file.
+  indent(cout, indent_level)
     << comp_data->get_name();
   
   EggOptcharUserData *user_data = 
     DCAST(EggOptcharUserData, comp_data->get_user_data());
   if (user_data->is_identity()) {
-    nout << " (identity)";
+    cout << " (identity)";
   } else if (user_data->is_static()) {
-    nout << " (static)";
+    cout << " (static)";
   }
   if (user_data->is_empty()) {
-    nout << " (empty)";
+    cout << " (empty)";
+  }
+  cout << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggOptchar::do_reparent
+//       Access: Private
+//  Description: Performs all of the queued up reparenting operations.
+////////////////////////////////////////////////////////////////////
+void EggOptchar::
+do_reparent() {
+  int num_characters = _collection->get_num_characters();
+  for (int ci = 0; ci < num_characters; ci++) {
+    EggCharacterData *char_data = _collection->get_character(ci);
+    char_data->do_reparent();
   }
-  nout << "\n";
 }
 
 

+ 23 - 0
pandatool/src/egg-optchar/eggOptchar.h

@@ -25,6 +25,7 @@
 #include "luse.h"
 
 #include "pvector.h"
+#include "vector_string.h"
 
 class EggCharacterData;
 class EggComponentData;
@@ -49,13 +50,35 @@ protected:
   virtual bool handle_args(Args &args);
 
 private:
+  static bool dispatch_vector_string_pair(const string &opt, const string &arg, void *var);
+
+  void determine_removed_components();
+  bool remove_joints();
+  EggJointData *find_best_parent(EggJointData *joint_data) const;
+
+  bool apply_user_reparents();
   void analyze_joints(EggJointData *joint_data);
   void analyze_sliders(EggCharacterData *char_data);
   void list_joints(EggJointData *joint_data, int indent_level);
+  void list_joints_p(EggJointData *joint_data);
   void list_scalars(EggCharacterData *char_data);
   void describe_component(EggComponentData *comp_data, int indent_level);
+  void do_reparent();
 
   bool _list_hierarchy;
+  bool _list_hierarchy_p;
+  bool _keep_all;
+
+  class StringPair {
+  public:
+    string _a;
+    string _b;
+  };
+  typedef pvector<StringPair> StringPairs;
+  StringPairs _reparent_joints;
+
+  vector_string _keep_components;
+  vector_string _expose_components;
 };
 
 #endif

+ 2 - 1
pandatool/src/egg-optchar/eggOptcharUserData.h

@@ -43,7 +43,8 @@ public:
     F_static   = 0x0001,
     F_identity = 0x0002,
     F_empty    = 0x0004,
-    F_remove   = 0x0008
+    F_remove   = 0x0008,
+    F_expose   = 0x0010,
   };
   int _flags;
   LMatrix4d _static_mat;

+ 3 - 2
pandatool/src/eggcharbase/Sources.pp

@@ -12,7 +12,8 @@
      eggCharacterCollection.h eggCharacterCollection.I \
      eggCharacterData.h eggCharacterData.I eggCharacterFilter.h \
      eggComponentData.h eggComponentData.I eggJointData.h \
-     eggJointData.I eggJointPointer.h eggJointNodePointer.h \
+     eggJointData.I eggJointPointer.h eggJointPointer.I \
+     eggJointNodePointer.h \
      eggMatrixTablePointer.h eggScalarTablePointer.h \
      eggSliderData.h eggSliderData.I \
      eggVertexPointer.h
@@ -32,7 +33,7 @@
     eggCharacterData.I eggCharacterData.h eggCharacterFilter.h \
     eggComponentData.I eggComponentData.h \
     eggJointData.h eggJointData.I \
-    eggJointPointer.h \
+    eggJointPointer.h eggJointPointer.I \
     eggJointNodePointer.h \
     eggMatrixTablePointer.h \
     eggScalarTablePointer.h \

+ 10 - 0
pandatool/src/eggcharbase/eggCharacterCollection.I

@@ -112,3 +112,13 @@ get_character_by_model_index(int model_index) const {
            (EggCharacterData *)NULL);
   return _characters_by_model_index[model_index];
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterCollection::ModelDescription::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE EggCharacterCollection::ModelDescription::
+ModelDescription() {
+  _root_node = (EggObject *)NULL;
+}

+ 16 - 5
pandatool/src/eggcharbase/eggCharacterCollection.cxx

@@ -98,7 +98,7 @@ add_egg(EggData *egg) {
     TopEggNodes::iterator ti;
     for (ti = top_nodes.begin(); ti != top_nodes.end(); ++ti) {
       EggNode *model_root = (*ti).first;
-      EggNodeList &egg_nodes = (*ti).second;
+      ModelDescription &desc = (*ti).second;
 
       int model_index = _next_model_index++;
       if (egg_info._models.empty()) {
@@ -109,8 +109,9 @@ add_egg(EggData *egg) {
       char_data->add_model(model_index, model_root);
       nassertr(model_index == (int)_characters_by_model_index.size(), -1);
       _characters_by_model_index.push_back(char_data);
+      root_joint->add_back_pointer(model_index, desc._root_node);
 
-      match_egg_nodes(char_data, root_joint, egg_nodes,
+      match_egg_nodes(char_data, root_joint, desc._top_nodes,
                       egg_index, model_index);
 
       scan_for_morphs(model_root, model_index, char_data);
@@ -268,12 +269,16 @@ scan_for_top_joints(EggNode *egg_node, EggNode *model_root,
     EggGroup *group = DCAST(EggGroup, egg_node);
 
     if (group->has_lod()) {
-      // This flag has an LOD specification.
+      // This group has an LOD specification; that indicates multiple
+      // skeleton hierarchies for this character, one for each LOD.
+      // We call each of these a separate model.
       model_root = group;
     }
     if (group->get_group_type() == EggGroup::GT_joint) {
       // A <Joint> node begins a model hierarchy.
-      _top_egg_nodes[character_name][model_root].push_back(group);
+      ModelDescription &desc = _top_egg_nodes[character_name][model_root];
+      desc._root_node = model_root;
+      desc._top_nodes.push_back(group);
       return;
     }
   }
@@ -307,12 +312,14 @@ scan_for_top_tables(EggTable *bundle, EggNode *model_root,
       if (table->get_name() == "<skeleton>") {
         // Here it is!  Now the immediate children of this node are
         // the top tables.
+        ModelDescription &desc = _top_egg_nodes[character_name][model_root];
+        desc._root_node = table;
 
         EggGroupNode::iterator cgi;
         for (cgi = table->begin(); cgi != table->end(); ++cgi) {
           EggNode *grandchild = (*cgi);
           if (grandchild->is_of_type(EggTable::get_class_type())) {
-            _top_egg_nodes[character_name][model_root].push_back(grandchild);
+            desc._top_nodes.push_back(grandchild);
           }
         }
       }
@@ -459,8 +466,10 @@ match_egg_nodes(EggCharacterData *char_data, EggJointData *joint_data,
       EggNode *egg_node = (*ei);
       EggJointData *data = make_joint_data(char_data);
       joint_data->_children.push_back(data);
+      char_data->_joints.push_back(data);
       char_data->_components.push_back(data);
       data->_parent = joint_data;
+      data->_new_parent = joint_data;
       found_egg_match(char_data, data, egg_node, egg_index, model_index);
     }
 
@@ -565,8 +574,10 @@ match_egg_nodes(EggCharacterData *char_data, EggJointData *joint_data,
           EggNode *egg_node = (*ei);
           EggJointData *data = make_joint_data(char_data);
           joint_data->_children.push_back(data);
+          char_data->_joints.push_back(data);
           char_data->_components.push_back(data);
           data->_parent = joint_data;
+          data->_new_parent = joint_data;
           found_egg_match(char_data, data, egg_node, egg_index, model_index);
         }
       }

+ 13 - 6
pandatool/src/eggcharbase/eggCharacterCollection.h

@@ -19,13 +19,13 @@
 #ifndef EGGCHARACTERCOLLECTION_H
 #define EGGCHARACTERCOLLECTION_H
 
-#include <pandatoolbase.h>
+#include "pandatoolbase.h"
 
 #include "eggCharacterData.h"
 
-#include <eggData.h>
-#include <eggNode.h>
-#include <pointerTo.h>
+#include "eggData.h"
+#include "eggNode.h"
+#include "pointerTo.h"
 
 class EggTable;
 class EggAttributes;
@@ -66,7 +66,7 @@ public:
   class EggInfo {
   public:
     PT(EggData) _egg;
-    typedef pvector<PT(EggNode)> Models;
+    typedef pvector< PT(EggNode) > Models;
     Models _models;
     int _first_model_index;
   };
@@ -95,7 +95,14 @@ private:
   // The _top_egg_nodes member is only used temporarily, when adding
   // each pre-existing egg file to the structure for the first time.
   typedef pvector<EggNode *> EggNodeList;
-  typedef pmap<EggNode *, EggNodeList> TopEggNodes;
+  class ModelDescription {
+  public:
+    INLINE ModelDescription();
+    EggNodeList _top_nodes;
+    EggObject *_root_node;
+  };
+
+  typedef pmap<EggNode *, ModelDescription> TopEggNodes;
   typedef pmap<string, TopEggNodes> TopEggNodesByName;
   TopEggNodesByName _top_egg_nodes;
 

+ 24 - 0
pandatool/src/eggcharbase/eggCharacterData.I

@@ -94,6 +94,30 @@ find_joint(const string &name) const {
   return _root_joint->find_joint(name);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterData::get_num_joints
+//       Access: Public
+//  Description: Returns the total number of joints in the character
+//               joint hierarchy.
+////////////////////////////////////////////////////////////////////
+INLINE int EggCharacterData::
+get_num_joints() const {
+  return _joints.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterData::get_joint
+//       Access: Public
+//  Description: Returns the nth joint in the character joint
+//               hierarchy.  This returns all of the joints in the
+//               hierarchy in an arbitrary ordering.
+////////////////////////////////////////////////////////////////////
+INLINE EggJointData *EggCharacterData::
+get_joint(int n) const {
+  nassertr(n >= 0 && n < (int)_joints.size(), NULL);
+  return _joints[n];
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCharacterData::get_num_sliders
 //       Access: Public

+ 111 - 0
pandatool/src/eggcharbase/eggCharacterData.cxx

@@ -72,6 +72,117 @@ add_model(int model_index, EggNode *model_root) {
   _models.push_back(m);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterData::get_num_frames
+//       Access: Public
+//  Description: Returns the number of frames of animation of the
+//               indicated model.  This is more reliable than asking a
+//               particular joint or slider of the animation for its
+//               number of frames, since a particular joint may have
+//               only 1 frame (if it is unanimated), even though the
+//               overall animation has many frames.
+////////////////////////////////////////////////////////////////////
+int EggCharacterData::
+get_num_frames(int model_index) const {
+  int max_num_frames = 0;
+  Components::const_iterator ci;
+  for (ci = _components.begin(); ci != _components.end(); ++ci) {
+    EggComponentData *component = (*ci);
+    int num_frames = component->get_num_frames(model_index);
+    if (num_frames > 1) {
+      // We have a winner.  Assume all other components will be
+      // similar.
+      return num_frames;
+    }
+    max_num_frames = max(max_num_frames, num_frames);
+  }
+
+  // Every component had either 1 frame or 0 frames.  Return the
+  // maximum of these.
+  return max_num_frames;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterData::do_reparent
+//       Access: Public
+//  Description: Begins the process of restructuring the joint
+//               hierarchy according to the previous calls to
+//               reparent_to() on various joints.  This will reparent
+//               the joint hierachy in all models as requested, while
+//               adjusting the transforms as appropriate so that each
+//               joint retains the same net transform across all
+//               frames that it had before the operation.  Returns
+//               true on success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool EggCharacterData::
+do_reparent() {
+  typedef pset<EggJointData *> InvalidSet;
+  InvalidSet invalid_set;
+
+  // First, make sure the list of new_children is accurate.
+  Joints::const_iterator ji;
+  for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
+    EggJointData *joint_data = (*ji);
+    joint_data->do_begin_reparent();
+  }
+  // We also need to clear the children on the root joint, but the
+  // root joint doesn't get any of the other operations (including
+  // finish_reparent) applied to it.
+  _root_joint->do_begin_reparent();
+
+  // Now compute the new transforms for the joints' new positions.
+  // This is done recursively through the new parent hierarchy, so we
+  // can take advantage of caching the net value for a particular
+  // frame.
+  Models::const_iterator mi;
+  for (mi = _models.begin(); mi != _models.end(); ++mi) {
+    int model_index = (*mi)._model_index;
+    int num_frames = get_num_frames(model_index);
+    for (int f = 0; f < num_frames; f++) {
+      // First, walk through all the joints and flush the computed net
+      // transforms from before.
+      for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
+        EggJointData *joint_data = (*ji);
+        joint_data->do_begin_compute_reparent();
+      }
+      _root_joint->do_begin_compute_reparent();
+
+      // Now go back through and compute the reparented transforms,
+      // caching net transforms as necessary.
+      for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
+        EggJointData *joint_data = (*ji);
+        if (!joint_data->do_compute_reparent(model_index, f)) {
+          // Oops, we got an invalid transform.
+          invalid_set.insert(joint_data);
+        }
+      }
+    }
+  }
+
+  // Now remove all of the old children and add in the new children.
+  for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
+    EggJointData *joint_data = (*ji);
+    if (!joint_data->do_finish_reparent()) {
+      invalid_set.insert(joint_data);
+    }
+  }
+
+  InvalidSet::const_iterator si;
+  for (si = invalid_set.begin(); si != invalid_set.end(); ++si) {
+    EggJointData *joint_data = (*si);
+    // Don't bother reporting joints that no longer have a parent,
+    // since we don't care about joints that are now outside the
+    // hierarchy.
+    if (joint_data->get_parent() != (EggJointData *)NULL) {
+      nout << "Warning: reparenting " << joint_data->get_name()
+           << " to " << joint_data->get_parent()->get_name()
+           << " results in a skew transform.\n";
+    }
+  }
+
+  return invalid_set.empty();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCharacterData::find_slider
 //       Access: Public

+ 7 - 0
pandatool/src/eggcharbase/eggCharacterData.h

@@ -68,9 +68,13 @@ public:
   INLINE int get_num_models() const;
   INLINE int get_model_index(int n) const;
   INLINE EggNode *get_model_root(int n) const;
+  int get_num_frames(int model_index) const;
 
   INLINE EggJointData *get_root_joint() const;
   INLINE EggJointData *find_joint(const string &name) const;
+  INLINE int get_num_joints() const;
+  INLINE EggJointData *get_joint(int n) const;
+  bool do_reparent();
 
   INLINE int get_num_sliders() const;
   INLINE EggSliderData *get_slider(int n) const;
@@ -100,6 +104,9 @@ protected:
   typedef pvector<EggSliderData *> Sliders;
   Sliders _sliders;
 
+  typedef pvector<EggJointData *> Joints;
+  Joints _joints;
+
   typedef pvector<EggComponentData *> Components;
   Components _components;
 

+ 2 - 0
pandatool/src/eggcharbase/eggComponentData.h

@@ -45,6 +45,8 @@ public:
   void add_name(const string &name);
   bool matches_name(const string &name) const;
 
+  virtual int get_num_frames(int model_index) const=0;
+
   virtual void add_back_pointer(int model_index, EggObject *egg_object)=0;
   virtual void write(ostream &out, int indent_level = 0) const=0;
 

+ 14 - 0
pandatool/src/eggcharbase/eggJointData.I

@@ -47,3 +47,17 @@ get_child(int n) const {
   nassertr(n >= 0 && n < (int)_children.size(), (EggJointData *)NULL);
   return _children[n];
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::reparent_to
+//       Access: Public
+//  Description: Indicates an intention to change the parent of this
+//               joint to the indicated joint, or NULL to remove it
+//               from the hierarchy.  The joint is not reparented
+//               immediately, but rather all of the joints are
+//               reparented at once when do_reparent() is called.
+////////////////////////////////////////////////////////////////////
+INLINE void EggJointData::
+reparent_to(EggJointData *new_parent) {
+  _new_parent = new_parent;
+}

+ 216 - 1
pandatool/src/eggcharbase/eggJointData.cxx

@@ -39,6 +39,7 @@ EggJointData(EggCharacterCollection *collection,
   EggComponentData(collection, char_data)
 {
   _parent = (EggJointData *)NULL;
+  _new_parent = (EggJointData *)NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -66,7 +67,7 @@ find_joint(const string &name) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: EggJointData::get_num_frames
-//       Access: Public
+//       Access: Public, Virtual
 //  Description: Returns the number of frames of animation for this
 //               particular joint in the indicated model.
 ////////////////////////////////////////////////////////////////////
@@ -119,6 +120,26 @@ get_net_frame(int model_index, int n) const {
   return mat;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::move_vertices_to
+//       Access: Public
+//  Description: Moves the vertices assigned to this joint into the
+//               indicated joint, without changing their weight
+//               assignments.
+////////////////////////////////////////////////////////////////////
+void EggJointData::
+move_vertices_to(EggJointData *new_owner) {
+  int num_models = get_num_models();
+  for (int model_index = 0; model_index < num_models; model_index++) {
+    if (has_model(model_index) && new_owner->has_model(model_index)) {
+      EggJointPointer *joint, *new_joint;
+      DCAST_INTO_V(joint, get_model(model_index));
+      DCAST_INTO_V(new_joint, new_owner->get_model(model_index));
+      joint->move_vertices_to(new_joint);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggJointData::do_rebuild
 //       Access: Public
@@ -186,6 +207,7 @@ optimize() {
 ////////////////////////////////////////////////////////////////////
 void EggJointData::
 add_back_pointer(int model_index, EggObject *egg_object) {
+  nassertv(egg_object != (EggObject *)NULL);
   if (egg_object->is_of_type(EggGroup::get_class_type())) {
     // It must be a <Joint>.
     EggJointNodePointer *joint = new EggJointNodePointer(egg_object);
@@ -226,3 +248,196 @@ write(ostream &out, int indent_level) const {
 
   indent(out, indent_level) << "}\n";
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::do_begin_reparent
+//       Access: Protected
+//  Description: Clears out the _children vector in preparation for
+//               refilling it from the _new_parent information.
+////////////////////////////////////////////////////////////////////
+void EggJointData::
+do_begin_reparent() {
+  _children.clear();
+
+  int num_models = get_num_models();
+  for (int model_index = 0; model_index < num_models; model_index++) {
+    if (has_model(model_index)) {
+      EggJointPointer *joint;
+      DCAST_INTO_V(joint, get_model(model_index));
+      joint->begin_rebuild();
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::do_begin_compute_reparent
+//       Access: Protected
+//  Description: Eliminates any cached values before beginning a walk
+//               through all the joints for do_compute_reparent(), for
+//               a given model/frame.
+////////////////////////////////////////////////////////////////////
+void EggJointData::
+do_begin_compute_reparent() { 
+  _got_new_net_frame = false;
+  _got_new_net_frame_inv = false;
+  _computed_reparent = false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::do_compute_reparent
+//       Access: Protected
+//  Description: Prepares the reparent operation by computing a new
+//               transform for each frame of each model, designed to
+//               keep the net transform the same when the joint is
+//               moved to its new parent.  Returns true on success,
+//               false on failure.
+////////////////////////////////////////////////////////////////////
+bool EggJointData::
+do_compute_reparent(int model_index, int n) {
+  if (_computed_reparent) {
+    // We've already done this joint.  This is possible because we
+    // have to recursively compute joints upwards, so we might visit
+    // the same joint more than once.
+    return _computed_ok;
+  }
+  _computed_reparent = true;
+
+  if (_parent == _new_parent) {
+    // Trivial (and most common) case: we are not moving the joint.
+    // No recomputation necessary.
+    _computed_ok = true;
+    return true;
+  }
+
+  EggBackPointer *back = get_model(model_index);
+  if (back == (EggBackPointer *)NULL) {
+    // This joint doesn't have any data to modify.
+    _computed_ok = true;
+    return true;
+  }
+
+  EggJointPointer *joint;
+  DCAST_INTO_R(joint, back, false);
+
+  LMatrix4d transform;
+  if (_parent == (EggJointData *)NULL) {
+    // We are moving from outside the joint hierarchy to within it.
+    transform = _new_parent->get_new_net_frame_inv(model_index, n);
+
+  } else if (_new_parent == (EggJointData *)NULL) {
+    // We are moving from within the hierarchy to outside it.
+    transform = _parent->get_new_net_frame(model_index, n);
+
+  } else {
+    // We are changing parents within the hierarchy.
+    transform = 
+      _parent->get_new_net_frame(model_index, n) *
+      _new_parent->get_new_net_frame_inv(model_index, n);
+  }
+
+  nassertr(n == joint->get_num_rebuild_frames(), false);
+
+  _computed_ok = joint->add_rebuild_frame(joint->get_frame(n) * transform);
+  return _computed_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::do_finish_reparent
+//       Access: Protected
+//  Description: Performs the actual reparenting operation
+//               by removing all of the old children and replacing
+//               them with the set of new children.  Returns true on
+//               success, false on failure.
+////////////////////////////////////////////////////////////////////
+bool EggJointData::
+do_finish_reparent() {
+  bool all_ok = true;
+
+  int num_models = get_num_models();
+  for (int model_index = 0; model_index < num_models; model_index++) {
+    EggJointPointer *parent_joint = NULL;
+    if (_new_parent != NULL && _new_parent->has_model(model_index)) {
+      DCAST_INTO_R(parent_joint, _new_parent->get_model(model_index), false);
+    }
+
+    if (has_model(model_index)) {
+      EggJointPointer *joint;
+      DCAST_INTO_R(joint, get_model(model_index), false);
+      joint->do_finish_reparent(parent_joint);
+      if (!joint->do_rebuild()) {
+        all_ok = false;
+      }
+    }
+  }
+
+  _parent = _new_parent;
+  if (_parent != (EggJointData *)NULL) {
+    _parent->_children.push_back(this);
+  }
+
+  return all_ok;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::get_new_net_frame
+//       Access: Private
+//  Description: Similar to get_net_frame(), but computed for the
+//               prospective new parentage of the node, before
+//               do_finish_reparent() is called.  This is generally
+//               useful only when called within do_compute_reparent().
+////////////////////////////////////////////////////////////////////
+const LMatrix4d &EggJointData::
+get_new_net_frame(int model_index, int n) {
+  if (!_got_new_net_frame) {
+    _new_net_frame = get_new_frame(model_index, n);
+    if (_new_parent != (EggJointData *)NULL) {
+      _new_net_frame = _new_net_frame * _new_parent->get_new_net_frame(model_index, n);
+    }
+    _got_new_net_frame = true;
+  }
+  return _new_net_frame;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::get_new_net_frame_inv
+//       Access: Private
+//  Description: Returns the inverse of get_new_net_frame().
+////////////////////////////////////////////////////////////////////
+const LMatrix4d &EggJointData::
+get_new_net_frame_inv(int model_index, int n) {
+  if (!_got_new_net_frame_inv) {
+    _new_net_frame_inv.invert_from(get_new_frame(model_index, n));
+    if (_new_parent != (EggJointData *)NULL) {
+      _new_net_frame_inv = _new_parent->get_new_net_frame_inv(model_index, n) * _new_net_frame_inv;
+    }
+    _got_new_net_frame_inv = true;
+  }
+  return _new_net_frame_inv;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::get_new_frame
+//       Access: Private
+//  Description: Returns the local transform matrix corresponding to
+//               this joint position in the nth frame in the indicated
+//               model, as it will be when do_finish_reparent() is
+//               called.
+////////////////////////////////////////////////////////////////////
+LMatrix4d EggJointData::
+get_new_frame(int model_index, int n) {
+  do_compute_reparent(model_index, n);
+
+  EggBackPointer *back = get_model(model_index);
+  if (back == (EggBackPointer *)NULL) {
+    return LMatrix4d::ident_mat();
+  }
+
+  EggJointPointer *joint;
+  DCAST_INTO_R(joint, back, LMatrix4d::ident_mat());
+
+  if (joint->get_num_rebuild_frames() > 0) {
+    return joint->get_rebuild_frame(n);
+  } else {
+    return joint->get_frame(n);
+  }
+}

+ 26 - 3
pandatool/src/eggcharbase/eggJointData.h

@@ -43,22 +43,42 @@ public:
   INLINE EggJointData *get_child(int n) const;
   EggJointData *find_joint(const string &name);
 
-  int get_num_frames(int model_index) const;
+  virtual int get_num_frames(int model_index) const;
   LMatrix4d get_frame(int model_index, int n) const;
   LMatrix4d get_net_frame(int model_index, int n) const;
 
+  INLINE void reparent_to(EggJointData *new_parent);
+  void move_vertices_to(EggJointData *new_owner);
+
   bool do_rebuild();
   void optimize();
 
   virtual void add_back_pointer(int model_index, EggObject *egg_object);
   virtual void write(ostream &out, int indent_level = 0) const;
 
+protected:
+  void do_begin_reparent();
+  void do_begin_compute_reparent();
+  bool do_compute_reparent(int model_index, int n);
+  bool do_finish_reparent();
+
+private:
+  const LMatrix4d &get_new_net_frame(int model_index, int n);
+  const LMatrix4d &get_new_net_frame_inv(int model_index, int n);
+  LMatrix4d get_new_frame(int model_index, int n);
+
+  // These are used to cache the above results for optimizing
+  // do_compute_reparent().
+  LMatrix4d _new_net_frame, _new_net_frame_inv;
+  bool _got_new_net_frame, _got_new_net_frame_inv;
+  bool _computed_reparent;
+  bool _computed_ok;
+
 protected:
   typedef pvector<EggJointData *> Children;
   Children _children;
   EggJointData *_parent;
-
-  friend class EggCharacterCollection;
+  EggJointData *_new_parent;
 
 
 public:
@@ -77,6 +97,9 @@ public:
 
 private:
   static TypeHandle _type_handle;
+
+  friend class EggCharacterCollection;
+  friend class EggCharacterData;
 };
 
 #include "eggJointData.I"

+ 41 - 1
pandatool/src/eggcharbase/eggJointNodePointer.cxx

@@ -35,7 +35,7 @@ EggJointNodePointer::
 EggJointNodePointer(EggObject *object) {
   _joint = DCAST(EggGroup, object);
 
-  if (_joint != (EggGroup *)NULL) {
+  if (_joint != (EggGroup *)NULL && _joint->is_joint()) {
     // Quietly insist that the joint has a transform, for neatness.  If
     // it does not, give it the identity transform.
     if (!_joint->has_transform()) {
@@ -94,6 +94,46 @@ set_frame(int n, const LMatrix4d &mat) {
   _joint->set_transform(mat);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointNodePointer::do_finish_reparent
+//       Access: Protected
+//  Description: Performs the actual reparenting operation
+//               by removing the node from its old parent and
+//               associating it with its new parent, if any.
+////////////////////////////////////////////////////////////////////
+void EggJointNodePointer::
+do_finish_reparent(EggJointPointer *new_parent) {
+  if (new_parent == (EggJointPointer *)NULL) {
+    // No new parent; unparent the joint.
+    EggGroupNode *egg_parent = _joint->get_parent();
+    if (egg_parent != (EggGroupNode *)NULL) {
+      egg_parent->remove_child(_joint.p());
+    }
+
+  } else {
+    // Reparent the joint to its new parent (implicitly unparenting it
+    // from its previous parent).
+    EggJointNodePointer *new_node = DCAST(EggJointNodePointer, new_parent);
+    if (new_node->_joint != _joint->get_parent()) {
+      new_node->_joint->add_child(_joint.p());
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointNodePointer::move_vertices_to
+//       Access: Public, Virtual
+//  Description: Moves the vertices assigned to this joint into the
+//               other joint (which should be of the same type).
+////////////////////////////////////////////////////////////////////
+void EggJointNodePointer::
+move_vertices_to(EggJointPointer *new_joint) {
+  EggJointNodePointer *new_node;
+  DCAST_INTO_V(new_node, new_joint);
+
+  new_node->_joint->steal_vrefs(_joint);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggJointNodePointer::add_rebuild_frame
 //       Access: Public, Virtual

+ 3 - 0
pandatool/src/eggcharbase/eggJointNodePointer.h

@@ -38,6 +38,9 @@ public:
   virtual LMatrix4d get_frame(int n) const;
   virtual void set_frame(int n, const LMatrix4d &mat);
 
+  virtual void do_finish_reparent(EggJointPointer *new_parent);
+  virtual void move_vertices_to(EggJointPointer *new_joint);
+
   virtual bool add_rebuild_frame(const LMatrix4d &mat);
   virtual bool do_rebuild();
 

+ 41 - 0
pandatool/src/eggcharbase/eggJointPointer.I

@@ -0,0 +1,41 @@
+// Filename: eggJointPointer.I
+// Created by:  drose (20Jul03)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_num_rebuild_frames
+//       Access: Public
+//  Description: Returns the number of rebuild frames that have been
+//               added so far.
+////////////////////////////////////////////////////////////////////
+INLINE int EggJointPointer::
+get_num_rebuild_frames() const {
+  return _rebuild_frames.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_rebuild_frame
+//       Access: Public
+//  Description: Returns the nth matrix that has been added to the set
+//               of rebuild frames.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4d &EggJointPointer::
+get_rebuild_frame(int n) const {
+  nassertr(n >= 0 && n < (int)_rebuild_frames.size(), LMatrix4d::ident_mat());
+  return _rebuild_frames[n];
+}

+ 10 - 0
pandatool/src/eggcharbase/eggJointPointer.cxx

@@ -34,6 +34,16 @@ add_frame(const LMatrix4d &) {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::move_vertices_to
+//       Access: Public, Virtual
+//  Description: Moves the vertices assigned to this joint into the
+//               other joint (which should be of the same type).
+////////////////////////////////////////////////////////////////////
+void EggJointPointer::
+move_vertices_to(EggJointPointer *) {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggJointPointer::begin_rebuild
 //       Access: Public

+ 7 - 0
pandatool/src/eggcharbase/eggJointPointer.h

@@ -41,8 +41,13 @@ public:
   virtual void set_frame(int n, const LMatrix4d &mat)=0;
   virtual bool add_frame(const LMatrix4d &mat);
 
+  virtual void do_finish_reparent(EggJointPointer *new_parent)=0;
+  virtual void move_vertices_to(EggJointPointer *new_joint);
+
   void begin_rebuild();
   virtual bool add_rebuild_frame(const LMatrix4d &mat);
+  INLINE int get_num_rebuild_frames() const;
+  INLINE const LMatrix4d &get_rebuild_frame(int n) const;
   virtual bool do_rebuild();
 
   virtual void optimize();
@@ -69,6 +74,8 @@ private:
   static TypeHandle _type_handle;
 };
 
+#include "eggJointPointer.I"
+
 #endif
 
 

+ 30 - 0
pandatool/src/eggcharbase/eggMatrixTablePointer.cxx

@@ -86,6 +86,10 @@ get_frame(int n) const {
     // If we have exactly one frame, then we have as many frames as we
     // want; just repeat the first frame.
     n = 0;
+
+  } else if (get_num_frames() == 0) {
+    // If we have no frames, we really have the identity matrix.
+    return LMatrix4d::ident_mat();
   }
 
   nassertr(n >= 0 && n < get_num_frames(), LMatrix4d::ident_mat());
@@ -122,6 +126,32 @@ add_frame(const LMatrix4d &mat) {
   return _xform->add_data(mat);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggMatrixTablePointer::do_finish_reparent
+//       Access: Protected
+//  Description: Performs the actual reparenting operation
+//               by removing the node from its old parent and
+//               associating it with its new parent, if any.
+////////////////////////////////////////////////////////////////////
+void EggMatrixTablePointer::
+do_finish_reparent(EggJointPointer *new_parent) {
+  if (new_parent == (EggJointPointer *)NULL) {
+    // No new parent; unparent the joint.
+    EggGroupNode *egg_parent = _table->get_parent();
+    if (egg_parent != (EggGroupNode *)NULL) {
+      egg_parent->remove_child(_table.p());
+    }
+
+  } else {
+    // Reparent the joint to its new parent (implicitly unparenting it
+    // from its previous parent).
+    EggMatrixTablePointer *new_node = DCAST(EggMatrixTablePointer, new_parent);
+    if (new_node->_table != _table->get_parent()) {
+      new_node->_table->add_child(_table.p());
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggMatrixTablePointer::do_rebuild
 //       Access: Public, Virtual

+ 2 - 0
pandatool/src/eggcharbase/eggMatrixTablePointer.h

@@ -43,6 +43,8 @@ public:
   virtual void set_frame(int n, const LMatrix4d &mat);
   virtual bool add_frame(const LMatrix4d &mat);
 
+  virtual void do_finish_reparent(EggJointPointer *new_parent);
+
   virtual bool do_rebuild();
 
   virtual void optimize();

+ 1 - 1
pandatool/src/eggcharbase/eggSliderData.cxx

@@ -42,7 +42,7 @@ EggSliderData(EggCharacterCollection *collection,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: EggSliderData::get_num_frames
-//       Access: Public
+//       Access: Public, Virtual
 //  Description: Returns the number of frames of animation for this
 //               particular slider in the indicated model.
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
pandatool/src/eggcharbase/eggSliderData.h

@@ -37,7 +37,7 @@ public:
   EggSliderData(EggCharacterCollection *collection,
                 EggCharacterData *char_data);
 
-  int get_num_frames(int model_index) const;
+  virtual int get_num_frames(int model_index) const;
   double get_frame(int model_index, int n) const;
 
   virtual void add_back_pointer(int model_index, EggObject *egg_object);