Browse Source

change logic for uniqueness of NodePaths w.r.t. instances

David Rose 22 years ago
parent
commit
0b8b4f9cef

+ 0 - 6
panda/src/collide/collisionTraverser.h

@@ -108,12 +108,6 @@ private:
   PT(CollisionHandler) _default_handler;
   TypeHandle _graph_type;
 
-  // A map of NodePaths is slightly dangerous, since there is a
-  // (small) possiblity that a particular NodePath's sorting order
-  // relative to other NodePaths will spontaneously change.  This can
-  // only happen if two NodePaths get collapsed together due to a
-  // removal of a certain kind of instanced node; see the comments in
-  // NodePath.cxx.
   typedef pmap<NodePath,  PT(CollisionHandler) > Colliders;
   Colliders _colliders;
   typedef pvector<NodePath> OrderedColliders;

+ 22 - 52
panda/src/pgraph/nodePath.I

@@ -112,7 +112,6 @@ NodePath(const NodePath &copy) :
   _head(copy._head),
   _error_type(copy._error_type)
 {
-  uncollapse_head();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -124,7 +123,6 @@ INLINE void NodePath::
 operator = (const NodePath &copy) {
   _head = copy._head;
   _error_type = copy._error_type;
-  uncollapse_head();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -212,7 +210,6 @@ get_max_search_depth() {
 ////////////////////////////////////////////////////////////////////
 INLINE bool NodePath::
 is_empty() const {
-  uncollapse_head();
   return (_head == (NodePathComponent *)NULL);
 }
 
@@ -224,7 +221,6 @@ is_empty() const {
 ////////////////////////////////////////////////////////////////////
 INLINE bool NodePath::
 is_singleton() const {
-  uncollapse_head();
   return (_head != (NodePathComponent *)NULL && _head->is_top_node());
 }
 
@@ -429,7 +425,6 @@ set_state(const RenderState *state) {
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(RenderState) NodePath::
 get_net_state() const {
-  uncollapse_head();
   return r_get_net_state(_head);
 }
 
@@ -462,7 +457,6 @@ set_transform(const TransformState *transform) {
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(TransformState) NodePath::
 get_net_transform() const {
-  uncollapse_head();
   return r_get_net_transform(_head);
 }
 
@@ -501,7 +495,6 @@ set_prev_transform(const TransformState *transform) {
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(TransformState) NodePath::
 get_net_prev_transform() const {
-  uncollapse_head();
   return r_get_net_prev_transform(_head);
 }
 
@@ -1149,51 +1142,6 @@ is_hidden(DrawMask camera_mask) const {
   return !get_hidden_ancestor(camera_mask).is_empty();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: NodePath::unstash
-//       Access: Published
-//  Description: Undoes the effect of a previous stash() on this
-//               node: makes the referenced node (and the entire
-//               subgraph below this node) once again part of the
-//               scene graph.  Returns true if the node is unstashed,
-//               or false if it wasn't stashed to begin with.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodePath::
-unstash() {
-  nassertr(!is_singleton(), false);
-  PandaNode *parent_node = get_parent().node();
-  PandaNode *this_node = node();
-  nassertr(parent_node != (PandaNode *)NULL && this_node != (PandaNode *)NULL, false);
-  return parent_node->unstash_child(this_node);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodePath::stash
-//       Access: Published
-//  Description: Removes the referenced node (and the entire subgraph
-//               below this node) from the scene graph in any normal
-//               sense.  The node will no longer be visible and is not
-//               tested for collisions; furthermore, no normal scene
-//               graph traversal will visit the node.  The node's
-//               bounding volume no longer contributes to its parent's
-//               bounding volume.
-//
-//               A stashed node cannot be located by a normal find()
-//               operation (although a special find string can still
-//               retrieve it).
-//
-//               Returns true if the node is successfully stashed, or
-//               false if it was already stashed.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodePath::
-stash() {
-  nassertr(!is_singleton(), false);
-  PandaNode *parent_node = get_parent().node();
-  PandaNode *this_node = node();
-  nassertr(parent_node != (PandaNode *)NULL && this_node != (PandaNode *)NULL, false);
-  return parent_node->stash_child(this_node);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePath::is_stashed
 //       Access: Published
@@ -1335,6 +1283,28 @@ has_net_tag(const string &key) const {
   return find_net_tag(key).has_tag(key);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::set_name
+//       Access: Published
+//  Description: Changes the name of the referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE void NodePath::
+set_name(const string &name) {
+  nassertv_always(!is_empty());
+  node()->set_name(name);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::get_name
+//       Access: Published
+//  Description: Returns the name of the referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE string NodePath::
+get_name() const {
+  nassertr_always(!is_empty(), string());
+  return node()->get_name();
+}
+
 
 INLINE ostream &operator << (ostream &out, const NodePath &node_path) {
   node_path.output(out);

+ 80 - 55
panda/src/pgraph/nodePath.cxx

@@ -64,7 +64,6 @@ get_num_nodes() const {
   if (is_empty()) {
     return 0;
   }
-  uncollapse_head();
   return _head->get_length();
 }
 
@@ -80,7 +79,6 @@ PandaNode *NodePath::
 get_node(int index) const {
   nassertr(index >= 0 && index < get_num_nodes(), NULL);
 
-  uncollapse_head();
   NodePathComponent *comp = _head;
   while (index > 0) {
     // If this assertion fails, the index was out of range; the
@@ -108,7 +106,6 @@ get_top_node() const {
     return (PandaNode *)NULL;
   }
 
-  uncollapse_head();
   NodePathComponent *comp = _head;
   while (!comp->is_top_node()) {
     comp = comp->get_next();
@@ -186,9 +183,17 @@ get_sort() const {
   PandaNode *child = node();
   nassertr(parent != (PandaNode *)NULL && child != (PandaNode *)NULL, 0);
   int child_index = parent->find_child(child);
-  nassertr(child_index != -1, 0);
+  if (child_index != -1) {
+    return parent->get_child_sort(child_index);
+  }
+
+  child_index = parent->find_stashed(child);
+  if (child_index != -1) {
+    return parent->get_stashed_sort(child_index);
+  }
 
-  return parent->get_child_sort(child_index);
+  nassertr(false, 0);
+  return 0;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -286,14 +291,12 @@ reparent_to(const NodePath &other, int sort) {
   nassertv(verify_complete());
   nassertv(other.verify_complete());
   nassertv_always(!is_empty());
-  nassertv_always(!other.is_empty());
-
-  uncollapse_head();
-  other.uncollapse_head();
-  PandaNode::reparent(other._head, _head, sort);
 
   // Reparenting implicitly resents the delta vector.
   node()->reset_prev_transform();
+
+  bool reparented = PandaNode::reparent(other._head, _head, sort, false);
+  nassertv(reparented);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -310,7 +313,6 @@ wrt_reparent_to(const NodePath &other, int sort) {
   nassertv(verify_complete());
   nassertv(other.verify_complete());
   nassertv_always(!is_empty());
-  nassertv_always(!other.is_empty());
 
   if (get_transform() == get_prev_transform()) {
     set_transform(get_transform(other));
@@ -345,13 +347,17 @@ instance_to(const NodePath &other, int sort) const {
   nassertr(verify_complete(), NodePath::fail());
   nassertr(other.verify_complete(), NodePath::fail());
   nassertr_always(!is_empty(), NodePath::fail());
-  nassertr(!other.is_empty(), NodePath::fail());
-
-  uncollapse_head();
-  other.uncollapse_head();
 
   NodePath new_instance;
-  new_instance._head = PandaNode::attach(other._head, node(), sort);
+
+  // First, we'll attach to NULL, to guarantee we get a brand new
+  // instance.
+  new_instance._head = PandaNode::attach(NULL, node(), sort);
+
+  // Now, we'll reparent the new instance to the target node.
+  bool reparented = PandaNode::reparent(other._head, new_instance._head,
+                                        sort, false);
+  nassertr(reparented, new_instance);
 
   // instance_to() doesn't reset the velocity delta, unlike most of
   // the other reparenting operations.  The reasoning is that
@@ -414,6 +420,9 @@ copy_to(const NodePath &other, int sort) const {
 //               of this NodePath.  This is the preferred way to add
 //               nodes to the graph.
 //
+//               If the node was already a child of the parent, this
+//               returns a NodePath to the existing child.
+//
 //               This does *not* automatically extend the current
 //               NodePath to reflect the attachment; however, a
 //               NodePath that does reflect this extension is
@@ -425,7 +434,6 @@ attach_new_node(PandaNode *node, int sort) const {
   nassertr_always(!is_empty(), NodePath());
   nassertr(node != (PandaNode *)NULL, NodePath());
 
-  uncollapse_head();
   NodePath new_path(*this);
   new_path._head = PandaNode::attach(_head, node, sort);
   return new_path;
@@ -462,7 +470,6 @@ remove_node() {
   // NodePath is clear.
   if (!is_empty() && !is_singleton()) {
     node()->reset_prev_transform();
-    uncollapse_head();
     PandaNode::detach(_head);
   }
 
@@ -493,7 +500,6 @@ detach_node() {
   nassertv(_error_type != ET_not_found);
   if (!is_empty() && !is_singleton()) {
     node()->reset_prev_transform();
-    uncollapse_head();
     PandaNode::detach(_head);
   }
 }
@@ -506,8 +512,6 @@ detach_node() {
 ////////////////////////////////////////////////////////////////////
 void NodePath::
 output(ostream &out) const {
-  uncollapse_head();
-
   switch (_error_type) {
   case ET_not_found:
     out << "**not found**";
@@ -3025,6 +3029,59 @@ get_hidden_ancestor(DrawMask camera_mask) const {
   return not_found();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::stash
+//       Access: Published
+//  Description: Removes the referenced node (and the entire subgraph
+//               below this node) from the scene graph in any normal
+//               sense.  The node will no longer be visible and is not
+//               tested for collisions; furthermore, no normal scene
+//               graph traversal will visit the node.  The node's
+//               bounding volume no longer contributes to its parent's
+//               bounding volume.
+//
+//               A stashed node cannot be located by a normal find()
+//               operation (although a special find string can still
+//               retrieve it).
+////////////////////////////////////////////////////////////////////
+void NodePath::
+stash(int sort) {
+  nassertv_always(!is_singleton() && !is_empty());
+  nassertv(verify_complete());
+
+  bool reparented = PandaNode::reparent(_head->get_next(), _head, sort, true);
+  nassertv(reparented);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::unstash
+//       Access: Published
+//  Description: Undoes the effect of a previous stash() on this
+//               node: makes the referenced node (and the entire
+//               subgraph below this node) once again part of the
+//               scene graph.
+////////////////////////////////////////////////////////////////////
+void NodePath::
+unstash(int sort) {
+  nassertv_always(!is_singleton() && !is_empty());
+  nassertv(verify_complete());
+
+  bool reparented = PandaNode::reparent(_head->get_next(), _head, sort, false);
+  nassertv(reparented);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodePath::unstash_all
+//       Access: Published
+//  Description: Unstashes this node and all stashed child nodes.
+////////////////////////////////////////////////////////////////////
+void NodePath::
+unstash_all() {
+  NodePathCollection stashed_descendents = find_all_matches("**/@@*");
+  stashed_descendents.unstash();
+  unstash();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePath::get_stashed_ancestor
 //       Access: Published
@@ -3074,9 +3131,6 @@ get_stashed_ancestor() const {
 ////////////////////////////////////////////////////////////////////
 int NodePath::
 compare_to(const NodePath &other) const {
-  uncollapse_head();
-  other.uncollapse_head();
-
   // Nowadays, the NodePathComponents at the head are pointerwise
   // equivalent if and only if the NodePaths are equivalent.  So we
   // only have to compare pointers.
@@ -3090,8 +3144,7 @@ compare_to(const NodePath &other) const {
 //     Function: NodePath::verify_complete
 //       Access: Published
 //  Description: Returns true if all of the nodes described in the
-//               NodePath are connected and the top node is the top
-//               of the graph, or false otherwise.
+//               NodePath are connected, or false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool NodePath::
 verify_complete() const {
@@ -3099,7 +3152,6 @@ verify_complete() const {
     return true;
   }
 
-  uncollapse_head();
   const NodePathComponent *comp = _head;
   nassertr(comp != (const NodePathComponent *)NULL, false);
 
@@ -3133,15 +3185,7 @@ verify_complete() const {
     length--;
   }
 
-  // Now that we've reached the top, we should have no parents.
-  if (length == 0 && node->get_num_parents() == 0) {
-    return true;
-  }
-
-  pgraph_cat.warning()
-    << *this << " is incomplete; top node " << *node << " indicates length "
-    << length << " with " << node->get_num_parents() << " parents.\n";
-  return false;
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -3409,22 +3453,6 @@ write_bam_file(const string &filename) const {
   return okflag;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: NodePath::uncollapse_head
-//       Access: Private
-//  Description: Quietly and transparently uncollapses the _head
-//               pointer if it needs it.  This can happen only when
-//               two distinct NodePaths are collapsed into the same
-//               path after the removal of an instance somewhere
-//               higher up the chain.
-////////////////////////////////////////////////////////////////////
-void NodePath::
-uncollapse_head() const {
-  if (_head != (NodePathComponent *)NULL && _head->is_collapsed()) {
-    ((NodePath *)this)->_head = _head->uncollapse();
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePath::find_common_ancestor
 //       Access: Private, Static
@@ -3441,9 +3469,6 @@ NodePathComponent *NodePath::
 find_common_ancestor(const NodePath &a, const NodePath &b,
                      int &a_count, int &b_count) {
   nassertr(!a.is_empty() && !b.is_empty(), NULL);
-  a.uncollapse_head();
-  b.uncollapse_head();
-
   NodePathComponent *ac = a._head;
   NodePathComponent *bc = b._head;
   a_count = 0;

+ 6 - 3
panda/src/pgraph/nodePath.h

@@ -552,8 +552,9 @@ PUBLISHED:
   INLINE bool is_hidden(DrawMask camera_mask = DrawMask::all_on()) const;
   NodePath get_hidden_ancestor(DrawMask camera_mask = DrawMask::all_on()) const;
 
-  INLINE bool stash();
-  INLINE bool unstash();
+  void stash(int sort = 0);
+  void unstash(int sort = 0);
+  void unstash_all();
   INLINE bool is_stashed() const;
   NodePath get_stashed_ancestor() const;
 
@@ -587,10 +588,12 @@ PUBLISHED:
   INLINE bool has_net_tag(const string &key) const;
   NodePath find_net_tag(const string &key) const;
 
+  INLINE void set_name(const string &name);
+  INLINE string get_name() const;
+
   bool write_bam_file(const string &filename) const;
 
 private:
-  void uncollapse_head() const;
   static NodePathComponent *
   find_common_ancestor(const NodePath &a, const NodePath &b,
                        int &a_count, int &b_count);

+ 0 - 31
panda/src/pgraph/nodePathComponent.I

@@ -101,41 +101,10 @@ INLINE NodePathComponent::
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode *NodePathComponent::
 get_node() const {
-  // We don't have to bother checking if the component has been
-  // collapsed here, since the _node pointer will still be the same
-  // even if it has.
   nassertr(_node != (PandaNode *)NULL, _node);
   return _node;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: NodePathComponent::is_collapsed
-//       Access: Public
-//  Description: Returns true if this component has been collapsed
-//               with another component.  In this case, the component
-//               itself is invalid, and the collapsed component should
-//               be used instead.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodePathComponent::
-is_collapsed() const {
-  CDReader cdata(_cycler);
-  return (cdata->_length == 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodePathComponent::get_collapsed
-//       Access: Public
-//  Description: If is_collapsed() returns true, this is the component
-//               that this one has been collapsed with, and should be
-//               replaced with.
-////////////////////////////////////////////////////////////////////
-INLINE NodePathComponent *NodePathComponent::
-get_collapsed() const {
-  nassertr(is_collapsed(), (NodePathComponent *)NULL);
-  CDReader cdata(_cycler);
-  return cdata->_next;
-}
-
 INLINE ostream &operator << (ostream &out, const NodePathComponent &comp) {
   comp.output(out);
   return out;

+ 4 - 86
panda/src/pgraph/nodePathComponent.cxx

@@ -45,9 +45,6 @@ make_copy() const {
 ////////////////////////////////////////////////////////////////////
 int NodePathComponent::
 get_key() const {
-  if (is_collapsed()) {
-    return get_collapsed()->get_key();
-  }
   if (_key == 0) {
     // The first time someone asks for a particular component's key,
     // we make it up on the spot.  This helps keep us from wasting
@@ -67,9 +64,6 @@ get_key() const {
 ////////////////////////////////////////////////////////////////////
 bool NodePathComponent::
 is_top_node() const {
-  if (is_collapsed()) {
-    return get_collapsed()->is_top_node();
-  }
   CDReader cdata(_cycler);
   return (cdata->_next == (NodePathComponent *)NULL);
 }
@@ -81,9 +75,6 @@ is_top_node() const {
 ////////////////////////////////////////////////////////////////////
 int NodePathComponent::
 get_length() const {
-  if (is_collapsed()) {
-    return get_collapsed()->get_length();
-  }
   CDReader cdata(_cycler);
   return cdata->_length;
 }
@@ -95,24 +86,9 @@ get_length() const {
 ////////////////////////////////////////////////////////////////////
 NodePathComponent *NodePathComponent::
 get_next() const {
-  if (is_collapsed()) {
-    return get_collapsed()->get_next();
-  }
-
   CDReader cdata(_cycler);
   NodePathComponent *next = cdata->_next;
   
-  // If the next component has been collapsed, transparently update
-  // the pointer to get the actual node, and store the new pointer,
-  // before we return.  Collapsing can happen at any time to any
-  // component in the path and we have to deal with it.
-  if (next != (NodePathComponent *)NULL && next->is_collapsed()) {
-    next = next->uncollapse();
-
-    CDWriter cdata_w(((NodePathComponent *)this)->_cycler, cdata);
-    cdata_w->_next = next;
-  }
-  
   return next;
 }
 
@@ -139,29 +115,6 @@ fix_length() {
   return true;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: NodePathComponent::uncollapse
-//       Access: Public
-//  Description: Returns this component pointer if the component is
-//               not collapsed; or if it has been collapsed, returns
-//               the pointer it has been collapsed into.
-//
-//               Collapsing can happen at any time to any component in
-//               the path and we have to deal with it.  It happens
-//               when a node is removed further up the path that
-//               results in two instances becoming the same thing.
-////////////////////////////////////////////////////////////////////
-NodePathComponent *NodePathComponent::
-uncollapse() {
-  NodePathComponent *comp = this;
-
-  while (comp->is_collapsed()) {
-    comp = comp->get_collapsed();
-  }
-
-  return comp;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: NodePathComponent::output
 //       Access: Public
@@ -206,13 +159,9 @@ output(ostream &out) const {
 ////////////////////////////////////////////////////////////////////
 void NodePathComponent::
 set_next(NodePathComponent *next) {
-  if (is_collapsed()) {
-    get_collapsed()->set_next(next);
-  } else {
-    nassertv(next != (NodePathComponent *)NULL);
-    CDWriter cdata(_cycler);
-    cdata->_next = next;
-  }
+  nassertv(next != (NodePathComponent *)NULL);
+  CDWriter cdata(_cycler);
+  cdata->_next = next;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -223,37 +172,6 @@ set_next(NodePathComponent *next) {
 ////////////////////////////////////////////////////////////////////
 void NodePathComponent::
 set_top_node() {
-  if (is_collapsed()) {
-    get_collapsed()->set_top_node();
-  } else {
-    CDWriter cdata(_cycler);
-    cdata->_next = (NodePathComponent *)NULL;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodePathComponent::collapse_with
-//       Access: Private
-//  Description: Indicates that this component pointer is no longer
-//               valid, and that the indicated component should be
-//               used instead.  This is done whenever two
-//               NodePathComponents have been collapsed together due
-//               to an instance being removed higher up in the graph.
-////////////////////////////////////////////////////////////////////
-void NodePathComponent::
-collapse_with(NodePathComponent *next) {
-  nassertv(!is_collapsed());
-  nassertv(next != (NodePathComponent *)NULL);
   CDWriter cdata(_cycler);
-
-  // We indicate a component has been collapsed by setting its length
-  // to zero.
-  cdata->_next = next;
-  cdata->_length = 0;
-
-  if (_key != 0 && next->_key == 0) {
-    // If we had a key set and the other one didn't, it inherits our
-    // key.  Otherwise, we inherit the other's key.
-    next->_key = _key;
-  }
+  cdata->_next = (NodePathComponent *)NULL;
 }

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

@@ -56,21 +56,17 @@ public:
   INLINE PandaNode *get_node() const;
   int get_key() const;
   bool is_top_node() const;
-  INLINE bool is_collapsed() const;
   
   NodePathComponent *get_next() const;
   int get_length() const;
-  INLINE NodePathComponent *get_collapsed() const;
 
   bool fix_length();
-  NodePathComponent *uncollapse();
 
   void output(ostream &out) const;
   
 private:
   void set_next(NodePathComponent *next);
   void set_top_node();
-  void collapse_with(NodePathComponent *next);
 
   // We don't have to cycle the _node and _key elements, since these
   // are permanent properties of this object.  (Well, the _key is

+ 67 - 134
panda/src/pgraph/pandaNode.cxx

@@ -29,68 +29,15 @@
 
 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.
-//
-// The following rules all follow from rules (1) and (2):
-//
-//   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.
+// different inputs.  The PandaNode interfaces try to guess which
+// NodePaths should be updated as a result of the scene graph change,
+// while the NodePath interfaces already know.
 //
 // The NodePath support interface functions are strictly called from
 // within the NodePath class, and are used to implement
@@ -1607,7 +1554,15 @@ r_copy_children(const PandaNode *from, PandaNode::InstanceMap &inst_map) {
 ////////////////////////////////////////////////////////////////////
 PT(NodePathComponent) PandaNode::
 attach(NodePathComponent *parent, PandaNode *child_node, int sort) {
-  nassertr(parent != (NodePathComponent *)NULL, (NodePathComponent *)NULL);
+  if (parent == (NodePathComponent *)NULL) {
+    // Attaching to NULL means to create a new "instance" with no
+    // attachments, and no questions asked.
+    PT(NodePathComponent) child = 
+      new NodePathComponent(child_node, (NodePathComponent *)NULL);
+    CDWriter cdata_child(child_node->_cycler);
+    cdata_child->_paths.insert(child);
+    return child;
+  }
 
   // See if the child was already attached to the parent.  If it was,
   // we'll use that same NodePathComponent.
@@ -1619,7 +1574,7 @@ attach(NodePathComponent *parent, PandaNode *child_node, int sort) {
     child = get_top_component(child_node, true);
   }
 
-  reparent(parent, child, sort);
+  reparent(parent, child, sort, false);
   return child;
 }
 
@@ -1688,39 +1643,58 @@ detach(NodePathComponent *child) {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::reparent
 //       Access: Private, Static
-//  Description: Switches a node from one parent to another.
+//  Description: Switches a node from one parent to another.  Returns
+//               true if the new connection is allowed, or false if it
+//               conflicts with another instance (that is, another
+//               instance of the child is already attached to the
+//               indicated parent).
 ////////////////////////////////////////////////////////////////////
-void PandaNode::
-reparent(NodePathComponent *new_parent, NodePathComponent *child, int sort) {
-  nassertv(new_parent != (NodePathComponent *)NULL);
-  nassertv(child != (NodePathComponent *)NULL);
+bool PandaNode::
+reparent(NodePathComponent *new_parent, NodePathComponent *child, int sort,
+         bool as_stashed) {
+  nassertr(child != (NodePathComponent *)NULL, false);
 
   if (!child->is_top_node()) {
     detach(child);
   }
 
-  // Adjust the NodePathComponents.
-  child->set_next(new_parent);
+  if (new_parent != (NodePathComponent *)NULL) {
+    PandaNode *child_node = child->get_node();
+    PandaNode *parent_node = new_parent->get_node();
 
-  PandaNode *child_node = child->get_node();
-  PandaNode *parent_node = new_parent->get_node();
+    if (child_node->find_parent(parent_node) >= 0) {
+      // Whoops, there's already another instance of the child there.
+      return false;
+    }
 
-  // Now reattach at the indicated sort position.
-  CDWriter cdata_parent(parent_node->_cycler);
-  CDWriter cdata_child(child_node->_cycler);
-  
-  cdata_parent->_down.insert(DownConnection(child_node, sort));
-  cdata_child->_up.insert(UpConnection(parent_node));
-  
-  cdata_child->_paths.insert(child);
-  child_node->fix_path_lengths(cdata_child);
+    // Redirect the connection to the indicated new parent.
+    child->set_next(new_parent);
+    
+    // Now reattach the child node at the indicated sort position.
+    CDWriter cdata_parent(parent_node->_cycler);
+    CDWriter cdata_child(child_node->_cycler);
 
-  // Mark the bounding volumes stale.
-  parent_node->force_bound_stale();
+    if (as_stashed) {
+      cdata_parent->_stashed.insert(DownConnection(child_node, sort));
+    } else {
+      cdata_parent->_down.insert(DownConnection(child_node, sort));
+    }
+    cdata_child->_up.insert(UpConnection(parent_node));
+    
+    cdata_child->_paths.insert(child);
+    child_node->fix_path_lengths(cdata_child);
+    
+    // Mark the bounding volumes stale.
+    if (!as_stashed) {
+      parent_node->force_bound_stale();
+    }
 
-  // Call callback hooks.
-  parent_node->children_changed();
-  child_node->parents_changed();
+    // Call callback hooks.
+    parent_node->children_changed();
+    child_node->parents_changed();
+  }
+
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1904,8 +1878,7 @@ delete_component(NodePathComponent *component) {
     }
   }
 
-  // This may legitimately be zero if we are deleting a collapsed NodePath.
-  //  nassertv(max_num_erased == 1);
+  nassertv(max_num_erased == 1);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1916,65 +1889,25 @@ delete_component(NodePathComponent *component) {
 //               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.
+//               that reference the indicated parent node.  These
+//               components remain unattached; there may therefore be
+//               multiple "instances" of a node that all have no
+//               parent, even while there are other instances that do
+//               have parents.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 sever_connection(PandaNode *parent_node, PandaNode *child_node) {
   CDWriter cdata_child(child_node->_cycler);
-  PT(NodePathComponent) collapsed = (NodePathComponent *)NULL;
 
   Paths::iterator pi;
-  pi = cdata_child->_paths.begin();
-  while (pi != cdata_child->_paths.end()) {
-    Paths::iterator pnext = pi;
-    ++pnext;
+  for (pi = cdata_child->_paths.begin();
+       pi != cdata_child->_paths.end();
+       ++pi) {
     if (!(*pi)->is_top_node() && 
         (*pi)->get_next()->get_node() == parent_node) {
-      if (collapsed == (NodePathComponent *)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
-          // the first parent arbitrarily to combine with, and don't
-          // complain if there's ambiguity.
-          collapsed = child_node->get_generic_component(true);
-        }
-
-        if (collapsed == (NodePathComponent *)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);
-      }
+      // Sever the component here.
+      (*pi)->set_top_node();
     }
-    pi = pnext;
   }
   child_node->fix_path_lengths(cdata_child);
 }
@@ -1983,7 +1916,7 @@ sever_connection(PandaNode *parent_node, PandaNode *child_node) {
 //     Function: PandaNode::new_connection
 //       Access: Private, Static
 //  Description: This is called internally when a parent-child
-//               connection is establshed to update the
+//               connection is established to update the
 //               NodePathComponents that might be involved.
 //
 //               It adjusts any NodePathComponents the child has that

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

@@ -221,8 +221,8 @@ private:
   static PT(NodePathComponent) attach(NodePathComponent *parent, 
                                        PandaNode *child, int sort);
   static void detach(NodePathComponent *child);
-  static void reparent(NodePathComponent *new_parent,
-                       NodePathComponent *child, int sort);
+  static bool reparent(NodePathComponent *new_parent,
+                       NodePathComponent *child, int sort, bool as_stashed);
   static PT(NodePathComponent) get_component(NodePathComponent *parent,
                                               PandaNode *child);
   static PT(NodePathComponent) get_top_component(PandaNode *child,