Explorar el Código

pgraph lod, sequence nodes

David Rose hace 24 años
padre
commit
8b743a58fe
Se han modificado 36 ficheros con 1866 adiciones y 246 borrados
  1. 2 2
      panda/src/display/graphicsStateGuardian.cxx
  2. 3 0
      panda/src/egg2pg/Sources.pp
  3. 1 1
      panda/src/egg2pg/egg2pg_composite1.cxx
  4. 2 0
      panda/src/egg2pg/egg2pg_composite2.cxx
  5. 3 4
      panda/src/egg2pg/eggBinner.cxx
  6. 2 2
      panda/src/egg2pg/eggBinner.h
  7. 19 29
      panda/src/egg2pg/qpeggLoader.cxx
  8. 2 2
      panda/src/egg2sg/Sources.pp
  9. 0 1
      panda/src/egg2sg/egg2sg_composite2.cxx
  10. 12 0
      panda/src/pgraph/Sources.pp
  11. 10 0
      panda/src/pgraph/config_pgraph.cxx
  12. 97 0
      panda/src/pgraph/cullTraverserData.I
  13. 158 0
      panda/src/pgraph/cullTraverserData.cxx
  14. 79 0
      panda/src/pgraph/cullTraverserData.h
  15. 21 1
      panda/src/pgraph/cullableObject.I
  16. 10 0
      panda/src/pgraph/cullableObject.h
  17. 91 0
      panda/src/pgraph/pandaNode.cxx
  18. 7 0
      panda/src/pgraph/pandaNode.h
  19. 1 0
      panda/src/pgraph/pgraph_composite1.cxx
  20. 3 0
      panda/src/pgraph/pgraph_composite2.cxx
  21. 54 0
      panda/src/pgraph/qpcamera.cxx
  22. 10 2
      panda/src/pgraph/qpcamera.h
  23. 82 187
      panda/src/pgraph/qpcullTraverser.cxx
  24. 5 13
      panda/src/pgraph/qpcullTraverser.h
  25. 67 0
      panda/src/pgraph/qplensNode.cxx
  26. 12 2
      panda/src/pgraph/qplensNode.h
  27. 163 0
      panda/src/pgraph/qplodNode.I
  28. 218 0
      panda/src/pgraph/qplodNode.cxx
  29. 109 0
      panda/src/pgraph/qplodNode.h
  30. 130 0
      panda/src/pgraph/qpsequenceNode.I
  31. 183 0
      panda/src/pgraph/qpsequenceNode.cxx
  32. 98 0
      panda/src/pgraph/qpsequenceNode.h
  33. 65 0
      panda/src/pgraph/selectiveChildNode.I
  34. 75 0
      panda/src/pgraph/selectiveChildNode.cxx
  35. 69 0
      panda/src/pgraph/selectiveChildNode.h
  36. 3 0
      panda/src/pgraph/transformState.cxx

+ 2 - 2
panda/src/display/graphicsStateGuardian.cxx

@@ -942,8 +942,8 @@ begin_decal_base_first() {
 ////////////////////////////////////////////////////////////////////
 CPT(RenderState) GraphicsStateGuardian::
 begin_decal_nested() {
-  // We keep the depth buffer off during this operation, although
-  // perhaps it doesn't matter so much here.
+  // We should keep the depth buffer off during this operation, so
+  // that decals on decals will render properly.
   static CPT(RenderState) decal_nested;
   if (decal_nested == (const RenderState *)NULL) {
     decal_nested = RenderState::make

+ 3 - 0
panda/src/egg2pg/Sources.pp

@@ -10,11 +10,13 @@
 
   #define SOURCES \
     config_egg2pg.h \
+    eggBinner.h \
     qpeggLoader.h \
     qpload_egg_file.h
 
   #define INCLUDED_SOURCES \
     config_egg2pg.cxx \
+    eggBinner.cxx \
     qpeggLoader.cxx \
     qpload_egg_file.cxx
 
@@ -26,6 +28,7 @@
 
   #define INSTALL_HEADERS \
     config_egg2pg.h \
+    eggBinner.h \
     qpeggLoader.h \
     qpload_egg_file.h
 

+ 1 - 1
panda/src/egg2pg/egg2pg_composite1.cxx

@@ -1,2 +1,2 @@
 #include "config_egg2pg.cxx"
-#include "qpeggLoader.cxx"
+#include "eggBinner.cxx"

+ 2 - 0
panda/src/egg2pg/egg2pg_composite2.cxx

@@ -1 +1,3 @@
+#include "qpeggLoader.cxx"
 #include "qpload_egg_file.cxx"
+

+ 3 - 4
panda/src/egg2sg/eggBinner.cxx → panda/src/egg2pg/eggBinner.cxx

@@ -18,10 +18,9 @@
 
 #include "eggBinner.h"
 
-#include <eggSwitchCondition.h>
-#include <eggGroup.h>
-
-#include <assert.h>
+#include "eggSwitchCondition.h"
+#include "eggGroup.h"
+#include "dcast.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: EggBinner::get_bin_number

+ 2 - 2
panda/src/egg2sg/eggBinner.h → panda/src/egg2pg/eggBinner.h

@@ -19,9 +19,9 @@
 #ifndef EGGBINNER_H
 #define EGGBINNER_H
 
-#include <pandabase.h>
+#include "pandabase.h"
 
-#include <eggBinMaker.h>
+#include "eggBinMaker.h"
 
 ///////////////////////////////////////////////////////////////////
 //       Class : EggBinner

+ 19 - 29
panda/src/egg2pg/qpeggLoader.cxx

@@ -35,6 +35,8 @@
 #include "materialAttrib.h"
 #include "materialPool.h"
 #include "qpgeomNode.h"
+#include "qpsequenceNode.h"
+#include "qplodNode.h"
 #include "string_utils.h"
 #include "eggPrimitive.h"
 #include "eggPoint.h"
@@ -45,41 +47,40 @@
 #include "eggPolygon.h"
 #include "eggBin.h"
 #include "eggTable.h"
+#include "eggBinner.h"
 #include "nodeChain.h"
 
 #include <ctype.h>
 #include <algorithm>
 
-/*
 // This class is used in make_node(EggBin *) to sort LOD instances in
 // order by switching distance.
 class LODInstance {
 public:
-  LODInstance(EggNode *egg_node, RenderRelation *arc);
+  LODInstance(EggNode *egg_node);
   bool operator < (const LODInstance &other) const {
     return _d->_switch_in < other._d->_switch_in;
   }
 
-  RenderRelation *_arc;
+  EggNode *_egg_node;
   const EggSwitchConditionDistance *_d;
 };
 
 LODInstance::
-LODInstance(EggNode *egg_node, RenderRelation *arc) {
-  assert(arc != NULL);
-  _arc = arc;
+LODInstance(EggNode *egg_node) {
+  nassertv(egg_node != NULL);
+  _egg_node = egg_node;
 
   // We expect this egg node to be an EggGroup with an LOD
   // specification.  That's what the EggBinner collected together,
   // after all.
   EggGroup *egg_group = DCAST(EggGroup, egg_node);
-  assert(egg_group->has_lod());
+  nassertv(egg_group->has_lod());
   const EggSwitchCondition &sw = egg_group->get_lod();
 
   // For now, this is the only kind of switch condition there is.
   _d = DCAST(EggSwitchConditionDistance, &sw);
 }
-*/
 
 
 ////////////////////////////////////////////////////////////////////
@@ -116,11 +117,9 @@ void qpEggLoader::
 build_graph() {
   //  _deferred_arcs.clear();
 
-  /*
   // First, bin up the LOD nodes.
   EggBinner binner;
   binner.make_bins(&_data);
-  */
 
   // Then load up all of the textures.
   load_textures();
@@ -1201,23 +1200,18 @@ make_node(EggPrimitive *egg_prim, PandaNode *parent) {
 ////////////////////////////////////////////////////////////////////
 PandaNode *qpEggLoader::
 make_node(EggBin *egg_bin, PandaNode *parent) {
-  return (PandaNode *)NULL;
-  /*
   // Presently, an EggBin can only mean an LOD node (i.e. a parent of
   // one or more EggGroups with LOD specifications).  Later it might
   // mean other things as well.
 
-  assert((EggBinner::BinNumber)egg_bin->get_bin_number() == EggBinner::BN_lod);
-  LODNode *lod_node = new LODNode;
-  lod_node->set_name(egg_bin->get_name());
+  nassertr((EggBinner::BinNumber)egg_bin->get_bin_number() == EggBinner::BN_lod, NULL);
+  qpLODNode *lod_node = new qpLODNode(egg_bin->get_name());
 
   pvector<LODInstance> instances;
 
   EggGroup::const_iterator ci;
   for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
-    PandaNode *arc = make_node(*ci, lod_node);
-    assert(arc != (PandaNode *)NULL);
-    LODInstance instance(*ci, arc);
+    LODInstance instance(*ci);
     instances.push_back(instance);
   }
 
@@ -1228,26 +1222,24 @@ make_node(EggBin *egg_bin, PandaNode *parent) {
   if (!instances.empty()) {
     // Set up the LOD node's center.  All of the children should have
     // the same center, because that's how we binned them.
-    lod_node->_lod._center = LCAST(float, instances[0]._d->_center);
+    lod_node->set_center(LCAST(float, instances[0]._d->_center));
   }
 
   for (size_t i = 0; i < instances.size(); i++) {
-    // Put the children in the proper order within the scene graph.
+    // Create the children in the proper order within the scene graph.
     const LODInstance &instance = instances[i];
+    make_node(instance._egg_node, lod_node);
 
     // All of the children should have the same center, because that's
     // how we binned them.
-    assert(lod_node->_lod._center.almost_equal
-           (LCAST(float, instance._d->_center), 0.01));
-
-    instance._arc->set_sort(i);
+    nassertr(lod_node->get_center().almost_equal
+             (LCAST(float, instance._d->_center), 0.01), NULL);
 
     // Tell the LOD node about this child's switching distances.
     lod_node->add_switch(instance._d->_switch_in, instance._d->_switch_out);
   }
 
   return create_group_arc(egg_bin, parent, lod_node);
-  */
 }
 
 
@@ -1379,16 +1371,14 @@ make_node(EggGroup *egg_group, PandaNode *parent) {
 
   } else if (egg_group->get_switch_flag() &&
              egg_group->get_switch_fps() != 0.0) {
-    /*
     // Create a sequence node.
-    node = new SequenceNode(1.0 / egg_group->get_switch_fps());
-    node->set_name(egg_group->get_name());
+    node = new qpSequenceNode(egg_group->get_switch_fps(), 
+                              egg_group->get_name());
 
     EggGroup::const_iterator ci;
     for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
       make_node(*ci, node);
     }
-    */
 
   } else if (egg_group->get_model_flag() || egg_group->get_dcs_flag()) {
     // A model or DCS flag; create a model node.

+ 2 - 2
panda/src/egg2sg/Sources.pp

@@ -13,13 +13,13 @@
   #define SOURCES \
      animBundleMaker.h characterMaker.h computedVerticesMaker.I  \
      computedVerticesMaker.h config_egg2sg.h  \
-     deferredArcProperty.h deferredArcTraverser.h eggBinner.h  \
+     deferredArcProperty.h deferredArcTraverser.h  \
      eggLoader.h load_egg_file.h loaderFileTypeEgg.h 
 
   #define INCLUDED_SOURCES \
      animBundleMaker.cxx characterMaker.cxx computedVerticesMaker.cxx  \
      config_egg2sg.cxx deferredArcProperty.cxx  \
-     deferredArcTraverser.cxx eggBinner.cxx eggLoader.cxx  \
+     deferredArcTraverser.cxx eggLoader.cxx  \
      load_egg_file.cxx loaderFileTypeEgg.cxx 
 
   #define INSTALL_HEADERS \

+ 0 - 1
panda/src/egg2sg/egg2sg_composite2.cxx

@@ -1,6 +1,5 @@
 
 #include "config_egg2sg.cxx"
-#include "eggBinner.cxx"
 #include "eggLoader.cxx"
 #include "load_egg_file.cxx"
 #include "loaderFileTypeEgg.cxx"

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

@@ -21,6 +21,7 @@
     cullHandler.h cullHandler.I \
     cullResult.h cullResult.I \
     qpcullTraverser.h qpcullTraverser.I \
+    cullTraverserData.h cullTraverserData.I \
     cullableObject.h cullableObject.I \
     decalAttrib.h decalAttrib.I \
     depthTestAttrib.h depthTestAttrib.I \
@@ -28,12 +29,15 @@
     drawCullHandler.h drawCullHandler.I \
     qpgeomNode.h qpgeomNode.I \
     qplensNode.h qplensNode.I \
+    qplodNode.h qplodNode.I \
     materialAttrib.h materialAttrib.I \
     nodeChain.h nodeChain.I \
     nodeChainComponent.h nodeChainComponent.I \
     pandaNode.h pandaNode.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \
+    selectiveChildNode.h selectiveChildNode.I \
+    qpsequenceNode.h qpsequenceNode.I \
     textureApplyAttrib.h textureApplyAttrib.I \
     textureAttrib.h textureAttrib.I \
     transformState.h transformState.I \
@@ -56,6 +60,7 @@
     cullHandler.cxx \
     cullResult.cxx \
     qpcullTraverser.cxx \
+    cullTraverserData.cxx \
     cullableObject.cxx \
     decalAttrib.cxx \
     depthTestAttrib.cxx \
@@ -63,12 +68,15 @@
     drawCullHandler.cxx \
     qpgeomNode.cxx \
     qplensNode.cxx \
+    qplodNode.cxx \
     materialAttrib.cxx \
     nodeChain.cxx \
     nodeChainComponent.cxx \
     pandaNode.cxx \
     renderAttrib.cxx \
     renderState.cxx \
+    selectiveChildNode.cxx \
+    qpsequenceNode.cxx \
     textureApplyAttrib.cxx \
     textureAttrib.cxx \
     transformState.cxx \
@@ -96,6 +104,7 @@
     cullHandler.h cullHandler.I \
     cullResult.h cullResult.I \
     qpcullTraverser.h qpcullTraverser.I \
+    cullTraverserData.h cullTraverserData.I \
     cullableObject.h cullableObject.I \
     decalAttrib.h decalAttrib.I \
     depthTestAttrib.h depthTestAttrib.I \
@@ -103,12 +112,15 @@
     drawCullHandler.h drawCullHandler.I \
     qpgeomNode.h qpgeomNode.I \
     qplensNode.h qplensNode.I \
+    qplodNode.h qplodNode.I \
     materialAttrib.h materialAttrib.I \
     nodeChain.h nodeChain.I \
     nodeChainComponent.h nodeChainComponent.I \
     pandaNode.h pandaNode.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \
+    selectiveChildNode.h selectiveChildNode.I \
+    qpsequenceNode.h qpsequenceNode.I \
     textureApplyAttrib.h textureApplyAttrib.I \
     textureAttrib.h textureAttrib.I \
     transformState.h transformState.I \

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

@@ -33,12 +33,15 @@
 #include "depthWriteAttrib.h"
 #include "qpgeomNode.h"
 #include "qplensNode.h"
+#include "qplodNode.h"
 #include "materialAttrib.h"
 #include "nodeChain.h"
 #include "nodeChainComponent.h"
 #include "pandaNode.h"
 #include "renderAttrib.h"
 #include "renderState.h"
+#include "selectiveChildNode.h"
+#include "qpsequenceNode.h"
 #include "textureApplyAttrib.h"
 #include "textureAttrib.h"
 #include "transformState.h"
@@ -90,18 +93,22 @@ init_libpgraph() {
   DepthWriteAttrib::init_type();
   qpGeomNode::init_type();
   qpLensNode::init_type();
+  qpLODNode::init_type();
   MaterialAttrib::init_type();
   NodeChain::init_type();
   NodeChainComponent::init_type();
   PandaNode::init_type();
   RenderAttrib::init_type();
   RenderState::init_type();
+  SelectiveChildNode::init_type();
+  qpSequenceNode::init_type();
   TextureApplyAttrib::init_type();
   TextureAttrib::init_type();
   TransformState::init_type();
   TransparencyAttrib::init_type();
 
   BillboardAttrib::register_with_read_factory();
+  qpCamera::register_with_read_factory();
   ColorAttrib::register_with_read_factory();
   ColorWriteAttrib::register_with_read_factory();
   CullBinAttrib::register_with_read_factory();
@@ -110,9 +117,12 @@ init_libpgraph() {
   DepthTestAttrib::register_with_read_factory();
   DepthWriteAttrib::register_with_read_factory();
   qpGeomNode::register_with_read_factory();
+  qpLensNode::register_with_read_factory();
+  qpLODNode::register_with_read_factory();
   MaterialAttrib::register_with_read_factory();
   PandaNode::register_with_read_factory();
   RenderState::register_with_read_factory();
+  qpSequenceNode::register_with_read_factory();
   TextureApplyAttrib::register_with_read_factory();
   TextureAttrib::register_with_read_factory();
   TransformState::register_with_read_factory();

+ 97 - 0
panda/src/pgraph/cullTraverserData.I

@@ -0,0 +1,97 @@
+// Filename: cullTraverserData.I
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullTraverserData::
+CullTraverserData(const TransformState *render_transform,
+                  const TransformState *net_transform,
+                  const RenderState *state,
+                  GeometricBoundingVolume *view_frustum,
+                  GeometricBoundingVolume *guard_band,
+                  const TransformState *camera_transform) :
+  _render_transform(render_transform),
+  _net_transform(net_transform),
+  _state(state),
+  _view_frustum(view_frustum),
+  _guard_band(guard_band),
+  _camera_transform(camera_transform)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::Copy Constructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullTraverserData::
+CullTraverserData(const CullTraverserData &copy) :
+  _render_transform(copy._render_transform),
+  _net_transform(copy._net_transform),
+  _state(copy._state),
+  _view_frustum(copy._view_frustum),
+  _guard_band(copy._guard_band),
+  _camera_transform(copy._camera_transform)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::Copy Assignment Operator
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void CullTraverserData::
+operator = (const CullTraverserData &copy) {
+  _render_transform = copy._render_transform;
+  _net_transform = copy._net_transform;
+  _state = copy._state;
+  _view_frustum = copy._view_frustum;
+  _guard_band = copy._guard_band;
+  _camera_transform = copy._camera_transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE CullTraverserData::
+~CullTraverserData() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::is_in_view
+//       Access: Public
+//  Description: Returns true if the node is within the view frustum,
+//               false otherwise.  If the node's bounding volume falls
+//               completely within the view frustum, this will also
+//               reset the view frustum pointer, saving some work for
+//               future nodes.
+////////////////////////////////////////////////////////////////////
+INLINE bool CullTraverserData::
+is_in_view(PandaNode *node) {
+  if (_view_frustum == (GeometricBoundingVolume *)NULL) {
+    // If we don't have a frustum, we're always in.
+    return true;
+  }
+  return is_in_view_impl(node);
+}

+ 158 - 0
panda/src/pgraph/cullTraverserData.cxx

@@ -0,0 +1,158 @@
+// Filename: cullTraverserData.cxx
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "cullTraverserData.h"
+#include "config_pgraph.h"
+#include "pandaNode.h"
+#include "colorAttrib.h"
+#include "textureAttrib.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::apply_transform_and_state
+//       Access: Public
+//  Description: Applies the transform and state from the indicated
+//               node onto the current data.  This also evaluates
+//               billboards, etc.
+////////////////////////////////////////////////////////////////////
+void CullTraverserData::
+apply_transform_and_state(PandaNode *node) {
+  const TransformState *node_transform = node->get_transform();
+  if (!node_transform->is_identity()) {
+    _render_transform = _render_transform->compose(node_transform);
+    _net_transform = _net_transform->compose(node_transform);
+    
+    if ((_view_frustum != (GeometricBoundingVolume *)NULL) ||
+        (_guard_band != (GeometricBoundingVolume *)NULL)) {
+      // We need to move the viewing frustums into the node's
+      // coordinate space by applying the node's inverse transform.
+      if (node_transform->is_singular()) {
+        // But we can't invert a singular transform!  Instead of
+        // trying, we'll just give up on frustum culling from this
+        // point down.
+        _view_frustum = (GeometricBoundingVolume *)NULL;
+        _guard_band = (GeometricBoundingVolume *)NULL;
+
+      } else {
+        CPT(TransformState) inv_transform = 
+          node_transform->invert_compose(TransformState::make_identity());
+
+        // Copy the bounding volumes for the frustums so we can
+        // transform them.
+        if (_view_frustum != (GeometricBoundingVolume *)NULL) {
+          _view_frustum = DCAST(GeometricBoundingVolume, _view_frustum->make_copy());
+          _view_frustum->xform(inv_transform->get_mat());
+        }
+        
+        if (_guard_band != (GeometricBoundingVolume *)NULL) {
+          _guard_band = DCAST(GeometricBoundingVolume, _guard_band->make_copy());
+          _guard_band->xform(inv_transform->get_mat());
+        }
+      }
+    }
+  }
+
+  const RenderState *node_state = node->get_state();
+  _state = _state->compose(node_state);
+
+  const BillboardAttrib *billboard = node_state->get_billboard();
+  if (billboard != (const BillboardAttrib *)NULL) {
+    // Got to apply a billboard transform here.
+    CPT(TransformState) billboard_transform = 
+      billboard->do_billboard(_net_transform, _camera_transform);
+    _render_transform = _render_transform->compose(billboard_transform);
+    _net_transform = _net_transform->compose(billboard_transform);
+
+    // We can't reliably cull within a billboard, because the geometry
+    // might get rotated out of its bounding volume.  So once we get
+    // within a billboard, we consider it all visible.
+    _view_frustum = (GeometricBoundingVolume *)NULL;
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::is_in_view_impl
+//       Access: Private
+//  Description: The private implementation of is_in_view().
+////////////////////////////////////////////////////////////////////
+bool CullTraverserData::
+is_in_view_impl(PandaNode *node) {
+  // By the time we get here, we know we have a viewing frustum.
+  nassertr(_view_frustum != (GeometricBoundingVolume *)NULL, true);
+
+  const BoundingVolume &node_volume = node->get_bound();
+  nassertr(node_volume.is_of_type(GeometricBoundingVolume::get_class_type()), false);
+  const GeometricBoundingVolume *node_gbv =
+    DCAST(GeometricBoundingVolume, &node_volume);
+
+  int result = _view_frustum->contains(node_gbv);
+  if (result == BoundingVolume::IF_no_intersection) {
+    // No intersection at all.  Cull.
+    if (!qpfake_view_frustum_cull) {
+      return false;
+    }
+
+    // If we have fake view-frustum culling enabled, instead of
+    // actually culling an object we simply force it to be drawn in
+    // red wireframe.
+    _view_frustum = (GeometricBoundingVolume *)NULL;
+    CPT(RenderState) fake_effect = get_fake_view_frustum_cull_effect();
+    _state = _state->compose(fake_effect);
+    
+  } else if ((result & BoundingVolume::IF_all) != 0) {
+    // The node and its descendants are completely enclosed within
+    // the frustum.  No need to cull further.
+    _view_frustum = (GeometricBoundingVolume *)NULL;
+
+  } else {
+    if (node->is_final()) {
+      // The bounding volume is partially, but not completely,
+      // within the viewing frustum.  Normally we'd keep testing
+      // child bounding volumes as we continue down.  But this node
+      // has the "final" flag, so the user is claiming that there is
+      // some important reason we should consider everything visible
+      // at this point.  So be it.
+      _view_frustum = (GeometricBoundingVolume *)NULL;
+    }
+  }
+
+  return true;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: CullTraverserData::get_fake_view_frustum_cull_effect
+//       Access: Private, Static
+//  Description: Returns a RenderState for rendering stuff in red
+//               wireframe, strictly for the fake_view_frustum_cull
+//               effect.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) CullTraverserData::
+get_fake_view_frustum_cull_effect() {
+  // Once someone asks for this pointer, we hold its reference count
+  // and never free it.
+  static CPT(RenderState) effect = (const RenderState *)NULL;
+  if (effect == (const RenderState *)NULL) {
+    effect = RenderState::make
+      (ColorAttrib::make_flat(Colorf(1.0f, 0.0f, 0.0f, 1.0f)),
+       TextureAttrib::make_off(),
+       1000);
+  }
+  return effect;
+}
+

+ 79 - 0
panda/src/pgraph/cullTraverserData.h

@@ -0,0 +1,79 @@
+// Filename: cullTraverserData.h
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 CULLTRAVERSERDATA_H
+#define CULLTRAVERSERDATA_H
+
+#include "pandabase.h"
+
+#include "renderState.h"
+#include "transformState.h"
+#include "geometricBoundingVolume.h"
+#include "pointerTo.h"
+
+class PandaNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : CullTraverserData
+// Description : This collects together the pieces of data that are
+//               accumulated for each node while walking the scene
+//               graph during the cull traversal.
+//
+//               Having this as a separate object simplifies the
+//               parameter list to CullTraverser::r_traverse(), as
+//               well as to other functions like
+//               PandaNode::cull_callback().  It also makes it easier
+//               to add cull parameters, and provides a place to
+//               abstract out some of the cull behavior (like
+//               view-frustum culling).
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA CullTraverserData {
+public:
+  INLINE CullTraverserData(const TransformState *render_transform,
+                           const TransformState *net_transform,
+                           const RenderState *state,
+                           GeometricBoundingVolume *view_frustum,
+                           GeometricBoundingVolume *guard_band,
+                           const TransformState *camera_transform);
+  INLINE CullTraverserData(const CullTraverserData &copy);
+  INLINE void operator = (const CullTraverserData &copy);
+  INLINE ~CullTraverserData();
+
+  INLINE bool is_in_view(PandaNode *node);
+  void apply_transform_and_state(PandaNode *node);
+
+  CPT(TransformState) _render_transform;
+  CPT(TransformState) _net_transform;
+  CPT(RenderState) _state;
+  PT(GeometricBoundingVolume) _view_frustum;
+  PT(GeometricBoundingVolume) _guard_band;
+
+  // This one is not modified during traversal, so it doesn't need to
+  // be reference counted (we trust the original owner of this pointer
+  // to reference count it and hold it during the lifetime of the
+  // traversal).
+  const TransformState *_camera_transform;
+
+private:
+  bool is_in_view_impl(PandaNode *node);
+  static CPT(RenderState) get_fake_view_frustum_cull_effect();
+};
+
+#include "cullTraverserData.I"
+
+#endif

+ 21 - 1
panda/src/pgraph/cullableObject.I

@@ -19,7 +19,8 @@
 ////////////////////////////////////////////////////////////////////
 //     Function: CullableObject::Constructor
 //       Access: Public
-//  Description: 
+//  Description: Creates an empty CullableObject whose pointers can be
+//               filled in later.
 ////////////////////////////////////////////////////////////////////
 INLINE CullableObject::
 CullableObject(CullableObject *next) :
@@ -27,6 +28,25 @@ CullableObject(CullableObject *next) :
 {
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CullableObject::Constructor
+//       Access: Public
+//  Description: Creates a CullableObject based on the ith Geom from
+//               the indicated GeomNode, with the render state from
+//               the indicated CullTraverserData.
+////////////////////////////////////////////////////////////////////
+INLINE CullableObject::
+CullableObject(const CullTraverserData &data,
+               qpGeomNode *geom_node, int i,
+               CullableObject *next) :
+  _geom(geom_node->get_geom(i)),
+  _state(data._state->compose(geom_node->get_geom_state(i))),
+  _transform(data._render_transform),
+  _next(next)
+{
+}
+  
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CullableObject::Copy Constructor
 //       Access: Private

+ 10 - 0
panda/src/pgraph/cullableObject.h

@@ -26,6 +26,8 @@
 #include "transformState.h"
 #include "pointerTo.h"
 #include "referenceCount.h"
+#include "qpgeomNode.h"
+#include "cullTraverserData.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : CullableObject
@@ -37,6 +39,14 @@
 class EXPCL_PANDA CullableObject {
 public:
   INLINE CullableObject(CullableObject *next = NULL);
+  INLINE CullableObject(const CullTraverserData &data,
+                        qpGeomNode *geom_node, int i,
+                        CullableObject *next = NULL);
+
+  // We will allocate and destroy hundreds or thousands of these a
+  // frame during the normal course of rendering.  As an optimization,
+  // then, we should consider implementing operator new and delete
+  // here to minimize this overhead.  Should be simple.
 
 private:
   INLINE CullableObject(const CullableObject &copy);

+ 91 - 0
panda/src/pgraph/pandaNode.cxx

@@ -234,6 +234,97 @@ combine_with(PandaNode *other) {
   return (PandaNode *)NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool PandaNode::
+has_cull_callback() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::cull_callback
+//       Access: Public, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool PandaNode::
+cull_callback(CullTraverserData &) {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::has_selective_visibility
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if this kind of node has some restrictions on
+//               the set of children that should be rendered.  Node
+//               with this property include LODNodes, SwitchNodes, and
+//               SequenceNodes.
+//
+//               If this function returns true,
+//               get_first_visible_child() and
+//               get_next_visible_child() will be called to walk
+//               through the list of children during cull, instead of
+//               iterating through the entire list.  This method is
+//               called after cull_callback(), so cull_callback() may
+//               be responsible for the decisions as to which children
+//               are visible at the moment.
+////////////////////////////////////////////////////////////////////
+bool PandaNode::
+has_selective_visibility() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_first_visible_child
+//       Access: Public, Virtual
+//  Description: Returns the index number of the first visible child
+//               of this node, or a number >= get_num_children() if
+//               there are no visible children of this node.  This is
+//               called during the cull traversal, but only if
+//               has_selective_visibility() has already returned true.
+//               See has_selective_visibility().
+////////////////////////////////////////////////////////////////////
+int PandaNode::
+get_first_visible_child() const {
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_next_visible_child
+//       Access: Public, Virtual
+//  Description: Returns the index number of the next visible child
+//               of this node following the indicated child, or a
+//               number >= get_num_children() if there are no more
+//               visible children of this node.  See
+//               has_selective_visibility() and
+//               get_first_visible_child().
+////////////////////////////////////////////////////////////////////
+int PandaNode::
+get_next_visible_child(int n) const {
+  return n + 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::find_child
 //       Access: Published

+ 7 - 0
panda/src/pgraph/pandaNode.h

@@ -38,6 +38,7 @@
 #include "notify.h"
 
 class NodeChainComponent;
+class CullTraverserData;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : PandaNode
@@ -65,6 +66,12 @@ public:
   virtual void xform(const LMatrix4f &mat);
   virtual PandaNode *combine_with(PandaNode *other); 
 
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(CullTraverserData &data);
+  virtual bool has_selective_visibility() const;
+  virtual int get_first_visible_child() const;
+  virtual int get_next_visible_child(int n) const;
+
 PUBLISHED:
   INLINE int get_num_parents() const;
   INLINE PandaNode *get_parent(int n) const;

+ 1 - 0
panda/src/pgraph/pgraph_composite1.cxx

@@ -13,5 +13,6 @@
 #include "cullHandler.cxx"
 #include "cullResult.cxx"
 #include "qpcullTraverser.cxx"
+#include "cullTraverserData.cxx"
 #include "cullableObject.cxx"
 

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

@@ -4,12 +4,15 @@
 #include "drawCullHandler.cxx"
 #include "qpgeomNode.cxx"
 #include "qplensNode.cxx"
+#include "qplodNode.cxx"
 #include "materialAttrib.cxx"
 #include "nodeChain.cxx"
 #include "nodeChainComponent.cxx"
 #include "pandaNode.cxx"
 #include "renderAttrib.cxx"
 #include "renderState.cxx"
+#include "selectiveChildNode.cxx"
+#include "qpsequenceNode.cxx"
 #include "test_pgraph.cxx"
 #include "textureApplyAttrib.cxx"
 #include "textureAttrib.cxx"

+ 54 - 0
panda/src/pgraph/qpcamera.cxx

@@ -141,3 +141,57 @@ remove_display_region(DisplayRegion *display_region) {
     _display_regions.erase(dri);
   }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               qpCamera.
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  qpLensNode::write_datagram(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type qpCamera is encountered
+//               in the Bam file.  It should create the qpCamera
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *qpCamera::
+make_from_bam(const FactoryParams &params) {
+  qpCamera *node = new qpCamera("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new qpCamera.
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  qpLensNode::fillin(scan, manager);
+}

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

@@ -16,8 +16,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef QPCAMERA_H
-#define QPCAMERA_H
+#ifndef qpCAMERA_H
+#define qpCAMERA_H
 
 #include "pandabase.h"
 
@@ -65,6 +65,14 @@ private:
   typedef pvector<DisplayRegion *> DisplayRegions;
   DisplayRegions _display_regions;
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 82 - 187
panda/src/pgraph/qpcullTraverser.cxx

@@ -17,14 +17,13 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "qpcullTraverser.h"
+#include "cullTraverserData.h"
 #include "transformState.h"
 #include "renderState.h"
 #include "billboardAttrib.h"
 #include "cullHandler.h"
 #include "dcast.h"
 #include "qpgeomNode.h"
-#include "colorAttrib.h"
-#include "textureAttrib.h"
 #include "config_pgraph.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -130,8 +129,10 @@ void qpCullTraverser::
 traverse(PandaNode *root) {
   nassertv(_cull_handler != (CullHandler *)NULL);
 
-  r_traverse(root, _render_transform, TransformState::make_identity(),
-             _initial_state, _view_frustum, _guard_band);
+  CullTraverserData data(_render_transform, TransformState::make_identity(),
+                         _initial_state, _view_frustum, _guard_band,
+                         _camera_transform);
+  r_traverse(root, data);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -140,134 +141,55 @@ traverse(PandaNode *root) {
 //  Description: The recursive traversal implementation.
 ////////////////////////////////////////////////////////////////////
 void qpCullTraverser::
-r_traverse(PandaNode *node, 
-           const TransformState *render_transform,
-           const TransformState *net_transform,
-           const RenderState *state, 
-           GeometricBoundingVolume *view_frustum,
-           GeometricBoundingVolume *guard_band) {
-  CPT(RenderState) next_state = state;
+r_traverse(PandaNode *node, const CullTraverserData &data) {
+  CullTraverserData next_data(data);
 
-  if (view_frustum != (GeometricBoundingVolume *)NULL) {
-    // If we have a viewing frustum, check to see if the node's
-    // bounding volume falls within it.
-    const BoundingVolume &node_volume = node->get_bound();
-    nassertv(node_volume.is_of_type(GeometricBoundingVolume::get_class_type()));
-    const GeometricBoundingVolume *node_gbv =
-      DCAST(GeometricBoundingVolume, &node_volume);
+  // Most nodes will have no transform or state, and will not
+  // contain decals or require a special cull callback.  As an
+  // optimization, we should tag nodes with these properties as
+  // being "fancy", and skip this processing for non-fancy nodes.
 
-    int result = view_frustum->contains(node_gbv);
-    if (result == BoundingVolume::IF_no_intersection) {
-      // No intersection at all.  Cull.
-      if (!qpfake_view_frustum_cull) {
-        return;
-      }
-
-      // If we have fake view-frustum culling enabled, instead of
-      // actually culling an object we simply force it to be drawn in
-      // red wireframe.
-      view_frustum = (GeometricBoundingVolume *)NULL;
-      CPT(RenderState) fake_effect = get_fake_view_frustum_cull_effect();
-      next_state = next_state->compose(fake_effect);
-
-    } else if ((result & BoundingVolume::IF_all) != 0) {
-      // The node and its descendants are completely enclosed within
-      // the frustum.  No need to cull further.
-      view_frustum = (GeometricBoundingVolume *)NULL;
+  if (next_data.is_in_view(node)) {
+    next_data.apply_transform_and_state(node);
 
-    } else {
-      if (node->is_final()) {
-        // The bounding volume is partially, but not completely,
-        // within the viewing frustum.  Normally we'd keep testing
-        // child bounding volumes as we continue down.  But this node
-        // has the "final" flag, so the user is claiming that there is
-        // some important reason we should consider everything visible
-        // at this point.  So be it.
-        view_frustum = (GeometricBoundingVolume *)NULL;
+    if (node->has_cull_callback()) {
+      if (!node->cull_callback(next_data)) {
+        return;
       }
     }
-  }
-
-  CPT(TransformState) next_render_transform = render_transform;
-  CPT(TransformState) next_net_transform = net_transform;
-  PT(GeometricBoundingVolume) next_view_frustum = view_frustum;
-  PT(GeometricBoundingVolume) next_guard_band = guard_band;
 
-  const TransformState *node_transform = node->get_transform();
-  if (!node_transform->is_identity()) {
-    next_render_transform = render_transform->compose(node_transform);
-    next_net_transform = net_transform->compose(node_transform);
-
-    if ((view_frustum != (GeometricBoundingVolume *)NULL) ||
-        (guard_band != (GeometricBoundingVolume *)NULL)) {
-      // We need to move the viewing frustums into the node's
-      // coordinate space by applying the node's inverse transform.
-      if (node_transform->is_singular()) {
-        // But we can't invert a singular transform!  Instead of
-        // trying, we'll just give up on frustum culling from this
-        // point down.
-        view_frustum = (GeometricBoundingVolume *)NULL;
-        guard_band = (GeometricBoundingVolume *)NULL;
-
-      } else {
-        CPT(TransformState) inv_transform = 
-          node_transform->invert_compose(TransformState::make_identity());
-        
-        if (view_frustum != (GeometricBoundingVolume *)NULL) {
-          next_view_frustum = DCAST(GeometricBoundingVolume, view_frustum->make_copy());
-          next_view_frustum->xform(inv_transform->get_mat());
-        }
+    const RenderState *node_state = node->get_state();
+    if (node_state->has_decal()) {
+      start_decal(node, next_data);
+      
+    } else {
+      if (node->is_geom_node()) {
+        qpGeomNode *geom_node = DCAST(qpGeomNode, node);
         
-        if (guard_band != (GeometricBoundingVolume *)NULL) {
-          next_guard_band = DCAST(GeometricBoundingVolume, guard_band->make_copy());
-          next_guard_band->xform(inv_transform->get_mat());
+        // Get all the Geoms, with no decalling.
+        int num_geoms = geom_node->get_num_geoms();
+        for (int i = 0; i < num_geoms; i++) {
+          CullableObject *object = new CullableObject(next_data, geom_node, i);
+          _cull_handler->record_object(object);
         }
       }
-    }
-  }
-
-  const RenderState *node_state = node->get_state();
-  next_state = next_state->compose(node_state);
-
-  const BillboardAttrib *billboard = node_state->get_billboard();
-  if (billboard != (const BillboardAttrib *)NULL) {
-    // Got to apply a billboard transform here.
-    CPT(TransformState) billboard_transform = 
-      billboard->do_billboard(net_transform, _camera_transform);
-    next_render_transform = next_render_transform->compose(billboard_transform);
-    next_net_transform = next_net_transform->compose(billboard_transform);
 
-    // We can't reliably cull within a billboard, because the geometry
-    // might get rotated out of its bounding volume.  So once we get
-    // within a billboard, we consider it all visible.
-    next_view_frustum = (GeometricBoundingVolume *)NULL;
-  }
-
-  if (node_state->has_decal()) {
-    start_decal(node, next_render_transform, next_state);
+      // Now visit all the node's children.
+      PandaNode::Children cr = node->get_children();
+      int num_children = cr.get_num_children();
+      if (node->has_selective_visibility()) {
+        int i = node->get_first_visible_child();
+        while (i < num_children) {
+          r_traverse(cr.get_child(i), next_data);
+          i = node->get_next_visible_child(i);
+        }
 
-  } else {
-    if (node->is_geom_node()) {
-      qpGeomNode *geom_node = DCAST(qpGeomNode, node);
-      
-      // Get all the Geoms, with no decalling.
-      int num_geoms = geom_node->get_num_geoms();
-      for (int i = 0; i < num_geoms; i++) {
-        CullableObject *object = new CullableObject;
-        object->_geom = geom_node->get_geom(i);
-        object->_state = next_state->compose(geom_node->get_geom_state(i));
-        object->_transform = next_render_transform;
-        _cull_handler->record_object(object);
+      } else {
+        for (int i = 0; i < num_children; i++) {
+          r_traverse(cr.get_child(i), next_data);
+        }
       }
     }
-
-    // Now visit all the node's children.
-    PandaNode::Children cr = node->get_children();
-    int num_children = cr.get_num_children();
-    for (int i = 0; i < num_children; i++) {
-      r_traverse(cr.get_child(i), next_render_transform, next_net_transform,
-                 next_state, next_view_frustum, next_guard_band);
-    }
   }
 }
 
@@ -276,16 +198,10 @@ r_traverse(PandaNode *node,
 //       Access: Private
 //  Description: Collects a base node and all of the decals applied to
 //               it.  This involves recursing below the base GeomNode
-//               to find all the decal geoms; we don't bother to apply
-//               any view-frustum culling at this point, and we don't
-//               presently support billboards or LOD's within the
-//               decals.  Hard to justify the duplicate code this
-//               would require.
+//               to find all the decal geoms.
 ////////////////////////////////////////////////////////////////////
 void qpCullTraverser::
-start_decal(PandaNode *node, 
-            const TransformState *render_transform,
-            const RenderState *state) {
+start_decal(PandaNode *node, const CullTraverserData &data) {
   if (!node->is_geom_node()) {
     pgraph_cat.error()
       << "DecalAttrib applied to " << *node << ", not a GeomNode.\n";
@@ -297,17 +213,22 @@ start_decal(PandaNode *node,
   // CullableObject node, followed by all of the decal Geoms, in
   // order.
 
-  const TransformState *next_render_transform = render_transform;
-  const RenderState *next_state = state;
-
   // Since the CullableObject is a linked list which gets built in
   // LIFO order, we start with the decals.
   CullableObject *decals = (CullableObject *)NULL;
   PandaNode::Children cr = node->get_children();
   int num_children = cr.get_num_children();
-  for (int i = num_children - 1; i >= 0; i--) {
-    decals =
-      r_get_decals(cr.get_child(i), next_render_transform, next_state, decals);
+  if (node->has_selective_visibility()) {
+    int i = node->get_first_visible_child();
+    while (i < num_children) {
+      decals = r_get_decals(cr.get_child(i), data, decals);
+      i = node->get_next_visible_child(i);
+    }
+    
+  } else {
+    for (int i = num_children - 1; i >= 0; i--) {
+      decals = r_get_decals(cr.get_child(i), data, decals);
+    }
   }
 
   // Now create a new, empty CullableObject to separate the decals
@@ -317,14 +238,9 @@ start_decal(PandaNode *node,
   // And now get the base Geoms, again in reverse order.
   CullableObject *object = separator;
   qpGeomNode *geom_node = DCAST(qpGeomNode, node);
-      
-  // Get all the Geoms, with no decalling.
   int num_geoms = geom_node->get_num_geoms();
   for (int i = num_geoms - 1; i >= 0; i--) {
-    object = new CullableObject(object);
-    object->_geom = geom_node->get_geom(i);
-    object->_state = next_state->compose(geom_node->get_geom_state(i));
-    object->_transform = next_render_transform;
+    object = new CullableObject(data, geom_node, i, object);
   }
 
   if (object != separator) {
@@ -345,60 +261,39 @@ start_decal(PandaNode *node,
 //               they were encountered in the scene graph).
 ////////////////////////////////////////////////////////////////////
 CullableObject *qpCullTraverser::
-r_get_decals(PandaNode *node, 
-             const TransformState *render_transform,
-             const RenderState *state,
+r_get_decals(PandaNode *node, const CullTraverserData &data,
              CullableObject *decals) {
-  const TransformState *node_transform = node->get_transform();
-  const RenderState *node_state = node->get_state();
+  CullTraverserData next_data(data);
 
-  CPT(TransformState) next_render_transform = 
-    render_transform->compose(node_transform);
-  CPT(RenderState) next_state =
-    state->compose(node_state);
+  if (next_data.is_in_view(node)) {
+    next_data.apply_transform_and_state(node);
 
-  // First, visit all of the node's children.
-  PandaNode::Children cr = node->get_children();
-  int num_children = cr.get_num_children();
-  for (int i = num_children - 1; i >= 0; i--) {
-    decals =
-      r_get_decals(cr.get_child(i), next_render_transform, next_state, decals);
-  }
-
-  // Now, tack on any geoms within the node.
-  if (node->is_geom_node()) {
-    qpGeomNode *geom_node = DCAST(qpGeomNode, node);
+    // First, visit all of the node's children.
+    PandaNode::Children cr = node->get_children();
+    int num_children = cr.get_num_children();
+    if (node->has_selective_visibility()) {
+      int i = node->get_first_visible_child();
+      while (i < num_children) {
+        decals = r_get_decals(cr.get_child(i), next_data, decals);
+        i = node->get_next_visible_child(i);
+      }
+      
+    } else {
+      for (int i = num_children - 1; i >= 0; i--) {
+        decals = r_get_decals(cr.get_child(i), next_data, decals);
+      }
+    }
 
-    int num_geoms = geom_node->get_num_geoms();
-    for (int i = num_geoms - 1; i >= 0; i--) {
-      decals = new CullableObject(decals);
-      decals->_geom = geom_node->get_geom(i);
-      decals->_state = next_state->compose(geom_node->get_geom_state(i));
-      decals->_transform = next_render_transform;
+    // Now, tack on any geoms within the node.
+    if (node->is_geom_node()) {
+      qpGeomNode *geom_node = DCAST(qpGeomNode, node);
+      
+      int num_geoms = geom_node->get_num_geoms();
+      for (int i = num_geoms - 1; i >= 0; i--) {
+        decals = new CullableObject(next_data, geom_node, i, decals);
+      }
     }
   }
 
   return decals;
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpCullTraverser::get_fake_view_frustum_cull_effect
-//       Access: Private, Static
-//  Description: Returns a RenderState for rendering stuff in red
-//               wireframe, strictly for the fake_view_frustum_cull
-//               effect.
-////////////////////////////////////////////////////////////////////
-CPT(RenderState) qpCullTraverser::
-get_fake_view_frustum_cull_effect() {
-  // Once someone asks for this pointer, we hold its reference count
-  // and never free it.
-  static CPT(RenderState) effect = (const RenderState *)NULL;
-  if (effect == (const RenderState *)NULL) {
-    effect = RenderState::make
-      (ColorAttrib::make_flat(Colorf(1.0f, 0.0f, 0.0f, 1.0f)),
-       TextureAttrib::make_off(),
-       1000);
-  }
-  return effect;
-}
-

+ 5 - 13
panda/src/pgraph/qpcullTraverser.h

@@ -28,6 +28,7 @@
 
 class PandaNode;
 class CullHandler;
+class CullTraverserData;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : CullTraverser
@@ -51,21 +52,12 @@ public:
   void traverse(PandaNode *root);
 
 private:
-  void r_traverse(PandaNode *node, const TransformState *render_transform,
-                  const TransformState *net_transform,
-                  const RenderState *state,
-                  GeometricBoundingVolume *view_frustum,
-                  GeometricBoundingVolume *guard_band);
-  void start_decal(PandaNode *node, 
-                   const TransformState *render_transform,
-                   const RenderState *state);
-  CullableObject *r_get_decals(PandaNode *node, 
-                               const TransformState *render_transform,
-                               const RenderState *state,
+  void r_traverse(PandaNode *node, const CullTraverserData &data);
+  void start_decal(PandaNode *node, const CullTraverserData &data);
+  CullableObject *r_get_decals(PandaNode *node,
+                               const CullTraverserData &data,
                                CullableObject *decals);
 
-  static CPT(RenderState) get_fake_view_frustum_cull_effect();
-
   CPT(RenderState) _initial_state;
   CPT(TransformState) _camera_transform;
   CPT(TransformState) _render_transform;

+ 67 - 0
panda/src/pgraph/qplensNode.cxx

@@ -80,3 +80,70 @@ write(ostream &out, int indent_level) const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               qpLensNode.
+////////////////////////////////////////////////////////////////////
+void qpLensNode::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void qpLensNode::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  PandaNode::write_datagram(manager, dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::complete_pointers
+//       Access: Public, Virtual
+//  Description: Receives an array of pointers, one for each time
+//               manager->read_pointer() was called in fillin().
+//               Returns the number of pointers processed.
+////////////////////////////////////////////////////////////////////
+int qpLensNode::
+complete_pointers(TypedWritable **p_list, BamReader *manager) {
+  int pi = PandaNode::complete_pointers(p_list, manager);
+
+  return pi;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type qpLensNode is encountered
+//               in the Bam file.  It should create the qpLensNode
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *qpLensNode::
+make_from_bam(const FactoryParams &params) {
+  qpLensNode *node = new qpLensNode("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new qpLensNode.
+////////////////////////////////////////////////////////////////////
+void qpLensNode::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  PandaNode::fillin(scan, manager);
+}

+ 12 - 2
panda/src/pgraph/qplensNode.h

@@ -16,8 +16,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef QPLENSNODE_H
-#define QPLENSNODE_H
+#ifndef qpLENSNODE_H
+#define qpLENSNODE_H
 
 #include "pandabase.h"
 
@@ -55,6 +55,16 @@ PUBLISHED:
 protected:
   PT(Lens) _lens;
 
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual int complete_pointers(TypedWritable **plist,
+                                BamReader *manager);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
 public:
   static TypeHandle get_class_type() {
     return _type_handle;

+ 163 - 0
panda/src/pgraph/qplodNode.I

@@ -0,0 +1,163 @@
+// Filename: qplodNode.I
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::CData::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpLODNode::CData::
+CData() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::CData::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpLODNode::CData::
+CData(const qpLODNode::CData &copy) :
+  _lod(copy._lod)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpLODNode::
+qpLODNode(const string &name) :
+  SelectiveChildNode(name)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::add_switch
+//       Access: Published
+//  Description: Adds a switch range to the qpLODNode.  This implies
+//               that the corresponding child node has been parented
+//               to the node.
+//
+//               The sense of in vs. out distances is as if the object
+//               were coming towards you from far away: it switches
+//               "in" at the far distance, and switches "out" at the
+//               close distance.  Thus, "in" should be larger than
+//               "out".
+////////////////////////////////////////////////////////////////////
+INLINE void qpLODNode::
+add_switch(float in, float out) {
+  CDWriter cdata(_cycler);
+  cdata->_lod._switch_vector.push_back(LODSwitch(in, out));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::set_switch
+//       Access: Published
+//  Description: Changes the switching range of a particular child of
+//               the qpLODNode.  See add_switch().
+////////////////////////////////////////////////////////////////////
+INLINE bool qpLODNode::
+set_switch(int index, float in, float out) {
+  CDWriter cdata(_cycler);
+  nassertr(index >= 0 && index < (int)cdata->_lod._switch_vector.size(), false);
+  cdata->_lod._switch_vector[index].set_range(in, out);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::clear_switches
+//       Access: Published
+//  Description: Removes the set of switching ranges for the qpLODNode,
+//               presumably in conjunction with removing all of its
+//               children.  See add_switch().
+////////////////////////////////////////////////////////////////////
+INLINE void qpLODNode::
+clear_switches(void) {
+  CDWriter cdata(_cycler);
+  cdata->_lod._switch_vector.erase(cdata->_lod._switch_vector.begin(),
+                                   cdata->_lod._switch_vector.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::get_num_switches
+//       Access: Published
+//  Description: Returns the number of switch ranges added to the
+//               qpLODNode.  This should correspond to the number of
+//               children of the node in order for the qpLODNode to
+//               function correctly.
+////////////////////////////////////////////////////////////////////
+INLINE int qpLODNode::
+get_num_switches() const {
+  CDReader cdata(_cycler);
+  return cdata->_lod._switch_vector.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::get_in
+//       Access: Published
+//  Description: Returns the "in" distance of the indicated switch
+//               range.  This should be larger than the "out" distance
+//               of the same range.
+////////////////////////////////////////////////////////////////////
+INLINE float qpLODNode::
+get_in(int index) const {
+  CDReader cdata(_cycler);
+  nassertr(index >= 0 && index < (int)cdata->_lod._switch_vector.size(), 0.0);
+  return cdata->_lod._switch_vector[index].get_in();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::get_out
+//       Access: Published
+//  Description: Returns the "out" distance of the indicated switch
+//               range.  This should be smaller than the "in" distance
+//               of the same range.
+////////////////////////////////////////////////////////////////////
+INLINE float qpLODNode::
+get_out(int index) const {
+  CDReader cdata(_cycler);
+  nassertr(index >= 0 && index < (int)cdata->_lod._switch_vector.size(), 0.0);
+  return cdata->_lod._switch_vector[index].get_out();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::set_center
+//       Access: Published
+//  Description: Specifies the center of the LOD.  This is the point
+//               that is compared to the camera (in camera space) to
+//               determine the particular LOD that should be chosen.
+////////////////////////////////////////////////////////////////////
+INLINE void qpLODNode::
+set_center(const LPoint3f &center) {
+  CDWriter cdata(_cycler);
+  cdata->_lod._center = center;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::get_center
+//       Access: Published
+//  Description: Returns the center of the LOD.  This is the point
+//               that is compared to the camera (in camera space) to
+//               determine the particular LOD that should be chosen.
+////////////////////////////////////////////////////////////////////
+INLINE const LPoint3f &qpLODNode::
+get_center() const {
+  CDReader cdata(_cycler);
+  return cdata->_lod._center;
+}

+ 218 - 0
panda/src/pgraph/qplodNode.cxx

@@ -0,0 +1,218 @@
+// Filename: qplodNode.cxx
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qplodNode.h"
+#include "cullTraverserData.h"
+
+TypeHandle qpLODNode::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::CData::make_copy
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+CycleData *qpLODNode::CData::
+make_copy() const {
+  return new CData(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpLODNode::
+qpLODNode(const qpLODNode &copy) :
+  SelectiveChildNode(copy)
+{
+  CDWriter cdata(_cycler);
+  CDReader cdata_copy(copy._cycler);
+
+  cdata->_lod = cdata_copy->_lod;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+operator = (const qpLODNode &copy) {
+  SelectiveChildNode::operator = (copy);
+
+  CDWriter cdata(_cycler);
+  CDReader cdata_copy(copy._cycler);
+
+  cdata->_lod = cdata_copy->_lod;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpLODNode::
+make_copy() const {
+  return new qpLODNode(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::xform
+//       Access: Public, Virtual
+//  Description: Transforms the contents of this PandaNode by the
+//               indicated matrix, if it means anything to do so.  For
+//               most kinds of PandaNodes, this does nothing.
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+xform(const LMatrix4f &mat) {
+  CDWriter cdata(_cycler);
+  cdata->_lod.xform(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpLODNode::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::cull_callback
+//       Access: Public, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpLODNode::
+cull_callback(CullTraverserData &data) {
+  if (data._net_transform->is_singular()) {
+    // If we're under a singular transform, we can't compute the LOD;
+    // select none of them instead.
+    select_child(get_num_children());
+
+  } else { 
+    CDReader cdata(_cycler);
+    LPoint3f camera_pos(0, 0, 0);
+
+    // Get the LOD center in camera space
+    CPT(TransformState) rel_transform =
+      data._net_transform->invert_compose(data._camera_transform);
+    LPoint3f center = cdata->_lod._center * rel_transform->get_mat();
+    
+    // Determine which child to traverse
+    int index = cdata->_lod.compute_child(camera_pos, center);
+    select_child(index);
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::output
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+output(ostream &out) const {
+  SelectiveChildNode::output(out);
+  CDReader cdata(_cycler);
+  out << " ";
+  cdata->_lod.output(out);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               qpLODNode.
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  SelectiveChildNode::write_datagram(manager, dg);
+
+  CDReader cdata(_cycler);
+  cdata->_lod.write_datagram(dg);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type qpLODNode is encountered
+//               in the Bam file.  It should create the qpLODNode
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *qpLODNode::
+make_from_bam(const FactoryParams &params) {
+  qpLODNode *node = new qpLODNode("");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLODNode::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new qpLODNode.
+////////////////////////////////////////////////////////////////////
+void qpLODNode::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  SelectiveChildNode::fillin(scan, manager);
+
+  CDWriter cdata(_cycler);
+  cdata->_lod.read_datagram(scan);
+}

+ 109 - 0
panda/src/pgraph/qplodNode.h

@@ -0,0 +1,109 @@
+// Filename: qplodNode.h
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpLODNODE_H
+#define qpLODNODE_H
+
+#include "pandabase.h"
+
+#include "selectiveChildNode.h"
+
+#include "LOD.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpLODNode
+// Description : A Level-of-Detail node.  This selects only one of its
+//               children for rendering, according to the distance
+//               from the camera and the table indicated in the
+//               associated LOD object.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpLODNode : public SelectiveChildNode {
+PUBLISHED:
+  INLINE qpLODNode(const string &name);
+
+public:
+  INLINE qpLODNode(const qpLODNode &copy);
+  INLINE void operator = (const qpLODNode &copy);
+
+  virtual PandaNode *make_copy() const;
+  virtual void xform(const LMatrix4f &mat);
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(CullTraverserData &data);
+
+  virtual void output(ostream &out) const;
+
+PUBLISHED:
+  // The sense of in vs. out distances is as if the object were coming
+  // towards you from far away: it switches "in" at the far distance,
+  // and switches "out" at the close distance.  Thus, "in" should be
+  // larger than "out".
+
+  INLINE void add_switch(float in, float out);
+  INLINE bool set_switch(int index, float in, float out);
+  INLINE void clear_switches(void);
+
+  INLINE int get_num_switches() const;
+  INLINE float get_in(int index) const;
+  INLINE float get_out(int index) const;
+
+  INLINE void set_center(const LPoint3f &center);
+  INLINE const LPoint3f &get_center() const;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+private:
+  class EXPCL_PANDA CData : public CycleData {
+  public:
+    INLINE CData();
+    INLINE CData(const CData &copy);
+    virtual CycleData *make_copy() const;
+
+    LOD _lod;
+  };
+
+  PipelineCycler<CData> _cycler;
+  typedef CycleDataReader<CData> CDReader;
+  typedef CycleDataWriter<CData> CDWriter;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    SelectiveChildNode::init_type();
+    register_type(_type_handle, "qpLODNode",
+                  SelectiveChildNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qplodNode.I"
+
+#endif

+ 130 - 0
panda/src/pgraph/qpsequenceNode.I

@@ -0,0 +1,130 @@
+// Filename: qpsequenceNode.I
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::CData::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpSequenceNode::CData::
+CData() {
+  _cycle_rate = 0.0f;
+  _frame_offset = 0.0f;
+  _start_time = 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::CData::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpSequenceNode::CData::
+CData(const qpSequenceNode::CData &copy) :
+  _cycle_rate(copy._cycle_rate),
+  _frame_offset(copy._frame_offset),
+  _start_time(copy._start_time)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpSequenceNode::
+qpSequenceNode(float cycle_rate, const string &name) :
+  SelectiveChildNode(name)
+{
+  CDWriter cdata(_cycler);
+  cdata->_cycle_rate = cycle_rate;
+  cdata->_frame_offset = 0.0f;
+
+  float now = ClockObject::get_global_clock()->get_frame_time();
+  cdata->_start_time = now;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::set_cycle_rate
+//       Access: Published
+//  Description: Sets the rate of cycling for the children of the
+//               SequenceNode, in cycles per second.
+////////////////////////////////////////////////////////////////////
+INLINE void qpSequenceNode::
+set_cycle_rate(float cycle_rate) {
+  // Do some fussing so we keep the same frame visible while we
+  // change this.
+  CDWriter cdata(_cycler);
+  float now = ClockObject::get_global_clock()->get_frame_time();
+  cdata->_frame_offset = calc_frame(now);
+  cdata->_start_time = now;
+  cdata->_cycle_rate = cycle_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::get_cycle_rate
+//       Access: Published
+//  Description: Returns the rate of cycling for the children of the
+//               SequenceNode, in cycles per second.
+////////////////////////////////////////////////////////////////////
+INLINE float qpSequenceNode::
+get_cycle_rate() const {
+  CDReader cdata(_cycler);
+  return cdata->_cycle_rate;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::get_visible_child
+//       Access: Published
+//  Description: Returns the index of the child that should be visible
+//               for this particular frame, if there are any children.
+////////////////////////////////////////////////////////////////////
+INLINE int qpSequenceNode::
+get_visible_child() const {
+  int num_children = get_num_children();
+  if (num_children == 0) {
+    return 0;
+  }
+
+  float frame = calc_frame();
+
+  return ((int)frame) % num_children;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::calc_frame
+//       Access: Private
+//  Description: Returns the floating-point frame number at the
+//               indicated time.
+////////////////////////////////////////////////////////////////////
+INLINE float qpSequenceNode::
+calc_frame(float now) const {
+  CDReader cdata(_cycler);
+  return (now - cdata->_start_time) * cdata->_cycle_rate + cdata->_frame_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::calc_frame
+//       Access: Private
+//  Description: Returns the floating-point frame number at the
+//               current time.
+////////////////////////////////////////////////////////////////////
+INLINE float qpSequenceNode::
+calc_frame() const {
+  return calc_frame(ClockObject::get_global_clock()->get_frame_time());
+}

+ 183 - 0
panda/src/pgraph/qpsequenceNode.cxx

@@ -0,0 +1,183 @@
+// Filename: qpsequenceNode.cxx
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandabase.h"
+#include "qpsequenceNode.h"
+
+TypeHandle qpSequenceNode::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::CData::make_copy
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+CycleData *qpSequenceNode::CData::
+make_copy() const {
+  return new CData(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpSequenceNode::
+qpSequenceNode(const qpSequenceNode &copy) :
+  SelectiveChildNode(copy)
+{
+  CDWriter cdata(_cycler);
+  CDReader cdata_copy(copy._cycler);
+
+  cdata->_cycle_rate = cdata_copy->_cycle_rate;
+  cdata->_start_time = cdata_copy->_start_time;
+  cdata->_frame_offset = cdata_copy->_frame_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpSequenceNode::
+operator = (const qpSequenceNode &copy) {
+  SelectiveChildNode::operator = (copy);
+
+  CDWriter cdata(_cycler);
+  CDReader cdata_copy(copy._cycler);
+
+  cdata->_cycle_rate = cdata_copy->_cycle_rate;
+  cdata->_start_time = cdata_copy->_start_time;
+  cdata->_frame_offset = cdata_copy->_frame_offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpSequenceNode::
+make_copy() const {
+  return new qpSequenceNode(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::has_cull_callback
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if cull_callback() has been defined.  Otherwise,
+//               returns false to indicate cull_callback() does not
+//               need to be called for this node during the cull
+//               traversal.
+////////////////////////////////////////////////////////////////////
+bool qpSequenceNode::
+has_cull_callback() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::cull_callback
+//       Access: Public, Virtual
+//  Description: If has_cull_callback() returns true, this function
+//               will be called during the cull traversal to perform
+//               any additional operations that should be performed at
+//               cull time.  This may include additional manipulation
+//               of render state or additional visible/invisible
+//               decisions, or any other arbitrary operation.
+//
+//               By the time this function is called, the node has
+//               already passed the bounding-volume test for the
+//               viewing frustum, and the node's transform and state
+//               have already been applied to the indicated
+//               CullTraverserData object.
+//
+//               The return value is true if this node should be
+//               visible, or false if it should be culled.
+////////////////////////////////////////////////////////////////////
+bool qpSequenceNode::
+cull_callback(CullTraverserData &) {
+  select_child(get_visible_child());
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               qpSequenceNode.
+////////////////////////////////////////////////////////////////////
+void qpSequenceNode::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void qpSequenceNode::
+write_datagram(BamWriter *manager, Datagram &dg) {
+  SelectiveChildNode::write_datagram(manager, dg);
+
+  CDReader cdata(_cycler);
+  dg.add_float32(cdata->_cycle_rate);
+  dg.add_float32(calc_frame());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type qpSequenceNode is encountered
+//               in the Bam file.  It should create the qpSequenceNode
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *qpSequenceNode::
+make_from_bam(const FactoryParams &params) {
+  qpSequenceNode *node = new qpSequenceNode(0.0f, "");
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  node->fillin(scan, manager);
+
+  return node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpSequenceNode::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new qpSequenceNode.
+////////////////////////////////////////////////////////////////////
+void qpSequenceNode::
+fillin(DatagramIterator &scan, BamReader *manager) {
+  SelectiveChildNode::fillin(scan, manager);
+
+  CDWriter cdata(_cycler);
+  cdata->_cycle_rate = scan.get_float32();
+  cdata->_frame_offset = scan.get_float32();
+
+  float now = ClockObject::get_global_clock()->get_frame_time();
+  cdata->_start_time = now;
+}

+ 98 - 0
panda/src/pgraph/qpsequenceNode.h

@@ -0,0 +1,98 @@
+// Filename: qpsequenceNode.h
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpSEQUENCENODE_H
+#define qpSEQUENCENODE_H
+
+#include "pandabase.h"
+
+#include "selectiveChildNode.h"
+#include "clockObject.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : SequenceNode
+// Description : A node that automatically cycles through rendering
+//               each one of its children according to its frame rate.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpSequenceNode : public SelectiveChildNode {
+PUBLISHED:
+  INLINE qpSequenceNode(float cycle_rate, const string &name);
+
+public:
+  qpSequenceNode(const qpSequenceNode &copy);
+  void operator = (const qpSequenceNode &copy);
+
+  virtual PandaNode *make_copy() const;
+
+  virtual bool has_cull_callback() const;
+  virtual bool cull_callback(CullTraverserData &data);
+
+PUBLISHED:
+  INLINE void set_cycle_rate(float cycle_rate);
+  INLINE float get_cycle_rate() const;
+
+  INLINE int get_visible_child() const;
+
+private:
+  INLINE float calc_frame(float now) const;
+  INLINE float calc_frame() const;
+
+  class EXPCL_PANDA CData : public CycleData {
+  public:
+    INLINE CData();
+    INLINE CData(const CData &copy);
+    virtual CycleData *make_copy() const;
+
+    float _cycle_rate;
+    float _frame_offset;
+    float _start_time;
+  };
+
+  PipelineCycler<CData> _cycler;
+  typedef CycleDataReader<CData> CDReader;
+  typedef CycleDataWriter<CData> CDWriter;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    SelectiveChildNode::init_type();
+    register_type(_type_handle, "qpSequenceNode",
+                  SelectiveChildNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qpsequenceNode.I"
+
+#endif

+ 65 - 0
panda/src/pgraph/selectiveChildNode.I

@@ -0,0 +1,65 @@
+// Filename: selectiveChildNode.I
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE SelectiveChildNode::
+SelectiveChildNode(const string &name) :
+  PandaNode(name),
+  _selected_child(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE SelectiveChildNode::
+SelectiveChildNode(const SelectiveChildNode &copy) :
+  PandaNode(copy),
+  _selected_child(copy._selected_child)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void SelectiveChildNode::
+operator = (const SelectiveChildNode &copy) {
+  PandaNode::operator = (copy);
+  _selected_child = copy._selected_child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::select_child
+//       Access: Protected
+//  Description: Selects the indicated child for rendering.  This is
+//               normally called during the cull_callback() method,
+//               but it may be called at any time.
+////////////////////////////////////////////////////////////////////
+INLINE void SelectiveChildNode::
+select_child(int n) {
+  _selected_child = n;
+}

+ 75 - 0
panda/src/pgraph/selectiveChildNode.cxx

@@ -0,0 +1,75 @@
+// Filename: selectiveChildNode.cxx
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "selectiveChildNode.h"
+
+TypeHandle SelectiveChildNode::_type_handle;
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::has_selective_visibility
+//       Access: Public, Virtual
+//  Description: Should be overridden by derived classes to return
+//               true if this kind of node has some restrictions on
+//               the set of children that should be rendered.  Node
+//               with this property include LODNodes, SwitchNodes, and
+//               SequenceNodes.
+//
+//               If this function returns true,
+//               get_first_visible_child() and
+//               get_next_visible_child() will be called to walk
+//               through the list of children during cull, instead of
+//               iterating through the entire list.  This method is
+//               called after cull_callback(), so cull_callback() may
+//               be responsible for the decisions as to which children
+//               are visible at the moment.
+////////////////////////////////////////////////////////////////////
+bool SelectiveChildNode::
+has_selective_visibility() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::get_first_visible_child
+//       Access: Public, Virtual
+//  Description: Returns the index number of the first visible child
+//               of this node, or a number >= get_num_children() if
+//               there are no visible children of this node.  This is
+//               called during the cull traversal, but only if
+//               has_selective_visibility() has already returned true.
+//               See has_selective_visibility().
+////////////////////////////////////////////////////////////////////
+int SelectiveChildNode::
+get_first_visible_child() const {
+  return _selected_child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: SelectiveChildNode::get_next_visible_child
+//       Access: Public, Virtual
+//  Description: Returns the index number of the next visible child
+//               of this node following the indicated child, or a
+//               number >= get_num_children() if there are no more
+//               visible children of this node.  See
+//               has_selective_visibility() and
+//               get_first_visible_child().
+////////////////////////////////////////////////////////////////////
+int SelectiveChildNode::
+get_next_visible_child(int n) const {
+  return get_num_children();
+}

+ 69 - 0
panda/src/pgraph/selectiveChildNode.h

@@ -0,0 +1,69 @@
+// Filename: selectiveChildNode.h
+// Created by:  drose (06Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 SELECTIVECHILDNODE_H
+#define SELECTIVECHILDNODE_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : SelectiveChildNode
+// Description : A base class for nodes like LODNode and SequenceNode
+//               that select only one visible child at a time.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA SelectiveChildNode : public PandaNode {
+PUBLISHED:
+  INLINE SelectiveChildNode(const string &name);
+
+public:
+  INLINE SelectiveChildNode(const SelectiveChildNode &copy);
+  INLINE void operator = (const SelectiveChildNode &copy);
+
+  virtual bool has_selective_visibility() const;
+  virtual int get_first_visible_child() const;
+  virtual int get_next_visible_child(int n) const;
+
+protected:
+  INLINE void select_child(int n);
+
+private:
+  int _selected_child;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "SelectiveChildNode",
+                  PandaNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "selectiveChildNode.I"
+
+#endif

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

@@ -480,6 +480,9 @@ do_compose(const TransformState *other) const {
 ////////////////////////////////////////////////////////////////////
 CPT(TransformState) TransformState::
 do_invert_compose(const TransformState *other) const {
+  // Perhaps we should cache the inverse matrix operation separately,
+  // as a further optimization.
+
   LMatrix4f new_mat;
   new_mat.invert_from(get_mat());
   new_mat = other->get_mat() * new_mat;