Browse Source

pgraph flatten

David Rose 24 years ago
parent
commit
9c3a3c9c22

+ 2 - 0
direct/src/ffi/FFIRename.py

@@ -118,6 +118,7 @@ pgraphClassRenameDictionary = {
     'PGEntry'                   : 'SpPGEntry',
     'PGEntry'                   : 'SpPGEntry',
     'PGWaitBar'                 : 'SpPGWaitBar',
     'PGWaitBar'                 : 'SpPGWaitBar',
     'PartBundleNode'            : 'SpPartBundleNode',
     'PartBundleNode'            : 'SpPartBundleNode',
+    'SceneGraphReducer'         : 'SpSceneGraphReducer',
     'SequenceNode'              : 'SpSequenceNode',
     'SequenceNode'              : 'SpSequenceNode',
     'TextNode'                  : 'SpTextNode',
     'TextNode'                  : 'SpTextNode',
     'Trackball'                 : 'SpTrackball',
     'Trackball'                 : 'SpTrackball',
@@ -150,6 +151,7 @@ pgraphClassRenameDictionary = {
     'QpPGEntry'                 : 'PGEntry',
     'QpPGEntry'                 : 'PGEntry',
     'QpPGWaitBar'               : 'PGWaitBar',
     'QpPGWaitBar'               : 'PGWaitBar',
     'QpPartBundleNode'          : 'PartBundleNode',
     'QpPartBundleNode'          : 'PartBundleNode',
+    'QpSceneGraphReducer'       : 'SceneGraphReducer',
     'QpSequenceNode'            : 'SequenceNode',
     'QpSequenceNode'            : 'SequenceNode',
     'QpTextNode'                : 'TextNode',
     'QpTextNode'                : 'TextNode',
     'QpTrackball'               : 'Trackball',
     'QpTrackball'               : 'Trackball',

+ 1 - 0
direct/src/showbase/qpShowBase.py

@@ -216,6 +216,7 @@ class ShowBase:
         rendering 3-d geometry.
         rendering 3-d geometry.
         """
         """
         self.render = NodePath('render')
         self.render = NodePath('render')
+        self.render.setTwoSided(0)
 
 
     def setupRender2d(self):
     def setupRender2d(self):
         """setupRender2d(self)
         """setupRender2d(self)

+ 0 - 4
panda/src/egg2pg/qpeggLoader.cxx

@@ -1008,7 +1008,6 @@ setup_bucket(BuilderBucket &bucket, PandaNode *parent,
     break;
     break;
 
 
   default:
   default:
-    bucket.add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_none));
     break;
     break;
   }
   }
 
 
@@ -1050,9 +1049,6 @@ setup_bucket(BuilderBucket &bucket, PandaNode *parent,
     // The primitive is marked with backface culling disabled--we want
     // The primitive is marked with backface culling disabled--we want
     // to see both sides.
     // to see both sides.
     bucket.add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
     bucket.add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_none));
-
-  } else {
-    bucket.add_attrib(CullFaceAttrib::make(CullFaceAttrib::M_cull_clockwise));
   }
   }
 }
 }
 
 

+ 4 - 9
panda/src/egg2pg/qpload_egg_file.cxx

@@ -19,11 +19,8 @@
 #include "qpload_egg_file.h"
 #include "qpload_egg_file.h"
 #include "qpeggLoader.h"
 #include "qpeggLoader.h"
 #include "config_egg2pg.h"
 #include "config_egg2pg.h"
-
-/*
-#include "sceneGraphReducer.h"
+#include "qpsceneGraphReducer.h"
 #include "renderRelation.h"
 #include "renderRelation.h"
-*/
 
 
 static PT(PandaNode)
 static PT(PandaNode)
 load_from_loader(qpEggLoader &loader) {
 load_from_loader(qpEggLoader &loader) {
@@ -37,13 +34,11 @@ load_from_loader(qpEggLoader &loader) {
     return NULL;
     return NULL;
   }
   }
 
 
-  /*
-  if (loader._root != (NamedNode *)NULL && egg_flatten) {
-    SceneGraphReducer gr(RenderRelation::get_class_type());
+  if (loader._root != (PandaNode *)NULL && egg_flatten) {
+    qpSceneGraphReducer gr;
     int num_reduced = gr.flatten(loader._root, egg_flatten_siblings);
     int num_reduced = gr.flatten(loader._root, egg_flatten_siblings);
-    egg2pg_cat.info() << "Flattened " << num_reduced << " arcs.\n";
+    egg2pg_cat.info() << "Flattened " << num_reduced << " nodes.\n";
   }
   }
-  */
 
 
   return loader._root;
   return loader._root;
 }
 }

+ 6 - 0
panda/src/pgraph/Sources.pp

@@ -35,6 +35,7 @@
     qpfog.I qpfog.h \
     qpfog.I qpfog.h \
     fogAttrib.I fogAttrib.h \
     fogAttrib.I fogAttrib.h \
     qpgeomNode.I qpgeomNode.h \
     qpgeomNode.I qpgeomNode.h \
+    qpgeomTransformer.I qpgeomTransformer.h \
     qplensNode.I qplensNode.h \
     qplensNode.I qplensNode.h \
     qplodNode.I qplodNode.h \
     qplodNode.I qplodNode.h \
     materialAttrib.I materialAttrib.h \
     materialAttrib.I materialAttrib.h \
@@ -47,6 +48,7 @@
     renderEffects.I renderEffects.h \
     renderEffects.I renderEffects.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderState.I renderState.h \
     renderState.I renderState.h \
+    qpsceneGraphReducer.I qpsceneGraphReducer.h \
     selectiveChildNode.I selectiveChildNode.h \
     selectiveChildNode.I selectiveChildNode.h \
     qpsequenceNode.I qpsequenceNode.h \
     qpsequenceNode.I qpsequenceNode.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \
@@ -86,6 +88,7 @@
     qpfog.cxx \
     qpfog.cxx \
     fogAttrib.cxx \
     fogAttrib.cxx \
     qpgeomNode.cxx \
     qpgeomNode.cxx \
+    qpgeomTransformer.cxx \
     qplensNode.cxx \
     qplensNode.cxx \
     qplodNode.cxx \
     qplodNode.cxx \
     materialAttrib.cxx \
     materialAttrib.cxx \
@@ -98,6 +101,7 @@
     renderEffects.cxx \
     renderEffects.cxx \
     renderModeAttrib.cxx \
     renderModeAttrib.cxx \
     renderState.cxx \
     renderState.cxx \
+    qpsceneGraphReducer.cxx \
     selectiveChildNode.cxx \
     selectiveChildNode.cxx \
     qpsequenceNode.cxx \
     qpsequenceNode.cxx \
     texMatrixAttrib.cxx \
     texMatrixAttrib.cxx \
@@ -139,6 +143,7 @@
     qpfog.I qpfog.h \
     qpfog.I qpfog.h \
     fogAttrib.I fogAttrib.h \
     fogAttrib.I fogAttrib.h \
     qpgeomNode.I qpgeomNode.h \
     qpgeomNode.I qpgeomNode.h \
+    qpgeomTransformer.I qpgeomTransformer.h \
     qplensNode.I qplensNode.h \
     qplensNode.I qplensNode.h \
     qplodNode.I qplodNode.h \
     qplodNode.I qplodNode.h \
     materialAttrib.I materialAttrib.h \
     materialAttrib.I materialAttrib.h \
@@ -151,6 +156,7 @@
     renderEffects.I renderEffects.h \
     renderEffects.I renderEffects.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderModeAttrib.I renderModeAttrib.h \
     renderState.I renderState.h \
     renderState.I renderState.h \
+    qpsceneGraphReducer.I qpsceneGraphReducer.h \
     selectiveChildNode.I selectiveChildNode.h \
     selectiveChildNode.I selectiveChildNode.h \
     qpsequenceNode.I qpsequenceNode.h \
     qpsequenceNode.I qpsequenceNode.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \

+ 6 - 6
panda/src/pgraph/billboardEffect.cxx

@@ -47,15 +47,15 @@ make(const LVector3f &up_vector, bool eye_relative,
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: BillboardEffect::safe_to_combine
+//     Function: BillboardEffect::safe_to_transform
 //       Access: Public, Virtual
 //       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.
+//  Description: Returns true if it is generally safe to transform
+//               this particular kind of RenderEffect by calling the
+//               xform() method, false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool BillboardEffect::
 bool BillboardEffect::
-safe_to_combine() const {
-  return true;
+safe_to_transform() const {
+  return false;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/pgraph/billboardEffect.h

@@ -55,7 +55,7 @@ PUBLISHED:
   INLINE const LPoint3f &get_look_at_point() const;
   INLINE const LPoint3f &get_look_at_point() const;
 
 
 public:
 public:
-  virtual bool safe_to_combine() const;
+  virtual bool safe_to_transform() const;
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
 
 
   CPT(TransformState) do_billboard(const TransformState *net_transform,
   CPT(TransformState) do_billboard(const TransformState *net_transform,

+ 4 - 0
panda/src/pgraph/config_pgraph.cxx

@@ -70,6 +70,10 @@ ConfigureFn(config_pgraph) {
 // helps make culling errors obvious.
 // helps make culling errors obvious.
 const bool qpfake_view_frustum_cull = config_pgraph.GetBool("fake-view-frustum-cull", false);
 const bool qpfake_view_frustum_cull = config_pgraph.GetBool("fake-view-frustum-cull", false);
 
 
+// Set this true to make ambiguous path warning messages generate an
+// assertion failure instead of just a warning (which can then be
+// trapped with assert-abort).
+const bool unambiguous_graph = config_pgraph.GetBool("unambiguous-graph", false);
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libpgraph
 //     Function: init_libpgraph

+ 1 - 0
panda/src/pgraph/config_pgraph.h

@@ -27,6 +27,7 @@ ConfigureDecl(config_pgraph, EXPCL_PANDA, EXPTP_PANDA);
 NotifyCategoryDecl(pgraph, EXPCL_PANDA, EXPTP_PANDA);
 NotifyCategoryDecl(pgraph, EXPCL_PANDA, EXPTP_PANDA);
 
 
 extern const bool qpfake_view_frustum_cull;
 extern const bool qpfake_view_frustum_cull;
+extern const bool unambiguous_graph;
 
 
 extern EXPCL_PANDA void init_libpgraph();
 extern EXPCL_PANDA void init_libpgraph();
 
 

+ 1 - 1
panda/src/pgraph/cullFaceAttrib.h

@@ -38,7 +38,7 @@ PUBLISHED:
   };
   };
 
 
 private:
 private:
-  INLINE CullFaceAttrib(Mode mode = M_cull_none);
+  INLINE CullFaceAttrib(Mode mode = M_cull_clockwise);
 
 
 PUBLISHED:
 PUBLISHED:
   static CPT(RenderAttrib) make(Mode mode);
   static CPT(RenderAttrib) make(Mode mode);

+ 12 - 0
panda/src/pgraph/decalEffect.cxx

@@ -35,6 +35,18 @@ make() {
   return return_new(effect);
   return return_new(effect);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DecalEffect::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 DecalEffect::
+safe_to_combine() const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DecalEffect::compare_to_impl
 //     Function: DecalEffect::compare_to_impl
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual

+ 1 - 0
panda/src/pgraph/decalEffect.h

@@ -37,6 +37,7 @@ PUBLISHED:
   static CPT(RenderEffect) make();
   static CPT(RenderEffect) make();
 
 
 protected:
 protected:
+  virtual bool safe_to_combine() const;
   virtual int compare_to_impl(const RenderEffect *other) const;
   virtual int compare_to_impl(const RenderEffect *other) const;
   virtual RenderEffect *make_default_impl() const;
   virtual RenderEffect *make_default_impl() const;
 
 

+ 78 - 0
panda/src/pgraph/pandaNode.I

@@ -52,6 +52,16 @@ get_child() const {
   return _child;
   return _child;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::DownConnection::set_child
+//       Access: Public
+//  Description: This is only called by PandaNode::replace_child().
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::DownConnection::
+set_child(PandaNode *child) {
+  _child = child;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::DownConnection::get_sort
 //     Function: PandaNode::DownConnection::get_sort
 //       Access: Public
 //       Access: Public
@@ -151,6 +161,16 @@ Children(const PandaNode::Children &copy) :
 {
 {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::Children::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::Children::
+operator = (const PandaNode::Children &copy) {
+  _cdata = copy._cdata;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Children::get_num_children
 //     Function: PandaNode::Children::get_num_children
 //       Access: Public
 //       Access: Public
@@ -172,6 +192,48 @@ get_child(int n) const {
   return _cdata->_down[n].get_child();
   return _cdata->_down[n].get_child();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ChildrenCopy::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::ChildrenCopy::
+ChildrenCopy(const PandaNode::ChildrenCopy &copy) :
+  _list(copy._list)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ChildrenCopy::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::ChildrenCopy::
+operator = (const PandaNode::ChildrenCopy &copy) {
+  _list = copy._list;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ChildrenCopy::get_num_children
+//       Access: Public
+//  Description: Returns the number of children of the node.
+////////////////////////////////////////////////////////////////////
+INLINE int PandaNode::ChildrenCopy::
+get_num_children() const {
+  return _list.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ChildrenCopy::get_child
+//       Access: Public
+//  Description: Returns the nth child of the node.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *PandaNode::ChildrenCopy::
+get_child(int n) const {
+  nassertr(n >= 0 && n < (int)_list.size(), NULL);
+  return _list[n];
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_num_parents
 //     Function: PandaNode::get_num_parents
 //       Access: Published
 //       Access: Published
@@ -715,3 +777,19 @@ get_children() const {
   return Children(cdata);
   return Children(cdata);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_children_copy
+//       Access: Public
+//  Description: Returns an object that can be used to walk through
+//               the list of children of the node.  Unlike
+//               get_children(), this function actually returns an
+//               object that protects you from self-modifying loops,
+//               because it makes and returns a copy of the complete
+//               children list.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::ChildrenCopy PandaNode::
+get_children_copy() const {
+  CDReader cdata(_cycler);
+  return ChildrenCopy(cdata);
+}
+

+ 432 - 205
panda/src/pgraph/pandaNode.cxx

@@ -27,6 +27,79 @@
 
 
 TypeHandle PandaNode::_type_handle;
 TypeHandle PandaNode::_type_handle;
 
 
+// 
+// We go through some considerable effort in this class to ensure that
+// NodePaths are kept consistent as we attach and detach nodes.  We
+// must enforce the following rules:
+//
+// 1) Each NodePath (i.e. chain of NodePathComponents) represents a
+// complete unbroken chain from a PandaNode to the root of the graph.
+//
+// 2) Each NodePathComponent chain is unique.  There are no two
+// different NodePathComponents that reference the same path to the
+// root.
+//
+// 3) If a PandaNode with no parents is attached to a new parent, all
+// NodePaths that previously indicated this node as the root of graph
+// must now be updated to include the complete chain to the new root.
+//
+// 4) If a PandaNode with other parents is attached to a new parent,
+// any previously existing NodePaths are not affected.
+//
+// 5) If a PandaNode is disconnected from its parent, and it has no
+// other parents, all NodePaths that previously passed through this
+// node to the old parent must now be updated to indicate this node is
+// now the root.
+//
+// 6) If a PandaNode is disconnected from its parent, and it has at
+// least one other parent, all NodePaths that previously passed
+// through this node to the old parent must now be updated to pass
+// through one of the other parents instead.
+//
+// Rules (5) and (6) can especially complicate things because they
+// introduce the possibility that two formerly distinct NodePaths are
+// now equivalent, which violates rule (2).  For example, if A is the
+// top of the graph with children B and C, and D is instanced to both
+// B and C, and E is a child of D, there might be two different
+// NodePaths to D: A/B/D/E and A/C/D/E.  If you then break the
+// connection between D and E, both NodePaths must now become just the
+// singleton E.
+//
+// Unfortunately, we cannot simply remove one of the extra
+// NodePathComponents, because there may be any number of NodePath
+// objects that reference them.  Instead, we define the concept of
+// "collapsed" NodePathComponents, which means one NodePathComponent
+// can be "collapsed" into a different one so that any attempt to
+// reference the first actually retrieves the second, rather like a
+// symbolic link in the file system.  When the NodePath traverses its
+// component chain, it will pass right over the collapsed component in
+// favor of the one it has been collapsed into.
+//
+
+
+//
+// There are two different interfaces here for making and breaking
+// parent-child connections: the fundamental PandaNode interface, via
+// add_child() and remove_child() (and related functions), and the
+// NodePath support interface, via attach(), detach(), and reparent().
+// They both do essentially the same thing, but with slightly
+// different inputs.  Both are responsible for keeping all NodePaths
+// up-to-date according to the above rules.
+//
+// The NodePath support interface functions are strictly called from
+// within the NodePath class, and are used to implement
+// NodePath::reparent_to() and NodePath::remove_node(), etc.  The
+// fundamental interface, on the other hand, is designed to be called
+// directly by the user.
+//
+// The fundamental interface has a slightly lower overhead because it
+// does not need to create a NodePathComponent chain where one does
+// not already exist; however, the NodePath support interface is more
+// useful when the NodePath already does exist, because it ensures
+// that the particular NodePath calling it is kept appropriately
+// up-to-date.
+//
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::CData::make_copy
 //     Function: PandaNode::CData::make_copy
@@ -260,6 +333,20 @@ fillin_down_list(PandaNode::Down &down_list,
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::ChildrenCopy::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+PandaNode::ChildrenCopy::
+ChildrenCopy(const PandaNode::CDReader &cdata) {
+  Children cr(cdata);
+  int num_children = cr.get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    _list.push_back(cr.get_child(i));
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::Constructor
 //     Function: PandaNode::Constructor
 //       Access: Published
 //       Access: Published
@@ -396,6 +483,18 @@ safe_to_combine() const {
   return true;
   return true;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::preserve_name
+//       Access: Public, Virtual
+//  Description: Returns true if the node's name has extrinsic meaning
+//               and must be preserved across a flatten operation,
+//               false otherwise.
+////////////////////////////////////////////////////////////////////
+bool PandaNode::
+preserve_name() const {
+  return false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::xform
 //     Function: PandaNode::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -577,20 +676,8 @@ add_child(PandaNode *child_node, int sort) {
   
   
   cdata->_down.insert(DownConnection(child_node, sort));
   cdata->_down.insert(DownConnection(child_node, sort));
   cdata_child->_up.insert(UpConnection(this));
   cdata_child->_up.insert(UpConnection(this));
-  
-  // We also have to adjust any qpNodePathComponents the child might
-  // have that reference the child as a top node.  Any other
-  // components we can leave alone, because we are making a new
-  // instance of the child.
-  Paths::iterator pi;
-  for (pi = cdata_child->_paths.begin();
-       pi != cdata_child->_paths.end();
-       ++pi) {
-    if ((*pi)->is_top_node()) {
-      (*pi)->set_next(get_generic_component());
-    }
-  }
-  child_node->fix_path_lengths(cdata_child);
+
+  new_connection(this, child_node);
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
@@ -616,35 +703,8 @@ remove_child(int n) {
   cdata->_down.erase(cdata->_down.begin() + n);
   cdata->_down.erase(cdata->_down.begin() + n);
   int num_erased = cdata_child->_up.erase(UpConnection(this));
   int num_erased = cdata_child->_up.erase(UpConnection(this));
   nassertv(num_erased == 1);
   nassertv(num_erased == 1);
-  
-  // Now sever any qpNodePathComponents on the child that reference
-  // this node.  If we have multiple of these, we have to collapse
-  // them together.
-  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
-  Paths::iterator pi;
-  pi = cdata_child->_paths.begin();
-  while (pi != cdata_child->_paths.end()) {
-    Paths::iterator pnext = pi;
-    ++pnext;
-    if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == this) {
-      if (collapsed == (qpNodePathComponent *)NULL) {
-        (*pi)->set_top_node();
-        collapsed = (*pi);
-      } else {
-        // This is a different component that used to reference a
-        // different instance, but now it's all just the same topnode.
-        // We have to collapse this and the previous one together.
-        // However, there might be some qpNodePaths out there that
-        // still keep a pointer to this one, so we can't remove it
-        // altogether.
-        (*pi)->collapse_with(collapsed);
-        cdata_child->_paths.erase(pi);
-      }
-    }
-    pi = pnext;
-  }
-  
-  child_node->fix_path_lengths(cdata_child);
+
+  sever_connection(this, child_node);
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
@@ -659,68 +719,115 @@ remove_child(int n) {
 //       Access: Published
 //       Access: Published
 //  Description: Removes the indicated child from the node.  Returns
 //  Description: Removes the indicated child from the node.  Returns
 //               true if the child was removed, false if it was not
 //               true if the child was removed, false if it was not
-//               already a child of the node.
+//               already a child of the node.  This will also
+//               successfully remove the child if it had been stashed.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PandaNode::
 bool PandaNode::
 remove_child(PandaNode *child_node) {
 remove_child(PandaNode *child_node) {
-  // Ensure the child_node is not deleted while we do this.
-  PT(PandaNode) keep_child = child_node;
-    
-  CDWriter cdata(_cycler);
-  CDWriter cdata_child(child_node->_cycler);
-  
-  // First, look for and remove this node from the child's parent
-  // list.
-  int num_erased = cdata_child->_up.erase(UpConnection(this));
-  if (num_erased == 0) {
-    // No such node; it wasn't our child to begin with.
+  // First, look for the parent in the child's up list, to ensure the
+  // child is known.
+  int parent_index = child_node->find_parent(this);
+  if (parent_index < 0) {
+    // Nope, no relation.
     return false;
     return false;
   }
   }
-  
-  // Now sever any qpNodePathComponents on the child that reference
-  // this node.  If we have multiple of these, we have to collapse
-  // them together (see above).
-  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
-  Paths::iterator pi;
-  pi = cdata_child->_paths.begin();
-  while (pi != cdata_child->_paths.end()) {
-    Paths::iterator pnext = pi;
-    ++pnext;
-    if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == this) {
-      if (collapsed == (qpNodePathComponent *)NULL) {
-        (*pi)->set_top_node();
-        collapsed = (*pi);
-      } else {
-        (*pi)->collapse_with(collapsed);
-        cdata_child->_paths.erase(pi);
-      }
-    }
-    pi = pnext;
+
+  int child_index = find_child(child_node);
+  if (child_index >= 0) {
+    // The child exists; remove it.
+    remove_child(child_index);
+    return true;
+  }
+
+  int stashed_index = find_stashed(child_node);
+  if (stashed_index >= 0) {
+    // The child has been stashed; remove it.
+    remove_stashed(stashed_index);
+    return true;
+  }
+
+  // Never heard of this child.  This shouldn't be possible, because
+  // the parent was in the child's up list, above.  Must be some
+  // internal error.
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::replace_child
+//       Access: Published
+//  Description: Searches for the orig_child node in the node's list
+//               of children, and replaces it with the new_child
+//               instead.  Returns true if the replacement is made, or
+//               false if the node is not a child.
+////////////////////////////////////////////////////////////////////
+bool PandaNode::
+replace_child(PandaNode *orig_child, PandaNode *new_child) {
+  // First, look for the parent in the child's up list, to ensure the
+  // child is known.
+  int parent_index = orig_child->find_parent(this);
+  if (parent_index < 0) {
+    // Nope, no relation.
+    return false;
+  }
+
+  if (orig_child == new_child) {
+    // Trivial no-op.
+    return true;
   }
   }
   
   
-  child_node->fix_path_lengths(cdata_child);
-  
-  // Now, look for and remove the child node from our down list.
-  Down::iterator di;
-  bool found = false;
-  for (di = cdata->_down.begin(); di != cdata->_down.end() && !found; ++di) {
-    if ((*di).get_child() == child_node) {
-      cdata->_down.erase(di);
-      found = true;
+  // Don't let orig_child be destructed yet.
+  PT(PandaNode) keep_orig_child = orig_child;
+
+  int child_index = find_child(orig_child);
+  if (child_index >= 0) {
+    // The child exists; replace it.
+    CDWriter cdata(_cycler);
+    DownConnection &down = cdata->_down[child_index];
+    nassertr(down.get_child() == orig_child, false);
+    down.set_child(new_child);
+
+  } else {
+    int stashed_index = find_stashed(orig_child);
+    if (stashed_index >= 0) {
+      // The child has been stashed; remove it.
+      CDWriter cdata(_cycler);
+      DownConnection &down = cdata->_stashed[stashed_index];
+      nassertr(down.get_child() == orig_child, false);
+      down.set_child(new_child);
+
+    } else {
+      // Never heard of this child.  This shouldn't be possible, because
+      // the parent was in the child's up list, above.  Must be some
+      // internal error.
+      nassertr(false, false);
+      return false;
     }
     }
   }
   }
+
+  // Now adjust the bookkeeping on both children.
+
+  CDWriter cdata_orig_child(orig_child->_cycler);
+  CDWriter cdata_new_child(new_child->_cycler);
   
   
-  nassertr(found, false);
+  cdata_new_child->_up.insert(UpConnection(this));
+  int num_erased = cdata_orig_child->_up.erase(UpConnection(this));
+  nassertr(num_erased == 1, false);
+
+  sever_connection(this, orig_child);
+  orig_child->parents_changed();
+
+  new_connection(this, new_child);
+  new_child->parents_changed();
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
-
-  // Call callback hooks.
   children_changed();
   children_changed();
-  child_node->parents_changed();
+
   return true;
   return true;
 }
 }
 
 
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::stash_child
 //     Function: PandaNode::stash_child
 //       Access: Published
 //       Access: Published
@@ -745,20 +852,8 @@ stash_child(int child_index) {
   
   
   cdata->_stashed.insert(DownConnection(child_node, sort));
   cdata->_stashed.insert(DownConnection(child_node, sort));
   cdata_child->_up.insert(UpConnection(this));
   cdata_child->_up.insert(UpConnection(this));
-  
-  // We also have to adjust any qpNodePathComponents the child might
-  // have that reference the child as a top node.  Any other
-  // components we can leave alone, because we are making a new
-  // instance of the child.
-  Paths::iterator pi;
-  for (pi = cdata_child->_paths.begin();
-       pi != cdata_child->_paths.end();
-       ++pi) {
-    if ((*pi)->is_top_node()) {
-      (*pi)->set_next(get_generic_component());
-    }
-  }
-  child_node->fix_path_lengths(cdata_child);
+
+  new_connection(this, child_node);
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
@@ -792,20 +887,8 @@ unstash_child(int stashed_index) {
   
   
   cdata->_down.insert(DownConnection(child_node, sort));
   cdata->_down.insert(DownConnection(child_node, sort));
   cdata_child->_up.insert(UpConnection(this));
   cdata_child->_up.insert(UpConnection(this));
-  
-  // We also have to adjust any qpNodePathComponents the child might
-  // have that reference the child as a top node.  Any other
-  // components we can leave alone, because we are making a new
-  // instance of the child.
-  Paths::iterator pi;
-  for (pi = cdata_child->_paths.begin();
-       pi != cdata_child->_paths.end();
-       ++pi) {
-    if ((*pi)->is_top_node()) {
-      (*pi)->set_next(get_generic_component());
-    }
-  }
-  child_node->fix_path_lengths(cdata_child);
+
+  new_connection(this, child_node);
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
@@ -837,12 +920,40 @@ find_stashed(PandaNode *node) const {
   return -1;
   return -1;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::add_stashed
+//       Access: Published
+//  Description: Adds a new child to the node, directly as a stashed
+//               child.  The child is not added in the normal sense,
+//               but will be revealed if unstash_child() is called on
+//               it later.
+//
+//               If the same child is added to a node more than once,
+//               the previous instance is first removed.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+add_stashed(PandaNode *child_node, int sort) {
+  // Ensure the child_node is not deleted while we do this.
+  PT(PandaNode) keep_child = child_node;
+  remove_child(child_node);
+  
+  CDWriter cdata(_cycler);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_stashed.insert(DownConnection(child_node, sort));
+  cdata_child->_up.insert(UpConnection(this));
+
+  new_connection(this, child_node);
+
+  // Call callback hooks.
+  children_changed();
+  child_node->parents_changed();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::remove_stashed
 //     Function: PandaNode::remove_stashed
 //       Access: Published
 //       Access: Published
-//  Description: Removes the nth stashed child from the node.  This is
-//               the only way to remove a child from the node that has
-//               previously been stashed, without unstashing it first.
+//  Description: Removes the nth stashed child from the node.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 void PandaNode::
 remove_stashed(int n) {
 remove_stashed(int n) {
@@ -855,35 +966,8 @@ remove_stashed(int n) {
   cdata->_stashed.erase(cdata->_stashed.begin() + n);
   cdata->_stashed.erase(cdata->_stashed.begin() + n);
   int num_erased = cdata_child->_up.erase(UpConnection(this));
   int num_erased = cdata_child->_up.erase(UpConnection(this));
   nassertv(num_erased == 1);
   nassertv(num_erased == 1);
-  
-  // Now sever any qpNodePathComponents on the child that reference
-  // this node.  If we have multiple of these, we have to collapse
-  // them together.
-  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
-  Paths::iterator pi;
-  pi = cdata_child->_paths.begin();
-  while (pi != cdata_child->_paths.end()) {
-    Paths::iterator pnext = pi;
-    ++pnext;
-    if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == this) {
-      if (collapsed == (qpNodePathComponent *)NULL) {
-        (*pi)->set_top_node();
-        collapsed = (*pi);
-      } else {
-        // This is a different component that used to reference a
-        // different instance, but now it's all just the same topnode.
-        // We have to collapse this and the previous one together.
-        // However, there might be some qpNodePaths out there that
-        // still keep a pointer to this one, so we can't remove it
-        // altogether.
-        (*pi)->collapse_with(collapsed);
-        cdata_child->_paths.erase(pi);
-      }
-    }
-    pi = pnext;
-  }
-  
-  child_node->fix_path_lengths(cdata_child);
+
+  sever_connection(this, child_node);
 
 
   // Mark the bounding volumes stale.
   // Mark the bounding volumes stale.
   force_bound_stale();
   force_bound_stale();
@@ -907,29 +991,8 @@ remove_all_children() {
     PT(PandaNode) child_node = (*di).get_child();
     PT(PandaNode) child_node = (*di).get_child();
     CDWriter cdata_child(child_node->_cycler);
     CDWriter cdata_child(child_node->_cycler);
     cdata_child->_up.erase(UpConnection(this));
     cdata_child->_up.erase(UpConnection(this));
-    
-    // Now sever any qpNodePathComponents on the child that
-    // reference this node.  If we have multiple of these, we have
-    // to collapse them together (see above).
-    qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
-    Paths::iterator pi;
-    pi = cdata_child->_paths.begin();
-    while (pi != cdata_child->_paths.end()) {
-      Paths::iterator pnext = pi;
-      ++pnext;
-      if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == this) {
-        if (collapsed == (qpNodePathComponent *)NULL) {
-          (*pi)->set_top_node();
-          collapsed = (*pi);
-        } else {
-          (*pi)->collapse_with(collapsed);
-          cdata_child->_paths.erase(pi);
-        }
-      }
-      pi = pnext;
-    }
-    
-    child_node->fix_path_lengths(cdata_child);
+
+    sever_connection(this, child_node);
     child_node->parents_changed();
     child_node->parents_changed();
   }
   }
   cdata->_down.clear();
   cdata->_down.clear();
@@ -938,29 +1001,8 @@ remove_all_children() {
     PT(PandaNode) child_node = (*di).get_child();
     PT(PandaNode) child_node = (*di).get_child();
     CDWriter cdata_child(child_node->_cycler);
     CDWriter cdata_child(child_node->_cycler);
     cdata_child->_up.erase(UpConnection(this));
     cdata_child->_up.erase(UpConnection(this));
-    
-    // Now sever any qpNodePathComponents on the child that
-    // reference this node.  If we have multiple of these, we have
-    // to collapse them together (see above).
-    qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
-    Paths::iterator pi;
-    pi = cdata_child->_paths.begin();
-    while (pi != cdata_child->_paths.end()) {
-      Paths::iterator pnext = pi;
-      ++pnext;
-      if (!(*pi)->is_top_node() && (*pi)->get_next()->get_node() == this) {
-        if (collapsed == (qpNodePathComponent *)NULL) {
-          (*pi)->set_top_node();
-          collapsed = (*pi);
-        } else {
-          (*pi)->collapse_with(collapsed);
-          cdata_child->_paths.erase(pi);
-        }
-      }
-      pi = pnext;
-    }
-    
-    child_node->fix_path_lengths(cdata_child);
+
+    sever_connection(this, child_node);
     child_node->parents_changed();
     child_node->parents_changed();
   }
   }
   cdata->_stashed.clear();
   cdata->_stashed.clear();
@@ -970,6 +1012,69 @@ remove_all_children() {
   children_changed();
   children_changed();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::steal_children
+//       Access: Published
+//  Description: Moves all the children from the other node onto this
+//               node.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+steal_children(PandaNode *other) {
+  if (other == this) {
+    // Trivial.
+    return;
+  }
+
+  // We do this through the high-level interface for convenience.
+  // This could begin to be a problem if we have a node with hundreds
+  // of children to copy; this could break down the ov_set.insert()
+  // method, which is an O(n^2) operation.  If this happens, we should
+  // rewrite this to do a simpler add_child() operation that involves
+  // push_back() instead of insert(), and then sort the down list at
+  // the end.
+
+  int num_children = other->get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child_node = other->get_child(i);
+    int sort = other->get_child_sort(i);
+    add_child(child_node, sort);
+  }
+  int num_stashed = other->get_num_stashed();
+  for (int i = 0; i < num_stashed; i++) {
+    PandaNode *child_node = other->get_stashed(i);
+    int sort = other->get_stashed_sort(i);
+    add_stashed(child_node, sort);
+  }
+
+  other->remove_all_children();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::copy_children
+//       Access: Published
+//  Description: Makes another instance of all the children of the
+//               other node, copying them to this node.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+copy_children(PandaNode *other) {
+  if (other == this) {
+    // Trivial.
+    return;
+  }
+  int num_children = other->get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child_node = other->get_child(i);
+    int sort = other->get_child_sort(i);
+    add_child(child_node, sort);
+  }
+  int num_stashed = other->get_num_stashed();
+  for (int i = 0; i < num_stashed; i++) {
+    PandaNode *child_node = other->get_stashed(i);
+    int sort = other->get_stashed_sort(i);
+    add_stashed(child_node, sort);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::output
 //     Function: PandaNode::output
 //       Access: Published, Virtual
 //       Access: Published, Virtual
@@ -1191,6 +1296,7 @@ r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
   CDReader from_cdata(from->_cycler);
   CDReader from_cdata(from->_cycler);
   Down::const_iterator di;
   Down::const_iterator di;
   for (di = from_cdata->_down.begin(); di != from_cdata->_down.end(); ++di) {
   for (di = from_cdata->_down.begin(); di != from_cdata->_down.end(); ++di) {
+    int sort = (*di).get_sort();
     PandaNode *source_child = (*di).get_child();
     PandaNode *source_child = (*di).get_child();
     PT(PandaNode) dest_child;
     PT(PandaNode) dest_child;
 
 
@@ -1207,7 +1313,7 @@ r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
       inst_map[source_child] = dest_child;
       inst_map[source_child] = dest_child;
     }
     }
 
 
-    add_child(dest_child);
+    add_child(dest_child, sort);
   }
   }
 }
 }
 
 
@@ -1230,7 +1336,7 @@ attach(qpNodePathComponent *parent, PandaNode *child_node, int sort) {
   if (child == (qpNodePathComponent *)NULL) {
   if (child == (qpNodePathComponent *)NULL) {
     // The child was not already attached to the parent, so get a new
     // The child was not already attached to the parent, so get a new
     // component.
     // component.
-    child = get_top_component(child_node);
+    child = get_top_component(child_node, true);
   }
   }
 
 
   reparent(parent, child, sort);
   reparent(parent, child, sort);
@@ -1403,9 +1509,14 @@ get_component(qpNodePathComponent *parent, PandaNode *child_node) {
 //               this for a node that has parents, unless you are
 //               this for a node that has parents, unless you are
 //               about to create a new instance (and immediately
 //               about to create a new instance (and immediately
 //               reconnect the qpNodePathComponent elsewhere).
 //               reconnect the qpNodePathComponent elsewhere).
+//
+//               If force is true, this will always return something,
+//               even if it needs to create a new top component;
+//               otherwise, if force is false, it will return NULL if
+//               there is not already a top component available.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PT(qpNodePathComponent) PandaNode::
 PT(qpNodePathComponent) PandaNode::
-get_top_component(PandaNode *child_node) {
+get_top_component(PandaNode *child_node, bool force) {
   {
   {
     CDReader cdata_child(child_node->_cycler);
     CDReader cdata_child(child_node->_cycler);
 
 
@@ -1422,6 +1533,12 @@ get_top_component(PandaNode *child_node) {
     }
     }
   }
   }
 
 
+  if (!force) {
+    // If we don't care to force the point, return NULL to indicate
+    // there's not already a top component.
+    return NULL;
+  }
+
   // We don't already have such a qpNodePathComponent; create and
   // We don't already have such a qpNodePathComponent; create and
   // return a new one.
   // return a new one.
   PT(qpNodePathComponent) child = 
   PT(qpNodePathComponent) child = 
@@ -1445,17 +1562,28 @@ PT(qpNodePathComponent) PandaNode::
 get_generic_component() {
 get_generic_component() {
   int num_parents = get_num_parents();
   int num_parents = get_num_parents();
   if (num_parents == 0) {
   if (num_parents == 0) {
-    return get_top_component(this);
+    // No parents; no ambiguity.  This is the root.
+    return get_top_component(this, true);
+  } 
+
+  PT(qpNodePathComponent) result;
+  if (num_parents == 1) {
+    // Only one parent; no ambiguity.
+    PT(qpNodePathComponent) parent = get_parent(0)->get_generic_component();
+    result = get_component(parent, this);
 
 
   } else {
   } else {
-    if (num_parents != 1) {
-      pgraph_cat.warning()
-        << *this << " has " << num_parents
-        << " parents; choosing arbitrary path to root.\n";
-    }
+    // Oops, multiple parents; the NodePath is ambiguous.
+    pgraph_cat.warning()
+      << *this << " has " << num_parents
+      << " parents; choosing arbitrary path to root.\n";
+  
     PT(qpNodePathComponent) parent = get_parent(0)->get_generic_component();
     PT(qpNodePathComponent) parent = get_parent(0)->get_generic_component();
-    return get_component(parent, this);
+    result = get_component(parent, this);
+    nassertr(!unambiguous_graph, result);
   }
   }
+
+  return result;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -1480,7 +1608,106 @@ delete_component(qpNodePathComponent *component) {
       _cycler.release_write_stage(i, cdata);
       _cycler.release_write_stage(i, cdata);
     }
     }
   }
   }
-  nassertv(max_num_erased == 1);
+
+  // This may legitimately be zero if we are deleting a collapsed NodePath.
+  //  nassertv(max_num_erased == 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::sever_connection
+//       Access: Private, Static
+//  Description: This is called internally when a parent-child
+//               connection is broken to update the NodePathComponents
+//               that reflected this connection.
+//
+//               It severs any NodePathComponents on the child node
+//               that reference the indicated parent node.  If the
+//               child node has additional parents, chooses one of
+//               them arbitrarily instead.  Collapses together
+//               instances below this node that used to differentiate
+//               on some instance above the old parent.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+sever_connection(PandaNode *parent_node, PandaNode *child_node) {
+  CDWriter cdata_child(child_node->_cycler);
+  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
+
+  Paths::iterator pi;
+  pi = cdata_child->_paths.begin();
+  while (pi != cdata_child->_paths.end()) {
+    Paths::iterator pnext = pi;
+    ++pnext;
+    if (!(*pi)->is_top_node() && 
+        (*pi)->get_next()->get_node() == parent_node) {
+      if (collapsed == (qpNodePathComponent *)NULL) {
+
+        // This is the first component we've found that references
+        // this node.  Should we sever it or reattach it?
+        if (child_node->get_num_parents() == 0) {
+          // If the node no longer has any parents, all of its paths will be
+          // severed here.  Collapse them all with the existing top
+          // component if there is one.
+          collapsed = get_top_component(child_node, false);
+          
+        } else {
+          // If the node still has one parent, all of its paths that
+          // referenced the old parent will be combined with the remaining
+          // parent.  If there are multiple parents, choose one arbitrarily
+          // to combine with.
+          collapsed = child_node->get_generic_component();
+        }
+
+        if (collapsed == (qpNodePathComponent *)NULL) {
+          // Sever the component here; there's nothing to attach it
+          // to.
+          (*pi)->set_top_node();
+          collapsed = (*pi);
+
+        } else {
+          // Collapse the new component with the pre-existing
+          // component.
+          (*pi)->collapse_with(collapsed);
+          cdata_child->_paths.erase(pi);
+        }
+
+      } else {
+        // This is the second (or later) component we've found that
+        // references this node.  We should collapse it with the first
+        // one.
+        (*pi)->collapse_with(collapsed);
+        cdata_child->_paths.erase(pi);
+      }
+    }
+    pi = pnext;
+  }
+  child_node->fix_path_lengths(cdata_child);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::new_connection
+//       Access: Private, Static
+//  Description: This is called internally when a parent-child
+//               connection is establshed to update the
+//               NodePathComponents that might be involved.
+//
+//               It adjusts any NodePathComponents the child has that
+//               reference the child as a top node.  Any other
+//               components we can leave alone, because we are making
+//               a new instance of the child.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+new_connection(PandaNode *parent_node, PandaNode *child_node) {
+  CDWriter cdata_child(child_node->_cycler);
+
+  Paths::iterator pi;
+  for (pi = cdata_child->_paths.begin();
+       pi != cdata_child->_paths.end();
+       ++pi) {
+    if ((*pi)->is_top_node()) {
+      (*pi)->set_next(parent_node->get_generic_component());
+    }
+  }
+  child_node->fix_path_lengths(cdata_child);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 34 - 2
panda/src/pgraph/pandaNode.h

@@ -36,6 +36,7 @@
 #include "luse.h"
 #include "luse.h"
 #include "ordered_vector.h"
 #include "ordered_vector.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
+#include "pointerToArray.h"
 #include "notify.h"
 #include "notify.h"
 
 
 class qpNodePathComponent;
 class qpNodePathComponent;
@@ -67,6 +68,7 @@ public:
   virtual bool safe_to_flatten() const;
   virtual bool safe_to_flatten() const;
   virtual bool safe_to_transform() const;
   virtual bool safe_to_transform() const;
   virtual bool safe_to_combine() const;
   virtual bool safe_to_combine() const;
+  virtual bool preserve_name() const;
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual PandaNode *combine_with(PandaNode *other); 
   virtual PandaNode *combine_with(PandaNode *other); 
 
 
@@ -89,18 +91,24 @@ PUBLISHED:
   void add_child(PandaNode *child_node, int sort = 0);
   void add_child(PandaNode *child_node, int sort = 0);
   void remove_child(int n);
   void remove_child(int n);
   bool remove_child(PandaNode *child_node);
   bool remove_child(PandaNode *child_node);
+  bool replace_child(PandaNode *orig_child, PandaNode *new_child);
 
 
   INLINE bool stash_child(PandaNode *child_node);
   INLINE bool stash_child(PandaNode *child_node);
   void stash_child(int child_index);
   void stash_child(int child_index);
   INLINE bool unstash_child(PandaNode *child_node);
   INLINE bool unstash_child(PandaNode *child_node);
   void unstash_child(int stashed_index);
   void unstash_child(int stashed_index);
+
   INLINE int get_num_stashed() const;
   INLINE int get_num_stashed() const;
   INLINE PandaNode *get_stashed(int n) const;
   INLINE PandaNode *get_stashed(int n) const;
   INLINE int get_stashed_sort(int n) const;
   INLINE int get_stashed_sort(int n) const;
   int find_stashed(PandaNode *node) const;
   int find_stashed(PandaNode *node) const;
+
+  void add_stashed(PandaNode *child_node, int sort = 0);
   void remove_stashed(int n);
   void remove_stashed(int n);
 
 
   void remove_all_children();
   void remove_all_children();
+  void steal_children(PandaNode *other);
+  void copy_children(PandaNode *other);
 
 
   INLINE void set_attrib(const RenderAttrib *attrib, int override = 0);
   INLINE void set_attrib(const RenderAttrib *attrib, int override = 0);
   INLINE const RenderAttrib *get_attrib(TypeHandle type) const;
   INLINE const RenderAttrib *get_attrib(TypeHandle type) const;
@@ -173,6 +181,8 @@ protected:
   BoundedObject _internal_bound;
   BoundedObject _internal_bound;
 
 
 private:
 private:
+  class CData;
+
   // parent-child manipulation for qpNodePath support.  Don't try to
   // parent-child manipulation for qpNodePath support.  Don't try to
   // call these directly.
   // call these directly.
   static PT(qpNodePathComponent) attach(qpNodePathComponent *parent, 
   static PT(qpNodePathComponent) attach(qpNodePathComponent *parent, 
@@ -182,10 +192,12 @@ private:
                        qpNodePathComponent *child, int sort);
                        qpNodePathComponent *child, int sort);
   static PT(qpNodePathComponent) get_component(qpNodePathComponent *parent,
   static PT(qpNodePathComponent) get_component(qpNodePathComponent *parent,
                                               PandaNode *child);
                                               PandaNode *child);
-  static PT(qpNodePathComponent) get_top_component(PandaNode *child);
+  static PT(qpNodePathComponent) get_top_component(PandaNode *child,
+                                                   bool force);
   PT(qpNodePathComponent) get_generic_component();
   PT(qpNodePathComponent) get_generic_component();
   void delete_component(qpNodePathComponent *component);
   void delete_component(qpNodePathComponent *component);
-  class CData;
+  static void sever_connection(PandaNode *parent_node, PandaNode *child_node);
+  static void new_connection(PandaNode *parent_node, PandaNode *child_node);
   void fix_path_lengths(const CData *cdata);
   void fix_path_lengths(const CData *cdata);
   void r_list_descendants(ostream &out, int indent_level) const;
   void r_list_descendants(ostream &out, int indent_level) const;
 
 
@@ -195,6 +207,7 @@ private:
     INLINE DownConnection(PandaNode *child, int sort);
     INLINE DownConnection(PandaNode *child, int sort);
     INLINE bool operator < (const DownConnection &other) const;
     INLINE bool operator < (const DownConnection &other) const;
     INLINE PandaNode *get_child() const;
     INLINE PandaNode *get_child() const;
+    INLINE void set_child(PandaNode *child);
     INLINE int get_sort() const;
     INLINE int get_sort() const;
 
 
   private:
   private:
@@ -273,6 +286,7 @@ public:
   public:
   public:
     INLINE Children(const CDReader &cdata);
     INLINE Children(const CDReader &cdata);
     INLINE Children(const Children &copy);
     INLINE Children(const Children &copy);
+    INLINE void operator = (const Children &copy);
 
 
     INLINE int get_num_children() const;
     INLINE int get_num_children() const;
     INLINE PandaNode *get_child(int n) const;
     INLINE PandaNode *get_child(int n) const;
@@ -283,6 +297,24 @@ public:
 
 
   INLINE Children get_children() const;
   INLINE Children get_children() const;
 
 
+  // This interface *does* protect you from self-modifying loops, by
+  // copying the list of children.
+  class EXPCL_PANDA ChildrenCopy {
+  public:
+    ChildrenCopy(const CDReader &cdata);
+    INLINE ChildrenCopy(const ChildrenCopy &copy);
+    INLINE void operator = (const ChildrenCopy &copy);
+
+    INLINE int get_num_children() const;
+    INLINE PandaNode *get_child(int n) const;
+
+  private:
+    typedef PTA(PT(PandaNode)) List;
+    List _list;
+  };
+
+  INLINE ChildrenCopy get_children_copy() const;
+
 public:
 public:
   static void register_with_read_factory();
   static void register_with_read_factory();
   virtual void write_datagram(BamWriter *manager, Datagram &dg);
   virtual void write_datagram(BamWriter *manager, Datagram &dg);

+ 2 - 0
panda/src/pgraph/pgraph_composite2.cxx

@@ -4,6 +4,7 @@
 #include "qpfog.cxx"
 #include "qpfog.cxx"
 #include "fogAttrib.cxx"
 #include "fogAttrib.cxx"
 #include "qpgeomNode.cxx"
 #include "qpgeomNode.cxx"
+#include "qpgeomTransformer.cxx"
 #include "qplensNode.cxx"
 #include "qplensNode.cxx"
 #include "qplodNode.cxx"
 #include "qplodNode.cxx"
 #include "materialAttrib.cxx"
 #include "materialAttrib.cxx"
@@ -16,6 +17,7 @@
 #include "renderEffects.cxx"
 #include "renderEffects.cxx"
 #include "renderModeAttrib.cxx"
 #include "renderModeAttrib.cxx"
 #include "renderState.cxx"
 #include "renderState.cxx"
+#include "qpsceneGraphReducer.cxx"
 #include "selectiveChildNode.cxx"
 #include "selectiveChildNode.cxx"
 #include "qpsequenceNode.cxx"
 #include "qpsequenceNode.cxx"
 #include "test_pgraph.cxx"
 #include "test_pgraph.cxx"

+ 3 - 0
panda/src/pgraph/qpgeomNode.I

@@ -112,6 +112,7 @@ add_geom(Geom *geom, const RenderState *state) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
 
 
   cdata->_geoms.push_back(GeomEntry(geom, state));
   cdata->_geoms.push_back(GeomEntry(geom, state));
+  mark_bound_stale();
   return cdata->_geoms.size() - 1;
   return cdata->_geoms.size() - 1;
 }
 }
 
 
@@ -126,6 +127,7 @@ remove_geom(int n) {
   nassertv(n >= 0 && n < (int)cdata->_geoms.size());
   nassertv(n >= 0 && n < (int)cdata->_geoms.size());
 
 
   cdata->_geoms.erase(cdata->_geoms.begin() + n);
   cdata->_geoms.erase(cdata->_geoms.begin() + n);
+  mark_bound_stale();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -137,4 +139,5 @@ INLINE void qpGeomNode::
 remove_all_geoms() {
 remove_all_geoms() {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
   cdata->_geoms.clear();
   cdata->_geoms.clear();
+  mark_bound_stale();
 }
 }

+ 68 - 0
panda/src/pgraph/qpgeomNode.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "qpgeomNode.h"
 #include "qpgeomNode.h"
+#include "qpgeomTransformer.h"
 #include "bamReader.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "bamWriter.h"
 #include "datagram.h"
 #include "datagram.h"
@@ -153,6 +154,73 @@ make_copy() const {
   return new qpGeomNode(*this);
   return new qpGeomNode(*this);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomNode::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this node by the indicated
+//               matrix, if it means anything to do so.  For most
+//               kinds of nodes, this does nothing.
+//
+//               For a GeomNode, this does the right thing, but it is
+//               better to use a GeomTransformer instead, since it
+//               will share the new arrays properly between different
+//               GeomNodes.
+////////////////////////////////////////////////////////////////////
+void qpGeomNode::
+xform(const LMatrix4f &mat) {
+  qpGeomTransformer transformer;
+  transformer.transform_vertices(this, mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomNode::combine_with
+//       Access: Public, Virtual
+//  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.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpGeomNode::
+combine_with(PandaNode *other) {
+  if (is_exact_type(get_class_type()) &&
+      other->is_exact_type(get_class_type())) {
+    // Two GeomNodes can combine by moving Geoms from one to the other.
+    qpGeomNode *gother = DCAST(qpGeomNode, other);
+    add_geoms_from(gother);
+    return this;
+  }
+
+  return PandaNode::combine_with(other);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomNode::add_geoms_from
+//       Access: Published
+//  Description: Copies the Geoms (and their associated RenderStates)
+//               from the indicated GeomNode into this one.
+////////////////////////////////////////////////////////////////////
+void qpGeomNode::
+add_geoms_from(const qpGeomNode *other) {
+  CDReader cdata_other(other->_cycler);
+  CDWriter cdata(_cycler);
+
+  Geoms::const_iterator gi;
+  for (gi = cdata_other->_geoms.begin(); 
+       gi != cdata_other->_geoms.end(); 
+       ++gi) {
+    const GeomEntry &entry = (*gi);
+    cdata->_geoms.push_back(entry);
+  }
+  mark_bound_stale();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomNode::write_geoms
 //     Function: qpGeomNode::write_geoms
 //       Access: Published
 //       Access: Published

+ 4 - 0
panda/src/pgraph/qpgeomNode.h

@@ -44,6 +44,8 @@ protected:
 public:
 public:
   virtual ~qpGeomNode();
   virtual ~qpGeomNode();
   virtual PandaNode *make_copy() const;
   virtual PandaNode *make_copy() const;
+  virtual void xform(const LMatrix4f &mat);
+  virtual PandaNode *combine_with(PandaNode *other); 
 
 
 PUBLISHED:
 PUBLISHED:
   INLINE int get_num_geoms() const;
   INLINE int get_num_geoms() const;
@@ -52,6 +54,7 @@ PUBLISHED:
   INLINE void set_geom_state(int n, const RenderState *state);
   INLINE void set_geom_state(int n, const RenderState *state);
 
 
   INLINE int add_geom(Geom *geom, const RenderState *state = RenderState::make_empty());
   INLINE int add_geom(Geom *geom, const RenderState *state = RenderState::make_empty());
+  void add_geoms_from(const qpGeomNode *other);
   INLINE void remove_geom(int n);
   INLINE void remove_geom(int n);
   INLINE void remove_all_geoms();
   INLINE void remove_all_geoms();
 
 
@@ -118,6 +121,7 @@ private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 
 
   friend class PandaNode::Children;
   friend class PandaNode::Children;
+  friend class qpGeomTransformer;
 };
 };
 
 
 #include "qpgeomNode.I"
 #include "qpgeomNode.I"

+ 5 - 0
panda/src/pgraph/qpnodePath.I

@@ -159,6 +159,7 @@ fail() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpNodePath::
 INLINE bool qpNodePath::
 is_empty() const {
 is_empty() const {
+  uncollapse_head();
   return (_head == (qpNodePathComponent *)NULL);
   return (_head == (qpNodePathComponent *)NULL);
 }
 }
 
 
@@ -272,6 +273,7 @@ attach_new_node(const string &name, int sort) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 output(ostream &out) const {
 output(ostream &out) const {
+  uncollapse_head();
   if (_head == (qpNodePathComponent *)NULL) {
   if (_head == (qpNodePathComponent *)NULL) {
     out << "(empty)";
     out << "(empty)";
   } else {
   } else {
@@ -1113,6 +1115,9 @@ operator < (const qpNodePath &other) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE int qpNodePath::
 INLINE int qpNodePath::
 compare_to(const qpNodePath &other) const {
 compare_to(const qpNodePath &other) const {
+  uncollapse_head();
+  other.uncollapse_head();
+
   // Nowadays, the NodePathComponents at the head are pointerwise
   // Nowadays, the NodePathComponents at the head are pointerwise
   // equivalent if and only iff the NodePaths are equivalent.  So we
   // equivalent if and only iff the NodePaths are equivalent.  So we
   // only have to compare pointers.
   // only have to compare pointers.

+ 9 - 18
panda/src/pgraph/qpnodePath.cxx

@@ -38,6 +38,7 @@
 #include "plist.h"
 #include "plist.h"
 #include "boundingSphere.h"
 #include "boundingSphere.h"
 #include "qpgeomNode.h"
 #include "qpgeomNode.h"
+#include "qpsceneGraphReducer.h"
 
 
 // stack seems to overflow on Intel C++ at 7000.  If we need more than 
 // stack seems to overflow on Intel C++ at 7000.  If we need more than 
 // 7000, need to increase stack size.
 // 7000, need to increase stack size.
@@ -2403,10 +2404,8 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point) {
 int qpNodePath::
 int qpNodePath::
 flatten_light() {
 flatten_light() {
   nassertr(!is_empty(), 0);
   nassertr(!is_empty(), 0);
-  /*
-  SceneGraphReducer gr(_graph_type);
-  gr.apply_transitions(arc());
-  */
+  qpSceneGraphReducer gr;
+  gr.apply_attribs(node());
 
 
   return 0;
   return 0;
 }
 }
@@ -2436,17 +2435,13 @@ flatten_light() {
 //               The return value is the number of arcs removed.
 //               The return value is the number of arcs removed.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
-flatten_medium(int max_children) {
+flatten_medium() {
   nassertr(!is_empty(), 0);
   nassertr(!is_empty(), 0);
-  /*
-  SceneGraphReducer gr(_graph_type);
-  gr.set_max_children(max_children);
-  gr.apply_transitions(arc());
+  qpSceneGraphReducer gr;
+  gr.apply_attribs(node());
   int num_removed = gr.flatten(node(), false);
   int num_removed = gr.flatten(node(), false);
 
 
   return num_removed;
   return num_removed;
-  */
-  return 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -2467,17 +2462,13 @@ flatten_medium(int max_children) {
 //               because of less-effective culling.
 //               because of less-effective culling.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
-flatten_strong(int max_children) {
+flatten_strong() {
   nassertr(!is_empty(), 0);
   nassertr(!is_empty(), 0);
-  /*
-  SceneGraphReducer gr(_graph_type);
-  gr.set_max_children(max_children);
-  gr.apply_transitions(arc());
+  qpSceneGraphReducer gr;
+  gr.apply_attribs(node());
   int num_removed = gr.flatten(node(), true);
   int num_removed = gr.flatten(node(), true);
 
 
   return num_removed;
   return num_removed;
-  */
-  return 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 2 - 2
panda/src/pgraph/qpnodePath.h

@@ -477,8 +477,8 @@ PUBLISHED:
   bool calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point);
   bool calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point);
 
 
   int flatten_light();
   int flatten_light();
-  int flatten_medium(int max_children = 1);
-  int flatten_strong(int max_children = 1);
+  int flatten_medium();
+  int flatten_strong();
 
 
   bool write_bam_file(const string &filename) const;
   bool write_bam_file(const string &filename) const;
 
 

+ 2 - 3
panda/src/pgraph/renderEffect.cxx

@@ -72,8 +72,7 @@ RenderEffect::
 //       Access: Public, Virtual
 //       Access: Public, Virtual
 //  Description: Returns true if it is generally safe to transform
 //  Description: Returns true if it is generally safe to transform
 //               this particular kind of RenderEffect by calling the
 //               this particular kind of RenderEffect 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 RenderEffect::
 bool RenderEffect::
 safe_to_transform() const {
 safe_to_transform() const {
@@ -89,7 +88,7 @@ safe_to_transform() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool RenderEffect::
 bool RenderEffect::
 safe_to_combine() const {
 safe_to_combine() const {
-  return false;
+  return true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 6 - 0
panda/src/pgraph/transparencyAttrib.cxx

@@ -66,21 +66,27 @@ output(ostream &out) const {
   switch (get_mode()) {
   switch (get_mode()) {
   case M_none:
   case M_none:
     out << "none";
     out << "none";
+    break;
 
 
   case M_alpha:
   case M_alpha:
     out << "alpha";
     out << "alpha";
+    break;
 
 
   case M_alpha_sorted:
   case M_alpha_sorted:
     out << "alpha sorted";
     out << "alpha sorted";
+    break;
 
 
   case M_multisample:
   case M_multisample:
     out << "multisample";
     out << "multisample";
+    break;
 
 
   case M_multisample_mask:
   case M_multisample_mask:
     out << "multisample mask";
     out << "multisample mask";
+    break;
 
 
   case M_binary:
   case M_binary:
     out << "binary";
     out << "binary";
+    break;
   }
   }
 }
 }
 
 

+ 3 - 4
panda/src/pgui/qppgItem.cxx

@@ -23,6 +23,7 @@
 #include "config_pgui.h"
 #include "config_pgui.h"
 
 
 #include "pandaNode.h"
 #include "pandaNode.h"
+#include "qpsceneGraphReducer.h"
 #include "throw_event.h"
 #include "throw_event.h"
 #include "string_utils.h"
 #include "string_utils.h"
 #include "qpnodePath.h"
 #include "qpnodePath.h"
@@ -122,11 +123,9 @@ xform(const LMatrix4f &mat) {
       // Apply the matrix to the previous transform.
       // Apply the matrix to the previous transform.
       node->set_transform(node->get_transform()->compose(TransformState::make_mat(mat)));
       node->set_transform(node->get_transform()->compose(TransformState::make_mat(mat)));
 
 
-      /*
       // Now flatten the transform into the subgraph.
       // Now flatten the transform into the subgraph.
-      SceneGraphReducer gr;
-      gr.apply_transitions(arc);
-      */
+      qpSceneGraphReducer gr;
+      gr.apply_attribs(node);
     }
     }
 
 
     // Transform the frame style too.
     // Transform the frame style too.

+ 40 - 10
panda/src/putil/cycleDataReader.I

@@ -28,9 +28,9 @@
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE CycleDataReader<CycleDataType>::
 INLINE CycleDataReader<CycleDataType>::
 CycleDataReader(const PipelineCycler<CycleDataType> &cycler) :
 CycleDataReader(const PipelineCycler<CycleDataType> &cycler) :
-  _cycler(cycler)
+  _cycler(&cycler)
 {
 {
-  _pointer = _cycler.read();
+  _pointer = _cycler->read();
   _write_pointer = (CycleDataType *)NULL;
   _write_pointer = (CycleDataType *)NULL;
 }
 }
 
 
@@ -47,10 +47,29 @@ CycleDataReader(const CycleDataReader<CycleDataType> &copy) :
   _write_pointer(copy._write_pointer)
   _write_pointer(copy._write_pointer)
 {
 {
   nassertv(_pointer != (const CycleDataType *)NULL);
   nassertv(_pointer != (const CycleDataType *)NULL);
-  // We cannot copy-construct a CycleDataReader that has elevated
-  // itself to a write pointer.
+  // We cannot copy a CycleDataReader that has elevated itself to a
+  // write pointer.
   nassertv(_write_pointer == (const CycleDataType *)NULL);
   nassertv(_write_pointer == (const CycleDataType *)NULL);
-  _cycler.increment_read(_pointer);
+  _cycler->increment_read(_pointer);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataReader::Copy Assignment (full)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE void CycleDataReader<CycleDataType>::
+operator = (const CycleDataReader<CycleDataType> &copy) {
+  _cycler = copy._cycler;
+  _pointer = copy._pointer;
+  _write_pointer = copy._write_pointer;
+
+  nassertv(_pointer != (const CycleDataType *)NULL);
+  // We cannot copy a CycleDataReader that has elevated itself to a
+  // write pointer.
+  nassertv(_write_pointer == (const CycleDataType *)NULL);
+  _cycler->increment_read(_pointer);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -65,9 +84,9 @@ INLINE CycleDataReader<CycleDataType>::
     // If the _write_pointer is non-NULL, then someone called
     // If the _write_pointer is non-NULL, then someone called
     // elevate_to_write() at some point, and we now actually hold a
     // elevate_to_write() at some point, and we now actually hold a
     // write pointer, not a read pointer.
     // write pointer, not a read pointer.
-    ((PipelineCycler<CycleDataType> &)_cycler).release_write(_write_pointer);
+    ((PipelineCycler<CycleDataType> *)_cycler)->release_write(_write_pointer);
   } else if (_pointer != (CycleDataType *)NULL) {
   } else if (_pointer != (CycleDataType *)NULL) {
-    _cycler.release_read(_pointer);
+    _cycler->release_read(_pointer);
   }
   }
 }
 }
 
 
@@ -80,7 +99,7 @@ INLINE CycleDataReader<CycleDataType>::
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE const CycleDataType *CycleDataReader<CycleDataType>::
 INLINE const CycleDataType *CycleDataReader<CycleDataType>::
 operator -> () const {
 operator -> () const {
-  nassertr(_pointer != (const CycleDataType *)NULL, _cycler.cheat());
+  nassertr(_pointer != (const CycleDataType *)NULL, _cycler->cheat());
   return _pointer;
   return _pointer;
 }
 }
 
 
@@ -93,7 +112,7 @@ operator -> () const {
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE CycleDataReader<CycleDataType>::
 INLINE CycleDataReader<CycleDataType>::
 operator const CycleDataType * () const {
 operator const CycleDataType * () const {
-  nassertr(_pointer != (const CycleDataType *)NULL, _cycler.cheat());
+  nassertr(_pointer != (const CycleDataType *)NULL, _cycler->cheat());
   return _pointer;
   return _pointer;
 }
 }
 
 
@@ -113,7 +132,7 @@ take_pointer() {
   const CycleDataType *pointer = _pointer;
   const CycleDataType *pointer = _pointer;
   _pointer = (CycleDataType *)NULL;
   _pointer = (CycleDataType *)NULL;
   _write_pointer = (CycleDataType *)NULL;
   _write_pointer = (CycleDataType *)NULL;
-  nassertr(pointer != (const CycleDataType *)NULL, _cycler.cheat());
+  nassertr(pointer != (const CycleDataType *)NULL, _cycler->cheat());
   return pointer;
   return pointer;
 }
 }
 
 
@@ -162,6 +181,17 @@ CycleDataReader(const CycleDataReader<CycleDataType> &copy) :
 {
 {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataReader::Copy Assignment (trivial)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE void CycleDataReader<CycleDataType>::
+operator = (const CycleDataReader<CycleDataType> &copy) {
+  _pointer = copy._pointer;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CycleDataReader::Destructor (trivial)
 //     Function: CycleDataReader::Destructor (trivial)
 //       Access: Public
 //       Access: Public

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

@@ -41,6 +41,7 @@ class CycleDataReader {
 public:
 public:
   INLINE CycleDataReader(const PipelineCycler<CycleDataType> &cycler);
   INLINE CycleDataReader(const PipelineCycler<CycleDataType> &cycler);
   INLINE CycleDataReader(const CycleDataReader<CycleDataType> &copy);
   INLINE CycleDataReader(const CycleDataReader<CycleDataType> &copy);
+  INLINE void operator = (const CycleDataReader<CycleDataType> &copy);
 
 
   INLINE ~CycleDataReader();
   INLINE ~CycleDataReader();
 
 
@@ -53,7 +54,7 @@ public:
 private:
 private:
 #ifdef DO_PIPELINING
 #ifdef DO_PIPELINING
   // This is the data stored for a real pipelining implementation.
   // This is the data stored for a real pipelining implementation.
-  const PipelineCycler<CycleDataType> &_cycler;
+  const PipelineCycler<CycleDataType> *_cycler;
   const CycleDataType *_pointer;
   const CycleDataType *_pointer;
   CycleDataType *_write_pointer;
   CycleDataType *_write_pointer;
 #else  // !DO_PIPELINING
 #else  // !DO_PIPELINING

+ 62 - 9
panda/src/putil/cycleDataWriter.I

@@ -28,9 +28,39 @@
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE CycleDataWriter<CycleDataType>::
 INLINE CycleDataWriter<CycleDataType>::
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler) :
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler) :
-  _cycler(cycler)
+  _cycler(&cycler)
 {
 {
-  _pointer = _cycler.write();
+  _pointer = _cycler->write();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataWriter::Copy Constructor (full)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE CycleDataWriter<CycleDataType>::
+CycleDataWriter(const CycleDataWriter<CycleDataType> &copy) :
+  _cycler(copy._cycler),
+  _pointer(copy._pointer)
+{
+  nassertv(_pointer != (CycleDataType *)NULL);
+  _cycler->increment_write(_pointer);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataWriter::Copy Assigment (full)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE void CycleDataWriter<CycleDataType>::
+operator = (const CycleDataWriter<CycleDataType> &copy) {
+  _cycler = copy._cycler;
+  _pointer = copy._pointer;
+
+  nassertv(_pointer != (CycleDataType *)NULL);
+  _cycler->increment_write(_pointer);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -46,7 +76,7 @@ template<class CycleDataType>
 INLINE CycleDataWriter<CycleDataType>::
 INLINE CycleDataWriter<CycleDataType>::
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler, 
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler, 
                 CycleDataWriter<CycleDataType> &take_from) :
                 CycleDataWriter<CycleDataType> &take_from) :
-  _cycler(cycler),
+  _cycler(&cycler),
   _pointer(take_from._pointer)
   _pointer(take_from._pointer)
 {
 {
   take_from._pointer = (CycleDataType *)NULL;
   take_from._pointer = (CycleDataType *)NULL;
@@ -64,9 +94,9 @@ template<class CycleDataType>
 INLINE CycleDataWriter<CycleDataType>::
 INLINE CycleDataWriter<CycleDataType>::
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler,
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler,
                 CycleDataReader<CycleDataType> &take_from) :
                 CycleDataReader<CycleDataType> &take_from) :
-  _cycler(cycler)
+  _cycler(&cycler)
 {
 {
-  _pointer = _cycler.elevate_read(take_from.take_pointer());
+  _pointer = _cycler->elevate_read(take_from.take_pointer());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -78,7 +108,7 @@ template<class CycleDataType>
 INLINE CycleDataWriter<CycleDataType>::
 INLINE CycleDataWriter<CycleDataType>::
 ~CycleDataWriter() {
 ~CycleDataWriter() {
   if (_pointer != (CycleDataType *)NULL) {
   if (_pointer != (CycleDataType *)NULL) {
-    _cycler.release_write(_pointer);
+    _cycler->release_write(_pointer);
   }
   }
 }
 }
 
 
@@ -91,7 +121,7 @@ INLINE CycleDataWriter<CycleDataType>::
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE CycleDataType *CycleDataWriter<CycleDataType>::
 INLINE CycleDataType *CycleDataWriter<CycleDataType>::
 operator -> () {
 operator -> () {
-  nassertr(_pointer != (CycleDataType *)NULL, _cycler.cheat());
+  nassertr(_pointer != (CycleDataType *)NULL, _cycler->cheat());
   return _pointer;
   return _pointer;
 }
 }
 
 
@@ -104,7 +134,7 @@ operator -> () {
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE const CycleDataType *CycleDataWriter<CycleDataType>::
 INLINE const CycleDataType *CycleDataWriter<CycleDataType>::
 operator -> () const {
 operator -> () const {
-  nassertr(_pointer != (CycleDataType *)NULL, _cycler.cheat());
+  nassertr(_pointer != (CycleDataType *)NULL, _cycler->cheat());
   return _pointer;
   return _pointer;
 }
 }
 
 
@@ -117,7 +147,7 @@ operator -> () const {
 template<class CycleDataType>
 template<class CycleDataType>
 INLINE CycleDataWriter<CycleDataType>::
 INLINE CycleDataWriter<CycleDataType>::
 operator CycleDataType * () {
 operator CycleDataType * () {
-  nassertr(_pointer != (CycleDataType *)NULL, _cycler.cheat());
+  nassertr(_pointer != (CycleDataType *)NULL, _cycler->cheat());
   return _pointer;
   return _pointer;
 }
 }
 
 
@@ -135,6 +165,29 @@ CycleDataWriter(PipelineCycler<CycleDataType> &cycler) {
   _pointer = cycler.write();
   _pointer = cycler.write();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataWriter::Copy Constructor (trivial)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE CycleDataWriter<CycleDataType>::
+CycleDataWriter(const CycleDataWriter<CycleDataType> &copy) :
+  _pointer(copy._pointer)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CycleDataWriter::Copy Assigment (trivial)
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE void CycleDataWriter<CycleDataType>::
+operator = (const CycleDataWriter<CycleDataType> &copy) {
+  _pointer = copy._pointer;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CycleDataWriter::Constructor (trivial)
 //     Function: CycleDataWriter::Constructor (trivial)
 //       Access: Public
 //       Access: Public

+ 4 - 1
panda/src/putil/cycleDataWriter.h

@@ -40,6 +40,9 @@ template<class CycleDataType>
 class CycleDataWriter {
 class CycleDataWriter {
 public:
 public:
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler);
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler);
+  INLINE CycleDataWriter(const CycleDataWriter<CycleDataType> &copy);
+  INLINE void operator = (const CycleDataWriter<CycleDataType> &copy);
+
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler, CycleDataWriter<CycleDataType> &take_from);
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler, CycleDataWriter<CycleDataType> &take_from);
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler, CycleDataReader<CycleDataType> &take_from);
   INLINE CycleDataWriter(PipelineCycler<CycleDataType> &cycler, CycleDataReader<CycleDataType> &take_from);
 
 
@@ -53,7 +56,7 @@ public:
 private:
 private:
 #ifdef DO_PIPELINING
 #ifdef DO_PIPELINING
   // This is the data stored for a real pipelining implementation.
   // This is the data stored for a real pipelining implementation.
-  PipelineCycler<CycleDataType> &_cycler;
+  PipelineCycler<CycleDataType> *_cycler;
   CycleDataType *_pointer;
   CycleDataType *_pointer;
 #else  // !DO_PIPELINING
 #else  // !DO_PIPELINING
   // This is all we need for the trivial, do-nothing implementation.
   // This is all we need for the trivial, do-nothing implementation.

+ 3 - 0
panda/src/testbed/pview.cxx

@@ -378,6 +378,9 @@ main(int argc, char *argv[]) {
   render.attach_new_node(camera);
   render.attach_new_node(camera);
   camera->set_scene(render);
   camera->set_scene(render);
 
 
+  // This is maybe here temporarily, and maybe not.
+  render.set_two_sided(0);
+
   // Set up a data graph for tracking user input.
   // Set up a data graph for tracking user input.
   PT(PandaNode) data_root = new PandaNode("data_root");
   PT(PandaNode) data_root = new PandaNode("data_root");
   PandaNode *mouse = setup_mouse(data_root, window);
   PandaNode *mouse = setup_mouse(data_root, window);

+ 3 - 4
panda/src/text/qptextNode.cxx

@@ -33,6 +33,7 @@
 #include "cullBinAttrib.h"
 #include "cullBinAttrib.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
 #include "transparencyAttrib.h"
 #include "transparencyAttrib.h"
+#include "qpsceneGraphReducer.h"
 #include "indent.h"
 #include "indent.h"
 
 
 #include <stdio.h>
 #include <stdio.h>
@@ -362,11 +363,9 @@ generate() {
   // applying them to the vertices.
   // applying them to the vertices.
 
 
   if (text_flatten) {
   if (text_flatten) {
-    /* ****
-    SceneGraphReducer gr(RenderRelation::get_class_type());
-    gr.apply_transitions(root_arc);
+    qpSceneGraphReducer gr;
+    gr.apply_attribs(root);
     gr.flatten(root, true);
     gr.flatten(root, true);
-    */
   }
   }
 
 
   return root;
   return root;