Browse Source

allow Actor flattening

David Rose 19 years ago
parent
commit
ccec9eb3df

+ 76 - 37
direct/src/actor/Actor.py

@@ -31,15 +31,16 @@ class Actor(DirectObject, NodePath):
         multiple different LOD's, as well as the multiple different
         pieces of a multipart Actor. """
         
-        def __init__(self, partBundle, partModel):
+        def __init__(self, partBundleNP, partBundle, partModel):
             # We also save the ModelRoot node along with the
             # PartBundle, so that the reference count in the ModelPool
             # will be accurate.
+            self.partBundleNP = partBundleNP
             self.partBundle = partBundle
             self.partModel = partModel
 
         def __repr__(self):
-            return 'Actor.PartDef(%s, %s)' % (repr(self.partBundle), repr(self.partModel))
+            return 'Actor.PartDef(%s, %s)' % (repr(self.partBundleNP), repr(self.partModel))
 
     class AnimDef:
 
@@ -84,7 +85,7 @@ class Actor(DirectObject, NodePath):
             return 'Actor.SubpartDef(%s, %s)' % (repr(self.truePartName), repr(self.subset))
 
     def __init__(self, models=None, anims=None, other=None, copy=1,
-                 lodNode = None):
+                 lodNode = None, flattenable = 1):
         """__init__(self, string | string:string{}, string:string{} |
         string:(string:string{}){}, Actor=None)
         Actor constructor: can be used to create single or multipart
@@ -169,10 +170,23 @@ class Actor(DirectObject, NodePath):
 
             # create base hierarchy
             self.gotName = 0
-            root = ModelNode('actor')
-            root.setPreserveTransform(1)
-            self.assign(NodePath(root))
-            self.setGeomNode(self.attachNewNode(ModelNode('actorGeom')))
+
+            if flattenable:
+                # If we want a flattenable Actor, don't create all
+                # those ModelNodes, and the GeomNode is the same as
+                # the root.
+                root = PandaNode('actor')
+                self.assign(NodePath(root))
+                self.setGeomNode(self)
+
+            else:
+                # A standard Actor has a ModelNode at the root, and
+                # another ModelNode to protect the GeomNode.
+                root = ModelNode('actor')
+                root.setPreserveTransform(1)
+                self.assign(NodePath(root))
+                self.setGeomNode(self.attachNewNode(ModelNode('actorGeom')))
+                
             self.__hasLOD = 0
 
             # load models
@@ -344,7 +358,7 @@ class Actor(DirectObject, NodePath):
         if partDef == None:
             Actor.notify.error("no part named: %s" % (partName))
 
-        self.__doListJoints(0, partDef.partBundle.node().getBundle(),
+        self.__doListJoints(0, partDef.partBundle,
                             subpartDef.subset.isIncludeEmpty(), subpartDef.subset)
 
     def __doListJoints(self, indentLevel, part, isIncluded, subset):
@@ -627,7 +641,7 @@ class Actor(DirectObject, NodePath):
             partDefs = self.__partBundleDict[lodnames[lod]].values()
             for partDef in partDefs:
                 # print "updating: %s" % (partBundle.node())
-                partDef.partBundle.node().updateToNow()
+                partDef.partBundle.update()
         else:
             self.notify.warning('update() - no lod: %d' % lod)
 
@@ -781,6 +795,21 @@ class Actor(DirectObject, NodePath):
             return None
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
+        if partDef != None:
+            return partDef.partBundleNP
+        return None
+
+    def getPartBundle(self, partName, lodName="lodRoot"):
+        """
+        Find the named part in the optional named lod and return its
+        associated PartBundle, or return None if not present
+        """
+        partBundleDict = self.__partBundleDict.get(lodName)
+        if not partBundleDict:
+            Actor.notify.warning("no lod named: %s" % (lodName))
+            return None
+        subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
+        partDef = partBundleDict.get(subpartDef.truePartName)
         if partDef != None:
             return partDef.partBundle
         return None
@@ -799,7 +828,7 @@ class Actor(DirectObject, NodePath):
 
         # remove the part
         if (partBundleDict.has_key(partName)):
-            partBundleDict[partName].partBundle.removeNode()
+            partBundleDict[partName].partBundleNP.removeNode()
             del(partBundleDict[partName])
 
         # find the corresponding anim control dict
@@ -824,7 +853,7 @@ class Actor(DirectObject, NodePath):
             return
         partDef = partBundleDict.get(partName)
         if partDef:
-            partDef.partBundle.hide()
+            partDef.partBundleNP.hide()
         else:
             Actor.notify.warning("no part named %s!" % (partName))
 
@@ -839,7 +868,7 @@ class Actor(DirectObject, NodePath):
             return
         partDef = partBundleDict.get(partName)
         if partDef:
-            partDef.partBundle.show()
+            partDef.partBundleNP.show()
         else:
             Actor.notify.warning("no part named %s!" % (partName))
 
@@ -854,8 +883,8 @@ class Actor(DirectObject, NodePath):
             return
         partDef = partBundleDict.get(partName)
         if partDef:
-            partDef.partBundle.show()
-            partDef.partBundle.getChildren().show()
+            partDef.partBundleNP.show()
+            partDef.partBundleNP.getChildren().show()
         else:
             Actor.notify.warning("no part named %s!" % (partName))
 
@@ -877,7 +906,7 @@ class Actor(DirectObject, NodePath):
 
         partDef = partBundleDict.get(subpartDef.truePartName)
         if partDef:
-            bundle = partDef.partBundle.node().getBundle()
+            bundle = partDef.partBundle
         else:
             Actor.notify.warning("no part named %s!" % (partName))
             return None
@@ -912,7 +941,7 @@ class Actor(DirectObject, NodePath):
 
         partDef = partBundleDict.get(subpartDef.truePartName)
         if partDef:
-            bundle = partDef.partBundle.node().getBundle()
+            bundle = partDef.partBundle
         else:
             Actor.notify.warning("no part named %s!" % (partName))
             return None
@@ -946,7 +975,7 @@ class Actor(DirectObject, NodePath):
         subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
         partDef = partBundleDict.get(subpartDef.truePartName)
         if partDef:
-            bundle = partDef.partBundle.node().getBundle()
+            bundle = partDef.partBundle
         else:
             Actor.notify.warning("no part named %s!" % (partName))
             return None
@@ -979,7 +1008,7 @@ class Actor(DirectObject, NodePath):
             subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
             partDef = partBundleDict.get(subpartDef.truePartName)
             if partDef:
-                joint = partDef.partBundle.find("**/" + jointName)
+                joint = partDef.partBundleNP.find("**/" + jointName)
                 if (joint.isEmpty()):
                     Actor.notify.warning("%s not found!" % (jointName))
                 else:
@@ -999,11 +1028,11 @@ class Actor(DirectObject, NodePath):
             if partDef:
                 anotherPartDef = partBundleDict.get(anotherPartName)
                 if anotherPartDef:
-                    joint = anotherPartDef.partBundle.find("**/" + jointName)
+                    joint = anotherPartDef.partBundleNP.find("**/" + jointName)
                     if (joint.isEmpty()):
                         Actor.notify.warning("%s not found!" % (jointName))
                     else:
-                        partDef.partBundle.reparentTo(joint)
+                        partDef.partBundleNP.reparentTo(joint)
                 else:
                     Actor.notify.warning("no part named %s!" % (anotherPartName))
             else:
@@ -1258,13 +1287,13 @@ class Actor(DirectObject, NodePath):
         for lodName, bundleDict in self.__partBundleDict.items():
             if partName == None:
                 for partDef in bundleDict.values():
-                    bundles.append(partDef.partBundle.node().getBundle())
+                    bundles.append(partDef.partBundle)
 
             else:
                 subpartDef = self.__subpartDict.get(partName, Actor.SubpartDef(partName))
                 partDef = partBundleDict.get(subpartDef.truePartName)
                 if partDef != None:
-                    bundles.append(partDef.partBundle.node().getBundle())
+                    bundles.append(partDef.partBundle)
                 else:
                     Actor.notify.warning("Couldn't find part: %s" % (partName))
 
@@ -1479,11 +1508,11 @@ class Actor(DirectObject, NodePath):
             raise StandardError, "Could not load Actor model %s" % (modelPath)
 
         if (model.node().isOfType(PartBundleNode.getClassType())):
-            bundle = model
+            bundleNP = model
         else:
-            bundle = model.find("**/+PartBundleNode")
+            bundleNP = model.find("**/+PartBundleNode")
             
-        if (bundle.isEmpty()):
+        if (bundleNP.isEmpty()):
             Actor.notify.warning("%s is not a character!" % (modelPath))
             model.reparentTo(self.__geomNode)
         else:
@@ -1496,7 +1525,7 @@ class Actor(DirectObject, NodePath):
 
             # Now extract out the PartBundleNode and integrate it with
             # the Actor.
-            self.__prepareBundle(bundle, model, partName, lodName)
+            self.__prepareBundle(bundleNP, model, partName, lodName)
 
             if numAnims != 0:
                 # If the model had some animations, store them in the
@@ -1516,7 +1545,7 @@ class Actor(DirectObject, NodePath):
                     # animControl, but put None in for the filename.
                     self.__animControlDict[lodName][partName][animName] = [None, animControl]
 
-    def __prepareBundle(self, bundle, model,
+    def __prepareBundle(self, bundleNP, model,
                         partName="modelRoot", lodName="lodRoot"):
         assert partName not in self.__subpartDict
 
@@ -1524,11 +1553,11 @@ class Actor(DirectObject, NodePath):
         # haven't already, to make it easier to identify this
         # actor in the scene graph.
         if not self.gotName:
-            self.node().setName(bundle.node().getName())
+            self.node().setName(bundleNP.node().getName())
             self.gotName = 1
 
         # we rename this node to make Actor copying easier
-        bundle.node().setName(Actor.partPrefix + partName)
+        bundleNP.node().setName(Actor.partPrefix + partName)
 
         if (self.__partBundleDict.has_key(lodName) == 0):
             # make a dictionary to store these parts in
@@ -1539,16 +1568,21 @@ class Actor(DirectObject, NodePath):
 
         if (lodName!="lodRoot"):
             # parent to appropriate node under LOD switch
-            bundle.reparentTo(self.__LODNode.find("**/" + str(lodName)))
+            bundleNP.reparentTo(self.__LODNode.find("**/" + str(lodName)))
         else:
-            bundle.reparentTo(self.__geomNode)
+            bundleNP.reparentTo(self.__geomNode)
+
+        node = bundleNP.node()
+        # A model loaded from disk will always have just one bundle.
+        assert(node.getNumBundles() == 1)
+        bundle = node.getBundle(0)
 
         if (needsDict):
-            bundleDict[partName] = Actor.PartDef(bundle, model.node())
+            bundleDict[partName] = Actor.PartDef(bundleNP, bundle, model.node())
             self.__partBundleDict[lodName] = bundleDict
             self.__updateSortedLODNames()
         else:
-            self.__partBundleDict[lodName][partName] = Actor.PartDef(bundle, model.node())
+            self.__partBundleDict[lodName][partName] = Actor.PartDef(bundleNP, bundle, model.node())
 
     def makeSubpart(self, partName, includeJoints, excludeJoints = [],
                     parent="modelRoot"):
@@ -1749,7 +1783,7 @@ class Actor(DirectObject, NodePath):
             return None
         animBundle = (animNode.find("**/+AnimBundleNode").node()).getBundle()
 
-        bundle = self.__partBundleDict[lodName][subpartDef.truePartName].partBundle.node().getBundle()
+        bundle = self.__partBundleDict[lodName][subpartDef.truePartName].partBundle
 
         # Are there any controls requested for joints in this bundle?
         # If so, apply them.
@@ -1795,12 +1829,17 @@ class Actor(DirectObject, NodePath):
                 return None
             for partName, partDef in other.__partBundleDict[lodName].items():
                 model = partDef.partModel.copySubgraph()
+
+                # We can really only copy from a non-flattened avatar.
+                assert partDef.partBundleNP.node().getNumBundles() == 1
                 
                 # find the part in our tree
-                partBundle = partLod.find("**/" + Actor.partPrefix + partName)
-                if (partBundle != None):
+                bundleNP = partLod.find("**/" + Actor.partPrefix + partName)
+                if (bundleNP != None):
                     # store the part bundle
-                    self.__partBundleDict[lodName][partName] = Actor.PartDef(partBundle, model)
+                    assert bundleNP.node().getNumBundles() == 1
+                    bundle = bundleNP.node().getBundle(0)
+                    self.__partBundleDict[lodName][partName] = Actor.PartDef(bundleNP, bundle, model)
                 else:
                     Actor.notify.error("lod: %s has no matching part: %s" %
                                        (lodName, partName))

+ 41 - 29
panda/src/chan/auto_bind.cxx

@@ -24,11 +24,11 @@
 #include "string_utils.h"
 #include "partGroup.h"
 
-typedef pset<AnimBundleNode *> AnimNodes;
-typedef pmap<string, AnimNodes> Anims;
+typedef pset<AnimBundle *> AnimBundles;
+typedef pmap<string, AnimBundles> Anims;
 
-typedef pset<PartBundleNode *> PartNodes;
-typedef pmap<string, PartNodes> Parts;
+typedef pset<PartBundle *> PartBundles;
+typedef pmap<string, PartBundles> Parts;
 
 
 ////////////////////////////////////////////////////////////////////
@@ -39,16 +39,16 @@ typedef pmap<string, PartNodes> Parts;
 //               sense.
 ////////////////////////////////////////////////////////////////////
 static void
-bind_anims(const PartNodes &parts, const AnimNodes &anims,
+bind_anims(const PartBundles &parts, const AnimBundles &anims,
            AnimControlCollection &controls,
            int hierarchy_match_flags) {
-  PartNodes::const_iterator pni;
+  PartBundles::const_iterator pbi;
 
-  for (pni = parts.begin(); pni != parts.end(); ++pni) {
-    PartBundle *part = (*pni)->get_bundle();
-    AnimNodes::const_iterator ani;
-    for (ani = anims.begin(); ani != anims.end(); ++ani) {
-      AnimBundle *anim = (*ani)->get_bundle();
+  for (pbi = parts.begin(); pbi != parts.end(); ++pbi) {
+    PartBundle *part = (*pbi);
+    AnimBundles::const_iterator abi;
+    for (abi = anims.begin(); abi != anims.end(); ++abi) {
+      AnimBundle *anim = (*abi);
       if (chan_cat.is_info()) {
         chan_cat.info()
           << "Attempting to bind " << *part << " to " << *anim << "\n";
@@ -56,7 +56,7 @@ bind_anims(const PartNodes &parts, const AnimNodes &anims,
 
       PT(AnimControl) control =
         part->bind_anim(anim, hierarchy_match_flags);
-      string name = (*ani)->get_name();
+      string name = (*abi)->get_name();
       if (name.empty()) {
         name = anim->get_name();
       }
@@ -100,10 +100,16 @@ static void
 r_find_bundles(PandaNode *node, Anims &anims, Parts &parts) {
   if (node->is_of_type(AnimBundleNode::get_class_type())) {
     AnimBundleNode *bn = DCAST(AnimBundleNode, node);
-    anims[bn->get_bundle()->get_name()].insert(bn);
+    AnimBundle *bundle = bn->get_bundle();
+    anims[bundle->get_name()].insert(bundle);
+
   } else if (node->is_of_type(PartBundleNode::get_class_type())) {
     PartBundleNode *bn = DCAST(PartBundleNode, node);
-    parts[bn->get_bundle()->get_name()].insert(bn);
+    int num_bundles = bn->get_num_bundles();
+    for (int i = 0; i < num_bundles; ++i) {
+      PartBundle *bundle = bn->get_bundle(i);
+      parts[bundle->get_name()].insert(bundle);
+    }
   }
 
   PandaNode::Children cr = node->get_children();
@@ -127,8 +133,10 @@ void
 auto_bind(PandaNode *root_node, AnimControlCollection &controls,
           int hierarchy_match_flags) {
   // First, locate all the bundles in the subgraph.
-  Anims anims; AnimNodes extraAnims;
-  Parts parts; PartNodes extraParts;
+  Anims anims; 
+  AnimBundles extra_anims;
+  Parts parts; 
+  PartBundles extra_parts;
   r_find_bundles(root_node, anims, parts);
   
   if (chan_cat.is_debug()) {
@@ -178,18 +186,20 @@ auto_bind(PandaNode *root_node, AnimControlCollection &controls,
     if ((*ai).first < (*pi).first) {
       // Here's an anim with no matching parts.
       if (hierarchy_match_flags & PartGroup::HMF_ok_wrong_root_name) {
-        AnimNodes::const_iterator ani;
-        for (ani = (*ai).second.begin(); ani != (*ai).second.end(); ++ani)
-          extraAnims.insert(*ani);
+        AnimBundles::const_iterator abi;
+        for (abi = (*ai).second.begin(); abi != (*ai).second.end(); ++abi) {
+          extra_anims.insert(*abi);
+        }
       }
       ++ai;
 
     } else if ((*pi).first < (*ai).first) {
       // And here's a part with no matching anims.
       if (hierarchy_match_flags & PartGroup::HMF_ok_wrong_root_name) {
-        PartNodes::const_iterator pni;
-        for (pni = (*pi).second.begin(); pni != (*pi).second.end(); ++pni)
-          extraParts.insert(*pni);
+        PartBundles::const_iterator pbi;
+        for (pbi = (*pi).second.begin(); pbi != (*pi).second.end(); ++pbi) {
+          extra_parts.insert(*pbi);
+        }
       }
       ++pi;
 
@@ -211,9 +221,10 @@ auto_bind(PandaNode *root_node, AnimControlCollection &controls,
     while (ai != anims.end()) {
       // Here's an anim with no matching parts.
       if (hierarchy_match_flags & PartGroup::HMF_ok_wrong_root_name) {
-	AnimNodes::const_iterator ani;
-	for (ani = (*ai).second.begin(); ani != (*ai).second.end(); ++ani)
-	  extraAnims.insert(*ani);
+	AnimBundles::const_iterator abi;
+	for (abi = (*ai).second.begin(); abi != (*ai).second.end(); ++abi) {
+	  extra_anims.insert(*abi);
+        }
       }
       ++ai;
     }
@@ -221,14 +232,15 @@ auto_bind(PandaNode *root_node, AnimControlCollection &controls,
     while (pi != parts.end()) {
       // And here's a part with no matching anims.
       if (hierarchy_match_flags & PartGroup::HMF_ok_wrong_root_name) {
-	PartNodes::const_iterator pni;
-	for (pni = (*pi).second.begin(); pni != (*pi).second.end(); ++pni)
-	  extraParts.insert(*pni);
+	PartBundles::const_iterator pbi;
+	for (pbi = (*pi).second.begin(); pbi != (*pi).second.end(); ++pbi) {
+	  extra_parts.insert(*pbi);
+        }
       }
       ++pi;
     }
     
-    bind_anims(extraParts, extraAnims, controls,
+    bind_anims(extra_parts, extra_anims, controls,
                hierarchy_match_flags);
   }
 }

+ 2 - 2
panda/src/chan/movingPartBase.cxx

@@ -128,7 +128,7 @@ do_update(PartBundle *root, const CycleData *root_cdata, PartGroup *parent,
   }
 
   if (parent_changed || needs_update) {
-    any_changed = update_internals(parent, needs_update, parent_changed,
+    any_changed = update_internals(root, parent, needs_update, parent_changed,
                                    current_thread);
   }
 
@@ -158,7 +158,7 @@ do_update(PartBundle *root, const CycleData *root_cdata, PartGroup *parent,
 //               result of the update, or false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool MovingPartBase::
-update_internals(PartGroup *, bool, bool, Thread *) {
+update_internals(PartBundle *, PartGroup *, bool, bool, Thread *) {
   return true;
 }
 

+ 3 - 2
panda/src/chan/movingPartBase.h

@@ -61,8 +61,9 @@ public:
                          bool anim_changed, Thread *current_thread);
 
   virtual void get_blend_value(const PartBundle *root)=0;
-  virtual bool update_internals(PartGroup *parent, bool self_changed,
-                                bool parent_changed, Thread *current_thread);
+  virtual bool update_internals(PartBundle *root, PartGroup *parent, 
+                                bool self_changed, bool parent_changed, 
+                                Thread *current_thread);
 
 protected:
   MovingPartBase();

+ 39 - 0
panda/src/chan/partBundle.I

@@ -105,6 +105,45 @@ get_frame_blend_flag() const {
   return cdata->_frame_blend_flag;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::set_root_xform
+//       Access: Published
+//  Description: Specifies the transform matrix which is implicitly
+//               applied at the root of the animated hierarchy.
+////////////////////////////////////////////////////////////////////
+INLINE void PartBundle::
+set_root_xform(const LMatrix4f &root_xform) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+  CDWriter cdata(_cycler);
+  cdata->_root_xform = root_xform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::xform
+//       Access: Published
+//  Description: Applies the indicated transform to the root of the
+//               animated hierarchy.
+////////////////////////////////////////////////////////////////////
+INLINE void PartBundle::
+xform(const LMatrix4f &mat) {
+  nassertv(Thread::get_current_pipeline_stage() == 0);
+  CDWriter cdata(_cycler);
+  cdata->_root_xform = cdata->_root_xform * mat;
+  do_xform(mat, invert(mat));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundle::get_root_xform
+//       Access: Published
+//  Description: Returns the transform matrix which is implicitly
+//               applied at the root of the animated hierarchy.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &PartBundle::
+get_root_xform() const {
+  CDReader cdata(_cycler);
+  return cdata->_root_xform;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::get_node
 //       Access: Published

+ 10 - 3
panda/src/chan/partBundle.cxx

@@ -63,7 +63,10 @@ PartBundle(const PartBundle &copy) :
 //               PartBundleNode is created.
 ////////////////////////////////////////////////////////////////////
 PartBundle::
-PartBundle(const string &name) : PartGroup(name) {
+PartBundle(const string &name) : 
+  PartGroup(name),
+  _node(NULL)
+{
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -234,7 +237,7 @@ bind_anim(AnimBundle *anim, int hierarchy_match_flags,
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::update
-//       Access: Public
+//       Access: Published
 //  Description: Updates all the parts in the bundle to reflect the
 //               data for the current frame (as set in each of the
 //               AnimControls).
@@ -269,7 +272,7 @@ update() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundle::force_update
-//       Access: Public
+//       Access: Published
 //  Description: Updates all the parts in the bundle to reflect the
 //               data for the current frame, whether we believe it
 //               needs it or not.
@@ -481,6 +484,7 @@ CData() {
   _blend_type = anim_blend_type;
   _anim_blend_flag = false;
   _frame_blend_flag = interpolate_frames;
+  _root_xform = LMatrix4f::ident_mat();
   _last_control_set = NULL;
   _net_blend = 0.0f;
   _anim_changed = false;
@@ -494,6 +498,9 @@ CData() {
 PartBundle::CData::
 CData(const PartBundle::CData &copy) :
   _blend_type(copy._blend_type),
+  _anim_blend_flag(copy._anim_blend_flag),
+  _frame_blend_flag(copy._frame_blend_flag),
+  _root_xform(copy._root_xform),
   _last_control_set(copy._last_control_set),
   _blend(copy._blend),
   _net_blend(copy._net_blend),

+ 9 - 3
panda/src/chan/partBundle.h

@@ -30,6 +30,7 @@
 #include "cycleDataLockedReader.h"
 #include "cycleDataReader.h"
 #include "cycleDataWriter.h"
+#include "luse.h"
 
 class AnimBundle;
 class PartBundleNode;
@@ -100,6 +101,10 @@ PUBLISHED:
   INLINE void set_frame_blend_flag(bool frame_blend_flag);
   INLINE bool get_frame_blend_flag() const;
 
+  INLINE void set_root_xform(const LMatrix4f &root_xform);
+  INLINE void xform(const LMatrix4f &mat);
+  INLINE const LMatrix4f &get_root_xform() const;
+
   INLINE PartBundleNode *get_node() const;
 
   void clear_control_effects();
@@ -113,13 +118,13 @@ PUBLISHED:
                             int hierarchy_match_flags = 0, 
                             const PartSubset &subset = PartSubset());
 
+  bool update();
+  bool force_update();
+
 public:
   // The following functions aren't really part of the public
   // interface; they're just public so we don't have to declare a
   // bunch of friends.
-
-  bool update();
-  bool force_update();
   virtual void control_activated(AnimControl *control);
 
 private:
@@ -146,6 +151,7 @@ private:
     BlendType _blend_type;
     bool _anim_blend_flag;
     bool _frame_blend_flag;
+    LMatrix4f _root_xform;
     AnimControl *_last_control_set;
     ChannelBlend _blend;
     float _net_blend;

+ 17 - 15
panda/src/chan/partBundleNode.I

@@ -28,10 +28,9 @@
 ////////////////////////////////////////////////////////////////////
 INLINE PartBundleNode::
 PartBundleNode(const string &name, PartBundle *bundle) :
-  PandaNode(name),
-  _bundle(bundle)
+  PandaNode(name)
 {
-  _bundle->_node = this;
+  add_bundle(bundle);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -49,20 +48,22 @@ PartBundleNode() : PandaNode("") {
 //  Description: Use make_copy() or copy_subgraph() to copy one of
 //               these.  
 //
-//               If the supplied PartBundle is non-null, it is
-//               assigned to the new node; otherwise, a copy is made
-//               of the complete PartGroup hierarchy.
+//               This constructor does not copy the bundle pointers.
 ////////////////////////////////////////////////////////////////////
 INLINE PartBundleNode::
-PartBundleNode(const PartBundleNode &copy, PartBundle *bundle) :
+PartBundleNode(const PartBundleNode &copy) :
   PandaNode(copy)
 {
-  if (bundle != (PartBundle *)NULL) {
-    _bundle = bundle;
-  } else {
-    _bundle = DCAST(PartBundle, copy._bundle->copy_subgraph());
-  }
-  _bundle->_node = this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundleNode::get_num_bundles
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int PartBundleNode::
+get_num_bundles() const {
+  return _bundles.size();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -71,6 +72,7 @@ PartBundleNode(const PartBundleNode &copy, PartBundle *bundle) :
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE PartBundle *PartBundleNode::
-get_bundle() const {
-  return _bundle;
+get_bundle(int n) const {
+  nassertr(n >= 0 && n < (int)_bundles.size(), NULL);
+  return _bundles[n];
 }

+ 79 - 4
panda/src/chan/partBundleNode.cxx

@@ -24,6 +24,19 @@
 
 TypeHandle PartBundleNode::_type_handle;
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundleNode::Destructor
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PartBundleNode::
+~PartBundleNode() {
+  Bundles::iterator bi;
+  for (bi = _bundles.begin(); bi != _bundles.end(); ++bi) {
+    nassertv((*bi)->_node == this);
+    (*bi)->_node = NULL;
+  }
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundleNode::safe_to_flatten
@@ -39,6 +52,50 @@ safe_to_flatten() const {
   return false;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundleNode::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this PandaNode by the
+//               indicated matrix, if it means anything to do so.  For
+//               most kinds of PandaNodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void PartBundleNode::
+xform(const LMatrix4f &mat) {
+  Bundles::iterator bi;
+  for (bi = _bundles.begin(); bi != _bundles.end(); ++bi) {
+    (*bi)->xform(mat);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundleNode::add_bundle
+//       Access: Protected
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void PartBundleNode::
+add_bundle(PartBundle *bundle) {
+  nassertv(bundle->_node == NULL);
+  _bundles.push_back(bundle);
+  bundle->_node = this;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PartBundleNode::steal_bundles
+//       Access: Protected
+//  Description: Moves the PartBundles from the other node onto this
+//               one.
+////////////////////////////////////////////////////////////////////
+void PartBundleNode::
+steal_bundles(PartBundleNode *other) {
+  Bundles::iterator bi;
+  for (bi = other->_bundles.begin(); bi != other->_bundles.end(); ++bi) {
+    PartBundle *bundle = (*bi);
+    _bundles.push_back(bundle);
+    bundle->_node = this;
+  }
+  other->_bundles.clear();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PartBundleNode::write_datagram
 //       Access: Public, Virtual
@@ -48,7 +105,12 @@ safe_to_flatten() const {
 void PartBundleNode::
 write_datagram(BamWriter *manager, Datagram &dg) {
   PandaNode::write_datagram(manager, dg);
-  manager->write_pointer(dg, _bundle);
+
+  dg.add_uint16(_bundles.size());
+  Bundles::iterator bi;
+  for (bi = _bundles.begin(); bi != _bundles.end(); ++bi) {
+    manager->write_pointer(dg, (*bi));
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -61,8 +123,13 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 int PartBundleNode::
 complete_pointers(TypedWritable **p_list, BamReader* manager) {
   int pi = PandaNode::complete_pointers(p_list, manager);
-  _bundle = DCAST(PartBundle, p_list[pi++]);
-  _bundle->_node = this;
+
+  Bundles::iterator bi;
+  for (bi = _bundles.begin(); bi != _bundles.end(); ++bi) {
+    (*bi) = DCAST(PartBundle, p_list[pi++]);
+    (*bi)->_node = this;
+  }
+
   return pi;
 }
 
@@ -76,5 +143,13 @@ complete_pointers(TypedWritable **p_list, BamReader* manager) {
 void PartBundleNode::
 fillin(DatagramIterator &scan, BamReader* manager) {
   PandaNode::fillin(scan, manager);
-  manager->read_pointer(scan);
+
+  int num_bundles = 1;
+  if (manager->get_file_minor_ver() >= 5) {
+    num_bundles = scan.get_uint16();
+  }
+
+  for (int i = 0; i < num_bundles; ++i) {
+    manager->read_pointer(scan);
+  }
 }

+ 12 - 3
panda/src/chan/partBundleNode.h

@@ -25,6 +25,7 @@
 
 #include "pandaNode.h"
 #include "dcast.h"
+#include "pvector.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PartBundleNode
@@ -38,16 +39,24 @@ public:
 
 protected:
   INLINE PartBundleNode();
-  INLINE PartBundleNode(const PartBundleNode &copy, PartBundle *bundle = NULL);
+  INLINE PartBundleNode(const PartBundleNode &copy);
 
 public:
+  virtual ~PartBundleNode();
   virtual bool safe_to_flatten() const;
+  virtual void xform(const LMatrix4f &mat);
 
 PUBLISHED:
-  INLINE PartBundle *get_bundle() const;
+  INLINE int get_num_bundles() const;
+  INLINE PartBundle *get_bundle(int n) const;
+
+protected:
+  void add_bundle(PartBundle *bundle);
+  void steal_bundles(PartBundleNode *other);
 
 private:
-  PT(PartBundle) _bundle;
+  typedef pvector< PT(PartBundle) > Bundles;
+  Bundles _bundles;
 
 public:
   virtual void write_datagram(BamWriter* manager, Datagram &me);

+ 16 - 0
panda/src/chan/partGroup.cxx

@@ -375,6 +375,22 @@ do_update(PartBundle *root, const CycleData *root_cdata, PartGroup *,
   return any_changed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PartGroup::do_xform
+//       Access: Public, Virtual
+//  Description: Called by PartBundle::xform(), this indicates the
+//               indicated transform is being applied to the root
+//               joint.
+////////////////////////////////////////////////////////////////////
+void PartGroup::
+do_xform(const LMatrix4f &mat, const LMatrix4f &inv_mat) {
+  Children::const_iterator ci;
+
+  for (ci = _children.begin(); ci != _children.end(); ++ci) {
+    (*ci)->do_xform(mat, inv_mat);
+  }
+}
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PartGroup::write_descendants

+ 2 - 0
panda/src/chan/partGroup.h

@@ -27,6 +27,7 @@
 #include "typedef.h"
 #include "thread.h"
 #include "plist.h"
+#include "luse.h"
 
 class AnimControl;
 class AnimGroup;
@@ -89,6 +90,7 @@ public:
   virtual bool do_update(PartBundle *root, const CycleData *root_cdata,
                          PartGroup *parent, bool parent_changed, 
                          bool anim_changed, Thread *current_thread);
+  virtual void do_xform(const LMatrix4f &mat, const LMatrix4f &inv_mat);
 
 protected:
   void write_descendants(ostream &out, int indent_level) const;

+ 2 - 62
panda/src/char/character.I

@@ -24,8 +24,8 @@
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE CharacterJointBundle *Character::
-get_bundle() const {
-  return DCAST(CharacterJointBundle, PartBundleNode::get_bundle());
+get_bundle(int i) const {
+  return DCAST(CharacterJointBundle, PartBundleNode::get_bundle(i));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -50,63 +50,3 @@ get_part(int n) const {
   nassertr(n >= 0 && n < (int)_parts.size(), NULL);
   return _parts[n];
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: Character::find_joint
-//       Access: Published
-//  Description: Returns a pointer to the joint with the given name,
-//               if there is such a joint, or NULL if there is no such
-//               joint.  This will not return a pointer to a slider.
-////////////////////////////////////////////////////////////////////
-INLINE CharacterJoint *Character::
-find_joint(const string &name) const {
-  PartGroup *part = get_bundle()->find_child(name);
-  if (part != (PartGroup *)NULL &&
-      part->is_of_type(CharacterJoint::get_class_type())) {
-    return DCAST(CharacterJoint, part);
-  }
-
-  return NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Character::find_slider
-//       Access: Published
-//  Description: Returns a pointer to the slider with the given name,
-//               if there is such a slider, or NULL if there is no such
-//               slider.  This will not return a pointer to a joint.
-////////////////////////////////////////////////////////////////////
-INLINE CharacterSlider *Character::
-find_slider(const string &name) const {
-  PartGroup *part = get_bundle()->find_child(name);
-  if (part != (PartGroup *)NULL &&
-      part->is_of_type(CharacterSlider::get_class_type())) {
-    return DCAST(CharacterSlider, part);
-  }
-
-  return NULL;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Character::write_parts
-//       Access: Published
-//  Description: Writes a list of the Character's joints and sliders,
-//               in their hierchical structure, to the indicated
-//               output stream.
-////////////////////////////////////////////////////////////////////
-INLINE void Character::
-write_parts(ostream &out) const {
-  get_bundle()->write(out, 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: Character::write_part_values
-//       Access: Published
-//  Description: Writes a list of the Character's joints and sliders,
-//               along with each current position, in their hierchical
-//               structure, to the indicated output stream.
-////////////////////////////////////////////////////////////////////
-INLINE void Character::
-write_part_values(ostream &out) const {
-  get_bundle()->write_with_value(out, 0);
-}

+ 126 - 30
panda/src/char/character.cxx

@@ -41,19 +41,24 @@ PStatCollector Character::_animation_pcollector("*:Animation");
 ////////////////////////////////////////////////////////////////////
 Character::
 Character(const Character &copy) :
-  PartBundleNode(copy, new CharacterJointBundle(copy.get_bundle()->get_name())),
+  PartBundleNode(copy),
   _parts(copy._parts),
   _joints_pcollector(copy._joints_pcollector),
   _skinning_pcollector(copy._skinning_pcollector)
 {
   set_cull_callback();
 
-  // Now make a copy of the joint/slider hierarchy.  We could just use
-  // the copy_subgraph feature of the PartBundleNode's copy
-  // constructor, but if we do it ourselves we can simultaneously
-  // update our _parts list.
+  // Copy the bundle(s).
+  int num_bundles = copy.get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    PartBundle *orig_bundle = copy.get_bundle(i);
+    PartBundle *new_bundle = 
+      new CharacterJointBundle(orig_bundle->get_name());
+    add_bundle(new_bundle);
 
-  copy_joints(get_bundle(), copy.get_bundle());
+    // Make a copy of the joint/slider hierarchy.
+    copy_joints(new_bundle, orig_bundle);
+  }
 
   _last_auto_update = -1.0;
 }
@@ -79,7 +84,10 @@ Character(const string &name) :
 ////////////////////////////////////////////////////////////////////
 Character::
 ~Character() {
-  r_clear_joint_characters(get_bundle());
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    r_clear_joint_characters(get_bundle(i));
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -99,28 +107,31 @@ make_copy() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: Character::safe_to_transform
+//     Function: Character::combine_with
 //       Access: Public, Virtual
-//  Description: Returns true if it is generally safe to transform
-//               this particular kind of Node by calling the xform()
-//               method, false otherwise.  For instance, it's usually
-//               a bad idea to attempt to xform a Character.
+//  Description: Collapses this node with the other node, if possible,
+//               and returns a pointer to the combined node, or NULL
+//               if the two nodes cannot safely be combined.
+//
+//               The return value may be this, other, or a new node
+//               altogether.
+//
+//               This function is called from GraphReducer::flatten(),
+//               and need not deal with children; its job is just to
+//               decide whether to collapse the two nodes and what the
+//               collapsed node should look like.
 ////////////////////////////////////////////////////////////////////
-bool Character::
-safe_to_transform() const {
-  return false;
-}
+PandaNode *Character::
+combine_with(PandaNode *other) {
+  if (is_exact_type(get_class_type()) &&
+      other->is_exact_type(get_class_type())) {
+    // Two Characters can combine by moving PartBundles from one to the other.
+    Character *c_other = DCAST(Character, other);
+    steal_bundles(c_other);
+    return this;
+  }
 
-////////////////////////////////////////////////////////////////////
-//     Function: Character::safe_to_flatten_below
-//       Access: Public, Virtual
-//  Description: Returns true if a flatten operation may safely
-//               continue past this node, or false if nodes below this
-//               node may not be molested.
-////////////////////////////////////////////////////////////////////
-bool Character::
-safe_to_flatten_below() const {
-  return false;
+  return PandaNode::combine_with(other);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -191,6 +202,78 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point, bool &found_any,
                                       found_any, transform, current_thread);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Character::find_joint
+//       Access: Published
+//  Description: Returns a pointer to the joint with the given name,
+//               if there is such a joint, or NULL if there is no such
+//               joint.  This will not return a pointer to a slider.
+////////////////////////////////////////////////////////////////////
+CharacterJoint *Character::
+find_joint(const string &name) const {
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    PartGroup *part = get_bundle(i)->find_child(name);
+    if (part != (PartGroup *)NULL &&
+        part->is_of_type(CharacterJoint::get_class_type())) {
+      return DCAST(CharacterJoint, part);
+    }
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Character::find_slider
+//       Access: Published
+//  Description: Returns a pointer to the slider with the given name,
+//               if there is such a slider, or NULL if there is no such
+//               slider.  This will not return a pointer to a joint.
+////////////////////////////////////////////////////////////////////
+CharacterSlider *Character::
+find_slider(const string &name) const {
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    PartGroup *part = get_bundle(i)->find_child(name);
+    if (part != (PartGroup *)NULL &&
+        part->is_of_type(CharacterSlider::get_class_type())) {
+      return DCAST(CharacterSlider, part);
+    }
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Character::write_parts
+//       Access: Published
+//  Description: Writes a list of the Character's joints and sliders,
+//               in their hierchical structure, to the indicated
+//               output stream.
+////////////////////////////////////////////////////////////////////
+void Character::
+write_parts(ostream &out) const {
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    get_bundle(i)->write(out, 0);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Character::write_part_values
+//       Access: Published
+//  Description: Writes a list of the Character's joints and sliders,
+//               along with each current position, in their hierchical
+//               structure, to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void Character::
+write_part_values(ostream &out) const {
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    get_bundle(i)->write_with_value(out, 0);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: Character::update_to_now
 //       Access: Published
@@ -242,7 +325,10 @@ force_update() {
   PStatTimer timer(_joints_pcollector);
 
   // Update all the joints and sliders.
-  get_bundle()->force_update();
+  int num_bundles = get_num_bundles();
+  for (int i = 0; i < num_bundles; ++i) {
+    get_bundle(i)->force_update();
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -255,9 +341,15 @@ void Character::
 do_update() {
   // Update all the joints and sliders.
   if (even_animation) {
-    get_bundle()->force_update();
+    int num_bundles = get_num_bundles();
+    for (int i = 0; i < num_bundles; ++i) {
+      get_bundle(i)->force_update();
+    }
   } else {
-    get_bundle()->update();
+    int num_bundles = get_num_bundles();
+    for (int i = 0; i < num_bundles; ++i) {
+      get_bundle(i)->update();
+    }
   }
 }
 
@@ -321,7 +413,11 @@ r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map,
   NodeMap node_map;
   JointMap joint_map;
 
-  fill_joint_map(joint_map, get_bundle(), from_char->get_bundle());
+  int num_bundles = get_num_bundles();
+  nassertv(from_char->get_num_bundles() == num_bundles);
+  for (int i = 0; i < num_bundles; ++i) {
+    fill_joint_map(joint_map, get_bundle(i), from_char->get_bundle(i));
+  }
 
   GeomVertexMap gvmap;
   GeomJointMap gjmap;

+ 6 - 7
panda/src/char/character.h

@@ -52,8 +52,7 @@ public:
 
   virtual PandaNode *make_copy() const;
 
-  virtual bool safe_to_transform() const;
-  virtual bool safe_to_flatten_below() const;
+  virtual PandaNode *combine_with(PandaNode *other); 
   virtual bool cull_callback(CullTraverser *trav, CullTraverserData &data);
 
   virtual CPT(TransformState)
@@ -63,16 +62,16 @@ public:
                       Thread *current_thread) const;
 
 PUBLISHED:
-  INLINE CharacterJointBundle *get_bundle() const;
+  INLINE CharacterJointBundle *get_bundle(int i) const;
 
   INLINE int get_num_parts() const;
   INLINE PartGroup *get_part(int n) const;
 
-  INLINE CharacterJoint *find_joint(const string &name) const;
-  INLINE CharacterSlider *find_slider(const string &name) const;
+  CharacterJoint *find_joint(const string &name) const;
+  CharacterSlider *find_slider(const string &name) const;
 
-  INLINE void write_parts(ostream &out) const;
-  INLINE void write_part_values(ostream &out) const;
+  void write_parts(ostream &out) const;
+  void write_part_values(ostream &out) const;
 
   void update_to_now();
   void update();

+ 22 - 7
panda/src/char/characterJoint.cxx

@@ -60,7 +60,7 @@ CharacterJoint(const CharacterJoint &copy) :
 ////////////////////////////////////////////////////////////////////
 CharacterJoint::
 CharacterJoint(Character *character,
-               PartGroup *parent, const string &name,
+               PartBundle *root, PartGroup *parent, const string &name,
                const LMatrix4f &initial_value) :
   MovingPartMatrix(parent, name, initial_value),
   _character(character)
@@ -69,7 +69,7 @@ CharacterJoint(Character *character,
 
   // Now that we've constructed and we're in the tree, let's call
   // update_internals() to get our _net_transform set properly.
-  update_internals(parent, true, false, current_thread);
+  update_internals(root, parent, true, false, current_thread);
 
   // And then compute its inverse.  This is needed for
   // ComputedVertices, during animation.
@@ -114,8 +114,8 @@ make_copy() const {
 //               transforms for this particular joint.
 ////////////////////////////////////////////////////////////////////
 bool CharacterJoint::
-update_internals(PartGroup *parent, bool self_changed, bool parent_changed,
-                 Thread *current_thread) {
+update_internals(PartBundle *root, PartGroup *parent, bool self_changed, 
+                 bool parent_changed, Thread *current_thread) {
 
   nassertr(parent != (PartGroup *)NULL, false);
 
@@ -131,10 +131,10 @@ update_internals(PartGroup *parent, bool self_changed, bool parent_changed,
     }
 
   } else {
-    // The joint *is* a toplevel joint, and the only thing that
-    // affects its net transform is the joint itself.
+    // The joint is a toplevel joint, so therefore it gets its root
+    // transform from the bundle.
     if (self_changed) {
-      _net_transform = _value;
+      _net_transform = _value * root->get_root_xform();
       net_changed = true;
     }
   }
@@ -176,6 +176,21 @@ update_internals(PartGroup *parent, bool self_changed, bool parent_changed,
   return self_changed || net_changed;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterJoint::do_xform
+//       Access: Public, Virtual
+//  Description: Called by PartBundle::xform(), this indicates the
+//               indicated transform is being applied to the root
+//               joint.
+////////////////////////////////////////////////////////////////////
+void CharacterJoint::
+do_xform(const LMatrix4f &mat, const LMatrix4f &inv_mat) {
+  _initial_net_transform_inverse = inv_mat * _initial_net_transform_inverse;
+
+  MovingPartMatrix::do_xform(mat, inv_mat);
+}
+
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CharacterJoint::add_net_transform

+ 5 - 3
panda/src/char/characterJoint.h

@@ -39,14 +39,16 @@ protected:
 
 public:
   CharacterJoint(Character *character,
-                 PartGroup *parent, const string &name,
+                 PartBundle *root, PartGroup *parent, const string &name,
                  const LMatrix4f &initial_value);
   virtual ~CharacterJoint();
 
   virtual PartGroup *make_copy() const;
 
-  virtual bool update_internals(PartGroup *parent, bool self_changed,
-                                bool parent_changed, Thread *current_thread);
+  virtual bool update_internals(PartBundle *root, PartGroup *parent, 
+                                bool self_changed, bool parent_changed, 
+                                Thread *current_thread);
+  virtual void do_xform(const LMatrix4f &mat, const LMatrix4f &inv_mat);
 
 PUBLISHED:
   bool add_net_transform(PandaNode *node);

+ 15 - 0
panda/src/char/characterJointEffect.cxx

@@ -53,6 +53,21 @@ make(Character *character) {
 ////////////////////////////////////////////////////////////////////
 bool CharacterJointEffect::
 safe_to_transform() const {
+  // We now accept that it will be OK to transform the joint--we allow
+  // this on the assumption that anything that transforms the joint
+  // will also transform the Character node, above the joint.
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterJointEffect::safe_to_combine
+//       Access: Public, Virtual
+//  Description: Returns true if this kind of effect can safely be
+//               combined with sibling nodes that share the exact same
+//               effect, or false if this is not a good idea.
+////////////////////////////////////////////////////////////////////
+bool CharacterJointEffect::
+safe_to_combine() const {
   return false;
 }
 

+ 1 - 0
panda/src/char/characterJointEffect.h

@@ -48,6 +48,7 @@ PUBLISHED:
 
 public:
   virtual bool safe_to_transform() const;
+  virtual bool safe_to_combine() const;
   virtual void output(ostream &out) const;
 
   virtual bool has_cull_callback() const;

+ 1 - 1
panda/src/char/characterSlider.cxx

@@ -88,7 +88,7 @@ make_copy() const {
 //               result of the update, or false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool CharacterSlider::
-update_internals(PartGroup *, bool, bool, Thread *current_thread) {
+update_internals(PartBundle *, PartGroup *, bool, bool, Thread *current_thread) {
   // Tell our related CharacterVertexSliders that they now need to
   // recompute themselves.
   VertexSliders::iterator vsi;

+ 3 - 2
panda/src/char/characterSlider.h

@@ -43,8 +43,9 @@ public:
 
   virtual PartGroup *make_copy() const;
 
-  virtual bool update_internals(PartGroup *parent, bool self_changed,
-                                bool parent_changed, Thread *current_thread);
+  virtual bool update_internals(PartBundle *root, PartGroup *parent, 
+                                bool self_changed, bool parent_changed, 
+                                Thread *current_thread);
 
 private:
   typedef pset<CharacterVertexSlider *> VertexSliders;

+ 9 - 3
panda/src/egg2pg/characterMaker.cxx

@@ -47,7 +47,7 @@ CharacterMaker(EggGroup *root, EggLoader &loader)
   : _loader(loader), _egg_root(root) {
 
   _character_node = new Character(_egg_root->get_name());
-  _bundle = _character_node->get_bundle();
+  _bundle = _character_node->get_bundle(0);
 
   _morph_root = (PartGroup *)NULL;
   _skeleton_root = new PartGroup(_bundle, "<skeleton>");
@@ -261,7 +261,8 @@ build_joint_hierarchy(EggNode *egg_node, PartGroup *part, int index) {
       LMatrix4f matf = LCAST(float, matd);
 
       CharacterJoint *joint =
-        new CharacterJoint(_character_node, part, egg_group->get_name(), matf);
+        new CharacterJoint(_character_node, _character_node->get_bundle(0),
+                           part, egg_group->get_name(), matf);
       index = _parts.size();
       _parts.push_back(joint);
 
@@ -269,7 +270,12 @@ build_joint_hierarchy(EggNode *egg_node, PartGroup *part, int index) {
         // If the joint requested an explicit DCS, create a node for
         // it.
         PT(ModelNode) geom_node = new ModelNode(egg_group->get_name());
-        geom_node->set_preserve_transform(ModelNode::PT_local);
+
+        // We don't need to insist on PT_local on this exposed node,
+        // as long as we assume that any operation that transforms
+        // this node will also transform the Character node above it.
+        //geom_node->set_preserve_transform(ModelNode::PT_local);
+
         joint->_geom_node = geom_node.p();
       }
 

+ 12 - 15
panda/src/pgraph/pandaNode.cxx

@@ -213,8 +213,7 @@ safe_to_flatten() const {
 //       Access: Public, Virtual
 //  Description: Returns true if it is generally safe to transform
 //               this particular kind of PandaNode by calling the
-//               xform() method, false otherwise.  For instance, it's
-//               usually a bad idea to attempt to xform a Character.
+//               xform() method, false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool PandaNode::
 safe_to_transform() const {
@@ -356,15 +355,6 @@ xform(const LMatrix4f &) {
 ////////////////////////////////////////////////////////////////////
 PandaNode *PandaNode::
 combine_with(PandaNode *other) {
-  // This is a little bit broken right now w.r.t. NodePaths, since any
-  // NodePaths attached to the lost node will simply be disconnected.
-  // This isn't the right thing to do; we should collapse those
-  // NodePaths with these NodePaths instead.  To do this properly, we
-  // will need to combine this functionality with that of stealing the
-  // other node's children into one method.  Not too difficult, but
-  // there are more pressing problems to work on right now.
-
-
   // An unadorned PandaNode always combines with any other PandaNodes by
   // yielding completely.  However, if we are actually some fancy PandaNode
   // type that derives from PandaNode but didn't redefine this function, we
@@ -1655,10 +1645,17 @@ replace_node(PandaNode *other) {
 
   // Switch the parents.
   Thread *current_thread = Thread::get_current_thread();
-  Parents parents = other->get_parents();
-  for (int i = 0; i < parents.get_num_parents(); ++i) {
-    PandaNode *parent = parents.get_parent(i);
-    parent->replace_child(other, this, current_thread);
+  Parents other_parents = other->get_parents();
+  for (int i = 0; i < other_parents.get_num_parents(); ++i) {
+    PandaNode *parent = other_parents.get_parent(i);
+    if (find_parent(parent) != -1) {
+      // This node was already a child of this parent; don't change
+      // it.
+      parent->remove_child(other);
+    } else {
+      // This node was not yet a child of this parent; now it is.
+      parent->replace_child(other, this, current_thread);
+    }
   }
 }
 

+ 4 - 27
panda/src/pgraph/sceneGraphReducer.cxx

@@ -520,15 +520,8 @@ do_flatten_child(PandaNode *grandparent_node, PandaNode *parent_node,
 
   choose_name(new_parent, parent_node, child_node);
 
-  if (new_parent != child_node) {
-    new_parent->steal_children(child_node);
-    new_parent->copy_tags(child_node);
-  }
-
-  if (new_parent != parent_node) {
-    grandparent_node->replace_child(parent_node, new_parent);
-    new_parent->copy_tags(parent_node);
-  }
+  new_parent->replace_node(child_node);
+  new_parent->replace_node(parent_node);
 
   return true;
 }
@@ -564,24 +557,8 @@ do_flatten_siblings(PandaNode *parent_node, PandaNode *child1,
 
   choose_name(new_child, child2, child1);
 
-  if (new_child == child1) {
-    new_child->steal_children(child2);
-    parent_node->remove_child(child2);
-    new_child->copy_tags(child2);
-
-  } else if (new_child == child2) {
-    new_child->steal_children(child1);
-    parent_node->remove_child(child1);
-    new_child->copy_tags(child1);
-
-  } else {
-    new_child->steal_children(child1);
-    new_child->steal_children(child2);
-    parent_node->remove_child(child2);
-    parent_node->replace_child(child1, new_child);
-    new_child->copy_tags(child1);
-    new_child->copy_tags(child2);
-  }
+  new_child->replace_node(child1);
+  new_child->replace_node(child2);
 
   return new_child;
 }

+ 2 - 1
panda/src/putil/bam.h

@@ -36,11 +36,12 @@ static const unsigned short _bam_major_ver = 6;
 // Bumped to major version 5 on 5/6/05 for new Geom implementation.
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 
-static const unsigned short _bam_minor_ver = 4;
+static const unsigned short _bam_minor_ver = 5;
 // Bumped to minor version 1 on 3/12/06 to add Texture::_compression.
 // Bumped to minor version 2 on 3/17/06 to add PandaNode::_draw_control_mask.
 // Bumped to minor version 3 on 3/21/06 to add Texture::_ram_images.
 // Bumped to minor version 4 on 7/26/06 to add CharacterJoint::_character.
+// Bumped to minor version 5 on 11/15/06 to add PartBundleNode::_num_bundles.
 
 
 #endif