Browse Source

add -optimal to egg-optchar

David Rose 22 years ago
parent
commit
0c2a4f1550

+ 66 - 11
pandatool/src/egg-optchar/eggOptchar.cxx

@@ -32,6 +32,7 @@
 #include "dcast.h"
 #include "pset.h"
 #include "compose_matrix.h"
+#include "fftCompressor.h"
 
 #include <algorithm>
 
@@ -119,6 +120,21 @@ EggOptchar() {
      "is not affected (the effect is similar to NodePath::wrt_reparent_to).",
      &EggOptchar::dispatch_vector_string_pair, NULL, &_reparent_joints);
 
+  if (FFTCompressor::is_compression_available()) {
+    add_option
+      ("optimal", "", 0,
+       "Computes the optimal joint hierarchy for the character by analyzing "
+       "all of the joint animation and reparenting joints to minimize "
+       "transformations.  This can repair skeletons that have been flattened "
+       "or whose hierarchy was otherwise damaged in conversion; it can also "
+       "detect joints that are constrained to follow other joints and should "
+       "therefore be parented to the master joints.  The result is a file "
+       "from which more joints may be successfully removed, that generally "
+       "compresses better and with fewer artifacts.  However, this is a "
+       "fairly expensive operation.",
+       &EggOptchar::dispatch_none, &_optimal_hierarchy);
+  }
+
   add_option
     ("q", "quantum", 0,
      "Quantize joint membership values to the given unit.  This is "
@@ -127,6 +143,7 @@ EggOptchar() {
      "values.",
      &EggOptchar::dispatch_double, NULL, &_vref_quantum);
 
+  _optimal_hierarchy = false;
   _vref_quantum = 0.01;
 }
 
@@ -174,7 +191,8 @@ run() {
     for (ci = 0; ci < num_characters; ci++) {
       EggCharacterData *char_data = _collection->get_character(ci);
       nout << "Character: " << char_data->get_name() << "\n";
-      list_joints_p(char_data->get_root_joint());
+      int col = 0;
+      list_joints_p(char_data->get_root_joint(), col);
       // A newline to cout is needed after the above call.
       cout << "\n";
       nout << char_data->get_num_joints() << " joints.\n";
@@ -390,10 +408,14 @@ determine_removed_components() {
   for (int ci = 0; ci < num_characters; ci++) {
     EggCharacterData *char_data = _collection->get_character(ci);
     int num_components = char_data->get_num_components();
+    cerr << char_data->get_name() << " has " << num_components << " components.\n";
     for (int i = 0; i < num_components; i++) {
       EggComponentData *comp_data = char_data->get_component(i);
+      nassertv(comp_data != (EggComponentData *)NULL);
+
       EggOptcharUserData *user_data = 
         DCAST(EggOptcharUserData, comp_data->get_user_data());
+      nassertv(user_data != (EggOptcharUserData *)NULL);
 
       const string &name = comp_data->get_name();
       if (_keep_all || keep_names.find(name) != keep_names.end()) {
@@ -469,9 +491,11 @@ move_vertices() {
         joint_data->move_vertices_to(best_joint);
 
         // Now we can't remove the joint.
-        EggOptcharUserData *best_user_data = 
-          DCAST(EggOptcharUserData, best_joint->get_user_data());
-        best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove);
+        if (best_joint != (EggJointData *)NULL) {
+          EggOptcharUserData *best_user_data = 
+            DCAST(EggOptcharUserData, best_joint->get_user_data());
+          best_user_data->_flags &= ~(EggOptcharUserData::F_empty | EggOptcharUserData::F_remove);
+        }
       }
     }
   }
@@ -488,6 +512,7 @@ move_vertices() {
 ////////////////////////////////////////////////////////////////////
 bool EggOptchar::
 process_joints() {
+  cerr << "process_joints\n";
   bool removed_any = false;
   int num_characters = _collection->get_num_characters();
   for (int ci = 0; ci < num_characters; ci++) {
@@ -594,14 +619,16 @@ find_best_parent(EggJointData *joint_data) const {
 ////////////////////////////////////////////////////////////////////
 EggJointData *EggOptchar::
 find_best_vertex_joint(EggJointData *joint_data) const {
+  if (joint_data == (EggJointData *)NULL) {
+    return NULL;
+  }
+
   EggOptcharUserData *user_data = 
     DCAST(EggOptcharUserData, joint_data->get_user_data());
 
   if ((user_data->_flags & EggOptcharUserData::F_static) != 0) {
     // Keep going.
-    if (joint_data->get_parent() != (EggJointData *)NULL) {
-      return find_best_vertex_joint(joint_data->get_parent());
-    }
+    return find_best_vertex_joint(joint_data->get_parent());
   }
 
   // This is the one!
@@ -618,6 +645,7 @@ find_best_vertex_joint(EggJointData *joint_data) const {
 bool EggOptchar::
 apply_user_reparents() {
   bool did_anything = false;
+
   int num_characters = _collection->get_num_characters();
 
   StringPairs::const_iterator spi;
@@ -645,6 +673,18 @@ apply_user_reparents() {
     }
   }
 
+  if (_optimal_hierarchy) {
+    did_anything = true;
+    for (int ci = 0; ci < num_characters; ci++) {
+      EggCharacterData *char_data = _collection->get_character(ci);
+      nout << "Computing optimal hierarchy for "
+           << char_data->get_name() << ".\n";
+      char_data->choose_optimal_hierarchy();
+      nout << "Done computing optimal hierarchy for "
+           << char_data->get_name() << ".\n";
+    }
+  }
+
   return did_anything;
 }
 
@@ -849,19 +889,34 @@ list_joints(EggJointData *joint_data, int indent_level, bool verbose) {
 //               -p joint,parent commands.
 ////////////////////////////////////////////////////////////////////
 void EggOptchar::
-list_joints_p(EggJointData *joint_data) {
+list_joints_p(EggJointData *joint_data, int &col) {
   // As above, don't list the root joint.
 
   int num_children = joint_data->get_num_children();
+  static const int max_col = 72;
+
   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);
+    string text = string(" -p ") + child_data->get_name() + 
+      string(",") + joint_data->get_name();
+    if (col == 0) {
+      cout << "    " << text;
+      col = 4 + text.length();
+    } else {
+      col += text.length();
+      if (col >= max_col) {
+        cout << " \\\n    " << text;
+        col = 4 + text.length();
+      } else {
+        cout << text;
+      }
+    }
+
+    list_joints_p(child_data, col);
   }
 }
 

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

@@ -67,7 +67,7 @@ private:
   void analyze_joints(EggJointData *joint_data);
   void analyze_sliders(EggCharacterData *char_data);
   void list_joints(EggJointData *joint_data, int indent_level, bool verbose);
-  void list_joints_p(EggJointData *joint_data);
+  void list_joints_p(EggJointData *joint_data, int &col);
   void list_scalars(EggCharacterData *char_data, bool verbose);
   void describe_component(EggComponentData *comp_data, int indent_level,
                           bool verbose);
@@ -108,6 +108,7 @@ private:
   typedef pvector<FlagGroupsEntry> FlagGroups;
   FlagGroups _flag_groups;
 
+  bool _optimal_hierarchy;
   double _vref_quantum;
 };
 

+ 1 - 0
pandatool/src/eggcharbase/Sources.pp

@@ -4,6 +4,7 @@
     eggbase progbase
   #define OTHER_LIBS \
     egg:c panda:m
+  #define USE_PACKAGES zlib
     
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx 
 

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

@@ -238,6 +238,61 @@ do_reparent() {
   return invalid_set.empty();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggCharacterData::choose_optimal_hierarchy
+//       Access: Public
+//  Description: Chooses the best possible parent joint for each of
+//               the joints in the hierarchy, based on the score
+//               computed by EggJointData::score_reparent_to().  This
+//               is a fairly expensive operation that involves lots of
+//               recomputing of transforms across the hierarchy.
+//
+//               The joints are not actually reparented yet, but the
+//               new_parent of each joint is set.  Call do_reparent()
+//               to actually perform the suggested reparenting
+//               operation.
+////////////////////////////////////////////////////////////////////
+void EggCharacterData::
+choose_optimal_hierarchy() {
+  Joints::const_iterator ji, jj;
+  for (ji = _joints.begin(); ji != _joints.end(); ++ji) {
+    EggJointData *joint_data = (*ji);
+
+    EggJointData *best_parent = joint_data->get_parent();
+    int best_score = joint_data->score_reparent_to(best_parent);
+
+    for (jj = _joints.begin(); jj != _joints.end(); ++jj) {
+      EggJointData *possible_parent = (*jj);
+      if (possible_parent != joint_data && possible_parent != best_parent &&
+          !joint_data->is_new_ancestor(possible_parent)) {
+
+        int score = joint_data->score_reparent_to(possible_parent);
+        if (score >= 0 && (best_score < 0 || score < best_score)) {
+          best_parent = possible_parent;
+          best_score = score;
+        }
+      }
+    }
+
+    // Also consider reparenting the node to the root.
+    EggJointData *possible_parent = get_root_joint();
+    if (possible_parent != best_parent) {
+      int score = joint_data->score_reparent_to(possible_parent);
+      if (score >= 0 && (best_score < 0 || score < best_score)) {
+        best_parent = possible_parent;
+        best_score = score;
+      }
+    }
+
+    if (best_parent != (EggJointData *)NULL && 
+        best_parent != joint_data->_parent) {
+      nout << "best parent for " << joint_data->get_name() << " is "
+           << best_parent->get_name() << "\n";
+      joint_data->reparent_to(best_parent);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggCharacterData::find_slider
 //       Access: Public

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

@@ -79,6 +79,7 @@ public:
   INLINE int get_num_joints() const;
   INLINE EggJointData *get_joint(int n) const;
   bool do_reparent();
+  void choose_optimal_hierarchy();
 
   INLINE int get_num_sliders() const;
   INLINE EggSliderData *get_slider(int n) const;

+ 209 - 11
pandatool/src/eggcharbase/eggJointData.cxx

@@ -24,6 +24,8 @@
 #include "eggGroup.h"
 #include "eggTable.h"
 #include "indent.h"
+#include "fftCompressor.h"
+#include "zStream.h"
 
 TypeHandle EggJointData::_type_handle;
 
@@ -73,11 +75,61 @@ get_frame(int model_index, int n) const {
 ////////////////////////////////////////////////////////////////////
 LMatrix4d EggJointData::
 get_net_frame(int model_index, int n) const {
-  LMatrix4d mat = get_frame(model_index, n);
-  if (_parent != (EggJointData *)NULL) {
-    mat = mat * _parent->get_net_frame(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_net_frames() < n) {
+    // Recursively get the previous frame's net, so we have a place to
+    // stuff this frame's value.
+    get_net_frame(model_index, n - 1);
   }
-  return mat;
+
+  if (joint->get_num_net_frames() == n) {
+    // Compute this frame's net, and stuff it in.
+    LMatrix4d mat = get_frame(model_index, n);
+    if (_parent != (EggJointData *)NULL) {
+      mat = mat * _parent->get_net_frame(model_index, n);
+    }
+    joint->add_net_frame(mat);
+  }
+
+  return joint->get_net_frame(n);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::get_net_frame_inv
+//       Access: Public
+//  Description: Returns the inverse of get_net_frame().
+////////////////////////////////////////////////////////////////////
+LMatrix4d EggJointData::
+get_net_frame_inv(int model_index, int n) const {
+  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_net_frame_invs() < n) {
+    // Recursively get the previous frame's net, so we have a place to
+    // stuff this frame's value.
+    get_net_frame_inv(model_index, n - 1);
+  }
+
+  if (joint->get_num_net_frame_invs() == n) {
+    // Compute this frame's net inverse, and stuff it in.
+    LMatrix4d mat = get_net_frame(model_index, n);
+    mat.invert_in_place();
+    joint->add_net_frame_inv(mat);
+  }
+
+  return joint->get_net_frame_inv(n);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -116,15 +168,139 @@ force_initial_rest_frame() {
 ////////////////////////////////////////////////////////////////////
 void EggJointData::
 move_vertices_to(EggJointData *new_owner) {
+  int num_models = get_num_models();
+
+  if (new_owner == (EggJointData *)NULL) {
+    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->move_vertices_to((EggJointPointer *)NULL);
+      }
+    }
+  } else {
+    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::score_reparent
+//       Access: Public
+//  Description: Computes a score >= 0 reflecting the similarity of
+//               the current joint's animation (in world space) to
+//               that of the indicated potential parent joint (in
+//               world space).  The lower the number, the more similar
+//               the motion, and the more suitable is the proposed
+//               parent-child relationship.  Returns -1 if there is an
+//               error.
+////////////////////////////////////////////////////////////////////
+int EggJointData::
+score_reparent_to(EggJointData *new_parent) {
+  if (!FFTCompressor::is_compression_available()) {
+    // If we don't have compression compiled in, we can't meaningfully
+    // score the joints.
+    return -1;
+  }
+
+  // First, build up a big array of the new transforms this joint
+  // would receive in all frames of all models, were it reparented to
+  // the indicated joint.
+  vector_float i, j, k, a, b, c, x, y, z;
+  vector_LVecBase3f hprs;
+  int num_rows = 0;
+
   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);
+    EggBackPointer *back = get_model(model_index);
+    if (back != (EggBackPointer *)NULL) {
+      EggJointPointer *joint;
+      DCAST_INTO_R(joint, back, false);
+
+      int num_frames = get_num_frames(model_index);
+      for (int n = 0; n < num_frames; n++) {
+        LMatrix4d transform;
+        if (_parent == new_parent) {
+          // We already have this parent.
+          transform = LMatrix4d::ident_mat();
+          
+        } else if (_parent == (EggJointData *)NULL) {
+          // We are moving from outside the joint hierarchy to within it.
+          transform = new_parent->get_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_net_frame(model_index, n);
+          
+        } else {
+          // We are changing parents within the hierarchy.
+          transform = 
+            _parent->get_net_frame(model_index, n) *
+            new_parent->get_net_frame_inv(model_index, n);
+        }
+
+        transform = joint->get_frame(n) * transform;
+        LVecBase3d scale, shear, hpr, translate;
+        if (!decompose_matrix(transform, scale, shear, hpr, translate)) {
+          // Invalid transform.
+          return -1;
+        }
+        i.push_back(scale[0]);
+        j.push_back(scale[1]);
+        k.push_back(scale[2]);
+        a.push_back(shear[0]);
+        b.push_back(shear[1]);
+        c.push_back(shear[2]);
+        hprs.push_back(LCAST(float, hpr));
+        x.push_back(translate[0]);
+        y.push_back(translate[1]);
+        z.push_back(translate[2]);
+        num_rows++;
+      }
     }
   }
+
+  if (num_rows == 0) {
+    // No data, no score.
+    return -1;
+  }
+
+  // Now, we derive a score, by the simple expedient of using the
+  // FFTCompressor to compress the generated transforms, and measuring
+  // the length of the resulting bitstream.
+  FFTCompressor compressor;
+  Datagram dg;
+  compressor.write_reals(dg, &i[0], num_rows);
+  compressor.write_reals(dg, &j[0], num_rows);
+  compressor.write_reals(dg, &k[0], num_rows);
+  compressor.write_reals(dg, &a[0], num_rows);
+  compressor.write_reals(dg, &b[0], num_rows);
+  compressor.write_reals(dg, &c[0], num_rows);
+  compressor.write_hprs(dg, &hprs[0], num_rows);
+  compressor.write_reals(dg, &x[0], num_rows);
+  compressor.write_reals(dg, &y[0], num_rows);
+  compressor.write_reals(dg, &z[0], num_rows);
+
+
+#ifndef HAVE_ZLIB
+  return dg.get_length();
+
+#else
+  // The FFTCompressor does minimal run-length encoding, but to really
+  // get an accurate measure we should zlib-compress the resulting
+  // stream.
+  ostringstream sstr;
+  OCompressStream zstr(&sstr, false);
+  zstr.write((const char *)dg.get_data(), dg.get_length());
+  zstr.flush();
+  return sstr.str().length();
+#endif
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -363,12 +539,12 @@ do_compute_reparent(int model_index, int 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);
+    transform = _parent->get_net_frame(model_index, n);
 
   } else {
     // We are changing parents within the hierarchy.
     transform = 
-      _parent->get_new_net_frame(model_index, n) *
+      _parent->get_net_frame(model_index, n) *
       _new_parent->get_new_net_frame_inv(model_index, n);
   }
 
@@ -401,6 +577,7 @@ do_finish_reparent() {
       EggJointPointer *joint;
       DCAST_INTO_R(joint, get_model(model_index), false);
       joint->do_finish_reparent(parent_joint);
+      joint->clear_net_frames();
       if (!joint->do_rebuild()) {
         all_ok = false;
       }
@@ -462,6 +639,27 @@ find_joint_matches(const string &name) {
   return (EggJointData *)NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointData::is_new_ancestor
+//       Access: Protected
+//  Description: Returns true if this joint is an ancestor of the
+//               indicated joint, in the "new" hierarchy (that is, the
+//               one defined by _new_parent, as set by reparent_to()
+//               before do_finish_reparent() is called).
+////////////////////////////////////////////////////////////////////
+bool EggJointData::
+is_new_ancestor(EggJointData *child) const {
+  if (child == this) {
+    return true;
+  }
+
+  if (child->_new_parent == (EggJointData *)NULL) {
+    return false;
+  }
+
+  return is_new_ancestor(child->_new_parent);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggJointData::get_new_net_frame
 //       Access: Private

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

@@ -45,6 +45,7 @@ public:
 
   LMatrix4d get_frame(int model_index, int n) const;
   LMatrix4d get_net_frame(int model_index, int n) const;
+  LMatrix4d get_net_frame_inv(int model_index, int n) const;
 
   INLINE bool has_rest_frame() const;
   INLINE bool rest_frames_differ() const;
@@ -53,6 +54,7 @@ public:
 
   INLINE void reparent_to(EggJointData *new_parent);
   void move_vertices_to(EggJointData *new_owner);
+  int score_reparent_to(EggJointData *new_parent);
 
   bool do_rebuild();
   void optimize();
@@ -72,6 +74,7 @@ private:
   EggJointData *find_joint_exact(const string &name);
   EggJointData *find_joint_matches(const string &name);
 
+  bool is_new_ancestor(EggJointData *child) const;
   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);

+ 8 - 3
pandatool/src/eggcharbase/eggJointNodePointer.cxx

@@ -128,10 +128,15 @@ do_finish_reparent(EggJointPointer *new_parent) {
 ////////////////////////////////////////////////////////////////////
 void EggJointNodePointer::
 move_vertices_to(EggJointPointer *new_joint) {
-  EggJointNodePointer *new_node;
-  DCAST_INTO_V(new_node, new_joint);
+  if (new_joint == (EggJointPointer *)NULL) {
+    _joint->unref_all_vertices();
 
-  new_node->_joint->steal_vrefs(_joint);
+  } else {
+    EggJointNodePointer *new_node;
+    DCAST_INTO_V(new_node, new_joint);
+
+    new_node->_joint->steal_vrefs(_joint);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -39,3 +39,93 @@ get_rebuild_frame(int n) const {
   nassertr(n >= 0 && n < (int)_rebuild_frames.size(), LMatrix4d::ident_mat());
   return _rebuild_frames[n];
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::clear_net_frames
+//       Access: Public
+//  Description: Resets the cache of net frames for this joint.
+////////////////////////////////////////////////////////////////////
+INLINE void EggJointPointer::
+clear_net_frames() {
+  _net_frames.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::add_net_frame
+//       Access: Public, Virtual
+//  Description: Adds a new frame to the set of net frames.  This is
+//               used to cache the net transform from the root for
+//               this particular joint.
+////////////////////////////////////////////////////////////////////
+INLINE void EggJointPointer::
+add_net_frame(const LMatrix4d &mat) {
+  _net_frames.push_back(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_num_net_frames
+//       Access: Public
+//  Description: Returns the number of net frames that have been
+//               added so far.
+////////////////////////////////////////////////////////////////////
+INLINE int EggJointPointer::
+get_num_net_frames() const {
+  return _net_frames.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_net_frame
+//       Access: Public
+//  Description: Returns the nth matrix that has been added to the set
+//               of net frames.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4d &EggJointPointer::
+get_net_frame(int n) const {
+  nassertr(n >= 0 && n < (int)_net_frames.size(), LMatrix4d::ident_mat());
+  return _net_frames[n];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::clear_net_frame_invs
+//       Access: Public
+//  Description: Resets the cache of net_inv frames for this joint.
+////////////////////////////////////////////////////////////////////
+INLINE void EggJointPointer::
+clear_net_frame_invs() {
+  _net_frame_invs.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::add_net_frame_inv
+//       Access: Public, Virtual
+//  Description: Adds a new frame to the set of net_inv frames.  This is
+//               used to cache the inverse net transform from the root
+//               for this particular joint.
+////////////////////////////////////////////////////////////////////
+INLINE void EggJointPointer::
+add_net_frame_inv(const LMatrix4d &mat) {
+  _net_frame_invs.push_back(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_num_net_frame_invs
+//       Access: Public
+//  Description: Returns the number of net_inv frames that have been
+//               added so far.
+////////////////////////////////////////////////////////////////////
+INLINE int EggJointPointer::
+get_num_net_frame_invs() const {
+  return _net_frame_invs.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggJointPointer::get_net_frame_inv
+//       Access: Public
+//  Description: Returns the nth matrix that has been added to the set
+//               of net_inv frames.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4d &EggJointPointer::
+get_net_frame_inv(int n) const {
+  nassertr(n >= 0 && n < (int)_net_frame_invs.size(), LMatrix4d::ident_mat());
+  return _net_frame_invs[n];
+}

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

@@ -50,6 +50,16 @@ public:
   INLINE const LMatrix4d &get_rebuild_frame(int n) const;
   virtual bool do_rebuild();
 
+  INLINE void clear_net_frames();
+  INLINE void add_net_frame(const LMatrix4d &mat);
+  INLINE int get_num_net_frames() const;
+  INLINE const LMatrix4d &get_net_frame(int n) const;
+
+  INLINE void clear_net_frame_invs();
+  INLINE void add_net_frame_inv(const LMatrix4d &mat);
+  INLINE int get_num_net_frame_invs() const;
+  INLINE const LMatrix4d &get_net_frame_inv(int n) const;
+
   virtual void optimize();
   virtual void expose(EggGroup::DCSType dcs_type);
   virtual void zero_channels(const string &components);
@@ -57,6 +67,8 @@ public:
 protected:
   typedef pvector<LMatrix4d> RebuildFrames;
   RebuildFrames _rebuild_frames;
+  RebuildFrames _net_frames;
+  RebuildFrames _net_frame_invs;
 
 public:
   static TypeHandle get_class_type() {