Browse Source

pgraph nodepath fixes, pstats, invalid transforms

David Rose 24 years ago
parent
commit
33c59b176b

+ 21 - 7
panda/src/display/graphicsEngine.cxx

@@ -24,6 +24,13 @@
 #include "cullResult.h"
 #include "qpcullTraverser.h"
 #include "clockObject.h"
+#include "pStatTimer.h"
+#include "pStatClient.h"
+
+#ifndef CPPPARSER
+PStatCollector GraphicsEngine::_cull_pcollector("Cull");
+PStatCollector GraphicsEngine::_draw_pcollector("Draw");
+#endif  // CPPPARSER
 
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsEngine::Constructor
@@ -89,6 +96,7 @@ render_frame() {
   // **** This doesn't belong here; it really belongs in the Pipeline,
   // but here it is for now.
   ClockObject::get_global_clock()->tick();
+  PStatClient::main_tick();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -199,6 +207,9 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) {
 void GraphicsEngine::
 do_cull(CullHandler *cull_handler, const qpNodePath &camera,
         GraphicsStateGuardian *gsg) {
+  // Statistics
+  PStatTimer timer(_cull_pcollector);
+
   if (camera.is_empty()) {
     // No camera, no draw.
     return;
@@ -227,12 +238,12 @@ do_cull(CullHandler *cull_handler, const qpNodePath &camera,
   qpCullTraverser trav;
   trav.set_cull_handler(cull_handler);
 
-  // We will need both the camera transform (the net transform from
-  // the scene to the camera) and the world transform (the camera
-  // transform inverse, or the net transform from the camera to the
-  // scene).
-  CPT(TransformState) camera_transform = scene.get_transform(camera);
-  CPT(TransformState) world_transform = camera.get_transform(scene);
+  // We will need both the camera transform (the net transform to the
+  // camera from the scene) and the world transform (the camera
+  // transform inverse, or the net transform to the scene from the
+  // camera).
+  CPT(TransformState) camera_transform = camera.get_transform(scene);
+  CPT(TransformState) world_transform = scene.get_transform(camera);
 
   // The render transform is the same as the world transform, except
   // it is converted into the GSG's internal coordinate system.  This
@@ -247,7 +258,7 @@ do_cull(CullHandler *cull_handler, const qpNodePath &camera,
     render_transform = cs_transform->compose(render_transform);
   }
 
-  trav.set_camera_transform(scene.get_transform(camera));
+  trav.set_camera_transform(camera_transform);
   trav.set_render_transform(render_transform);
 
   if (qpview_frustum_cull) {
@@ -281,6 +292,9 @@ do_cull(CullHandler *cull_handler, const qpNodePath &camera,
 void GraphicsEngine::
 do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg,
         DisplayRegion *dr) {
+  // Statistics
+  PStatTimer timer(_draw_pcollector);
+
   if (set_gsg_lens(gsg, dr)) {
     DisplayRegionStack old_dr = gsg->push_display_region(dr);
     gsg->prepare_display_region();

+ 4 - 0
panda/src/display/graphicsEngine.h

@@ -23,6 +23,7 @@
 #include "graphicsWindow.h"
 #include "pointerTo.h"
 #include "pset.h"
+#include "pStatCollector.h"
 
 class Pipeline;
 class DisplayRegion;
@@ -68,6 +69,9 @@ private:
 
   typedef pset<PT(GraphicsWindow)> Windows;
   Windows _windows;
+
+  static PStatCollector _cull_pcollector;
+  static PStatCollector _draw_pcollector;
 };
 
 #include "graphicsEngine.I"

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

@@ -383,6 +383,9 @@ add_child(PandaNode *child_node, int sort) {
   }
 
   child_node->fix_chain_lengths();
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -430,6 +433,9 @@ remove_child(int n) {
   }
 
   child_node->fix_chain_lengths();
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -476,6 +482,9 @@ remove_child(PandaNode *child_node) {
 
   child_node->fix_chain_lengths();
 
+  // Mark the bounding volumes stale.
+  force_bound_stale();
+
   CDWriter cdata(_cycler);
 
   // Now, look for and remove the child node from our down list.
@@ -529,6 +538,9 @@ remove_all_children() {
 
     child_node->fix_chain_lengths();
   }
+
+  // Mark the bounding volumes stale.
+  force_bound_stale();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -732,6 +744,9 @@ detach(qpNodePathComponent *child) {
 
   child_node->fix_chain_lengths();
 
+  // Mark the bounding volumes stale.
+  parent_node->force_bound_stale();
+
   // Now look for the child and break the actual connection.
 
   // First, look for and remove the parent node from the child's up
@@ -785,6 +800,9 @@ reparent(qpNodePathComponent *new_parent, qpNodePathComponent *child, int sort)
 
   cdata_child->_chains.insert(child);
   child_node->fix_chain_lengths();
+
+  // Mark the bounding volumes stale.
+  parent_node->force_bound_stale();
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -706,18 +706,6 @@ set_pos(const qpNodePath &other, float x, float y, float z) {
   set_pos(other, LPoint3f(x, y, z));
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::get_pos
-//       Access: Published
-//  Description: Returns the relative position of the referenced node
-//               as seen from the other node.
-////////////////////////////////////////////////////////////////////
-INLINE LPoint3f qpNodePath::
-get_pos(const qpNodePath &other) const {
-  LMatrix4f mat = get_mat(other);
-  return mat.get_row3(3);
-}
-
 INLINE float qpNodePath::
 get_x(const qpNodePath &other) const {
   return get_pos(other)[0];

+ 26 - 14
panda/src/pgraph/qpnodePath.cxx

@@ -322,17 +322,17 @@ set_state(const qpNodePath &other, const RenderState *state) const {
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::get_transform
 //       Access: Published
-//  Description: Returns the relative transform from this node to the
-//               other node; i.e. the transformation of the other node
-//               as seen from this node.
+//  Description: Returns the relative transform to this node from the
+//               other node; i.e. the transformation of this node
+//               as seen from the other node.
 ////////////////////////////////////////////////////////////////////
 CPT(TransformState) qpNodePath::
 get_transform(const qpNodePath &other) const {
-  if (is_empty()) {
-    return other.get_net_transform();
-  }
   if (other.is_empty()) {
-    return get_net_transform()->invert_compose(TransformState::make_identity());
+    return get_net_transform();
+  }
+  if (is_empty()) {
+    return other.get_net_transform()->invert_compose(TransformState::make_identity());
   }
     
   nassertr(verify_complete(), TransformState::make_identity());
@@ -343,7 +343,7 @@ get_transform(const qpNodePath &other) const {
 
   CPT(TransformState) a_transform = r_get_partial_transform(_head, a_count);
   CPT(TransformState) b_transform = r_get_partial_transform(other._head, b_count);
-  return a_transform->invert_compose(b_transform);
+  return b_transform->invert_compose(a_transform);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -351,8 +351,8 @@ get_transform(const qpNodePath &other) const {
 //       Access: Published
 //  Description: Sets the transform object on this node, relative to
 //               the other node.  This computes a new transform object
-//               that has the indicated value when seen relative to
-//               the other node.
+//               that will have the indicated value when seen from the
+//               other node.
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 set_transform(const qpNodePath &other, const TransformState *transform) const {
@@ -361,7 +361,7 @@ set_transform(const qpNodePath &other, const TransformState *transform) const {
 
   // First, we perform a wrt to the parent, to get the conversion.
   qpNodePath parent = get_parent();
-  CPT(TransformState) rel_trans = parent.get_transform(other);
+  CPT(TransformState) rel_trans = other.get_transform(parent);
 
   CPT(TransformState) new_trans = rel_trans->compose(transform);
   set_transform(new_trans);
@@ -491,11 +491,11 @@ get_hpr(float roll) const {
 //               leaving translation and rotation untouched.
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
-set_scale(const LVecBase3f &sv3) {
+set_scale(const LVecBase3f &scale) {
   nassertv(!is_empty());
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
-  set_transform(transform->set_scale(sv3));
+  set_transform(transform->set_scale(scale));
 }
 
 void qpNodePath::
@@ -794,6 +794,18 @@ set_z(const qpNodePath &other, float z) {
   set_pos(other, pos);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_pos
+//       Access: Published
+//  Description: Returns the relative position of the referenced node
+//               as seen from the other node.
+////////////////////////////////////////////////////////////////////
+LPoint3f qpNodePath::
+get_pos(const qpNodePath &other) const {
+  nassertr(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
+  return get_transform(other)->get_pos();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::set_hpr
 //       Access: Published
@@ -2254,7 +2266,7 @@ r_output(ostream &out, qpNodePathComponent *comp) const {
   } else {
     out << "+" << node->get_type();
   }
-  out << "[" << comp->get_length() << "]";
+  //  out << "[" << comp->get_length() << "]";
 }
 
 ////////////////////////////////////////////////////////////////////

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

@@ -168,7 +168,7 @@ PUBLISHED:
 
   INLINE void set_scale(float scale);
   INLINE void set_scale(float sx, float sy, float sz);
-  void set_scale(const LVecBase3f &sv3);
+  void set_scale(const LVecBase3f &scale);
   void set_sx(float sx);
   void set_sy(float sy);
   void set_sz(float sz);
@@ -232,7 +232,7 @@ PUBLISHED:
   void set_x(const qpNodePath &other, float x);
   void set_y(const qpNodePath &other, float y);
   void set_z(const qpNodePath &other, float z);
-  INLINE LPoint3f get_pos(const qpNodePath &other) const;
+  LPoint3f get_pos(const qpNodePath &other) const;
   INLINE float get_x(const qpNodePath &other) const;
   INLINE float get_y(const qpNodePath &other) const;
   INLINE float get_z(const qpNodePath &other) const;

+ 48 - 4
panda/src/pgraph/transformState.I

@@ -92,6 +92,18 @@ is_identity() const {
   return ((_flags & F_is_identity) != 0);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::is_invalid
+//       Access: Published
+//  Description: Returns true if the transform represents an invalid
+//               matrix, for instance the result of inverting a
+//               singular matrix, or false if the transform is valid.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+is_invalid() const {
+  return ((_flags & F_is_invalid) != 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::is_singular
 //       Access: Published
@@ -119,8 +131,10 @@ is_singular() const {
 //               that was constructed with a 4x4 may return true here
 //               if the matrix is a simple affine matrix with no skew.
 //
-//               If this returns true, you may safely call get_pos(),
-//               etc., to retrieve the components.
+//               If this returns true, you may safely call get_hpr()
+//               and get_scale() to retrieve the components.  (You
+//               may always safely call get_pos() whether this returns
+//               true or false.)
 ////////////////////////////////////////////////////////////////////
 INLINE bool TransformState::
 has_components() const {
@@ -128,16 +142,32 @@ has_components() const {
   return ((_flags & F_has_components) != 0);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::components_given
+//       Access: Published
+//  Description: Returns true if the transform was specified
+//               componentwise, or false if it was specified with a
+//               general 4x4 matrix.  If this is true, the components
+//               returned by get_pos(), get_hpr(), and get_scale()
+//               will be exactly those that were set; otherwise, these
+//               functions will return computed values.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+components_given() const {
+  return ((_flags & F_components_given) != 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::has_pos
 //       Access: Published
 //  Description: Returns true if the transform's pos component can be
 //               extracted out separately.  This is generally always
-//               true.
+//               true, unless the transform is invalid
+//               (i.e. is_invalid() returns true).
 ////////////////////////////////////////////////////////////////////
 INLINE bool TransformState::
 has_pos() const {
-  return true;
+  return !is_invalid();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -165,6 +195,18 @@ has_scale() const {
   return has_components();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::has_mat
+//       Access: Published
+//  Description: Returns true if the transform can be described as a
+//               matrix.  This is generally always true, unless
+//               is_invalid() is true.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+has_mat() const {
+  return !is_invalid();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::get_pos
 //       Access: Published
@@ -174,6 +216,7 @@ has_scale() const {
 INLINE const LVecBase3f &TransformState::
 get_pos() const {
   check_components();
+  nassertr(has_pos(), _pos);
   return _pos;
 }
 
@@ -212,6 +255,7 @@ get_scale() const {
 ////////////////////////////////////////////////////////////////////
 INLINE const LMatrix4f &TransformState::
 get_mat() const {
+  nassertr(has_mat(), LMatrix4f::ident_mat());
   check_mat();
   return _mat;
 }

+ 94 - 24
panda/src/pgraph/transformState.cxx

@@ -154,12 +154,22 @@ TransformState::
 ////////////////////////////////////////////////////////////////////
 bool TransformState::
 operator < (const TransformState &other) const {
-  bool components_given = (_flags & F_components_given) != 0;
-  bool other_components_given = (other._flags & F_components_given) != 0;
-  if (components_given != other_components_given) {
-    return components_given < other_components_given;
+  static const int significant_flags = 
+    (F_is_invalid | F_is_identity | F_components_given);
+
+  int flags = (_flags & significant_flags);
+  int other_flags = (other._flags & significant_flags);
+  if (flags != other_flags) {
+    return flags < other_flags;
+  }
+
+  if ((_flags & (F_is_invalid | F_is_identity)) != 0) {
+    // All invalid transforms are equivalent to each other, and all
+    // identity transforms are equivalent to each other.
+    return 0;
   }
-  if (components_given) {
+
+  if ((_flags & F_components_given) != 0) {
     // If the transform was specified componentwise, compare them
     // componentwise.
     int c = _pos.compare_to(other._pos);
@@ -195,6 +205,19 @@ make_identity() {
   return _identity_state;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_invalid
+//       Access: Published, Static
+//  Description: Constructs an invalid transform; for instance, the
+//               result of inverting a singular matrix.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+make_invalid() {
+  TransformState *state = new TransformState;
+  state->_flags = F_is_invalid | F_singular_known | F_is_singular | F_components_known | F_mat_known;
+  return return_new(state);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::make_pos_hpr_scale
 //       Access: Published, Static
@@ -211,12 +234,12 @@ make_pos_hpr_scale(const LVecBase3f &pos, const LVecBase3f &hpr,
     return make_identity();
   }
 
-  TransformState *attrib = new TransformState;
-  attrib->_pos = pos;
-  attrib->_hpr = hpr;
-  attrib->_scale = scale;
-  attrib->_flags = F_components_given | F_components_known | F_has_components;
-  return return_new(attrib);
+  TransformState *state = new TransformState;
+  state->_pos = pos;
+  state->_hpr = hpr;
+  state->_scale = scale;
+  state->_flags = F_components_given | F_components_known | F_has_components;
+  return return_new(state);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -232,10 +255,10 @@ make_mat(const LMatrix4f &mat) {
     return make_identity();
   }
 
-  TransformState *attrib = new TransformState;
-  attrib->_mat = mat;
-  attrib->_flags = F_mat_known;
-  return return_new(attrib);
+  TransformState *state = new TransformState;
+  state->_mat = mat;
+  state->_flags = F_mat_known;
+  return return_new(state);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -283,7 +306,7 @@ set_hpr(const LVecBase3f &hpr) const {
 CPT(TransformState) TransformState::
 set_scale(const LVecBase3f &scale) const {
   nassertr(has_components(), this);
-  return make_pos_hpr_scale(get_pos(), get_scale(), get_hpr());
+  return make_pos_hpr_scale(get_pos(), get_hpr(), scale);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -313,6 +336,14 @@ compose(const TransformState *other) const {
     return this;
   }
 
+  // If either transform is invalid, the result is invalid.
+  if (is_invalid()) {
+    return this;
+  }
+  if (other->is_invalid()) {
+    return other;
+  }
+
   if (other == this) {
     // compose(this) has to be handled as a special case, because the
     // caching problem is so different.
@@ -389,6 +420,14 @@ invert_compose(const TransformState *other) const {
   // Unlike compose(), the case of other->is_identity() is not quite as
   // trivial for invert_compose().
 
+  // If either transform is invalid, the result is invalid.
+  if (is_invalid()) {
+    return this;
+  }
+  if (other->is_invalid()) {
+    return other;
+  }
+
   if (other == this) {
     // a->invert_compose(a) always produces identity.
     return make_identity();
@@ -432,10 +471,18 @@ invert_compose(const TransformState *other) const {
 void TransformState::
 output(ostream &out) const {
   out << "T:";
-  if (is_identity()) {
+  if (is_invalid()) {
+    out << "(invalid)";
+
+  } else if (is_identity()) {
     out << "(identity)";
 
   } else if (has_components()) {
+    if (components_given()) {
+      out << "c";
+    } else {
+      out << "m";
+    }
     char lead = '(';
     if (!get_pos().almost_equal(LVecBase3f(0.0f, 0.0f, 0.0f))) {
       out << lead << "pos " << get_pos();
@@ -477,11 +524,11 @@ write(ostream &out, int indent_level) const {
 //  Description: This function is used to share a common TransformState
 //               pointer for all equivalent TransformState objects.
 //
-//               See the similar logic in RenderAttrib.  The idea is
-//               to create a new TransformState object and pass it
+//               See the similar logic in RenderState.  The idea is to
+//               create a new TransformState object and pass it
 //               through this function, which will share the pointer
-//               with a previously-created TransformState object if it is
-//               equivalent.
+//               with a previously-created TransformState object if it
+//               is equivalent.
 ////////////////////////////////////////////////////////////////////
 CPT(TransformState) TransformState::
 return_new(TransformState *state) {
@@ -517,6 +564,12 @@ return_new(TransformState *state) {
 ////////////////////////////////////////////////////////////////////
 CPT(TransformState) TransformState::
 do_compose(const TransformState *other) const {
+  nassertr((_flags & F_is_invalid) == 0, this);
+  nassertr((other->_flags & F_is_invalid) == 0, other);
+
+  // We should do this operation componentwise if both transforms were
+  // given componentwise.
+
   LMatrix4f new_mat = other->get_mat() * get_mat();
   return make_mat(new_mat);
 }
@@ -528,11 +581,20 @@ 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.
+  nassertr((_flags & F_is_invalid) == 0, this);
+  nassertr((other->_flags & F_is_invalid) == 0, other);
+
+  // We should do this operation componentwise if both transforms were
+  // given componentwise.
+
+  // Perhaps we should cache the result of the inverse matrix
+  // operation separately, as a further optimization.
 
   LMatrix4f new_mat;
-  new_mat.invert_from(get_mat());
+  bool invertible = new_mat.invert_from(get_mat());
+  if (!invertible) {
+    return make_invalid();
+  }
   new_mat = other->get_mat() * new_mat;
   return make_mat(new_mat);
 }
@@ -545,6 +607,7 @@ do_invert_compose(const TransformState *other) const {
 ////////////////////////////////////////////////////////////////////
 void TransformState::
 calc_singular() {
+  nassertv((_flags & F_is_invalid) == 0);
   bool singular = false;
 
   if (has_components()) {
@@ -569,6 +632,7 @@ calc_singular() {
 ////////////////////////////////////////////////////////////////////
 void TransformState::
 calc_components() {
+  nassertv((_flags & F_is_invalid) == 0);
   if ((_flags & F_is_identity) != 0) {
     _scale.set(1.0f, 1.0f, 1.0f);
     _hpr.set(0.0f, 0.0f, 0.0f);
@@ -601,6 +665,7 @@ calc_components() {
 ////////////////////////////////////////////////////////////////////
 void TransformState::
 calc_mat() {
+  nassertv((_flags & F_is_invalid) == 0);
   if ((_flags & F_is_identity) != 0) {
     _mat = LMatrix4f::ident_mat();
 
@@ -639,6 +704,11 @@ write_datagram(BamWriter *manager, Datagram &dg) {
     int flags = F_is_identity | F_singular_known;
     dg.add_uint16(flags);
 
+  } else if ((_flags & F_is_invalid) != 0) {
+    // Invalid, nothing much to that either.
+    int flags = F_is_invalid | F_singular_known | F_is_singular | F_components_known | F_mat_known;
+    dg.add_uint16(flags);
+
   } else if ((_flags & F_components_given) != 0) {
     // A component-based transform.
     int flags = F_components_given | F_components_known | F_has_components;

+ 5 - 0
panda/src/pgraph/transformState.h

@@ -66,6 +66,7 @@ public:
 
 PUBLISHED:
   static CPT(TransformState) make_identity();
+  INLINE static CPT(TransformState) make_invalid();
   INLINE static CPT(TransformState) make_pos(const LVecBase3f &pos);
   INLINE static CPT(TransformState) make_hpr(const LVecBase3f &hpr);
   INLINE static CPT(TransformState) make_pos_hpr(const LVecBase3f &pos,
@@ -78,11 +79,14 @@ PUBLISHED:
   static CPT(TransformState) make_mat(const LMatrix4f &mat);
 
   INLINE bool is_identity() const;
+  INLINE bool is_invalid() const;
   INLINE bool is_singular() const;
   INLINE bool has_components() const;
+  INLINE bool components_given() const;
   INLINE bool has_pos() const;
   INLINE bool has_hpr() const;
   INLINE bool has_scale() const;
+  INLINE bool has_mat() const;
   INLINE const LVecBase3f &get_pos() const;
   INLINE const LVecBase3f &get_hpr() const;
   INLINE const LVecBase3f &get_scale() const;
@@ -149,6 +153,7 @@ private:
     F_components_known =  0x0010,
     F_has_components   =  0x0020,
     F_mat_known        =  0x0040,
+    F_is_invalid       =  0x0080,
   };
   LVecBase3f _pos, _hpr, _scale;
   LMatrix4f _mat;

+ 101 - 10
panda/src/testbed/pview.cxx

@@ -35,6 +35,10 @@
 #include "dSearchPath.h"
 #include "loader.h"
 #include "auto_bind.h"
+#include "pStatClient.h"
+#include "notify.h"
+#include "qpnodePath.h"
+#include "cullBinManager.h"
 
 // These are in support of legacy data graph operations.
 #include "namedNode.h"
@@ -66,6 +70,12 @@ bool run_flag = true;
 static double start_time = 0.0;
 static int start_frame_count = 0;
 
+// A priority number high enough to override any model file settings.
+static const int override_priority = 100;
+
+// This is the main scene graph.
+qpNodePath render;
+
 void 
 report_frame_rate() {
   double now = ClockObject::get_global_clock()->get_frame_time();
@@ -95,15 +105,15 @@ make_pipe() {
   // load-display Configrc variable.
   GraphicsPipe::resolve_modules();
 
-  cerr << "Known pipe types:" << endl;
-  GraphicsPipe::get_factory().write_types(cerr, 2);
+  nout << "Known pipe types:" << endl;
+  GraphicsPipe::get_factory().write_types(nout, 2);
 
   PT(GraphicsPipe) pipe;
   pipe = GraphicsPipe::get_factory().
     make_instance(InteractiveGraphicsPipe::get_class_type());
 
   if (pipe == (GraphicsPipe*)0L) {
-    cerr << "No interactive pipe is available!  Check your Configrc!\n";
+    nout << "No interactive pipe is available!  Check your Configrc!\n";
     exit(1);
   }
 
@@ -204,7 +214,7 @@ get_models(PandaNode *parent, int argc, char *argv[]) {
     for (int i = 1; i < argc; i++) {
       Filename filename = argv[i];
       
-      cerr << "Loading " << filename << "\n";
+      nout << "Loading " << filename << "\n";
 
       // First, we always try to resolve a filename from the current
       // directory.  This means a local filename will always be found
@@ -213,10 +223,10 @@ get_models(PandaNode *parent, int argc, char *argv[]) {
 
       PT(PandaNode) node = loader.qpload_sync(filename);
       if (node == (PandaNode *)NULL) {
-        cerr << "Unable to load " << filename << "\n";
+        nout << "Unable to load " << filename << "\n";
 
       } else {
-        node->ls(cerr, 0);
+        node->ls(nout, 0);
         parent->add_child(node);
       }
     }
@@ -259,9 +269,81 @@ event_esc(CPT_Event) {
 
 void
 event_f(CPT_Event) {
+  // 'f' : report frame rate.
   report_frame_rate();
 }
 
+void
+event_t(CPT_Event) {
+  // 't' : toggle texture.
+  static bool texture_off = false;
+
+  texture_off = !texture_off;
+  if (texture_off) {
+    nout << "Disabling texturing.\n";
+    render.set_texture_off(override_priority);
+  } else {
+    nout << "Enabling texturing.\n";
+    render.clear_texture();
+  }
+}
+
+void
+event_w(CPT_Event) {
+  // 'w' : toggle wireframe.
+  static bool wireframe = false;
+
+  wireframe = !wireframe;
+  if (wireframe) {
+    nout << "Setting wireframe mode.\n";
+    render.set_render_mode_wireframe(override_priority);
+  } else {
+    nout << "Clearing wireframe mode.\n";
+    render.clear_render_mode();
+  }
+}
+
+void
+event_s(CPT_Event) {
+  // 's' : toggle state sorting by putting everything into an 'unsorted' bin.
+  static bool sorting_off = false;
+
+  sorting_off = !sorting_off;
+  if (sorting_off) {
+    nout << "Disabling state sorting.\n";
+    render.set_bin("unsorted", 0, override_priority);
+  } else {
+    nout << "Enabling state sorting.\n";
+    render.clear_bin();
+  }
+}
+
+void
+event_S(CPT_Event) {
+  // shift 'S' : active PStats.
+#ifdef DO_PSTATS
+  nout << "Connecting to stats host" << endl;
+  PStatClient::connect();
+#else
+  nout << "Stats host not supported." << endl;
+#endif
+}
+
+void
+event_A(CPT_Event) {
+  // shift 'A' : deactive PStats.
+#ifdef DO_PSTATS
+  if (PStatClient::is_connected()) {
+    nout << "Disconnecting from stats host" << endl;
+    PStatClient::disconnect();
+  } else {
+    nout << "Stats host is already disconnected." << endl;
+  }
+#else
+  nout << "Stats host not supported." << endl;
+#endif
+}
+
 int
 main(int argc, char *argv[]) {
   // First, we need a GraphicsPipe, before we can open a window.
@@ -275,10 +357,14 @@ main(int argc, char *argv[]) {
   PT(qpCamera) camera = make_camera(window);
 
   // Now we just need to make a scene graph for the camera to render.
-  PT(PandaNode) render = new PandaNode("render");
-  render->add_child(camera);
+  render = qpNodePath(new PandaNode("render"));
+  render.attach_new_node(camera);
   camera->set_scene(qpNodePath(render));
 
+  // We will take advantage of this bin if the user toggles state
+  // sorting, above, in event_s().
+  CullBinManager::get_global_ptr()->add_bin("unsorted", CullBinManager::BT_unsorted, 0);
+
   // Set up a data graph for tracking user input.  For now, this uses
   // the old-style graph interface.
   PT(NamedNode) data_root = new NamedNode("data_root");
@@ -290,16 +376,21 @@ main(int argc, char *argv[]) {
   event_handler.add_hook("escape", event_esc);
   event_handler.add_hook("q", event_esc);
   event_handler.add_hook("f", event_f);
+  event_handler.add_hook("t", event_t);
+  event_handler.add_hook("w", event_w);
+  event_handler.add_hook("s", event_s);
+  event_handler.add_hook("shift-s", event_S);
+  event_handler.add_hook("shift-a", event_A);
 
 
   // Put something in the scene graph to look at.
-  get_models(render, argc, argv);
+  get_models(render.node(), argc, argv);
 
   // If we happened to load up both a character file and its matching
   // animation file, attempt to bind them together now and start the
   // animations looping.
   AnimControlCollection anim_controls;
-  auto_bind(render, anim_controls, ~0);
+  auto_bind(render.node(), anim_controls, ~0);
   anim_controls.loop_all(true);