Selaa lähdekoodia

pgraph stash and hide

David Rose 24 vuotta sitten
vanhempi
sitoutus
fc26653ada

+ 1 - 0
panda/src/display/graphicsEngine.cxx

@@ -237,6 +237,7 @@ do_cull(CullHandler *cull_handler, const qpNodePath &camera,
 
   qpCullTraverser trav;
   trav.set_cull_handler(cull_handler);
+  trav.set_camera_mask(camera_node->get_camera_mask());
 
   // We will need both the camera transform (the net transform to the
   // camera from the scene) and the world transform (the camera

+ 7 - 1
panda/src/pgraph/cullTraverserData.I

@@ -88,7 +88,13 @@ INLINE CullTraverserData::
 //               future nodes.
 ////////////////////////////////////////////////////////////////////
 INLINE bool CullTraverserData::
-is_in_view(PandaNode *node) {
+is_in_view(PandaNode *node, const DrawMask &camera_mask) {
+  if ((node->get_draw_mask() & camera_mask).is_zero()) {
+    // If there are no draw bits in common with the camera, the node
+    // is out.
+    return false;
+  }
+
   if (_view_frustum == (GeometricBoundingVolume *)NULL) {
     // If the transform is valid, but we don't have a frustum, it's
     // always in.

+ 2 - 1
panda/src/pgraph/cullTraverserData.h

@@ -25,6 +25,7 @@
 #include "transformState.h"
 #include "geometricBoundingVolume.h"
 #include "pointerTo.h"
+#include "drawMask.h"
 
 class PandaNode;
 
@@ -54,7 +55,7 @@ public:
   INLINE void operator = (const CullTraverserData &copy);
   INLINE ~CullTraverserData();
 
-  INLINE bool is_in_view(PandaNode *node);
+  INLINE bool is_in_view(PandaNode *node, const DrawMask &camera_mask);
   void apply_transform_and_state(PandaNode *node);
 
   CPT(TransformState) _render_transform;

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

@@ -107,6 +107,24 @@ INLINE PandaNode::CData::
 CData() {
   _state = RenderState::make_empty();
   _transform = TransformState::make_identity();
+  _draw_mask = DrawMask::all_on();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::CData::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::CData::
+CData(const PandaNode::CData &copy) :
+  _down(copy._down),
+  _stashed(copy._stashed),
+  _up(copy._up),
+  _paths(copy._paths),
+  _state(copy._state),
+  _transform(copy._transform),
+  _draw_mask(copy._draw_mask)
+{
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -237,6 +255,98 @@ get_child_sort(int n) const {
   return cdata->_down[n].get_sort();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::stash_child
+//       Access: Published
+//  Description: Stashes the indicated child node.  This removes the
+//               child from the list of active children and puts it on
+//               a special list of stashed children.  This child node
+//               no longer contributes to the bounding volume of the
+//               PandaNode, and is not visited in normal traversals.
+//               It is invisible and uncollidable.  The child may
+//               later be restored by calling unstash_child().
+//
+//               This function returns true if the child node was
+//               successfully stashed, or false if it was not a child
+//               of the node in the first place (e.g. it was
+//               previously stashed).
+////////////////////////////////////////////////////////////////////
+INLINE bool PandaNode::
+stash_child(PandaNode *child_node) {
+  int child_index = find_child(child_node);
+  if (child_index < 0) {
+    return false;
+  }
+  stash_child(child_index);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::unstash_child
+//       Access: Published
+//  Description: Returns the indicated stashed node to normal child
+//               status.  This removes the child from the list of
+//               stashed children and puts it on the normal list of
+//               active children.  This child node once again
+//               contributes to the bounding volume of the PandaNode,
+//               and will be visited in normal traversals.  It is
+//               visible and collidable.
+//
+//               This function returns true if the child node was
+//               successfully stashed, or false if it was not a child
+//               of the node in the first place (e.g. it was
+//               previously stashed).
+////////////////////////////////////////////////////////////////////
+INLINE bool PandaNode::
+unstash_child(PandaNode *child_node) {
+  int stashed_index = find_stashed(child_node);
+  if (stashed_index < 0) {
+    return false;
+  }
+  unstash_child(stashed_index);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_num_stashed
+//       Access: Published
+//  Description: Returns the number of stashed nodes this node has.
+//               These are former children of the node that have been
+//               moved to the special stashed list via stash_child().
+////////////////////////////////////////////////////////////////////
+INLINE int PandaNode::
+get_num_stashed() const {
+  CDReader cdata(_cycler);
+  return cdata->_stashed.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_stashed
+//       Access: Published
+//  Description: Returns the nth stashed node of this node.  See
+//               get_num_stashed().
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *PandaNode::
+get_stashed(int n) const {
+  CDReader cdata(_cycler);
+  nassertr(n >= 0 && n < (int)cdata->_stashed.size(), NULL);
+  return cdata->_stashed[n].get_child();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_stashed_sort
+//       Access: Published
+//  Description: Returns the sort index of the nth stashed node of this
+//               node (that is, the number that was passed to
+//               add_child()).  See get_num_stashed().
+////////////////////////////////////////////////////////////////////
+INLINE int PandaNode::
+get_stashed_sort(int n) const {
+  CDReader cdata(_cycler);
+  nassertr(n >= 0 && n < (int)cdata->_stashed.size(), -1);
+  return cdata->_stashed[n].get_sort();
+}
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::set_attrib
@@ -397,6 +507,37 @@ ls(ostream &out, int indent_level) const {
   r_list_descendants(out, indent_level);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::set_draw_mask
+//       Access: Published
+//  Description: Sets the hide/show bits of this particular node.
+//
+//               During the cull traversal, a node is not visited if
+//               none of its draw mask bits intersect with the
+//               camera's draw mask bits.  These masks can be used to
+//               selectively hide and show different parts of the
+//               scene graph from different cameras that are otherwise
+//               viewing the same scene.  See
+//               Camera::set_camera_mask().
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+set_draw_mask(DrawMask mask) {
+  CDWriter cdata(_cycler);
+  cdata->_draw_mask = mask;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_draw_mask
+//       Access: Published
+//  Description: Returns the hide/show bits of this particular node.
+//               See set_draw_mask().
+////////////////////////////////////////////////////////////////////
+INLINE DrawMask PandaNode::
+get_draw_mask() const {
+  CDReader cdata(_cycler);
+  return cdata->_draw_mask;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::set_bound
 //       Access: Published

+ 453 - 237
panda/src/pgraph/pandaNode.cxx

@@ -28,21 +28,6 @@
 TypeHandle PandaNode::_type_handle;
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::CData::Copy Constructor
-//       Access: Public
-//  Description:
-////////////////////////////////////////////////////////////////////
-PandaNode::CData::
-CData(const PandaNode::CData &copy) :
-  _down(copy._down),
-  _up(copy._up),
-  _chains(copy._chains),
-  _state(copy._state),
-  _transform(copy._transform)
-{
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::CData::make_copy
 //       Access: Public, Virtual
@@ -64,6 +49,8 @@ write_datagram(BamWriter *manager, Datagram &dg) const {
   manager->write_pointer(dg, _state);
   manager->write_pointer(dg, _transform);
 
+  dg.add_uint32(_draw_mask.get_word());
+
   // When we write a PandaNode, we write out its complete list of
   // child node pointers, but we only write out the parent node
   // pointers that have already been added to the bam file by a
@@ -96,13 +83,24 @@ write_datagram(BamWriter *manager, Datagram &dg) const {
 
   // **** We should smarten up the writing of the sort number--most of
   // the time these will all be zero.
-  Down::const_iterator ci;
-  for (ci = _down.begin(); ci != _down.end(); ++ci) {
-    PandaNode *child_node = (*ci).get_child();
-    int sort = (*ci).get_sort();
+  Down::const_iterator di;
+  for (di = _down.begin(); di != _down.end(); ++di) {
+    PandaNode *child_node = (*di).get_child();
+    int sort = (*di).get_sort();
     manager->write_pointer(dg, child_node);
     dg.add_int32(sort);
   }
+
+  int num_stashed = _stashed.size();
+  nassertv(num_stashed == (int)(PN_uint16)num_stashed);
+  dg.add_uint16(num_stashed);
+
+  for (di = _stashed.begin(); di != _stashed.end(); ++di) {
+    PandaNode *stashed_node = (*di).get_child();
+    int sort = (*di).get_sort();
+    manager->write_pointer(dg, stashed_node);
+    dg.add_int32(sort);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -141,6 +139,13 @@ complete_pointers(TypedWritable **p_list, BamReader *manager) {
     (*di) = DownConnection(child_node, sort);
   }
 
+  // Get the stashed pointers.
+  for (di = _stashed.begin(); di != _stashed.end(); ++di) {
+    int sort = (*di).get_sort();
+    PT(PandaNode) stashed_node = DCAST(PandaNode, p_list[pi++]);
+    (*di) = DownConnection(stashed_node, sort);
+  }
+
   return pi;
 }
 
@@ -157,6 +162,8 @@ fillin(DatagramIterator &scan, BamReader *manager) {
   manager->read_pointer(scan);
   manager->read_pointer(scan);
 
+  _draw_mask.set_word(scan.get_uint32());
+
   int num_parents = scan.get_uint16();
   // Read the list of parent nodes.  Push back a NULL for each one.
   _up.reserve(num_parents);
@@ -173,6 +180,15 @@ fillin(DatagramIterator &scan, BamReader *manager) {
     int sort = scan.get_int32();
     _down.push_back(DownConnection(NULL, sort));
   }
+
+  int num_stashed = scan.get_uint16();
+  // Read the list of stashed nodes.  Push back a NULL for each one.
+  _stashed.reserve(num_stashed);
+  for (int i = 0; i < num_stashed; i++) {
+    manager->read_pointer(scan);
+    int sort = scan.get_int32();
+    _stashed.push_back(DownConnection(NULL, sort));
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -460,10 +476,10 @@ find_child(PandaNode *node) const {
 
   // We have to search for the child by brute force, since we don't
   // know what sort index it was added as.
-  Down::const_iterator ci;
-  for (ci = cdata->_down.begin(); ci != cdata->_down.end(); ++ci) {
-    if ((*ci).get_child() == node) {
-      return ci - cdata->_down.begin();
+  Down::const_iterator di;
+  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+    if ((*di).get_child() == node) {
+      return di - cdata->_down.begin();
     }
   }
 
@@ -484,30 +500,28 @@ find_child(PandaNode *node) const {
 void PandaNode::
 add_child(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->_down.insert(DownConnection(child_node, sort));
-    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.
-    Chains::iterator ci;
-    for (ci = cdata_child->_chains.begin();
-	 ci != cdata_child->_chains.end();
-	 ++ci) {
-      if ((*ci)->is_top_node()) {
-	(*ci)->set_next(get_generic_component());
-      }
+  PT(PandaNode) keep_child = child_node;
+  remove_child(child_node);
+  
+  CDWriter cdata(_cycler);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_down.insert(DownConnection(child_node, sort));
+  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_chain_lengths(cdata_child);
   }
+  child_node->fix_path_lengths(cdata_child);
 
   // Mark the bounding volumes stale.
   force_bound_stale();
@@ -524,47 +538,44 @@ add_child(PandaNode *child_node, int sort) {
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 remove_child(int n) {
-  PT(PandaNode) child_node;
-  {
-    CDWriter cdata(_cycler);
-    nassertv(n >= 0 && n < (int)cdata->_down.size());
-    
-    child_node = cdata->_down[n].get_child();
-    CDWriter cdata_child(child_node->_cycler);
-    
-    cdata->_down.erase(cdata->_down.begin() + n);
-    int num_erased = cdata_child->_up.erase(UpConnection(this));
-    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;
-    Chains::iterator ci;
-    ci = cdata_child->_chains.begin();
-    while (ci != cdata_child->_chains.end()) {
-      Chains::iterator cnext = ci;
-      ++cnext;
-      if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-	if (collapsed == (qpNodePathComponent *)NULL) {
-	  (*ci)->set_top_node();
-	  collapsed = (*ci);
-	} 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.
-	  (*ci)->collapse_with(collapsed);
-	  cdata_child->_chains.erase(ci);
-	}
+  CDWriter cdata(_cycler);
+  nassertv(n >= 0 && n < (int)cdata->_down.size());
+  
+  PT(PandaNode) child_node = cdata->_down[n].get_child();
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_down.erase(cdata->_down.begin() + n);
+  int num_erased = cdata_child->_up.erase(UpConnection(this));
+  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);
       }
-      ci = cnext;
     }
-    
-    child_node->fix_chain_lengths(cdata_child);
+    pi = pnext;
   }
+  
+  child_node->fix_path_lengths(cdata_child);
 
   // Mark the bounding volumes stale.
   force_bound_stale();
@@ -586,53 +597,51 @@ 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.
-      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;
-    Chains::iterator ci;
-    ci = cdata_child->_chains.begin();
-    while (ci != cdata_child->_chains.end()) {
-      Chains::iterator cnext = ci;
-      ++cnext;
-      if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-	if (collapsed == (qpNodePathComponent *)NULL) {
-	  (*ci)->set_top_node();
-	  collapsed = (*ci);
-	} else {
-	  (*ci)->collapse_with(collapsed);
-	  cdata_child->_chains.erase(ci);
-	}
+  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.
+    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);
       }
-      ci = cnext;
     }
-    
-    child_node->fix_chain_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;
-      }
+    pi = pnext;
+  }
+  
+  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;
     }
-
-    nassertr(found, false);
   }
+  
+  nassertr(found, false);
 
   // Mark the bounding volumes stale.
   force_bound_stale();
@@ -643,47 +652,245 @@ remove_child(PandaNode *child_node) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::stash_child
+//       Access: Published
+//  Description: Stashes the indicated child node.  This removes the
+//               child from the list of active children and puts it on
+//               a special list of stashed children.  This child node
+//               no longer contributes to the bounding volume of the
+//               PandaNode, and is not visited in normal traversals.
+//               It is invisible and uncollidable.  The child may
+//               later be restored by calling unstash_child().
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+stash_child(int child_index) {
+  nassertv(child_index >= 0 && child_index < get_num_children());
+  PT(PandaNode) child_node = get_child(child_index);
+  int sort = get_child_sort(child_index);
+  
+  remove_child(child_index);
+  
+  CDWriter cdata(_cycler);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_stashed.insert(DownConnection(child_node, sort));
+  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);
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
+
+  // Call callback hooks.
+  children_changed();
+  child_node->parents_changed();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::unstash_child
+//       Access: Published
+//  Description: Returns the indicated stashed node to normal child
+//               status.  This removes the child from the list of
+//               stashed children and puts it on the normal list of
+//               active children.  This child node once again
+//               contributes to the bounding volume of the PandaNode,
+//               and will be visited in normal traversals.  It is
+//               visible and collidable.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+unstash_child(int stashed_index) {
+  nassertv(stashed_index >= 0 && stashed_index < get_num_stashed());
+  PT(PandaNode) child_node = get_stashed(stashed_index);
+  int sort = get_stashed_sort(stashed_index);
+  
+  remove_stashed(stashed_index);
+  
+  CDWriter cdata(_cycler);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_down.insert(DownConnection(child_node, sort));
+  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);
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
+
+  // Call callback hooks.
+  children_changed();
+  child_node->parents_changed();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::find_stashed
+//       Access: Published
+//  Description: Returns the index of the indicated stashed node, if
+//               it is a stashed child, or -1 if it is not.
+////////////////////////////////////////////////////////////////////
+int PandaNode::
+find_stashed(PandaNode *node) const {
+  CDReader cdata(_cycler);
+
+  // We have to search for the child by brute force, since we don't
+  // know what sort index it was added as.
+  Down::const_iterator di;
+  for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
+    if ((*di).get_child() == node) {
+      return di - cdata->_stashed.begin();
+    }
+  }
+
+  return -1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::remove_stashed
+//       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.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+remove_stashed(int n) {
+  CDWriter cdata(_cycler);
+  nassertv(n >= 0 && n < (int)cdata->_stashed.size());
+  
+  PT(PandaNode) child_node = cdata->_stashed[n].get_child();
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_stashed.erase(cdata->_stashed.begin() + n);
+  int num_erased = cdata_child->_up.erase(UpConnection(this));
+  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);
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
+
+  // Call callback hooks.
+  children_changed();
+  child_node->parents_changed();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::remove_all_children
 //       Access: Published
-//  Description: Removes all the children from the node at once.
+//  Description: Removes all the children from the node at once,
+//               including stashed children.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
 remove_all_children() {
-  {
-    CDWriter cdata(_cycler);
-    Down::iterator ci;
-    for (ci = cdata->_down.begin(); ci != cdata->_down.end(); ++ci) {
-      PT(PandaNode) child_node = (*ci).get_child();
-      {
-        CDWriter cdata_child(child_node->_cycler);
-        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;
-        Chains::iterator ci;
-        ci = cdata_child->_chains.begin();
-        while (ci != cdata_child->_chains.end()) {
-          Chains::iterator cnext = ci;
-          ++cnext;
-          if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-            if (collapsed == (qpNodePathComponent *)NULL) {
-              (*ci)->set_top_node();
-              collapsed = (*ci);
-            } else {
-              (*ci)->collapse_with(collapsed);
-              cdata_child->_chains.erase(ci);
-            }
-          }
-          ci = cnext;
+  CDWriter cdata(_cycler);
+  Down::iterator di;
+  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+    PT(PandaNode) child_node = (*di).get_child();
+    CDWriter cdata_child(child_node->_cycler);
+    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);
         }
-        
-        child_node->fix_chain_lengths(cdata_child);
       }
-      child_node->parents_changed();
+      pi = pnext;
     }
+    
+    child_node->fix_path_lengths(cdata_child);
+    child_node->parents_changed();
+  }
+  for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
+    PT(PandaNode) child_node = (*di).get_child();
+    CDWriter cdata_child(child_node->_cycler);
+    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);
+    child_node->parents_changed();
   }
 
   // Mark the bounding volumes stale.
@@ -896,51 +1103,59 @@ detach(qpNodePathComponent *child) {
   PandaNode *child_node = child->get_node();
   PandaNode *parent_node = child->get_next()->get_node();
   
-  {
-    // Break the qpNodePathComponent connection.
-    child->set_top_node();
-    
-    CDWriter cdata_child(child_node->_cycler);
-    CDWriter cdata_parent(parent_node->_cycler);
-    
-    // Any other components in the same child_node that previously
-    // referenced the same parent has now become invalid and must be
-    // collapsed into this one and removed from the chains set.
-    Chains::iterator ci;
-    ci = cdata_child->_chains.begin();
-    while (ci != cdata_child->_chains.end()) {
-      Chains::iterator cnext = ci;
-      ++cnext;
-      if ((*ci) != child && !(*ci)->is_top_node() && 
-	  (*ci)->get_next()->get_node() == parent_node) {
-	(*ci)->collapse_with(child);
-	cdata_child->_chains.erase(ci);
-      }
-      ci = cnext;
+  // Break the qpNodePathComponent connection.
+  child->set_top_node();
+  
+  CDWriter cdata_child(child_node->_cycler);
+  CDWriter cdata_parent(parent_node->_cycler);
+  
+  // Any other components in the same child_node that previously
+  // referenced the same parent has now become invalid and must be
+  // collapsed into this one and removed from the paths set.
+  Paths::iterator pi;
+  pi = cdata_child->_paths.begin();
+  while (pi != cdata_child->_paths.end()) {
+    Paths::iterator pnext = pi;
+    ++pnext;
+    if ((*pi) != child && !(*pi)->is_top_node() && 
+        (*pi)->get_next()->get_node() == parent_node) {
+      (*pi)->collapse_with(child);
+      cdata_child->_paths.erase(pi);
     }
-    
-    // Now look for the child and break the actual connection.
-    
-    // First, look for and remove the parent node from the child's up
-    // list.
-    int num_erased = cdata_child->_up.erase(UpConnection(parent_node));
-    nassertv(num_erased == 1);
-    
-    child_node->fix_chain_lengths(cdata_child);
-    
-    // Now, look for and remove the child node from the parent's down list.
-    Down::iterator di;
-    bool found = false;
-    for (di = cdata_parent->_down.begin(); 
-	 di != cdata_parent->_down.end() && !found; 
-	 ++di) {
-      if ((*di).get_child() == child_node) {
-	cdata_parent->_down.erase(di);
-	found = true;
-      }
+    pi = pnext;
+  }
+  
+  // Now look for the child and break the actual connection.
+  
+  // First, look for and remove the parent node from the child's up
+  // list.
+  int num_erased = cdata_child->_up.erase(UpConnection(parent_node));
+  nassertv(num_erased == 1);
+  
+  child_node->fix_path_lengths(cdata_child);
+  
+  // Now, look for and remove the child node from the parent's down
+  // list.  We also check in the stashed list, in case the child node
+  // has been stashed.
+  Down::iterator di;
+  bool found = false;
+  for (di = cdata_parent->_down.begin(); 
+       di != cdata_parent->_down.end() && !found; 
+       ++di) {
+    if ((*di).get_child() == child_node) {
+      cdata_parent->_down.erase(di);
+      found = true;
+    }
+  }
+  for (di = cdata_parent->_stashed.begin(); 
+       di != cdata_parent->_stashed.end() && !found; 
+       ++di) {
+    if ((*di).get_child() == child_node) {
+      cdata_parent->_stashed.erase(di);
+      found = true;
     }
-    nassertv(found);
   }
+  nassertv(found);
 
   // Mark the bounding volumes stale.
   parent_node->force_bound_stale();
@@ -970,17 +1185,15 @@ reparent(qpNodePathComponent *new_parent, qpNodePathComponent *child, int sort)
   PandaNode *child_node = child->get_node();
   PandaNode *parent_node = new_parent->get_node();
 
-  {
-    // 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->_chains.insert(child);
-    child_node->fix_chain_lengths(cdata_child);
-  }
+  // 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);
 
   // Mark the bounding volumes stale.
   parent_node->force_bound_stale();
@@ -1008,13 +1221,13 @@ get_component(qpNodePathComponent *parent, PandaNode *child_node) {
     // First, walk through the list of qpNodePathComponents we already
     // have on the child, looking for one that already exists,
     // referencing the indicated parent component.
-    Chains::const_iterator ci;
-    for (ci = cdata_child->_chains.begin(); 
-         ci != cdata_child->_chains.end(); 
-         ++ci) {
-      if ((*ci)->get_next() == parent) {
+    Paths::const_iterator pi;
+    for (pi = cdata_child->_paths.begin(); 
+         pi != cdata_child->_paths.end(); 
+         ++pi) {
+      if ((*pi)->get_next() == parent) {
         // If we already have such a component, just return it.
-        return (*ci);
+        return (*pi);
       }
     }
   }
@@ -1027,7 +1240,7 @@ get_component(qpNodePathComponent *parent, PandaNode *child_node) {
     PT(qpNodePathComponent) child = 
       new qpNodePathComponent(child_node, parent);
     CDWriter cdata_child(child_node->_cycler);
-    cdata_child->_chains.insert(child);
+    cdata_child->_paths.insert(child);
     return child;
   } else {
     // They aren't related.  Return NULL.
@@ -1051,13 +1264,13 @@ get_top_component(PandaNode *child_node) {
 
     // Walk through the list of qpNodePathComponents we already have on
     // the child, looking for one that already exists as a top node.
-    Chains::const_iterator ci;
-    for (ci = cdata_child->_chains.begin(); 
-         ci != cdata_child->_chains.end(); 
-         ++ci) {
-      if ((*ci)->is_top_node()) {
+    Paths::const_iterator pi;
+    for (pi = cdata_child->_paths.begin(); 
+         pi != cdata_child->_paths.end(); 
+         ++pi) {
+      if ((*pi)->is_top_node()) {
         // If we already have such a component, just return it.
-        return (*ci);
+        return (*pi);
       }
     }
   }
@@ -1067,7 +1280,7 @@ get_top_component(PandaNode *child_node) {
   PT(qpNodePathComponent) child = 
     new qpNodePathComponent(child_node, (qpNodePathComponent *)NULL);
   CDWriter cdata_child(child_node->_cycler);
-  cdata_child->_chains.insert(child);
+  cdata_child->_paths.insert(child);
 
   return child;
 }
@@ -1076,7 +1289,7 @@ get_top_component(PandaNode *child_node) {
 //     Function: PandaNode::get_generic_component
 //       Access: Private
 //  Description: Returns a qpNodePathComponent referencing this node as
-//               a chain from the root.  It is only valid to call this
+//               a path from the root.  It is only valid to call this
 //               if there is an unambiguous path from the root;
 //               otherwise, a warning will be issued and one path will
 //               be chosen arbitrarily.
@@ -1115,7 +1328,7 @@ delete_component(qpNodePathComponent *component) {
   for (int i = 0; i < num_stages; i++) {
     if (_cycler.is_stage_unique(i)) {
       CData *cdata = _cycler.write_stage(i);
-      int num_erased = cdata->_chains.erase(component);
+      int num_erased = cdata->_paths.erase(component);
       max_num_erased = max(max_num_erased, num_erased);
       _cycler.release_write_stage(i, cdata);
     }
@@ -1124,7 +1337,7 @@ delete_component(qpNodePathComponent *component) {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::fix_chain_lengths
+//     Function: PandaNode::fix_path_lengths
 //       Access: Private
 //  Description: Recursively fixes the _length member of each
 //               qpNodePathComponent at this level and below, after an
@@ -1132,25 +1345,30 @@ delete_component(qpNodePathComponent *component) {
 //               these up.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
-fix_chain_lengths(const CData *cdata) {
+fix_path_lengths(const CData *cdata) {
   bool any_wrong = false;
 
-  Chains::const_iterator ci;
-  for (ci = cdata->_chains.begin(); ci != cdata->_chains.end(); ++ci) {
-    if ((*ci)->fix_length()) {
+  Paths::const_iterator pi;
+  for (pi = cdata->_paths.begin(); pi != cdata->_paths.end(); ++pi) {
+    if ((*pi)->fix_length()) {
       any_wrong = true;
     }
   }
   
-  // If any chains were updated, we have to recurse on all of our
-  // children, since any one of those chains might be shared by any of
+  // If any paths were updated, we have to recurse on all of our
+  // children, since any one of those paths might be shared by any of
   // our child nodes.
   if (any_wrong) {
     Down::const_iterator di;
     for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
       PandaNode *child_node = (*di).get_child();
       CDReader cdata_child(child_node->_cycler);
-      child_node->fix_chain_lengths(cdata_child);
+      child_node->fix_path_lengths(cdata_child);
+    }
+    for (di = cdata->_stashed.begin(); di != cdata->_stashed.end(); ++di) {
+      PandaNode *child_node = (*di).get_child();
+      CDReader cdata_child(child_node->_cycler);
+      child_node->fix_path_lengths(cdata_child);
     }
   }
 }
@@ -1171,12 +1389,10 @@ r_list_descendants(ostream &out, int indent_level) const {
   }
 
   // Also report the number of stashed nodes at this level.
-  /*
   int num_stashed = get_num_stashed();
   if (num_stashed != 0) {
     indent(out, indent_level) << "(" << num_stashed << " stashed)\n";
   }
-  */
 }
 
 ////////////////////////////////////////////////////////////////////

+ 17 - 10
panda/src/pgraph/pandaNode.h

@@ -27,7 +27,7 @@
 #include "pipelineCycler.h"
 #include "renderState.h"
 #include "transformState.h"
-
+#include "drawMask.h"
 #include "typedWritable.h"
 #include "boundedObject.h"
 #include "namable.h"
@@ -88,13 +88,15 @@ PUBLISHED:
   void remove_child(int n);
   bool remove_child(PandaNode *child_node);
 
-  /*
-  bool stash_child(PandaNode *child);
-  bool unstash_child(PandaNode *child);
+  INLINE bool stash_child(PandaNode *child_node);
+  void stash_child(int child_index);
+  INLINE bool unstash_child(PandaNode *child_node);
+  void unstash_child(int stashed_index);
   INLINE int get_num_stashed() const;
   INLINE PandaNode *get_stashed(int n) const;
-  INLINE PandaNode *get_stashed_sort(int n) const;
-  */
+  INLINE int get_stashed_sort(int n) const;
+  int find_stashed(PandaNode *node) const;
+  void remove_stashed(int n);
 
   void remove_all_children();
 
@@ -111,6 +113,9 @@ PUBLISHED:
   INLINE const TransformState *get_transform() const;
   INLINE void clear_transform();
 
+  INLINE void set_draw_mask(DrawMask mask);
+  INLINE DrawMask get_draw_mask() const;
+
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
 
@@ -166,7 +171,7 @@ private:
   PT(qpNodePathComponent) get_generic_component();
   void delete_component(qpNodePathComponent *component);
   class CData;
-  void fix_chain_lengths(const CData *cdata);
+  void fix_path_lengths(const CData *cdata);
   void r_list_descendants(ostream &out, int indent_level) const;
 
 private:
@@ -204,24 +209,26 @@ private:
   // requested a qpNodePath for.  We don't keep reference counts; when
   // each qpNodePathComponent destructs, it removes itself from this
   // set.
-  typedef pset<qpNodePathComponent *> Chains;
+  typedef pset<qpNodePathComponent *> Paths;
   
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   public:
     INLINE CData();
-    CData(const CData &copy);
+    INLINE CData(const CData &copy);
     virtual CycleData *make_copy() const;
     virtual void write_datagram(BamWriter *manager, Datagram &dg) const;
     virtual int complete_pointers(TypedWritable **plist, BamReader *manager);
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
 
     Down _down;
+    Down _stashed;
     Up _up;
-    Chains _chains;
+    Paths _paths;
 
     CPT(RenderState) _state;
     CPT(TransformState) _transform;
+    DrawMask _draw_mask;
   };
 
   PipelineCycler<CData> _cycler;

+ 30 - 0
panda/src/pgraph/qpcamera.I

@@ -85,3 +85,33 @@ get_display_region(int n) const {
   nassertr(n >= 0 && n < (int)_display_regions.size(), (DisplayRegion *)NULL);
   return _display_regions[n];
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::set_camera_mask
+//       Access: Published
+//  Description: Changes the set of bits that represent the subset of
+//               the scene graph the camera will render.
+//
+//               During the cull traversal, a node is not visited if
+//               none of its draw mask bits intersect with the
+//               camera's camera mask bits.  These masks can be used
+//               to selectively hide and show different parts of the
+//               scene graph from different cameras that are otherwise
+//               viewing the same scene.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCamera::
+set_camera_mask(DrawMask mask) {
+  _camera_mask = mask;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::get_camera_mask
+//       Access: Published
+//  Description: Returns the set of bits that represent the subset of
+//               the scene graph the camera will render.  See
+//               set_camera_mask().
+////////////////////////////////////////////////////////////////////
+INLINE DrawMask qpCamera::
+get_camera_mask() const {
+  return _camera_mask;
+}

+ 10 - 2
panda/src/pgraph/qpcamera.cxx

@@ -31,7 +31,8 @@ TypeHandle qpCamera::_type_handle;
 qpCamera::
 qpCamera(const string &name) :
   qpLensNode(name),
-  _active(true)
+  _active(true),
+  _camera_mask(DrawMask::all_on())
 {
 }
 
@@ -44,7 +45,8 @@ qpCamera::
 qpCamera(const qpCamera &copy) :
   qpLensNode(copy),
   _active(copy._active),
-  _scene(copy._scene)
+  _scene(copy._scene),
+  _camera_mask(copy._camera_mask)
 {
 }
 
@@ -150,6 +152,9 @@ register_with_read_factory() {
 void qpCamera::
 write_datagram(BamWriter *manager, Datagram &dg) {
   qpLensNode::write_datagram(manager, dg);
+
+  dg.add_bool(_active);
+  dg.add_uint32(_camera_mask.get_word());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -182,4 +187,7 @@ make_from_bam(const FactoryParams &params) {
 void qpCamera::
 fillin(DatagramIterator &scan, BamReader *manager) {
   qpLensNode::fillin(scan, manager);
+
+  _active = scan.get_bool();
+  _camera_mask.set_word(scan.get_uint32());
 }

+ 6 - 0
panda/src/pgraph/qpcamera.h

@@ -23,6 +23,7 @@
 
 #include "qplensNode.h"
 #include "qpnodePath.h"
+#include "drawMask.h"
 
 class DisplayRegion;
 
@@ -55,6 +56,9 @@ PUBLISHED:
   INLINE int get_num_display_regions() const;
   INLINE DisplayRegion *get_display_region(int n) const;
 
+  INLINE void set_camera_mask(DrawMask mask);
+  INLINE DrawMask get_camera_mask() const;
+
 private:
   void add_display_region(DisplayRegion *display_region);
   void remove_display_region(DisplayRegion *display_region);
@@ -62,6 +66,8 @@ private:
   bool _active;
   qpNodePath _scene;
 
+  DrawMask _camera_mask;
+
   typedef pvector<DisplayRegion *> DisplayRegions;
   DisplayRegions _display_regions;
 

+ 94 - 0
panda/src/pgraph/qpcullTraverser.I

@@ -15,3 +15,97 @@
 // [email protected] .
 //
 ////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_initial_state
+//       Access: Public
+//  Description: Sets the initial RenderState at the top of the scene
+//               graph we are traversing.  If this is not set, the
+//               default is the empty state.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_initial_state(const RenderState *initial_state) {
+  _initial_state = initial_state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_camera_mask
+//       Access: Public
+//  Description: Specifies the visibility mask from the camera viewing
+//               the scene.  Any nodes that do not have at least some
+//               bits in common with this mask will not be drawn.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_camera_mask(const DrawMask &camera_mask) {
+  _camera_mask = camera_mask;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_camera_transform
+//       Access: Public
+//  Description: Specifies the position of the camera relative to the
+//               starting node, without any compensating
+//               coordinate-system transforms that might have been
+//               introduced for the purposes of rendering.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_camera_transform(const TransformState *camera_transform) {
+  _camera_transform = camera_transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_render_transform
+//       Access: Public
+//  Description: Specifies the position of the starting node relative
+//               to the camera, pretransformed as appropriate for
+//               rendering.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_render_transform(const TransformState *render_transform) {
+  _render_transform = render_transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_view_frustum
+//       Access: Public
+//  Description: Specifies the bounding volume that corresponds to the
+//               viewing frustum.  Any primitives that fall entirely
+//               outside of this volume are not drawn.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_view_frustum(GeometricBoundingVolume *view_frustum) {
+  _view_frustum = view_frustum;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_guard_band
+//       Access: Public
+//  Description: Specifies the bounding volume to use for detecting
+//               guard band clipping.  This is a render optimization
+//               for certain cards that support this feature; the
+//               guard band is a 2-d area than the frame buffer.
+//               If a primitive will appear entirely within the guard
+//               band after perspective transform, it may be drawn
+//               correctly with clipping disabled, for a small
+//               performance gain.
+//
+//               This is the bounding volume that corresponds to the
+//               2-d guard band.  If a primitive is entirely within
+//               this area, clipping will be disabled on the GSG.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_guard_band(GeometricBoundingVolume *guard_band) {
+  _guard_band = guard_band;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCullTraverser::set_cull_handler
+//       Access: Public
+//  Description: Specifies the object that will receive the culled
+//               Geoms.  This must be set before calling traverse().
+////////////////////////////////////////////////////////////////////
+INLINE void qpCullTraverser::
+set_cull_handler(CullHandler *cull_handler) {
+  _cull_handler = cull_handler;
+}

+ 3 - 83
panda/src/pgraph/qpcullTraverser.cxx

@@ -34,92 +34,12 @@
 qpCullTraverser::
 qpCullTraverser() {
   _initial_state = RenderState::make_empty();
+  _camera_mask = DrawMask::all_on();
   _camera_transform = DCAST(TransformState, TransformState::make_identity());
   _render_transform = DCAST(TransformState, TransformState::make_identity());
   _cull_handler = (CullHandler *)NULL;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_initial_state
-//       Access: Public
-//  Description: Sets the initial RenderState at the top of the scene
-//               graph we are traversing.  If this is not set, the
-//               default is the empty state.
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_initial_state(const RenderState *initial_state) {
-  _initial_state = initial_state;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_camera_transform
-//       Access: Public
-//  Description: Specifies the position of the camera relative to the
-//               starting node, without any compensating
-//               coordinate-system transforms that might have been
-//               introduced for the purposes of rendering.
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_camera_transform(const TransformState *camera_transform) {
-  _camera_transform = camera_transform;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_render_transform
-//       Access: Public
-//  Description: Specifies the position of the starting node relative
-//               to the camera, pretransformed as appropriate for
-//               rendering.
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_render_transform(const TransformState *render_transform) {
-  _render_transform = render_transform;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_view_frustum
-//       Access: Public
-//  Description: Specifies the bounding volume that corresponds to the
-//               viewing frustum.  Any primitives that fall entirely
-//               outside of this volume are not drawn.
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_view_frustum(GeometricBoundingVolume *view_frustum) {
-  _view_frustum = view_frustum;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_guard_band
-//       Access: Public
-//  Description: Specifies the bounding volume to use for detecting
-//               guard band clipping.  This is a render optimization
-//               for certain cards that support this feature; the
-//               guard band is a 2-d area than the frame buffer.
-//               If a primitive will appear entirely within the guard
-//               band after perspective transform, it may be drawn
-//               correctly with clipping disabled, for a small
-//               performance gain.
-//
-//               This is the bounding volume that corresponds to the
-//               2-d guard band.  If a primitive is entirely within
-//               this area, clipping will be disabled on the GSG.
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_guard_band(GeometricBoundingVolume *guard_band) {
-  _guard_band = guard_band;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::set_cull_handler
-//       Access: Public
-//  Description: Specifies the object that will receive the culled
-//               Geoms.  This must be set before calling traverse().
-////////////////////////////////////////////////////////////////////
-void qpCullTraverser::
-set_cull_handler(CullHandler *cull_handler) {
-  _cull_handler = cull_handler;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: qpCullTraverser::traverse
 //       Access: Public
@@ -154,7 +74,7 @@ r_traverse(PandaNode *node, const CullTraverserData &data) {
     return;
   }
 
-  if (next_data.is_in_view(node)) {
+  if (next_data.is_in_view(node, _camera_mask)) {
     next_data.apply_transform_and_state(node);
 
     if (node->has_cull_callback()) {
@@ -275,7 +195,7 @@ r_get_decals(PandaNode *node, const CullTraverserData &data,
     return decals;
   }
 
-  if (next_data.is_in_view(node)) {
+  if (next_data.is_in_view(node, _camera_mask)) {
     next_data.apply_transform_and_state(node);
 
     // First, visit all of the node's children.

+ 9 - 6
panda/src/pgraph/qpcullTraverser.h

@@ -25,6 +25,7 @@
 #include "transformState.h"
 #include "geometricBoundingVolume.h"
 #include "pointerTo.h"
+#include "drawMask.h"
 
 class PandaNode;
 class CullHandler;
@@ -43,12 +44,13 @@ class EXPCL_PANDA qpCullTraverser {
 public:
   qpCullTraverser();
 
-  void set_initial_state(const RenderState *initial_state);
-  void set_camera_transform(const TransformState *camera_transform);
-  void set_render_transform(const TransformState *render_transform);
-  void set_view_frustum(GeometricBoundingVolume *view_frustum);
-  void set_guard_band(GeometricBoundingVolume *guard_band);
-  void set_cull_handler(CullHandler *cull_handler);
+  INLINE void set_initial_state(const RenderState *initial_state);
+  INLINE void set_camera_mask(const DrawMask &draw_mask);
+  INLINE void set_camera_transform(const TransformState *camera_transform);
+  INLINE void set_render_transform(const TransformState *render_transform);
+  INLINE void set_view_frustum(GeometricBoundingVolume *view_frustum);
+  INLINE void set_guard_band(GeometricBoundingVolume *guard_band);
+  INLINE void set_cull_handler(CullHandler *cull_handler);
 
   void traverse(PandaNode *root);
 
@@ -60,6 +62,7 @@ private:
                                CullableObject *decals);
 
   CPT(RenderState) _initial_state;
+  DrawMask _camera_mask;
   CPT(TransformState) _camera_transform;
   CPT(TransformState) _render_transform;
   PT(GeometricBoundingVolume) _view_frustum;

+ 49 - 33
panda/src/pgraph/qpnodePath.I

@@ -922,59 +922,67 @@ adjust_all_priorities(int adjustment) {
 //       Access: Published
 //  Description: Undoes the effect of a previous hide() on this node:
 //               makes the referenced node (and the entire subgraph
-//               below this node) visible.
+//               below this node) visible to all cameras.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 show() {
-  nassertv(false);
+  nassertv(!is_empty());
+  node()->set_draw_mask(DrawMask::all_on());
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::hide
+//     Function: qpNodePath::show
 //       Access: Published
-//  Description: Makes the referenced node (and the entire subgraph
-//               below this node) invisible to rendering.  It remains
-//               part of the scene graph, its bounding volume still
-//               contributes to its parent's bounding volume, and it
-//               will still be involved in collision tests.
+//  Description: Makes the referenced node visible just to the
+//               cameras whose camera_mask shares the indicated bits.
+//               That is, this sets the indicated bits in the
+//               node's draw mask.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
-hide() {
-  nassertv(false);
+show(DrawMask camera_mask) {
+  nassertv(!is_empty());
+  node()->set_draw_mask(node()->get_draw_mask() | camera_mask);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::show_collision_solids
+//     Function: qpNodePath::hide
 //       Access: Published
-//  Description: Shows all the collision solids at and below the
-//               referenced node.
+//  Description: Makes the referenced node (and the entire subgraph
+//               below this node) invisible to all cameras.  It
+//               remains part of the scene graph, its bounding volume
+//               still contributes to its parent's bounding volume,
+//               and it will still be involved in collision tests.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
-show_collision_solids() {
-  nassertv(false);
+hide() {
+  nassertv(!is_empty());
+  node()->set_draw_mask(DrawMask::all_off());
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::hide_collision_solids
+//     Function: qpNodePath::hide
 //       Access: Published
-//  Description: Hides all the collision solids at and below the
-//               referenced node.
+//  Description: Makes the referenced node invisible just to the
+//               cameras whose camera_mask shares the indicated bits.
+//               That is, this clears the indicated bits from the
+//               node's draw mask.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
-hide_collision_solids() {
-  nassertv(false);
+hide(DrawMask camera_mask) {
+  nassertv(!is_empty());
+  node()->set_draw_mask(node()->get_draw_mask() & ~camera_mask);
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::is_hidden
 //       Access: Published
-//  Description: Returns true if the referenced node is hidden either
-//               directly, or because some ancestor is hidden.
+//  Description: Returns true if the referenced node is hidden from
+//               the indicated camera(s) either directly, or because
+//               some ancestor is hidden.
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpNodePath::
-is_hidden() const {
-  nassertr(false, false);
-  return false;
+is_hidden(DrawMask camera_mask) const {
+  return !get_hidden_ancestor(camera_mask).is_empty();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -983,11 +991,13 @@ is_hidden() const {
 //  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.
+//               scene graph.  Returns true if the node is unstashed,
+//               or false if it wasn't stashed to begin with.
 ////////////////////////////////////////////////////////////////////
-INLINE void qpNodePath::
+INLINE bool qpNodePath::
 unstash() {
-  nassertv(false);
+  nassertr(!is_singleton(), false);
+  return get_parent().node()->unstash_child(node());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1004,10 +1014,14 @@ unstash() {
 //               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 void qpNodePath::
+INLINE bool qpNodePath::
 stash() {
-  nassertv(false);
+  nassertr(!is_singleton(), false);
+  return get_parent().node()->stash_child(node());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1018,8 +1032,7 @@ stash() {
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpNodePath::
 is_stashed() const {
-  nassertr(false, false);
-  return false;
+  return !get_stashed_ancestor().is_empty();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1075,7 +1088,10 @@ operator < (const qpNodePath &other) const {
 ////////////////////////////////////////////////////////////////////
 INLINE int qpNodePath::
 compare_to(const qpNodePath &other) const {
-  return r_compare_to(_head, other._head);
+  // Nowadays, the NodePathComponents at the head are pointerwise
+  // equivalent if and only iff the NodePaths are equivalent.  So we
+  // only have to compare pointers.
+  return _head - other._head;
 }
 
 

+ 50 - 37
panda/src/pgraph/qpnodePath.cxx

@@ -2051,14 +2051,25 @@ get_transparency() const {
 //     Function: qpNodePath::get_hidden_ancestor
 //       Access: Published
 //  Description: Returns the NodePath at or above the referenced node
-//               that is hidden, or an empty NodePath if no ancestor
-//               of the referenced node is hidden (and the node should
-//               be visible).
+//               that is hidden to the indicated camera(s), or an
+//               empty NodePath if no ancestor of the referenced node
+//               is hidden (and the node should be visible).
 ////////////////////////////////////////////////////////////////////
 qpNodePath qpNodePath::
-get_hidden_ancestor() const {
-  nassertr(false, qpNodePath());
-  return qpNodePath();
+get_hidden_ancestor(DrawMask camera_mask) const {
+  qpNodePathComponent *comp;
+  for (comp = _head; 
+       comp != (qpNodePathComponent *)NULL; 
+       comp = comp->get_next()) {
+    PandaNode *node = comp->get_node();
+    if ((node->get_draw_mask() & camera_mask).is_zero()) {
+      qpNodePath result;
+      result._head = comp;
+      return result;
+    }
+  }
+
+  return not_found();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -2071,15 +2082,33 @@ get_hidden_ancestor() const {
 ////////////////////////////////////////////////////////////////////
 qpNodePath qpNodePath::
 get_stashed_ancestor() const {
-  nassertr(false, qpNodePath());
-  return qpNodePath();
+  qpNodePathComponent *comp = _head;
+  if (comp != (qpNodePathComponent *)NULL) {
+    qpNodePathComponent *next = comp->get_next();
+
+    while (next != (qpNodePathComponent *)NULL) {
+      PandaNode *node = comp->get_node();
+      PandaNode *parent_node = next->get_node();
+
+      if (parent_node->find_stashed(node) >= 0) {
+        qpNodePath result;
+        result._head = comp;
+        return result;
+      }
+
+      comp = next;
+      next = next->get_next();
+    }
+  }
+
+  return not_found();
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::verify_complete
 //       Access: Published
 //  Description: Returns true if all of the nodes described in the
-//               qpNodePath are connected *and* the top node is the top
+//               qpNodePath are connected and the top node is the top
 //               of the graph, or false otherwise.
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
@@ -2102,7 +2131,7 @@ verify_complete() const {
     PandaNode *next_node = comp->get_node();
     nassertr(next_node != (const PandaNode *)NULL, false);
 
-    if (next_node->find_child(node) < 0) {
+    if (node->find_parent(next_node) < 0) {
       return false;
     }
 
@@ -2252,15 +2281,25 @@ r_get_partial_transform(qpNodePathComponent *comp, int n) const {
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 r_output(ostream &out, qpNodePathComponent *comp) const {
+  PandaNode *node = comp->get_node();
   qpNodePathComponent *next = comp->get_next();
   if (next != (qpNodePathComponent *)NULL) {
     // This is not the head of the list; keep going up.
     r_output(out, next);
     out << "/";
+
+    PandaNode *parent_node = next->get_node();
+    if (parent_node->find_stashed(node) >= 0) {
+      // The node is stashed.
+      out << "@@";
+
+    } else if (node->find_parent(parent_node) < 0) {
+      // Oops, there's an error.  This shouldn't happen.
+      out << ".../";
+    }
   }
 
   // Now output this component.
-  PandaNode *node = comp->get_node();
   if (node->has_name()) {
     out << node->get_name();
   } else {
@@ -2268,29 +2307,3 @@ r_output(ostream &out, qpNodePathComponent *comp) const {
   }
   //  out << "[" << comp->get_length() << "]";
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::r_compare_to
-//       Access: Private, Static
-//  Description: The recursive implementation of compare_to().  Returns
-//               < 0 if a sorts before b, > 0 if b sorts before a, or
-//               == 0 if they are equivalent.
-////////////////////////////////////////////////////////////////////
-int qpNodePath::
-r_compare_to(const qpNodePathComponent *a, const qpNodePathComponent *b) {
-  if (a == b) {
-    return 0;
-
-  } else if (a == (const qpNodePathComponent *)NULL) {
-    return -1;
-
-  } else if (b == (const qpNodePathComponent *)NULL) {
-    return 1;
-
-  } else if (a->get_node() != b->get_node()) {
-    return a->get_node() - b->get_node();
-
-  } else {
-    return r_compare_to(a->get_next(), b->get_next());
-  }
-}

+ 8 - 9
panda/src/pgraph/qpnodePath.h

@@ -50,8 +50,8 @@ class Fog;
 //               easily be copied and passed by value.  Their data is
 //               stored as a series of NodePathComponents that are
 //               stored on the nodes.  Holding a NodePath will keep a
-//               reference count to all the nodes in the chain.
-//               However, if any node in the chain is removed or
+//               reference count to all the nodes in the path.
+//               However, if any node in the path is removed or
 //               reparented (perhaps through a different NodePath),
 //               the NodePath will automatically be updated to reflect
 //               the changes.
@@ -375,14 +375,14 @@ PUBLISHED:
 
   // Variants on show and hide
   INLINE void show();
+  INLINE void show(DrawMask camera_mask);
   INLINE void hide();
-  INLINE void show_collision_solids();
-  INLINE void hide_collision_solids();
-  INLINE bool is_hidden() const;
-  qpNodePath get_hidden_ancestor() const;
+  INLINE void hide(DrawMask camera_mask);
+  INLINE bool is_hidden(DrawMask camera_mask = DrawMask::all_on()) const;
+  qpNodePath get_hidden_ancestor(DrawMask camera_mask = DrawMask::all_on()) const;
 
-  INLINE void stash();
-  INLINE void unstash();
+  INLINE bool stash();
+  INLINE bool unstash();
   INLINE bool is_stashed() const;
   qpNodePath get_stashed_ancestor() const;
 
@@ -404,7 +404,6 @@ private:
   CPT(TransformState) r_get_net_transform(qpNodePathComponent *comp) const;
   CPT(TransformState) r_get_partial_transform(qpNodePathComponent *comp, int n) const;
   void r_output(ostream &out, qpNodePathComponent *comp) const;
-  static int r_compare_to(const qpNodePathComponent *a, const qpNodePathComponent *v);
 
   PT(qpNodePathComponent) _head;
   ErrorType _error_type;

+ 4 - 4
panda/src/pgraph/qpnodePathComponent.I

@@ -107,7 +107,7 @@ get_node() const {
 //     Function: qpNodePathComponent::is_top_node
 //       Access: Public
 //  Description: Returns true if this component represents the top
-//               node in the chain.
+//               node in the path.
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpNodePathComponent::
 is_top_node() const {
@@ -132,7 +132,7 @@ is_collapsed() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathComponent::get_length
 //       Access: Public
-//  Description: Returns the length of the chain.
+//  Description: Returns the length of the path.
 ////////////////////////////////////////////////////////////////////
 INLINE int qpNodePathComponent::
 get_length() const {
@@ -158,7 +158,7 @@ get_collapsed() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathComponent::set_next
 //       Access: Private
-//  Description: Sets the next pointer in the chain.
+//  Description: Sets the next pointer in the path.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePathComponent::
 set_next(qpNodePathComponent *next) {
@@ -172,7 +172,7 @@ set_next(qpNodePathComponent *next) {
 //     Function: qpNodePathComponent::set_top_node
 //       Access: Private
 //  Description: Severs any connection to the next pointer in the
-//               chain and makes this component a top node.
+//               path and makes this component a top node.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePathComponent::
 set_top_node() {

+ 4 - 4
panda/src/pgraph/qpnodePathComponent.cxx

@@ -34,7 +34,7 @@ make_copy() const {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathComponent::get_next
 //       Access: Public
-//  Description: Returns the next component in the chain.
+//  Description: Returns the next component in the path.
 ////////////////////////////////////////////////////////////////////
 qpNodePathComponent *qpNodePathComponent::
 get_next() const {
@@ -46,7 +46,7 @@ get_next() const {
   // 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 chain and we have to deal with it.
+  // component in the path and we have to deal with it.
   if (next != (qpNodePathComponent *)NULL && next->is_collapsed()) {
     next = next->uncollapse();
     ((qpNodePathComponent *)this)->set_next(next);
@@ -86,8 +86,8 @@ fix_length() {
 //               the pointer it has been collapsed into.
 //
 //               Collapsing can happen at any time to any component in
-//               the chain and we have to deal with it.  It happens
-//               when a node is removed further up the chain that
+//               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.
 ////////////////////////////////////////////////////////////////////
 qpNodePathComponent *qpNodePathComponent::

+ 1 - 3
panda/src/pgraph/transformState.cxx

@@ -484,9 +484,7 @@ output(ostream &out) const {
     out << "(identity)";
 
   } else if (has_components()) {
-    if (components_given()) {
-      out << "c";
-    } else {
+    if (!components_given()) {
       out << "m";
     }
     char lead = '(';

+ 2 - 0
panda/src/putil/Sources.pp

@@ -20,6 +20,7 @@
     cycleData.h cycleData.I \
     cycleDataReader.h cycleDataReader.I \
     cycleDataWriter.h cycleDataWriter.I \
+    drawMask.h \
     factoryBase.I factoryBase.h \
     factoryParam.I factoryParam.h factoryParams.I \
     factoryParams.h \
@@ -91,6 +92,7 @@
     cycleData.h cycleData.I \
     cycleDataReader.h cycleDataReader.I \
     cycleDataWriter.h cycleDataWriter.I \
+    drawMask.h \
     factoryBase.I factoryBase.h factoryParam.I factoryParam.h \
     factoryParams.I factoryParams.h \
     firstOfPairCompare.I firstOfPairCompare.h \

+ 51 - 39
panda/src/putil/bitMask.I

@@ -21,7 +21,7 @@ TypeHandle BitMask<WordType, num_bits>::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -33,7 +33,7 @@ BitMask() :
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -45,7 +45,7 @@ BitMask(WordType init_value) :
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Copy Constructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -57,7 +57,7 @@ BitMask(const BitMask<WordType, num_bits> &copy) :
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Copy Assignment Operator
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -68,7 +68,7 @@ operator = (const BitMask<WordType, num_bits> &copy) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Named all_on constructor
-//       Access: Public, Static
+//       Access: Published, Static
 //  Description: Returns a BitMask whose bits are all on.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -81,7 +81,7 @@ all_on() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Named all_on constructor
-//       Access: Public, Static
+//       Access: Published, Static
 //  Description: Returns a BitMask whose bits are all off.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -94,7 +94,7 @@ all_off() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Named lower_on constructor
-//       Access: Public, Static
+//       Access: Published, Static
 //  Description: Returns a BitMask whose lower num_bits bits are on.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -112,7 +112,7 @@ lower_on(int on_bits) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Named bit constructor
-//       Access: Public, Static
+//       Access: Published, Static
 //  Description: Returns a BitMask with only the indicated bit on.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -125,7 +125,7 @@ bit(int index) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::Destructor
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -135,7 +135,7 @@ INLINE BitMask<WordType, num_bits>::
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::get_num_bits
-//       Access: Public
+//       Access: Published
 //  Description: Returns the number of bits available to set in the
 //               bitmask.
 ////////////////////////////////////////////////////////////////////
@@ -147,7 +147,7 @@ get_num_bits() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::get_bit
-//       Access: Public
+//       Access: Published
 //  Description: Returns true if the nth bit is set, false if it is
 //               cleared.  index must be in the range [0,
 //               get_num_bits).
@@ -161,7 +161,7 @@ get_bit(int index) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::set_bit
-//       Access: Public
+//       Access: Published
 //  Description: Sets the nth bit on.  index must be in the range
 //               [0, get_num_bits).
 ////////////////////////////////////////////////////////////////////
@@ -174,7 +174,7 @@ set_bit(int index) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::clear_bit
-//       Access: Public
+//       Access: Published
 //  Description: Sets the nth bit off.  index must be in the range
 //               [0, get_num_bits).
 ////////////////////////////////////////////////////////////////////
@@ -187,7 +187,7 @@ clear_bit(int index) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::set_bit_to
-//       Access: Public
+//       Access: Published
 //  Description: Sets the nth bit either on or off, according to the
 //               indicated bool value.  index must be in the range [0,
 //               get_num_bits).
@@ -202,9 +202,21 @@ set_bit_to(int index, bool value) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: BitMask::is_zero
+//       Access: Published
+//  Description: Returns true if the entire bitmask is zero, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+template<class WordType, int num_bits>
+INLINE bool BitMask<WordType, num_bits>::
+is_zero() const {
+  return (_word == 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::extract
-//       Access: Public
+//       Access: Published
 //  Description: Returns a word that represents only the indicated
 //               range of bits within this BitMask, shifted to the
 //               least-significant position.
@@ -218,7 +230,7 @@ extract(int low_bit, int size) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::store
-//       Access: Public
+//       Access: Published
 //  Description: Stores the indicated word into the indicated range of
 //               bits with this BitMask.
 ////////////////////////////////////////////////////////////////////
@@ -232,7 +244,7 @@ store(WordType value, int low_bit, int size) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::get_word
-//       Access: Public
+//       Access: Published
 //  Description: Returns the entire BitMask as a single word.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -243,7 +255,7 @@ get_word() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::set_word
-//       Access: Public
+//       Access: Published
 //  Description: Sets the entire BitMask to the value indicated by the
 //               given word.
 ////////////////////////////////////////////////////////////////////
@@ -255,7 +267,7 @@ set_word(WordType value) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::invert_in_place
-//       Access: Public
+//       Access: Published
 //  Description: Inverts all the bits in the BitMask.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -266,7 +278,7 @@ invert_in_place() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::clear
-//       Access: Public
+//       Access: Published
 //  Description: Sets all the bits in the BitMask off.
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -277,7 +289,7 @@ clear() {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::output
-//       Access: Public
+//       Access: Published
 //  Description: Writes the BitMask out as a binary or a hex number,
 //               according to the number of bits.
 ////////////////////////////////////////////////////////////////////
@@ -293,7 +305,7 @@ output(ostream &out) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::output_binary
-//       Access: Public
+//       Access: Published
 //  Description: Writes the BitMask out as a binary number, with
 //               spaces every four bits.
 ////////////////////////////////////////////////////////////////////
@@ -310,7 +322,7 @@ output_binary(ostream &out, int spaces_every) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::output_hex
-//       Access: Public
+//       Access: Published
 //  Description: Writes the BitMask out as a hexadecimal number, with
 //               spaces every four digits.
 ////////////////////////////////////////////////////////////////////
@@ -334,7 +346,7 @@ output_hex(ostream &out, int spaces_every) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::write
-//       Access: Public
+//       Access: Published
 //  Description: Writes the BitMask out as a binary or a hex number,
 //               according to the number of bits.
 ////////////////////////////////////////////////////////////////////
@@ -346,7 +358,7 @@ write(ostream &out, int indent_level) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator ==
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -357,7 +369,7 @@ operator == (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator !=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -368,7 +380,7 @@ operator != (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator <
-//       Access: Public
+//       Access: Published
 //  Description: The ordering operator is of limited usefulness with a
 //               BitMask, however, it has a definition which places
 //               all unique BitMasks into a unique ordering.  It may
@@ -385,7 +397,7 @@ operator < (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::compare_to
-//       Access: Public
+//       Access: Published
 //  Description: Returns a number less than zero if this BitMask sorts
 //               before the indicated other BitMask, greater than zero
 //               if it sorts after, or 0 if they are equivalent.  This
@@ -405,7 +417,7 @@ compare_to(const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator &
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -418,7 +430,7 @@ operator & (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator |
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -431,7 +443,7 @@ operator | (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator ^
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -444,7 +456,7 @@ operator ^ (const BitMask<WordType, num_bits> &other) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator ~
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -457,7 +469,7 @@ operator ~ () const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator <<
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -470,7 +482,7 @@ operator << (int shift) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator >>
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -483,7 +495,7 @@ operator >> (int shift) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator &=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -494,7 +506,7 @@ operator &= (const BitMask<WordType, num_bits> &other) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator |=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -505,7 +517,7 @@ operator |= (const BitMask<WordType, num_bits> &other) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator ^=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -516,7 +528,7 @@ operator ^= (const BitMask<WordType, num_bits> &other) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator <<=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>
@@ -527,7 +539,7 @@ operator <<= (int shift) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: BitMask::operator >>=
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 template<class WordType, int num_bits>

+ 1 - 0
panda/src/putil/bitMask.h

@@ -54,6 +54,7 @@ PUBLISHED:
   INLINE void set_bit(int index);
   INLINE void clear_bit(int index);
   INLINE void set_bit_to(int index, bool value);
+  INLINE bool is_zero() const;
 
   INLINE WordType extract(int low_bit, int size) const;
   INLINE void store(WordType value, int low_bit, int size);

+ 34 - 0
panda/src/putil/drawMask.h

@@ -0,0 +1,34 @@
+// Filename: drawMask.h
+// Created by:  drose (13Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef DRAWMASK_H
+#define DRAWMASK_H
+
+#include <pandabase.h>
+
+#include "bitMask.h"
+
+// This is the data type of the draw mask: the set of bits that every
+// PandaNode has, as well as a Camera, and that a node must have at
+// least some bits in common with the current Camera in order to be
+// visible.
+
+typedef BitMask32 DrawMask;
+
+#endif
+