Jelajahi Sumber

pgraph NodePath

David Rose 24 tahun lalu
induk
melakukan
c7d0e2d117
36 mengubah file dengan 4646 tambahan dan 1267 penghapusan
  1. 1 1
      panda/src/display/displayRegion.I
  2. 3 3
      panda/src/display/displayRegion.cxx
  3. 4 4
      panda/src/display/displayRegion.h
  4. 3 3
      panda/src/display/graphicsEngine.cxx
  5. 1 1
      panda/src/display/graphicsEngine.h
  6. 6 6
      panda/src/egg2pg/qpeggLoader.cxx
  7. 9 6
      panda/src/pgraph/Sources.pp
  8. 5 5
      panda/src/pgraph/billboardAttrib.I
  9. 2 2
      panda/src/pgraph/billboardAttrib.cxx
  10. 4 4
      panda/src/pgraph/billboardAttrib.h
  11. 4 4
      panda/src/pgraph/config_pgraph.cxx
  12. 1 1
      panda/src/pgraph/materialAttrib.h
  13. 0 348
      panda/src/pgraph/nodeChain.I
  14. 0 514
      panda/src/pgraph/nodeChain.cxx
  15. 0 176
      panda/src/pgraph/nodeChain.h
  16. 14 11
      panda/src/pgraph/pandaNode.I
  17. 49 49
      panda/src/pgraph/pandaNode.cxx
  18. 18 18
      panda/src/pgraph/pandaNode.h
  19. 3 2
      panda/src/pgraph/pgraph_composite2.cxx
  20. 2 2
      panda/src/pgraph/qpcamera.I
  21. 4 4
      panda/src/pgraph/qpcamera.h
  22. 1 0
      panda/src/pgraph/qpcullTraverser.h
  23. 1086 0
      panda/src/pgraph/qpnodePath.I
  24. 2284 0
      panda/src/pgraph/qpnodePath.cxx
  25. 427 0
      panda/src/pgraph/qpnodePath.h
  26. 38 0
      panda/src/pgraph/qpnodePathCollection.I
  27. 397 0
      panda/src/pgraph/qpnodePathCollection.cxx
  28. 83 0
      panda/src/pgraph/qpnodePathCollection.h
  29. 46 46
      panda/src/pgraph/qpnodePathComponent.I
  30. 16 16
      panda/src/pgraph/qpnodePathComponent.cxx
  31. 23 23
      panda/src/pgraph/qpnodePathComponent.h
  32. 9 9
      panda/src/pgraph/test_pgraph.cxx
  33. 40 5
      panda/src/pgraph/transformState.I
  34. 53 1
      panda/src/pgraph/transformState.cxx
  35. 7 0
      panda/src/pgraph/transformState.h
  36. 3 3
      panda/src/testbed/pview.cxx

+ 1 - 1
panda/src/display/displayRegion.I

@@ -108,7 +108,7 @@ get_camera() const {
 //  Description: Returns the camera associated with this
 //               DisplayRegion, or NULL if no camera is associated.
 ////////////////////////////////////////////////////////////////////
-INLINE const NodeChain &DisplayRegion::
+INLINE const qpNodePath &DisplayRegion::
 get_qpcamera() const {
   return _qpcamera;
 }

+ 3 - 3
panda/src/display/displayRegion.cxx

@@ -98,7 +98,7 @@ operator = (const DisplayRegion&) {
 ////////////////////////////////////////////////////////////////////
 DisplayRegion::
 ~DisplayRegion() {
-  set_qpcamera(NodeChain());
+  set_qpcamera(qpNodePath());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -193,12 +193,12 @@ set_camera(Camera *camera) {
 //               was already associated with a different
 //               DisplayRegion, that association is removed.
 //
-//               The camera is actually set via a NodeChain, which
+//               The camera is actually set via a qpNodePath, which
 //               clarifies which instance of the camera (if there
 //               happen to be multiple instances) we should use.
 ////////////////////////////////////////////////////////////////////
 void DisplayRegion::
-set_qpcamera(const NodeChain &camera) {
+set_qpcamera(const qpNodePath &camera) {
   qpCamera *camera_node = (qpCamera *)NULL;
   if (!camera.is_empty()) {
     DCAST_INTO_V(camera_node, camera.node());

+ 4 - 4
panda/src/display/displayRegion.h

@@ -22,7 +22,7 @@
 
 #include "referenceCount.h"
 #include "camera.h"
-#include "nodeChain.h"
+#include "qpnodePath.h"
 #include "cullResult.h"
 #include "pointerTo.h"
 
@@ -69,8 +69,8 @@ PUBLISHED:
   void set_camera(Camera *camera);
   INLINE Camera *get_camera() const;
 
-  void set_qpcamera(const NodeChain &camera);
-  INLINE const NodeChain &get_qpcamera() const;
+  void set_qpcamera(const qpNodePath &camera);
+  INLINE const qpNodePath &get_qpcamera() const;
 
   INLINE void set_cull_frustum(LensNode *cull_frustum);
   INLINE LensNode *get_cull_frustum() const;
@@ -104,7 +104,7 @@ protected:
 
   GraphicsLayer *_layer;
   PT(Camera) _camera;
-  NodeChain _qpcamera;
+  qpNodePath _qpcamera;
   qpCamera *_camera_node;
   PT(LensNode) _cull_frustum;
 

+ 3 - 3
panda/src/display/graphicsEngine.cxx

@@ -197,7 +197,7 @@ cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr) {
 //  Description: Fires off a cull traversal using the indicated camera.
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
-do_cull(CullHandler *cull_handler, const NodeChain &camera,
+do_cull(CullHandler *cull_handler, const qpNodePath &camera,
         GraphicsStateGuardian *gsg) {
   if (camera.is_empty()) {
     // No camera, no draw.
@@ -218,7 +218,7 @@ do_cull(CullHandler *cull_handler, const NodeChain &camera,
     return;
   }
 
-  NodeChain scene = camera_node->get_scene();
+  qpNodePath scene = camera_node->get_scene();
   if (scene.is_empty()) {
     // No scene, no draw.
     return;
@@ -298,7 +298,7 @@ do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg,
 ////////////////////////////////////////////////////////////////////
 bool GraphicsEngine::
 set_gsg_lens(GraphicsStateGuardian *gsg, DisplayRegion *dr) {
-  const NodeChain &camera = dr->get_qpcamera();
+  const qpNodePath &camera = dr->get_qpcamera();
   if (camera.is_empty()) {
     // No camera, no draw.
     return false;

+ 1 - 1
panda/src/display/graphicsEngine.h

@@ -57,7 +57,7 @@ private:
   void cull_bin_draw();
   void cull_bin_draw(GraphicsWindow *win, DisplayRegion *dr);
 
-  void do_cull(CullHandler *cull_handler, const NodeChain &camera,
+  void do_cull(CullHandler *cull_handler, const qpNodePath &camera,
                GraphicsStateGuardian *gsg);
   void do_draw(CullResult *cull_result, GraphicsStateGuardian *gsg,
                DisplayRegion *dr);

+ 6 - 6
panda/src/egg2pg/qpeggLoader.cxx

@@ -20,7 +20,7 @@
 
 #include "qpeggLoader.h"
 #include "config_egg2pg.h"
-#include "nodeChain.h"
+#include "qpnodePath.h"
 #include "renderState.h"
 #include "transformState.h"
 #include "textureAttrib.h"
@@ -154,14 +154,14 @@ reparent_decals() {
     PandaNode *node = (*di);
     nassertv(node != (PandaNode *)NULL);
 
-    // The NodeChain interface is best for this.
-    NodeChain parent(node);
+    // The qpNodePath interface is best for this.
+    qpNodePath parent(node);
 
     // First, search for the GeomNode.
-    NodeChain geom_parent;
+    qpNodePath geom_parent;
     int num_children = parent.get_num_children();
     for (int i = 0; i < num_children; i++) {
-      NodeChain child = parent.get_child(i);
+      qpNodePath child = parent.get_child(i);
 
       if (child.node()->is_of_type(qpGeomNode::get_class_type())) {
         if (!geom_parent.is_empty()) {
@@ -187,7 +187,7 @@ reparent_decals() {
       // list.
       int i = 0;
       while (i < num_children) {
-        NodeChain child = parent.get_child(i);
+        qpNodePath child = parent.get_child(i);
 
         if (child.node()->is_of_type(qpGeomNode::get_class_type())) {
           i++;

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

@@ -31,8 +31,9 @@
     qplensNode.h qplensNode.I \
     qplodNode.h qplodNode.I \
     materialAttrib.h materialAttrib.I \
-    nodeChain.h nodeChain.I \
-    nodeChainComponent.h nodeChainComponent.I \
+    qpnodePath.h qpnodePath.I \
+    qpnodePathCollection.h qpnodePathCollection.I \
+    qpnodePathComponent.h qpnodePathComponent.I \
     pandaNode.h pandaNode.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \
@@ -70,8 +71,9 @@
     qplensNode.cxx \
     qplodNode.cxx \
     materialAttrib.cxx \
-    nodeChain.cxx \
-    nodeChainComponent.cxx \
+    qpnodePath.cxx \
+    qpnodePathCollection.cxx \
+    qpnodePathComponent.cxx \
     pandaNode.cxx \
     renderAttrib.cxx \
     renderState.cxx \
@@ -114,8 +116,9 @@
     qplensNode.h qplensNode.I \
     qplodNode.h qplodNode.I \
     materialAttrib.h materialAttrib.I \
-    nodeChain.h nodeChain.I \
-    nodeChainComponent.h nodeChainComponent.I \
+    qpnodePath.h qpnodePath.I \
+    qpnodePathCollection.h qpnodePathCollection.I \
+    qpnodePathComponent.h qpnodePathComponent.I \
     pandaNode.h pandaNode.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \

+ 5 - 5
panda/src/pgraph/billboardAttrib.I

@@ -37,7 +37,7 @@ BillboardAttrib() {
 INLINE CPT(RenderAttrib) BillboardAttrib::
 make_axis() {
   return make(LVector3f::up(), false, true, 
-              0.0f, NodeChain(), LPoint3f(0.0f, 0.0f, 0.0f));
+              0.0f, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -49,7 +49,7 @@ make_axis() {
 INLINE CPT(RenderAttrib) BillboardAttrib::
 make_point_eye() {
   return make(LVector3f::up(), true, false,
-              0.0f, NodeChain(), LPoint3f(0.0f, 0.0f, 0.0f));
+              0.0f, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -61,7 +61,7 @@ make_point_eye() {
 INLINE CPT(RenderAttrib) BillboardAttrib::
 make_point_world() {
   return make(LVector3f::up(), false, false,
-              0.0f, NodeChain(), LPoint3f(0.0f, 0.0f, 0.0f));
+              0.0f, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -73,7 +73,7 @@ make_point_world() {
 //               particularly useful and isn't normally created or
 //               stored in the graph; it might be implicitly
 //               discovered as the result of a
-//               NodeChain::get_rel_state().
+//               qpNodePath::get_rel_state().
 ////////////////////////////////////////////////////////////////////
 INLINE bool BillboardAttrib::
 is_off() const {
@@ -135,7 +135,7 @@ get_offset() const {
 //               will rotate towards the current camera node, wherever
 //               that might be.
 ////////////////////////////////////////////////////////////////////
-INLINE const NodeChain &BillboardAttrib::
+INLINE const qpNodePath &BillboardAttrib::
 get_look_at() const {
   return _look_at;
 }

+ 2 - 2
panda/src/pgraph/billboardAttrib.cxx

@@ -33,7 +33,7 @@ TypeHandle BillboardAttrib::_type_handle;
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) BillboardAttrib::
 make(const LVector3f &up_vector, bool eye_relative,
-     bool axial_rotate, float offset, const NodeChain &look_at,
+     bool axial_rotate, float offset, const qpNodePath &look_at,
      const LPoint3f &look_at_point) {
   BillboardAttrib *attrib = new BillboardAttrib;
   attrib->_up_vector = up_vector;
@@ -228,7 +228,7 @@ write_datagram(BamWriter *manager, Datagram &dg) {
   dg.add_float32(_offset);
   _look_at_point.write_datagram(dg);
 
-  // *** We don't write out the _look_at NodeChain right now.  Maybe
+  // *** We don't write out the _look_at qpNodePath right now.  Maybe
   // we should.
 }
 

+ 4 - 4
panda/src/pgraph/billboardAttrib.h

@@ -23,7 +23,7 @@
 
 #include "renderAttrib.h"
 #include "luse.h"
-#include "nodeChain.h"
+#include "qpnodePath.h"
 
 ////////////////////////////////////////////////////////////////////
 //       Class : BillboardAttrib
@@ -40,7 +40,7 @@ PUBLISHED:
                                 bool eye_relative,
                                 bool axial_rotate,
                                 float offset,
-                                const NodeChain &look_at,
+                                const qpNodePath &look_at,
                                 const LPoint3f &look_at_point);
   INLINE static CPT(RenderAttrib) make_axis();
   INLINE static CPT(RenderAttrib) make_point_eye();
@@ -51,7 +51,7 @@ PUBLISHED:
   INLINE bool get_eye_relative() const;
   INLINE bool get_axial_rotate() const;
   INLINE float get_offset() const;
-  INLINE const NodeChain &get_look_at() const;
+  INLINE const qpNodePath &get_look_at() const;
   INLINE const LPoint3f &get_look_at_point() const;
 
 public:
@@ -70,7 +70,7 @@ private:
   bool _eye_relative;
   bool _axial_rotate;
   float _offset;
-  NodeChain _look_at;
+  qpNodePath _look_at;
   LPoint3f _look_at_point;
 
 public:

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

@@ -35,8 +35,8 @@
 #include "qplensNode.h"
 #include "qplodNode.h"
 #include "materialAttrib.h"
-#include "nodeChain.h"
-#include "nodeChainComponent.h"
+#include "qpnodePath.h"
+#include "qpnodePathComponent.h"
 #include "pandaNode.h"
 #include "renderAttrib.h"
 #include "renderState.h"
@@ -95,8 +95,8 @@ init_libpgraph() {
   qpLensNode::init_type();
   qpLODNode::init_type();
   MaterialAttrib::init_type();
-  NodeChain::init_type();
-  NodeChainComponent::init_type();
+  qpNodePath::init_type();
+  qpNodePathComponent::init_type();
   PandaNode::init_type();
   RenderAttrib::init_type();
   RenderState::init_type();

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

@@ -36,7 +36,7 @@ private:
   INLINE MaterialAttrib();
 
 PUBLISHED:
-  static CPT(RenderAttrib) make(const Material *tex);
+  static CPT(RenderAttrib) make(const Material *material);
   static CPT(RenderAttrib) make_off();
 
   INLINE bool is_off() const;

+ 0 - 348
panda/src/pgraph/nodeChain.I

@@ -1,348 +0,0 @@
-// Filename: nodeChain.I
-// Created by:  drose (25Feb02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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: NodeChain::Default Constructor
-//       Access: Published
-//  Description: This constructs an empty NodeChain with no nodes.  It
-//               cannot be extended, since you cannot add nodes without
-//               first specifying the top node.  Use the constructor
-//               that receives a node if you ever want to do anything
-//               with this chain.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain::
-NodeChain() :
-  _error_type(ET_ok)
-{
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::Constructor
-//       Access: Published
-//  Description: This constructs an empty NodeChain with a single node.
-//               This chain may now be extended by repeatedly calling
-//               push_back() with each node below that node in
-//               sequence.
-//
-//               If the Node pointer is NULL, this quietly creates an
-//               empty NodeChain.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain::
-NodeChain(PandaNode *top_node) :
-  _error_type(ET_ok)
-{
-  if (top_node != (PandaNode *)NULL) {
-    _head = top_node->get_generic_component();
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::Copy Constructor
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain::
-NodeChain(const NodeChain &copy) :
-  _head(copy._head),
-  _error_type(copy._error_type)
-{
-  uncollapse_head();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::Copy Assignment Operator
-//       Access: Published
-//  Description:
-////////////////////////////////////////////////////////////////////
-INLINE void NodeChain::
-operator = (const NodeChain &copy) {
-  _head = copy._head;
-  _error_type = copy._error_type;
-  uncollapse_head();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::not_found named constructor
-//       Access: Published, Static
-//  Description: Creates a NodeChain with the ET_not_found error type
-//               set.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-not_found() {
-  NodeChain result;
-  result._error_type = ET_not_found;
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::removed named constructor
-//       Access: Published, Static
-//  Description: Creates a NodeChain with the ET_removed error type
-//               set.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-removed() {
-  NodeChain result;
-  result._error_type = ET_removed;
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::fail named constructor
-//       Access: Published, Static
-//  Description: Creates a NodeChain with the ET_fail error type
-//               set.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-fail() {
-  NodeChain result;
-  result._error_type = ET_fail;
-  return result;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::is_empty
-//       Access: Published
-//  Description: Returns true if the NodeChain contains no nodes.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-is_empty() const {
-  return (_head == (NodeChainComponent *)NULL);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::is_singleton
-//       Access: Published
-//  Description: Returns true if the NodeChain contains exactly one
-//               node.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-is_singleton() const {
-  uncollapse_head();
-  return (_head != (NodeChainComponent *)NULL && _head->is_top_node());
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_error_type
-//       Access: Published
-//  Description: If is_empty() is true, this returns a code that
-//               represents the reason why the NodeChain is empty.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain::ErrorType NodeChain::
-get_error_type() const {
-  return _error_type;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::node
-//       Access: Published
-//  Description: Returns the bottom node of the path, or NULL if the
-//               path is empty.
-////////////////////////////////////////////////////////////////////
-INLINE PandaNode *NodeChain::
-node() const {
-  if (is_empty()) {
-    return (PandaNode *)NULL;
-  }
-  return _head->get_node();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_num_children
-//       Access: Published
-//  Description: Returns the number of children of the bottom node.
-////////////////////////////////////////////////////////////////////
-INLINE int NodeChain::
-get_num_children() const {
-  nassertr(!is_empty(), 0);
-  return _head->get_node()->get_num_children();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_child
-//       Access: Published
-//  Description: Returns a NodeChain representing the nth child of the
-//               bottom node.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-get_child(int n) const {
-  nassertr(n >= 0 && n < get_num_children(), NodeChain());
-  NodeChain child;
-  child._head = PandaNode::get_component(_head, _head->get_node()->get_child(n));
-  return child;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::has_parent
-//       Access: Published
-//  Description: Returns true if the node at the bottom of the
-//               NodeChain has a parent; i.e. the NodeChain contains at
-//               least two nodes.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-has_parent() const {
-  return !is_empty() && !is_singleton();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_parent
-//       Access: Published
-//  Description: Returns the NodeChain to the parent of the bottom
-//               node: that is, this NodeChain, shortened by one node.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-get_parent() const {
-  nassertr(has_parent(), NodeChain::fail());
-  NodeChain parent;
-  parent._head = _head->get_next();
-  return parent;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::attach_new_node
-//       Access: Published
-//  Description: Creates an ordinary PandaNode and attaches it below
-//               the current NodeChain, returning a new NodeChain that
-//               references it.
-////////////////////////////////////////////////////////////////////
-INLINE NodeChain NodeChain::
-attach_new_node(const string &name, int sort) const {
-  nassertr(verify_complete(), NodeChain::fail());
-  nassertr(!is_empty(), *this);
-
-  return attach_new_node(new PandaNode(name), sort);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_net_state
-//       Access: Published
-//  Description: Returns the net state on this node from the root.
-////////////////////////////////////////////////////////////////////
-INLINE CPT(RenderState) NodeChain::
-get_net_state() const {
-  uncollapse_head();
-  return r_get_net_state(_head);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_net_transform
-//       Access: Published
-//  Description: Returns the net transform on this node from the root.
-////////////////////////////////////////////////////////////////////
-INLINE CPT(TransformState) NodeChain::
-get_net_transform() const {
-  uncollapse_head();
-  return r_get_net_transform(_head);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_mat
-//       Access: Published
-//  Description: Returns the matrix that describes the coordinate
-//               space of this node, relative to the other
-//               node's coordinate space.  If either NodeChain is
-//               empty, it is taken to indicate the top of the graph.
-////////////////////////////////////////////////////////////////////
-INLINE const LMatrix4f &NodeChain::
-get_mat(const NodeChain &other) const {
-  CPT(TransformState) transform = get_rel_transform(other);
-  // We can safely assume the transform won't go away when the
-  // function returns, since its reference count is also held in the
-  // cache.  This assumption allows us to return a reference to the
-  // matrix, instead of having to return a matrix on the stack.
-  nassertr(transform->get_ref_count() > 1, LMatrix4f::ident_mat());
-  return transform->get_mat();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::operator ==
-//       Access: Published
-//  Description: Returns true if the two chains are equivalent; that
-//               is, if they contain the same list of nodes in the same
-//               order.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-operator == (const NodeChain &other) const {
-  return (compare_to(other) == 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::operator !=
-//       Access: Published
-//  Description: Returns true if the two chains are not equivalent.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-operator != (const NodeChain &other) const {
-  return (compare_to(other) != 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::operator <
-//       Access: Published
-//  Description: Returns true if this NodeChain sorts before the other
-//               one, false otherwise.  The sorting order of two
-//               nonequivalent NodeChains is consistent but undefined,
-//               and is useful only for storing NodeChains in a sorted
-//               container like an STL set.
-////////////////////////////////////////////////////////////////////
-INLINE bool NodeChain::
-operator < (const NodeChain &other) const {
-  return (compare_to(other) < 0);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::compare_to
-//       Access: Published
-//  Description: Returns a number less than zero if this NodeChain
-//               sorts before the other one, greater than zero if it
-//               sorts after, or zero if they are equivalent.
-//
-//               Two NodeChains are considered equivalent if they
-//               consist of exactly the same list of nodes in the same
-//               order.  Otherwise, they are different; different
-//               NodeChains will be ranked in a consistent but
-//               undefined ordering; the ordering is useful only for
-//               placing the NodeChains in a sorted container like an
-//               STL set.
-////////////////////////////////////////////////////////////////////
-INLINE int NodeChain::
-compare_to(const NodeChain &other) const {
-  return r_compare_to(_head, other._head);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::output
-//       Access: Published
-//  Description: Writes a sensible description of the NodeChain to the
-//               indicated output stream.
-////////////////////////////////////////////////////////////////////
-INLINE void NodeChain::
-output(ostream &out) const {
-  if (_head == (NodeChainComponent *)NULL) {
-    out << "(empty)";
-  } else {
-    r_output(out, _head);
-  }
-}
-
-INLINE ostream &operator << (ostream &out, const NodeChain &node_chain) {
-  node_chain.output(out);
-  return out;
-}
-

+ 0 - 514
panda/src/pgraph/nodeChain.cxx

@@ -1,514 +0,0 @@
-// Filename: nodeChain.cxx
-// Created by:  drose (25Feb02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "nodeChain.h"
-#include "node.h"
-#include "namedNode.h"
-#include "config_pgraph.h"
-#include "plist.h"
-
-TypeHandle NodeChain::_type_handle;
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_num_nodes
-//       Access: Published
-//  Description: Returns the number of nodes in the path.
-////////////////////////////////////////////////////////////////////
-int NodeChain::
-get_num_nodes() const {
-  if (is_empty()) {
-    return 0;
-  }
-  uncollapse_head();
-  return _head->get_length();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_node
-//       Access: Published
-//  Description: Returns the nth node of the path, where 0 is the
-//               bottom node and get_num_nodes() - 1 is the top node.
-//               This requires iterating through the path.
-////////////////////////////////////////////////////////////////////
-PandaNode *NodeChain::
-get_node(int index) const {
-  nassertr(index >= 0 && index < get_num_nodes(), NULL);
-
-  uncollapse_head();
-  NodeChainComponent *comp = _head;
-  while (index > 0) {
-    // If this assertion fails, the index was out of range; the
-    // component's length must have been invalid.
-    nassertr(comp != (NodeChainComponent *)NULL, NULL);
-    comp = comp->get_next();
-    index--;
-  }
-
-  // If this assertion fails, the index was out of range; the
-  // component's length must have been invalid.
-  nassertr(comp != (NodeChainComponent *)NULL, NULL);
-  return comp->get_node();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_top_node
-//       Access: Published
-//  Description: Returns the top node of the path, or NULL if the path
-//               is empty.  This requires iterating through the path.
-////////////////////////////////////////////////////////////////////
-PandaNode *NodeChain::
-get_top_node() const {
-  if (is_empty()) {
-    return (PandaNode *)NULL;
-  }
-
-  uncollapse_head();
-  NodeChainComponent *comp = _head;
-  while (!comp->is_top_node()) {
-    comp = comp->get_next();
-    nassertr(comp != (NodeChainComponent *)NULL, NULL);
-  }
-
-  return comp->get_node();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::reparent_to
-//       Access: Published
-//  Description: Removes the bottom node of the NodeChain from its
-//               current parent and attaches it to the bottom node of
-//               the indicated NodeChain.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-reparent_to(const NodeChain &other, int sort) {
-  nassertv(other.verify_complete());
-  nassertv_always(!is_empty());
-  nassertv_always(!other.is_empty());
-
-  uncollapse_head();
-  other.uncollapse_head();
-  PandaNode::reparent(other._head, _head, sort);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::wrt_reparent_to
-//       Access: Published
-//  Description: This functions identically to reparent_to(), except
-//               the transform on this node is also adjusted so that
-//               the node remains in the same place in world
-//               coordinates, even if it is reparented into a
-//               different coordinate system.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-wrt_reparent_to(const NodeChain &other, int sort) {
-  nassertv(other.verify_complete());
-  nassertv_always(!is_empty());
-  nassertv_always(!other.is_empty());
-
-  //****
-  /*
-  LMatrix4f mat = get_mat(other);
-  set_mat(mat);
-  */
-
-  reparent_to(other, sort);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::instance_to
-//       Access: Published
-//  Description: Adds the bottom node of the NodeChain as a child of
-//               the bottom node of the indicated other NodeChain.  Any
-//               other parent-child relations of the node are
-//               unchanged; in particular, the node is not removed
-//               from its existing parent, if any.
-//
-//               If the node already had an existing parent, this
-//               method will create a new instance of the node within
-//               the scene graph.
-//
-//               This does not change the NodeChain itself, but does
-//               return a new NodeChain that reflects the new instance
-//               node.
-////////////////////////////////////////////////////////////////////
-NodeChain NodeChain::
-instance_to(const NodeChain &other, int sort) const {
-  nassertr(verify_complete(), NodeChain::fail());
-  nassertr(!is_empty(), NodeChain::fail());
-  nassertr(!other.is_empty(), NodeChain::fail());
-
-  uncollapse_head();
-  other.uncollapse_head();
-
-  NodeChain new_instance;
-  new_instance._head = PandaNode::attach(other._head, node(), sort);
-
-  return new_instance;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::copy_to
-//       Access: Published
-//  Description: Functions exactly like instance_to(), except a deep
-//               copy is made of the bottom node and all of its
-//               descendents, which is then parented to the indicated
-//               node.  A NodeChain to the newly created copy is
-//               returned.
-//
-//               Certain kinds of nodes may not be copied; if one of
-//               these is encountered, the node will be "copied" as
-//               the nearest copyable base class.  For instance, a
-//               Camera node in the graph will become a simple
-//               NamedNode.
-////////////////////////////////////////////////////////////////////
-NodeChain NodeChain::
-copy_to(const NodeChain &other, int sort) const {
-  //*****
-  return instance_to(other, sort);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::attach_new_node
-//       Access: Published
-//  Description: Attaches a new node, with or without existing
-//               parents, to the scene graph below the bottom node of
-//               this NodeChain.  This is the preferred way to add
-//               nodes to the graph.
-//
-//               This does *not* automatically extend the current
-//               NodeChain to reflect the attachment; however, a
-//               NodeChain that does reflect this extension is
-//               returned.
-////////////////////////////////////////////////////////////////////
-NodeChain NodeChain::
-attach_new_node(PandaNode *node, int sort) const {
-  nassertr(verify_complete(), NodeChain::fail());
-  nassertr(!is_empty(), NodeChain());
-  nassertr(node != (PandaNode *)NULL, NodeChain());
-
-  uncollapse_head();
-  NodeChain new_path(*this);
-  new_path._head = PandaNode::attach(_head, node, sort);
-  return new_path;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::remove_node
-//       Access: Published
-//  Description: Disconnects the bottom node from the scene graph.
-//               This will also delete the node if there are no other
-//               pointers to it.
-//
-//               Normally, this should be called only when you are
-//               really done with the node.  If you want to remove a
-//               node from the scene graph but keep it around for
-//               later, you should probably use reparent_to() and put
-//               it under a holding node instead.
-//
-//               After the node is removed, the NodeChain will have
-//               been cleared.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-remove_node() {
-  nassertv(_error_type != ET_not_found);
-  if (is_empty()) {
-    // If we have no arcs (maybe we were already removed), quietly do
-    // nothing except to ensure the NodeChain is clear.
-    (*this) = NodeChain::removed();
-    return;
-  }
-
-  uncollapse_head();
-  PandaNode::detach(_head);
-
-  (*this) = NodeChain::removed();
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_rel_state
-//       Access: Published
-//  Description: Returns the state changes that must be made to
-//               transition from the render state of this node to the
-//               render state of the other node.
-////////////////////////////////////////////////////////////////////
-CPT(RenderState) NodeChain::
-get_rel_state(const NodeChain &other) const {
-  if (is_empty()) {
-    return other.get_net_state();
-  }
-  if (other.is_empty()) {
-    return get_net_state()->invert_compose(RenderState::make_empty());
-  }
-    
-  nassertr(verify_complete(), RenderState::make_empty());
-  nassertr(other.verify_complete(), RenderState::make_empty());
-
-  int a_count, b_count;
-  find_common_ancestor(*this, other, a_count, b_count);
-
-  CPT(RenderState) a_state = r_get_partial_state(_head, a_count);
-  CPT(RenderState) b_state = r_get_partial_state(other._head, b_count);
-  return a_state->invert_compose(b_state);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::get_rel_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.
-////////////////////////////////////////////////////////////////////
-CPT(TransformState) NodeChain::
-get_rel_transform(const NodeChain &other) const {
-  if (is_empty()) {
-    return other.get_net_transform();
-  }
-  if (other.is_empty()) {
-    return get_net_transform()->invert_compose(TransformState::make_identity());
-  }
-    
-  nassertr(verify_complete(), TransformState::make_identity());
-  nassertr(other.verify_complete(), TransformState::make_identity());
-
-  int a_count, b_count;
-  find_common_ancestor(*this, other, a_count, b_count);
-
-  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);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::verify_complete
-//       Access: Published
-//  Description: Returns true if all of the nodes described in the
-//               NodeChain are connected *and* the top node is the top
-//               of the graph, or false otherwise.
-////////////////////////////////////////////////////////////////////
-bool NodeChain::
-verify_complete() const {
-  if (is_empty()) {
-    return true;
-  }
-
-  uncollapse_head();
-  const NodeChainComponent *comp = _head;
-  nassertr(comp != (const NodeChainComponent *)NULL, false);
-
-  PandaNode *node = comp->get_node();
-  nassertr(node != (const PandaNode *)NULL, false);
-  int length = comp->get_length();
-
-  comp = comp->get_next();
-  length--;
-  while (comp != (const NodeChainComponent *)NULL) {
-    PandaNode *next_node = comp->get_node();
-    nassertr(next_node != (const PandaNode *)NULL, false);
-
-    if (next_node->find_child(node) < 0) {
-      return false;
-    }
-
-    if (comp->get_length() != length) {
-      return false;
-    }
-
-    node = next_node;
-    comp = comp->get_next();
-    length--;
-  }
-
-  // Now that we've reached the top, we should have no parents.
-  return length == 0 && node->get_num_parents() == 0;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::uncollapse_head
-//       Access: Private
-//  Description: Quietly and transparently uncollapses the _head
-//               pointer if it needs it.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-uncollapse_head() const {
-  if (_head != (NodeChainComponent *)NULL && _head->is_collapsed()) {
-    ((NodeChain *)this)->_head = _head->uncollapse();
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::find_common_ancestor
-//       Access: Private, Static
-//  Description: Walks up from both NodeChains to find the first node
-//               that both have in common, if any.  Fills a_count and
-//               b_count with the number of nodes below the common
-//               node in each chain.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-find_common_ancestor(const NodeChain &a, const NodeChain &b,
-                     int &a_count, int &b_count) {
-  nassertv(!a.is_empty() && !b.is_empty());
-  a.uncollapse_head();
-  b.uncollapse_head();
-
-  NodeChainComponent *ac = a._head;
-  NodeChainComponent *bc = b._head;
-  a_count = 0;
-  b_count = 0;
-
-  // Shorten up the longer one until they are the same length.
-  while (ac->get_length() > bc->get_length()) {
-    nassertv(ac != (NodeChainComponent *)NULL);
-    ac = ac->get_next();
-    a_count++;
-  }
-  while (bc->get_length() > ac->get_length()) {
-    nassertv(bc != (NodeChainComponent *)NULL);
-    bc = bc->get_next();
-    b_count++;
-  }
-
-  // Now shorten them both up until we reach the same component.
-  while (ac != bc) {
-    // These shouldn't go to NULL unless they both go there together. 
-    nassertv(ac != (NodeChainComponent *)NULL);
-    nassertv(bc != (NodeChainComponent *)NULL);
-    ac = ac->get_next();
-    a_count++;
-    bc = bc->get_next();
-    b_count++;
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_get_net_state
-//       Access: Private
-//  Description: Recursively determines the net state chnages to the
-//               indicated component node from the root of the graph.
-////////////////////////////////////////////////////////////////////
-CPT(RenderState) NodeChain::
-r_get_net_state(NodeChainComponent *comp) const {
-  if (comp == (NodeChainComponent *)NULL) {
-    return RenderState::make_empty();
-  } else {
-    CPT(RenderState) state = comp->get_node()->get_state();
-    return r_get_net_state(comp->get_next())->compose(state);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_get_partial_state
-//       Access: Private
-//  Description: Recursively determines the net state changes to the
-//               indicated component node from the nth node above it.
-////////////////////////////////////////////////////////////////////
-CPT(RenderState) NodeChain::
-r_get_partial_state(NodeChainComponent *comp, int n) const {
-  nassertr(comp != (NodeChainComponent *)NULL, RenderState::make_empty());
-  if (n == 0) {
-    return RenderState::make_empty();
-  } else {
-    CPT(RenderState) state = comp->get_node()->get_state();
-    return r_get_partial_state(comp->get_next(), n - 1)->compose(state);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_get_net_transform
-//       Access: Private
-//  Description: Recursively determines the net transform to the
-//               indicated component node from the root of the graph.
-////////////////////////////////////////////////////////////////////
-CPT(TransformState) NodeChain::
-r_get_net_transform(NodeChainComponent *comp) const {
-  if (comp == (NodeChainComponent *)NULL) {
-    return TransformState::make_identity();
-  } else {
-    CPT(TransformState) transform = comp->get_node()->get_transform();
-    return r_get_net_transform(comp->get_next())->compose(transform);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_get_partial_transform
-//       Access: Private
-//  Description: Recursively determines the net transform to the
-//               indicated component node from the nth node above it.
-////////////////////////////////////////////////////////////////////
-CPT(TransformState) NodeChain::
-r_get_partial_transform(NodeChainComponent *comp, int n) const {
-  nassertr(comp != (NodeChainComponent *)NULL, TransformState::make_identity());
-  if (n == 0) {
-    return TransformState::make_identity();
-  } else {
-    CPT(TransformState) transform = comp->get_node()->get_transform();
-    return r_get_partial_transform(comp->get_next(), n - 1)->compose(transform);
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_output
-//       Access: Private
-//  Description: The recursive implementation of output(), this writes
-//               the names of each node component in order from
-//               beginning to end, by first walking to the end of the
-//               linked list and then outputting from there.
-////////////////////////////////////////////////////////////////////
-void NodeChain::
-r_output(ostream &out, NodeChainComponent *comp) const {
-  NodeChainComponent *next = comp->get_next();
-  if (next != (NodeChainComponent *)NULL) {
-    // This is not the head of the list; keep going up.
-    r_output(out, next);
-    out << "/";
-  }
-
-  // Now output this component.
-  PandaNode *node = comp->get_node();
-  if (node->has_name()) {
-    out << node->get_name();
-  } else {
-    out << "+" << node->get_type();
-  }
-  out << "[" << comp->get_length() << "]";
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: NodeChain::r_compare_to
-//       Access: Private, Static
-//  Description: The recursive implementation of compare_to().  Returns
-//               < 0 if a sorts before b, > 0 if b sorts before a, or
-//               == 0 if they are equivalent.
-////////////////////////////////////////////////////////////////////
-int NodeChain::
-r_compare_to(const NodeChainComponent *a, const NodeChainComponent *b) {
-  if (a == b) {
-    return 0;
-
-  } else if (a == (const NodeChainComponent *)NULL) {
-    return -1;
-
-  } else if (b == (const NodeChainComponent *)NULL) {
-    return 1;
-
-  } else if (a->get_node() != b->get_node()) {
-    return a->get_node() - b->get_node();
-
-  } else {
-    return r_compare_to(a->get_next(), b->get_next());
-  }
-}

+ 0 - 176
panda/src/pgraph/nodeChain.h

@@ -1,176 +0,0 @@
-// Filename: nodeChain.h
-// Created by:  drose (25Feb02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 NODECHAIN_H
-#define NODECHAIN_H
-
-#include "pandabase.h"
-
-#include "pandaNode.h"
-#include "renderState.h"
-#include "transformState.h"
-#include "nodeChainComponent.h"
-
-#include "pointerTo.h"
-#include "referenceCount.h"
-#include "notify.h"
-#include "typedObject.h"
-
-////////////////////////////////////////////////////////////////////
-//       Class : NodeChain
-// Description : A NodeChain is the fundamental system for
-//               disambiguating instances and manipulating the scene
-//               graph.  NodeChain is the base class of NodePath,
-//               which adds a bit more high-level functionality, but
-//               the fundamental scene graph manipulations are defined
-//               at the NodeChain level.
-//
-//               A NodeChain is a list of connected nodes from the
-//               root of the graph to any sub-node.  Each NodeChain
-//               therefore unqiuely describes one instance of a node.
-//
-//               NodeChains themselves are lightweight objects that
-//               may easily be copied and passed by value.  Their data
-//               is stored as a series of NodeChainComponents that are
-//               stored on the nodes.  Holding a NodeChain will keep a
-//               reference count to all the nodes in the chain.
-//               However, if any node in the chain is removed or
-//               reparented (perhaps through a different NodeChain),
-//               the NodeChain will automatically be updated to
-//               reflect the changes.
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA NodeChain {
-PUBLISHED:
-  // This enumeration is returned by get_error_type() for an empty
-  // NodeChain to report the reason it's empty.
-  enum ErrorType {
-    ET_ok = 0,     // i.e. not empty, or never assigned to anything.
-    ET_not_found,  // returned from a failed find() or similar function.
-    ET_removed,    // remove_node() was previously called on this NodeChain.
-    ET_fail,       // general failure return from some function.
-  };
-
-  INLINE NodeChain();
-  INLINE NodeChain(PandaNode *top_node);
-  INLINE NodeChain(const NodeChain &copy);
-  INLINE void operator = (const NodeChain &copy);
-
-  INLINE static NodeChain not_found();
-  INLINE static NodeChain removed();
-  INLINE static NodeChain fail();
-
-  // Methods to query a NodeChain's contents.
-  INLINE bool is_empty() const;
-  INLINE bool is_singleton() const;
-  int get_num_nodes() const;
-  PandaNode *get_node(int index) const;
-
-  INLINE ErrorType get_error_type() const;
-
-  PandaNode *get_top_node() const;
-  INLINE PandaNode *node() const;
-
-  // Methods that return collections of NodePaths derived from or
-  // related to this one.
-
-  /*
-  NodeChainCollection get_siblings() const;
-  NodeChainCollection get_children() const;
-  */
-  INLINE int get_num_children() const;
-  INLINE NodeChain get_child(int n) const;
-
-  INLINE bool has_parent() const;
-  INLINE NodeChain get_parent() const;
-
-  /*
-  INLINE NodeChain find_path_down_to(Node *dnode) const;
-  INLINE NodeChain find(const string &chain) const;
-
-  NodeChainCollection
-  find_all_paths_down_to(Node *dnode) const;
-
-  NodeChainCollection
-  find_all_matches(const string &chain) const;
-  */
-
-  // Methods that actually move nodes around in the scene graph.  The
-  // optional "sort" parameter can be used to force a particular
-  // ordering between sibling nodes, useful when dealing with LOD's
-  // and similar switch nodes.  If the sort value is the same, nodes
-  // will be arranged in the order they were added.
-  void reparent_to(const NodeChain &other, int sort = 0);
-  void wrt_reparent_to(const NodeChain &other, int sort = 0);
-  NodeChain instance_to(const NodeChain &other, int sort = 0) const;
-  NodeChain copy_to(const NodeChain &other, int sort = 0) const;
-  NodeChain attach_new_node(PandaNode *node, int sort = 0) const;
-  INLINE NodeChain attach_new_node(const string &name, int sort = 0) const;
-  void remove_node();
-
-
-  // Relative transform and state changes between nodes.
-  CPT(RenderState) get_rel_state(const NodeChain &other) const;
-  INLINE CPT(RenderState) get_net_state() const;
-
-  CPT(TransformState) get_rel_transform(const NodeChain &other) const;
-  INLINE CPT(TransformState) get_net_transform() const;
-
-  INLINE const LMatrix4f &get_mat(const NodeChain &other) const;
-
-  bool verify_complete() const;
-
-public:
-  INLINE bool operator == (const NodeChain &other) const;
-  INLINE bool operator != (const NodeChain &other) const;
-  INLINE bool operator < (const NodeChain &other) const;
-  INLINE int compare_to(const NodeChain &other) const;
-
-  INLINE void output(ostream &out) const;
-
-private:
-  void uncollapse_head() const;
-  static void find_common_ancestor(const NodeChain &a, const NodeChain &b,
-                                   int &a_count, int &b_count);
-
-  CPT(RenderState) r_get_net_state(NodeChainComponent *comp) const;
-  CPT(RenderState) r_get_partial_state(NodeChainComponent *comp, int n) const;
-  CPT(TransformState) r_get_net_transform(NodeChainComponent *comp) const;
-  CPT(TransformState) r_get_partial_transform(NodeChainComponent *comp, int n) const;
-  void r_output(ostream &out, NodeChainComponent *comp) const;
-  static int r_compare_to(const NodeChainComponent *a, const NodeChainComponent *v);
-
-  PT(NodeChainComponent) _head;
-  ErrorType _error_type;
-
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    register_type(_type_handle, "NodeChain");
-  }
-
-private:
-  static TypeHandle _type_handle;
-};
-
-INLINE ostream &operator << (ostream &out, const NodeChain &node_chain);
-
-#include "nodeChain.I"
-
-#endif

+ 14 - 11
panda/src/pgraph/pandaNode.I

@@ -271,6 +271,20 @@ get_attrib(TypeHandle type) const {
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::has_attrib
+//       Access: Published
+//  Description: Returns true if there is a render attribute of the
+//               indicated type defined on this node, or false if
+//               there is not.
+////////////////////////////////////////////////////////////////////
+INLINE bool PandaNode::
+has_attrib(TypeHandle type) const {
+  CDReader cdata(_cycler);
+  int index = cdata->_state->find_attrib(type);
+  return (index >= 0);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::clear_attrib
 //       Access: Published
@@ -370,17 +384,6 @@ clear_transform() {
   cdata->_transform = TransformState::make_identity();
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: PandaNode::ls
-//       Access: Published
-//  Description: Lists all the nodes at and below the current path
-//               hierarchically.
-////////////////////////////////////////////////////////////////////
-INLINE void PandaNode::
-ls() const {
-  ls(nout);
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::ls
 //       Access: Published

+ 49 - 49
panda/src/pgraph/pandaNode.cxx

@@ -18,7 +18,7 @@
 
 #include "pandaNode.h"
 #include "config_pgraph.h"
-#include "nodeChainComponent.h"
+#include "qpnodePathComponent.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "indent.h"
@@ -369,7 +369,7 @@ add_child(PandaNode *child_node, int sort) {
   cdata->_down.insert(DownConnection(child_node, sort));
   cdata_child->_up.insert(UpConnection(this));
 
-  // We also have to adjust any NodeChainComponents the child might
+  // We also have to adjust any qpNodePathComponents the child might
   // have that reference the child as a top node.  Any other
   // components we can leave alone, because we are making a new
   // instance of the child.
@@ -402,24 +402,24 @@ remove_child(int n) {
   int num_erased = cdata_child->_up.erase(UpConnection(this));
   nassertv(num_erased == 1);
 
-  // Now sever any NodeChainComponents on the child that reference
+  // Now sever any qpNodePathComponents on the child that reference
   // this node.  If we have multiple of these, we have to collapse
   // them together.
-  NodeChainComponent *collapsed = (NodeChainComponent *)NULL;
+  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
   Chains::iterator ci;
   ci = cdata_child->_chains.begin();
   while (ci != cdata_child->_chains.end()) {
     Chains::iterator cnext = ci;
     ++cnext;
     if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-      if (collapsed == (NodeChainComponent *)NULL) {
+      if (collapsed == (qpNodePathComponent *)NULL) {
         (*ci)->set_top_node();
         collapsed = (*ci);
       } else {
         // This is a different component that used to reference a
         // different instance, but now it's all just the same topnode.
         // We have to collapse this and the previous one together.
-        // However, there might be some NodeChains out there that
+        // However, there might be some qpNodePaths out there that
         // still keep a pointer to this one, so we can't remove it
         // altogether.
         (*ci)->collapse_with(collapsed);
@@ -453,17 +453,17 @@ remove_child(PandaNode *child_node) {
     return false;
   }
 
-  // Now sever any NodeChainComponents on the child that reference
+  // Now sever any qpNodePathComponents on the child that reference
   // this node.  If we have multiple of these, we have to collapse
   // them together (see above).
-  NodeChainComponent *collapsed = (NodeChainComponent *)NULL;
+  qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
   Chains::iterator ci;
   ci = cdata_child->_chains.begin();
   while (ci != cdata_child->_chains.end()) {
     Chains::iterator cnext = ci;
     ++cnext;
     if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-      if (collapsed == (NodeChainComponent *)NULL) {
+      if (collapsed == (qpNodePathComponent *)NULL) {
         (*ci)->set_top_node();
         collapsed = (*ci);
       } else {
@@ -506,17 +506,17 @@ remove_all_children() {
     CDWriter cdata_child(child_node->_cycler);
     cdata_child->_up.erase(UpConnection(this));
 
-    // Now sever any NodeChainComponents on the child that reference
+    // Now sever any qpNodePathComponents on the child that reference
     // this node.  If we have multiple of these, we have to collapse
     // them together (see above).
-    NodeChainComponent *collapsed = (NodeChainComponent *)NULL;
+    qpNodePathComponent *collapsed = (qpNodePathComponent *)NULL;
     Chains::iterator ci;
     ci = cdata_child->_chains.begin();
     while (ci != cdata_child->_chains.end()) {
       Chains::iterator cnext = ci;
       ++cnext;
       if (!(*ci)->is_top_node() && (*ci)->get_next()->get_node() == this) {
-        if (collapsed == (NodeChainComponent *)NULL) {
+        if (collapsed == (qpNodePathComponent *)NULL) {
           (*ci)->set_top_node();
           collapsed = (*ci);
         } else {
@@ -675,19 +675,19 @@ recompute_internal_bound() {
 //     Function: PandaNode::attach
 //       Access: Private, Static
 //  Description: Creates a new parent-child relationship, and returns
-//               the new NodeChainComponent.  If the child was already
+//               the new qpNodePathComponent.  If the child was already
 //               attached to the indicated parent, repositions it and
-//               returns the original NodeChainComponent.
+//               returns the original qpNodePathComponent.
 ////////////////////////////////////////////////////////////////////
-PT(NodeChainComponent) PandaNode::
-attach(NodeChainComponent *parent, PandaNode *child_node, int sort) {
-  nassertr(parent != (NodeChainComponent *)NULL, (NodeChainComponent *)NULL);
+PT(qpNodePathComponent) PandaNode::
+attach(qpNodePathComponent *parent, PandaNode *child_node, int sort) {
+  nassertr(parent != (qpNodePathComponent *)NULL, (qpNodePathComponent *)NULL);
 
   // See if the child was already attached to the parent.  If it was,
-  // we'll use that same NodeChainComponent.
-  PT(NodeChainComponent) child = get_component(parent, child_node);
+  // we'll use that same qpNodePathComponent.
+  PT(qpNodePathComponent) child = get_component(parent, child_node);
 
-  if (child == (NodeChainComponent *)NULL) {
+  if (child == (qpNodePathComponent *)NULL) {
     // The child was not already attached to the parent, so get a new
     // component.
     child = get_top_component(child_node);
@@ -703,13 +703,13 @@ attach(NodeChainComponent *parent, PandaNode *child_node, int sort) {
 //  Description: Breaks a parent-child relationship.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
-detach(NodeChainComponent *child) {
-  nassertv(child != (NodeChainComponent *)NULL);
+detach(qpNodePathComponent *child) {
+  nassertv(child != (qpNodePathComponent *)NULL);
   nassertv(!child->is_top_node());
   PandaNode *child_node = child->get_node();
   PandaNode *parent_node = child->get_next()->get_node();
 
-  // Break the NodeChainComponent connection.
+  // Break the qpNodePathComponent connection.
   child->set_top_node();
 
   CDWriter cdata_child(child_node->_cycler);
@@ -762,15 +762,15 @@ detach(NodeChainComponent *child) {
 //  Description: Switches a node from one parent to another.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
-reparent(NodeChainComponent *new_parent, NodeChainComponent *child, int sort) {
-  nassertv(new_parent != (NodeChainComponent *)NULL);
-  nassertv(child != (NodeChainComponent *)NULL);
+reparent(qpNodePathComponent *new_parent, qpNodePathComponent *child, int sort) {
+  nassertv(new_parent != (qpNodePathComponent *)NULL);
+  nassertv(child != (qpNodePathComponent *)NULL);
 
   if (!child->is_top_node()) {
     detach(child);
   }
 
-  // Adjust the NodeChainComponents.
+  // Adjust the qpNodePathComponents.
   child->set_next(new_parent);
 
   PandaNode *child_node = child->get_node();
@@ -790,19 +790,19 @@ reparent(NodeChainComponent *new_parent, NodeChainComponent *child, int sort) {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_component
 //       Access: Private, Static
-//  Description: Returns the NodeChainComponent based on the indicated
+//  Description: Returns the qpNodePathComponent based on the indicated
 //               child of the given parent, or NULL if there is no
 //               such parent-child relationship.
 ////////////////////////////////////////////////////////////////////
-PT(NodeChainComponent) PandaNode::
-get_component(NodeChainComponent *parent, PandaNode *child_node) {
-  nassertr(parent != (NodeChainComponent *)NULL, (NodeChainComponent *)NULL);
+PT(qpNodePathComponent) PandaNode::
+get_component(qpNodePathComponent *parent, PandaNode *child_node) {
+  nassertr(parent != (qpNodePathComponent *)NULL, (qpNodePathComponent *)NULL);
   PandaNode *parent_node = parent->get_node();
 
   {
     CDReader cdata_child(child_node->_cycler);
 
-    // First, walk through the list of NodeChainComponents we already
+    // First, walk through the list of qpNodePathComponents we already
     // have on the child, looking for one that already exists,
     // referencing the indicated parent component.
     Chains::const_iterator ci;
@@ -816,13 +816,13 @@ get_component(NodeChainComponent *parent, PandaNode *child_node) {
     }
   }
     
-  // We don't already have a NodeChainComponent referring to this
+  // We don't already have a qpNodePathComponent referring to this
   // parent-child relationship.  Are they actually related?
   int child_index = child_node->find_parent(parent_node);
   if (child_index >= 0) {
     // They are.  Create and return a new one.
-    PT(NodeChainComponent) child = 
-      new NodeChainComponent(child_node, parent);
+    PT(qpNodePathComponent) child = 
+      new qpNodePathComponent(child_node, parent);
     CDWriter cdata_child(child_node->_cycler);
     cdata_child->_chains.insert(child);
     return child;
@@ -835,18 +835,18 @@ get_component(NodeChainComponent *parent, PandaNode *child_node) {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_top_component
 //       Access: Private, Static
-//  Description: Returns a NodeChainComponent referencing the
+//  Description: Returns a qpNodePathComponent referencing the
 //               indicated node as a singleton.  It is invalid to call
 //               this for a node that has parents, unless you are
 //               about to create a new instance (and immediately
-//               reconnect the NodeChainComponent elsewhere).
+//               reconnect the qpNodePathComponent elsewhere).
 ////////////////////////////////////////////////////////////////////
-PT(NodeChainComponent) PandaNode::
+PT(qpNodePathComponent) PandaNode::
 get_top_component(PandaNode *child_node) {
   {
     CDReader cdata_child(child_node->_cycler);
 
-    // Walk through the list of NodeChainComponents we already have on
+    // Walk through the list of qpNodePathComponents we already have on
     // the child, looking for one that already exists as a top node.
     Chains::const_iterator ci;
     for (ci = cdata_child->_chains.begin(); 
@@ -859,10 +859,10 @@ get_top_component(PandaNode *child_node) {
     }
   }
 
-  // We don't already have such a NodeChainComponent; create and
+  // We don't already have such a qpNodePathComponent; create and
   // return a new one.
-  PT(NodeChainComponent) child = 
-    new NodeChainComponent(child_node, (NodeChainComponent *)NULL);
+  PT(qpNodePathComponent) child = 
+    new qpNodePathComponent(child_node, (qpNodePathComponent *)NULL);
   CDWriter cdata_child(child_node->_cycler);
   cdata_child->_chains.insert(child);
 
@@ -872,13 +872,13 @@ get_top_component(PandaNode *child_node) {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::get_generic_component
 //       Access: Private
-//  Description: Returns a NodeChainComponent referencing this node as
+//  Description: Returns a qpNodePathComponent referencing this node as
 //               a chain from the root.  It is only valid to call this
 //               if there is an unambiguous path from the root;
 //               otherwise, a warning will be issued and one path will
 //               be chosen arbitrarily.
 ////////////////////////////////////////////////////////////////////
-PT(NodeChainComponent) PandaNode::
+PT(qpNodePathComponent) PandaNode::
 get_generic_component() {
   int num_parents = get_num_parents();
   if (num_parents == 0) {
@@ -890,7 +890,7 @@ get_generic_component() {
         << *this << " has " << num_parents
         << " parents; choosing arbitrary path to root.\n";
     }
-    PT(NodeChainComponent) parent = get_parent(0)->get_generic_component();
+    PT(qpNodePathComponent) parent = get_parent(0)->get_generic_component();
     return get_component(parent, this);
   }
 }
@@ -898,12 +898,12 @@ get_generic_component() {
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::delete_component
 //       Access: Private
-//  Description: Removes a NodeChainComponent from the set prior to
+//  Description: Removes a qpNodePathComponent from the set prior to
 //               its deletion.  This should only be called by the
-//               NodeChainComponent destructor.
+//               qpNodePathComponent destructor.
 ////////////////////////////////////////////////////////////////////
 void PandaNode::
-delete_component(NodeChainComponent *component) {
+delete_component(qpNodePathComponent *component) {
   // We have to remove the component from all of the pipeline stages,
   // not just the current one.
   int max_num_erased = 0;
@@ -924,7 +924,7 @@ delete_component(NodeChainComponent *component) {
 //     Function: PandaNode::fix_chain_lengths
 //       Access: Private
 //  Description: Recursively fixes the _length member of each
-//               NodeChainComponent at this level and below, after an
+//               qpNodePathComponent at this level and below, after an
 //               add or delete child operation that might have messed
 //               these up.
 ////////////////////////////////////////////////////////////////////

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

@@ -37,7 +37,7 @@
 #include "pointerTo.h"
 #include "notify.h"
 
-class NodeChainComponent;
+class qpNodePathComponent;
 class CullTraverserData;
 
 ////////////////////////////////////////////////////////////////////
@@ -98,6 +98,7 @@ PUBLISHED:
 
   INLINE void set_attrib(const RenderAttrib *attrib, int override = 0);
   INLINE const RenderAttrib *get_attrib(TypeHandle type) const;
+  INLINE bool has_attrib(TypeHandle type) const;
   INLINE void clear_attrib(TypeHandle type);
 
   INLINE void set_state(const RenderState *state);
@@ -111,8 +112,7 @@ PUBLISHED:
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
 
-  INLINE void ls() const;
-  INLINE void ls(ostream &out, int indent_level = 0) const;
+  INLINE void ls(ostream &out, int indent_level) const;
 
   // A node has two bounding volumes: the BoundedObject it inherits
   // from is the "external" bound and represnts the node and all of
@@ -149,18 +149,18 @@ protected:
   BoundedObject _internal_bound;
 
 private:
-  // parent-child manipulation for NodeChain support.  Don't try to
+  // parent-child manipulation for qpNodePath support.  Don't try to
   // call these directly.
-  static PT(NodeChainComponent) attach(NodeChainComponent *parent, 
+  static PT(qpNodePathComponent) attach(qpNodePathComponent *parent, 
                                        PandaNode *child, int sort);
-  static void detach(NodeChainComponent *child);
-  static void reparent(NodeChainComponent *new_parent,
-                       NodeChainComponent *child, int sort);
-  static PT(NodeChainComponent) get_component(NodeChainComponent *parent,
+  static void detach(qpNodePathComponent *child);
+  static void reparent(qpNodePathComponent *new_parent,
+                       qpNodePathComponent *child, int sort);
+  static PT(qpNodePathComponent) get_component(qpNodePathComponent *parent,
                                               PandaNode *child);
-  static PT(NodeChainComponent) get_top_component(PandaNode *child);
-  PT(NodeChainComponent) get_generic_component();
-  void delete_component(NodeChainComponent *component);
+  static PT(qpNodePathComponent) get_top_component(PandaNode *child);
+  PT(qpNodePathComponent) get_generic_component();
+  void delete_component(qpNodePathComponent *component);
   void fix_chain_lengths();
   void r_list_descendants(ostream &out, int indent_level) const;
 
@@ -194,12 +194,12 @@ private:
   };
   typedef ov_set<UpConnection> Up;
 
-  // We also maintain a set of NodeChainComponents in the node.  This
+  // We also maintain a set of qpNodePathComponents in the node.  This
   // represents the set of instances of this node that we have
-  // requested a NodeChain for.  We don't keep reference counts; when
-  // each NodeChainComponent destructs, it removes itself from this
+  // requested a qpNodePath for.  We don't keep reference counts; when
+  // each qpNodePathComponent destructs, it removes itself from this
   // set.
-  typedef pset<NodeChainComponent *> Chains;
+  typedef pset<qpNodePathComponent *> Chains;
   
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
@@ -271,8 +271,8 @@ private:
   static TypeHandle _type_handle;
 
   friend class PandaNode::Children;
-  friend class NodeChain;
-  friend class NodeChainComponent;
+  friend class qpNodePath;
+  friend class qpNodePathComponent;
 };
 
 INLINE ostream &operator << (ostream &out, const PandaNode &node) {

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

@@ -6,8 +6,9 @@
 #include "qplensNode.cxx"
 #include "qplodNode.cxx"
 #include "materialAttrib.cxx"
-#include "nodeChain.cxx"
-#include "nodeChainComponent.cxx"
+#include "qpnodePath.cxx"
+#include "qpnodePathCollection.cxx"
+#include "qpnodePathComponent.cxx"
 #include "pandaNode.cxx"
 #include "renderAttrib.cxx"
 #include "renderState.cxx"

+ 2 - 2
panda/src/pgraph/qpcamera.I

@@ -48,7 +48,7 @@ is_active() const {
 //               represent the root of any subgraph.
 ////////////////////////////////////////////////////////////////////
 INLINE void qpCamera::
-set_scene(const NodeChain &scene) {
+set_scene(const qpNodePath &scene) {
   _scene = scene;
 }
 
@@ -58,7 +58,7 @@ set_scene(const NodeChain &scene) {
 //  Description: Returns the scene that will be rendered by the
 //               camera.  See set_scene().
 ////////////////////////////////////////////////////////////////////
-INLINE const NodeChain &qpCamera::
+INLINE const qpNodePath &qpCamera::
 get_scene() const {
   return _scene;
 }

+ 4 - 4
panda/src/pgraph/qpcamera.h

@@ -22,7 +22,7 @@
 #include "pandabase.h"
 
 #include "qplensNode.h"
-#include "nodeChain.h"
+#include "qpnodePath.h"
 
 class DisplayRegion;
 
@@ -49,8 +49,8 @@ PUBLISHED:
   INLINE void set_active(bool active);
   INLINE bool is_active() const;
 
-  INLINE void set_scene(const NodeChain &scene);
-  INLINE const NodeChain &get_scene() const;
+  INLINE void set_scene(const qpNodePath &scene);
+  INLINE const qpNodePath &get_scene() const;
 
   INLINE int get_num_display_regions() const;
   INLINE DisplayRegion *get_display_region(int n) const;
@@ -60,7 +60,7 @@ private:
   void remove_display_region(DisplayRegion *display_region);
 
   bool _active;
-  NodeChain _scene;
+  qpNodePath _scene;
 
   typedef pvector<DisplayRegion *> DisplayRegions;
   DisplayRegions _display_regions;

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

@@ -29,6 +29,7 @@
 class PandaNode;
 class CullHandler;
 class CullTraverserData;
+class CullableObject;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : CullTraverser

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

@@ -0,0 +1,1086 @@
+// Filename: qpnodePath.I
+// Created by:  drose (25Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpNodePath::Default Constructor
+//       Access: Published
+//  Description: This constructs an empty qpNodePath with no nodes.  It
+//               cannot be extended, since you cannot add nodes without
+//               first specifying the top node.  Use the constructor
+//               that receives a node if you ever want to do anything
+//               with this path.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath::
+qpNodePath() :
+  _error_type(ET_ok)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::Constructor
+//       Access: Published
+//  Description: This constructs an empty qpNodePath with a single node.
+//               This path may now be extended by repeatedly calling
+//               push_back() with each node below that node in
+//               sequence.
+//
+//               If the Node pointer is NULL, this quietly creates an
+//               empty qpNodePath.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath::
+qpNodePath(PandaNode *top_node) :
+  _error_type(ET_ok)
+{
+  if (top_node != (PandaNode *)NULL) {
+    _head = top_node->get_generic_component();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath::
+qpNodePath(const qpNodePath &copy) :
+  _head(copy._head),
+  _error_type(copy._error_type)
+{
+  uncollapse_head();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+operator = (const qpNodePath &copy) {
+  _head = copy._head;
+  _error_type = copy._error_type;
+  uncollapse_head();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::not_found named constructor
+//       Access: Published, Static
+//  Description: Creates a qpNodePath with the ET_not_found error type
+//               set.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+not_found() {
+  qpNodePath result;
+  result._error_type = ET_not_found;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::removed named constructor
+//       Access: Published, Static
+//  Description: Creates a qpNodePath with the ET_removed error type
+//               set.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+removed() {
+  qpNodePath result;
+  result._error_type = ET_removed;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::fail named constructor
+//       Access: Published, Static
+//  Description: Creates a qpNodePath with the ET_fail error type
+//               set.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+fail() {
+  qpNodePath result;
+  result._error_type = ET_fail;
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::is_empty
+//       Access: Published
+//  Description: Returns true if the qpNodePath contains no nodes.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+is_empty() const {
+  return (_head == (qpNodePathComponent *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::is_singleton
+//       Access: Published
+//  Description: Returns true if the qpNodePath contains exactly one
+//               node.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+is_singleton() const {
+  uncollapse_head();
+  return (_head != (qpNodePathComponent *)NULL && _head->is_top_node());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_error_type
+//       Access: Published
+//  Description: If is_empty() is true, this returns a code that
+//               represents the reason why the qpNodePath is empty.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath::ErrorType qpNodePath::
+get_error_type() const {
+  return _error_type;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::node
+//       Access: Published
+//  Description: Returns the referenced node of the path, or NULL if the
+//               path is empty.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *qpNodePath::
+node() const {
+  if (is_empty()) {
+    return (PandaNode *)NULL;
+  }
+  return _head->get_node();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_num_children
+//       Access: Published
+//  Description: Returns the number of children of the referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE int qpNodePath::
+get_num_children() const {
+  nassertr(!is_empty(), 0);
+  return _head->get_node()->get_num_children();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_child
+//       Access: Published
+//  Description: Returns a qpNodePath representing the nth child of the
+//               referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+get_child(int n) const {
+  nassertr(n >= 0 && n < get_num_children(), qpNodePath());
+  qpNodePath child;
+  child._head = PandaNode::get_component(_head, _head->get_node()->get_child(n));
+  return child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_parent
+//       Access: Published
+//  Description: Returns true if the referenced node has a parent;
+//               i.e. the NodePath chain contains at least two nodes.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+has_parent() const {
+  return !is_empty() && !is_singleton();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_parent
+//       Access: Published
+//  Description: Returns the qpNodePath to the parent of the referenced
+//               node: that is, this qpNodePath, shortened by one node.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+get_parent() const {
+  nassertr(has_parent(), qpNodePath::fail());
+  qpNodePath parent;
+  parent._head = _head->get_next();
+  return parent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::attach_new_node
+//       Access: Published
+//  Description: Creates an ordinary PandaNode and attaches it below
+//               the current qpNodePath, returning a new qpNodePath that
+//               references it.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpNodePath::
+attach_new_node(const string &name, int sort) const {
+  nassertr(verify_complete(), qpNodePath::fail());
+  nassertr(!is_empty(), *this);
+
+  return attach_new_node(new PandaNode(name), sort);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::output
+//       Access: Published
+//  Description: Writes a sensible description of the qpNodePath to the
+//               indicated output stream.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+output(ostream &out) const {
+  if (_head == (qpNodePathComponent *)NULL) {
+    out << "(empty)";
+  } else {
+    r_output(out, _head);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::ls
+//       Access: Published
+//  Description: Lists the hierarchy at and below the referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+ls() const {
+  ls(nout);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::ls
+//       Access: Published
+//  Description: Lists the hierarchy at and below the referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+ls(ostream &out, int indent_level) const {
+  if (is_empty()) {
+    out << "(empty)\n";
+  } else {
+    node()->ls(out, indent_level);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::ls_transitions
+//       Access: Published
+//  Description: Lists the hierarchy at and below the referenced node,
+//               along with the state transitions at each level.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+ls_transitions() const {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::ls_transforms
+//       Access: Published
+//  Description: Lists the hierarchy at and below the referenced node,
+//               along with the transforms at each level.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+ls_transforms() const {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_state
+//       Access: Published
+//  Description: Returns the complete state object set on this node.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(RenderState) qpNodePath::
+get_state() const {
+  nassertr(!is_empty(), RenderState::make_empty());
+  return node()->get_state();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_state
+//       Access: Published
+//  Description: Changes the complete state object on this node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_state(const RenderState *state) const {
+  nassertv(!is_empty());
+  node()->set_state(state);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_net_state
+//       Access: Published
+//  Description: Returns the net state on this node from the root.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(RenderState) qpNodePath::
+get_net_state() const {
+  uncollapse_head();
+  return r_get_net_state(_head);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_transform
+//       Access: Published
+//  Description: Returns the complete transform object set on this node.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) qpNodePath::
+get_transform() const {
+  nassertr(!is_empty(), TransformState::make_identity());
+  return node()->get_transform();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_transform
+//       Access: Published
+//  Description: Changes the complete transform object on this node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_transform(const TransformState *transform) const {
+  nassertv(!is_empty());
+  node()->set_transform(transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_net_transform
+//       Access: Published
+//  Description: Returns the net transform on this node from the root.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) qpNodePath::
+get_net_transform() const {
+  uncollapse_head();
+  return r_get_net_transform(_head);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos
+//       Access: Published
+//  Description: Sets the translation component of the transform,
+//               leaving rotation and scale untouched.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_pos(float x, float y, float z) {
+  set_pos(LPoint3f(x, y, z));
+}
+
+INLINE float qpNodePath::
+get_x() const {
+  return get_pos()[0];
+}
+
+INLINE float qpNodePath::
+get_y() const {
+  return get_pos()[1];
+}
+
+INLINE float qpNodePath::
+get_z() const {
+  return get_pos()[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr
+//       Access: Published
+//  Description: Sets the rotation component of the transform,
+//               leaving translation and scale untouched.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_hpr(float h, float p, float r) {
+  set_hpr(LVecBase3f(h, p, r));
+}
+
+INLINE float qpNodePath::
+get_h() const {
+  return get_hpr()[0];
+}
+
+INLINE float qpNodePath::
+get_p() const {
+  return get_hpr()[1];
+}
+
+INLINE float qpNodePath::
+get_r() const {
+  return get_hpr()[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_scale
+//       Access: Published
+//  Description: Sets the scale component of the transform,
+//               leaving translation and rotation untouched.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_scale(float scale) {
+  set_scale(LVecBase3f(scale, scale, scale));
+}
+
+INLINE void qpNodePath::
+set_scale(float sx, float sy, float sz) {
+  set_scale(LVecBase3f(sx, sy, sz));
+}
+
+INLINE float qpNodePath::
+get_sx() const {
+  return get_scale()[0];
+}
+
+INLINE float qpNodePath::
+get_sy() const {
+  return get_scale()[1];
+}
+
+INLINE float qpNodePath::
+get_sz() const {
+  return get_scale()[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr
+//       Access: Published
+//  Description: Sets the translation and rotation component of the
+//               transform, leaving scale untouched.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_pos_hpr(float x, float y, float z, float h, float p, float r) {
+  set_pos_hpr(LVecBase3f(x, y, z), LVecBase3f(h, p, r));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr_scale
+//       Access: Published
+//  Description: Sets the rotation and scale components of the
+//               transform, leaving translation untouched.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_hpr_scale(float h, float p, float r, float sx, float sy, float sz) {
+  set_hpr_scale(LVecBase3f(h, p, r), LVecBase3f(sx, sy, sz));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr_scale
+//       Access: Published
+//  Description: Completely replaces the transform with new
+//               translation, rotation, and scale components.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_pos_hpr_scale(float x, float y, float z, float h, float p, float r,
+                  float sx, float sy, float sz) {
+  set_pos_hpr_scale(LVecBase3f(x, y, z), LVecBase3f(h, p, r),
+                    LVecBase3f(sx, sy, sz));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_mat
+//       Access: Published
+//  Description: Completely removes any transform from the referenced
+//               node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+clear_mat() {
+  nassertv(!is_empty());
+  node()->clear_transform();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_mat
+//       Access: Published
+//  Description: Returns true if a non-identity transform matrix has
+//               been applied to the referenced node, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+has_mat() const {
+  nassertr(!is_empty(), false);
+  return !node()->get_transform()->is_identity();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_mat
+//       Access: Published
+//  Description: Returns the transform matrix that has been applied to
+//               the referenced node, or the identity matrix if no
+//               matrix has been applied.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpNodePath::
+get_mat() const {
+  nassertr(!is_empty(), LMatrix4f::ident_mat());
+
+  return node()->get_transform()->get_mat();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_color_scale
+//       Access: Published
+//  Description: Returns true if a color scale has been applied
+//               to the referenced node, false otherwise.  It is still
+//               possible that color at this node might have been
+//               scaled by an ancestor node.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+has_color_scale() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_color_scale
+//       Access: Published
+//  Description: Completely removes any color scale from the
+//               referenced node.  This is preferable to simply
+//               setting the color scale to identity, as it also
+//               removes the overhead associated with having a color
+//               scale at all.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+clear_color_scale() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_color_scale
+//       Access: Published
+//  Description: Sets the color scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_color_scale(float sr, float sg, float sb, float sa) {
+  set_color_scale(LVecBase4f(sr, sg, sb, sa));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_sr
+//       Access: Published
+//  Description: Sets the red scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_sr(float sr) {
+  LVecBase4f new_scale = get_color_scale();
+  new_scale[0] = sr;
+
+  set_color_scale(new_scale);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_sg
+//       Access: Published
+//  Description: Sets the alpha scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_sg(float sg) {
+  LVecBase4f new_scale = get_color_scale();
+  new_scale[1] = sg;
+
+  set_color_scale(new_scale);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_sb
+//       Access: Published
+//  Description: Sets the blue scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_sb(float sb) {
+  LVecBase4f new_scale = get_color_scale();
+  new_scale[2] = sb;
+
+  set_color_scale(new_scale);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_sa
+//       Access: Published
+//  Description: Sets the alpha scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_sa(float sa) {
+  LVecBase4f new_scale = get_color_scale();
+  new_scale[3] = sa;
+
+  set_color_scale(new_scale);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_sr
+//       Access: Published
+//  Description: Gets the red scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_sr() const {
+  return get_color_scale()[0];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_sg
+//       Access: Published
+//  Description: Gets the green scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_sg() const {
+  return get_color_scale()[1];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_sb
+//       Access: Published
+//  Description: Gets the blue scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_sb() const {
+  return get_color_scale()[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_sa
+//       Access: Published
+//  Description: Gets the alpha scale component of the transform
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_sa() const {
+  return get_color_scale()[3];
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at
+//       Access: Published
+//  Description: Sets the transform on this qpNodePath so that it
+//               rotates to face the indicated point in space.  This
+//               will overwrite any previously existing scale on the
+//               node, although it will preserve any translation.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+look_at(float x, float y, float z) {
+  look_at(LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up
+//       Access: Published
+//  Description: Behaves like look_at(), but with a strong preference
+//               to keeping the up vector oriented in the indicated
+//               "up" direction.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+heads_up(float x, float y, float z) {
+  heads_up(LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at_preserve_scale
+//       Access: Published
+//  Description: Functions like look_at(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, look_at() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+look_at_preserve_scale(float x, float y, float z) {
+  look_at_preserve_scale(LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up_preserve_scale
+//       Access: Published
+//  Description: Functions like heads_up(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, heads_up() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+heads_up_preserve_scale(float x, float y, float z) {
+  heads_up_preserve_scale(LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos
+//       Access: Published
+//  Description: Sets the translation component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+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];
+}
+
+INLINE float qpNodePath::
+get_y(const qpNodePath &other) const {
+  return get_pos(other)[1];
+}
+
+INLINE float qpNodePath::
+get_z(const qpNodePath &other) const {
+  return get_pos(other)[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr
+//       Access: Published
+//  Description: Sets the rotation component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_hpr(const qpNodePath &other, float h, float p, float r) {
+  set_hpr(other, LPoint3f(h, p, r));
+}
+
+INLINE float qpNodePath::
+get_h(const qpNodePath &other) const {
+  return get_hpr(other)[0];
+}
+
+INLINE float qpNodePath::
+get_p(const qpNodePath &other) const {
+  return get_hpr(other)[1];
+}
+
+INLINE float qpNodePath::
+get_r(const qpNodePath &other) const {
+  return get_hpr(other)[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_scale
+//       Access: Published
+//  Description: Sets the scale component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_scale(const qpNodePath &other, float sx, float sy, float sz) {
+  set_scale(other, LPoint3f(sx, sy, sz));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_scale
+//       Access: Published
+//  Description: Returns the relative scale of the referenced node
+//               as seen from the other node.
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_sx(const qpNodePath &other) const {
+  return get_scale(other)[0];
+}
+
+INLINE float qpNodePath::
+get_sy(const qpNodePath &other) const {
+  return get_scale(other)[1];
+}
+
+INLINE float qpNodePath::
+get_sz(const qpNodePath &other) const {
+  return get_scale(other)[2];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr
+//       Access: Published
+//  Description: Sets the translation and rotation component of the
+//               transform, relative to the other node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_pos_hpr(const qpNodePath &other,
+            float x, float y, float z,
+            float h, float p, float r) {
+  set_pos_hpr(other, LVecBase3f(x, y, z), LVecBase3f(h, p, r));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr_scale
+//       Access: Published
+//  Description: Sets the rotation and scale components of the
+//               transform, leaving translation untouched.  This, or
+//               set_pos_hpr_scale, is the preferred way to update a
+//               transform when both hpr and scale are to be changed.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_hpr_scale(const qpNodePath &other,
+	      float h, float p, float r, float sx, float sy, float sz) {
+  set_hpr_scale(other, LVecBase3f(h, p, r), LVecBase3f(sx, sy, sz));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr_scale
+//       Access: Published
+//  Description: Completely replaces the transform with new
+//               translation, rotation, and scale components, relative
+//               to the other node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_pos_hpr_scale(const qpNodePath &other,
+                  float x, float y, float z,
+                  float h, float p, float r,
+                  float sx, float sy, float sz) {
+  set_pos_hpr_scale(other, LVecBase3f(x, y, z), LVecBase3f(h, p, r),
+                    LVecBase3f(sx, sy, sz));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at
+//       Access: Published
+//  Description: Sets the transform on this qpNodePath so that it
+//               rotates to face the indicated point in space, which
+//               is relative to the other qpNodePath.  This
+//               will overwrite any previously existing scale on the
+//               node, although it will preserve any translation.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+look_at(const qpNodePath &other, float x, float y, float z) {
+  look_at(other, LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up
+//       Access: Published
+//  Description: Behaves like look_at(), but with a strong preference
+//               to keeping the up vector oriented in the indicated
+//               "up" direction.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+heads_up(const qpNodePath &other, float x, float y, float z) {
+  heads_up(other, LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at_preserve_scale
+//       Access: Published
+//  Description: Functions like look_at(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, look_at() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+look_at_preserve_scale(const qpNodePath &other, float x, float y, float z) {
+  look_at_preserve_scale(other, LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up_preserve_scale
+//       Access: Published
+//  Description: Functions like heads_up(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, heads_up() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+heads_up_preserve_scale(const qpNodePath &other, float x, float y, float z) {
+  heads_up_preserve_scale(other, LPoint3f(x, y, z));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_distance
+//       Access: Published
+//  Description: Returns the straight-line distance between this
+//               referenced node's coordinate frame's origin, and that
+//               of the other node's origin.
+////////////////////////////////////////////////////////////////////
+INLINE float qpNodePath::
+get_distance(const qpNodePath &other) const {
+  LPoint3f pos = get_pos(other);
+  return length(LVector3f(pos));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::adjust_all_priorities
+//       Access: Published
+//  Description: Adds the indicated adjustment amount (which may be
+//               negative) to the priority for all transitions on the
+//               referenced node, and for all nodes in the subgraph
+//               below.  This can be used to force these nodes not to
+//               be overridden by a high-level state change above.  If
+//               the priority would drop below zero, it is set to
+//               zero.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+adjust_all_priorities(int adjustment) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::show
+//       Access: Published
+//  Description: Undoes the effect of a previous hide() on this node:
+//               makes the referenced node (and the entire subgraph
+//               below this node) visible.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+show() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::hide
+//       Access: Published
+//  Description: Makes the referenced node (and the entire subgraph
+//               below this node) invisible to rendering.  It remains
+//               part of the scene graph, its bounding volume still
+//               contributes to its parent's bounding volume, and it
+//               will still be involved in collision tests.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+hide() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::show_collision_solids
+//       Access: Published
+//  Description: Shows all the collision solids at and below the
+//               referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+show_collision_solids() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::hide_collision_solids
+//       Access: Published
+//  Description: Hides all the collision solids at and below the
+//               referenced node.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+hide_collision_solids() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::is_hidden
+//       Access: Published
+//  Description: Returns true if the referenced node is hidden either
+//               directly, or because some ancestor is hidden.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+is_hidden() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::unstash
+//       Access: Published
+//  Description: Undoes the effect of a previous stash() on this
+//               node: makes the referenced node (and the entire
+//               subgraph below this node) once again part of the
+//               scene graph.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+unstash() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::stash
+//       Access: Published
+//  Description: Removes the referenced node (and the entire subgraph
+//               below this node) from the scene graph in any normal
+//               sense.  The node will no longer be visible and is not
+//               tested for collisions; furthermore, no normal scene
+//               graph traversal will visit the node.  The node's
+//               bounding volume no longer contributes to its parent's
+//               bounding volume.
+//
+//               A stashed node cannot be located by a normal find()
+//               operation (although a special find string can still
+//               retrieve it).
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+stash() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::is_stashed
+//       Access: Published
+//  Description: Returns true if the referenced node is stashed either
+//               directly, or because some ancestor is stashed.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+is_stashed() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::operator ==
+//       Access: Published
+//  Description: Returns true if the two paths are equivalent; that
+//               is, if they contain the same list of nodes in the same
+//               order.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+operator == (const qpNodePath &other) const {
+  return (compare_to(other) == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::operator !=
+//       Access: Published
+//  Description: Returns true if the two paths are not equivalent.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+operator != (const qpNodePath &other) const {
+  return (compare_to(other) != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::operator <
+//       Access: Published
+//  Description: Returns true if this qpNodePath sorts before the other
+//               one, false otherwise.  The sorting order of two
+//               nonequivalent qpNodePaths is consistent but undefined,
+//               and is useful only for storing qpNodePaths in a sorted
+//               container like an STL set.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpNodePath::
+operator < (const qpNodePath &other) const {
+  return (compare_to(other) < 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::compare_to
+//       Access: Published
+//  Description: Returns a number less than zero if this qpNodePath
+//               sorts before the other one, greater than zero if it
+//               sorts after, or zero if they are equivalent.
+//
+//               Two qpNodePaths are considered equivalent if they
+//               consist of exactly the same list of nodes in the same
+//               order.  Otherwise, they are different; different
+//               qpNodePaths will be ranked in a consistent but
+//               undefined ordering; the ordering is useful only for
+//               placing the qpNodePaths in a sorted container like an
+//               STL set.
+////////////////////////////////////////////////////////////////////
+INLINE int qpNodePath::
+compare_to(const qpNodePath &other) const {
+  return r_compare_to(_head, other._head);
+}
+
+
+INLINE ostream &operator << (ostream &out, const qpNodePath &node_path) {
+  node_path.output(out);
+  return out;
+}
+

+ 2284 - 0
panda/src/pgraph/qpnodePath.cxx

@@ -0,0 +1,2284 @@
+// Filename: qpnodePath.cxx
+// Created by:  drose (25Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpnodePath.h"
+#include "qpnodePathCollection.h"
+#include "node.h"
+#include "namedNode.h"
+#include "config_pgraph.h"
+#include "plist.h"
+#include "colorAttrib.h"
+#include "cullBinAttrib.h"
+#include "textureAttrib.h"
+#include "materialAttrib.h"
+#include "cullFaceAttrib.h"
+#include "billboardAttrib.h"
+#include "transparencyAttrib.h"
+#include "materialPool.h"
+#include "look_at.h"
+#include "compose_matrix.h"
+
+TypeHandle qpNodePath::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_num_nodes
+//       Access: Published
+//  Description: Returns the number of nodes in the path.
+////////////////////////////////////////////////////////////////////
+int qpNodePath::
+get_num_nodes() const {
+  if (is_empty()) {
+    return 0;
+  }
+  uncollapse_head();
+  return _head->get_length();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_node
+//       Access: Published
+//  Description: Returns the nth node of the path, where 0 is the
+//               referenced (bottom) node and get_num_nodes() - 1 is
+//               the top node.  This requires iterating through the
+//               path.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpNodePath::
+get_node(int index) const {
+  nassertr(index >= 0 && index < get_num_nodes(), NULL);
+
+  uncollapse_head();
+  qpNodePathComponent *comp = _head;
+  while (index > 0) {
+    // If this assertion fails, the index was out of range; the
+    // component's length must have been invalid.
+    nassertr(comp != (qpNodePathComponent *)NULL, NULL);
+    comp = comp->get_next();
+    index--;
+  }
+
+  // If this assertion fails, the index was out of range; the
+  // component's length must have been invalid.
+  nassertr(comp != (qpNodePathComponent *)NULL, NULL);
+  return comp->get_node();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_top_node
+//       Access: Published
+//  Description: Returns the top node of the path, or NULL if the path
+//               is empty.  This requires iterating through the path.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpNodePath::
+get_top_node() const {
+  if (is_empty()) {
+    return (PandaNode *)NULL;
+  }
+
+  uncollapse_head();
+  qpNodePathComponent *comp = _head;
+  while (!comp->is_top_node()) {
+    comp = comp->get_next();
+    nassertr(comp != (qpNodePathComponent *)NULL, NULL);
+  }
+
+  return comp->get_node();
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_children
+//       Access: Published
+//  Description: Returns the set of all child nodes of the referenced
+//               node.
+////////////////////////////////////////////////////////////////////
+qpNodePathCollection qpNodePath::
+get_children() const {
+  qpNodePathCollection result;
+  nassertr(!is_empty(), result);
+
+  PandaNode *bottom_node = node();
+
+  PandaNode::Children cr = bottom_node->get_children();
+  int num_children = cr.get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    qpNodePath child;
+    child._head = PandaNode::get_component(_head, cr.get_child(i));
+    result.add_path(child);
+  }
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::reparent_to
+//       Access: Published
+//  Description: Removes the referenced node of the qpNodePath from its
+//               current parent and attaches it to the referenced node of
+//               the indicated qpNodePath.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+reparent_to(const qpNodePath &other, int sort) {
+  nassertv(other.verify_complete());
+  nassertv_always(!is_empty());
+  nassertv_always(!other.is_empty());
+
+  uncollapse_head();
+  other.uncollapse_head();
+  PandaNode::reparent(other._head, _head, sort);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::wrt_reparent_to
+//       Access: Published
+//  Description: This functions identically to reparent_to(), except
+//               the transform on this node is also adjusted so that
+//               the node remains in the same place in world
+//               coordinates, even if it is reparented into a
+//               different coordinate system.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+wrt_reparent_to(const qpNodePath &other, int sort) {
+  nassertv(other.verify_complete());
+  nassertv_always(!is_empty());
+  nassertv_always(!other.is_empty());
+
+  set_transform(get_transform(other));
+  reparent_to(other, sort);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::instance_to
+//       Access: Published
+//  Description: Adds the referenced node of the NodePath as a child
+//               of the referenced node of the indicated other
+//               NodePath.  Any other parent-child relations of the
+//               node are unchanged; in particular, the node is not
+//               removed from its existing parent, if any.
+//
+//               If the node already had an existing parent, this
+//               method will create a new instance of the node within
+//               the scene graph.
+//
+//               This does not change the NodePath itself, but does
+//               return a new NodePath that reflects the new instance
+//               node.
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePath::
+instance_to(const qpNodePath &other, int sort) const {
+  nassertr(verify_complete(), qpNodePath::fail());
+  nassertr(!is_empty(), qpNodePath::fail());
+  nassertr(!other.is_empty(), qpNodePath::fail());
+
+  uncollapse_head();
+  other.uncollapse_head();
+
+  qpNodePath new_instance;
+  new_instance._head = PandaNode::attach(other._head, node(), sort);
+
+  return new_instance;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::copy_to
+//       Access: Published
+//  Description: Functions exactly like instance_to(), except a deep
+//               copy is made of the referenced node and all of its
+//               descendents, which is then parented to the indicated
+//               node.  A qpNodePath to the newly created copy is
+//               returned.
+//
+//               Certain kinds of nodes may not be copied; if one of
+//               these is encountered, the node will be "copied" as
+//               the nearest copyable base class.  For instance, a
+//               Camera node in the graph will become a simple
+//               NamedNode.
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePath::
+copy_to(const qpNodePath &other, int sort) const {
+  //*****
+  return instance_to(other, sort);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::attach_new_node
+//       Access: Published
+//  Description: Attaches a new node, with or without existing
+//               parents, to the scene graph below the referenced node
+//               of this NodePath.  This is the preferred way to add
+//               nodes to the graph.
+//
+//               This does *not* automatically extend the current
+//               NodePath to reflect the attachment; however, a
+//               NodePath that does reflect this extension is
+//               returned.
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePath::
+attach_new_node(PandaNode *node, int sort) const {
+  nassertr(verify_complete(), qpNodePath::fail());
+  nassertr(!is_empty(), qpNodePath());
+  nassertr(node != (PandaNode *)NULL, qpNodePath());
+
+  uncollapse_head();
+  qpNodePath new_path(*this);
+  new_path._head = PandaNode::attach(_head, node, sort);
+  return new_path;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::remove_node
+//       Access: Published
+//  Description: Disconnects the referenced node from the scene graph.
+//               This will also delete the node if there are no other
+//               pointers to it.
+//
+//               Normally, this should be called only when you are
+//               really done with the node.  If you want to remove a
+//               node from the scene graph but keep it around for
+//               later, you should probably use reparent_to() and put
+//               it under a holding node instead.
+//
+//               After the node is removed, the qpNodePath will have
+//               been cleared.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+remove_node() {
+  nassertv(_error_type != ET_not_found);
+  if (is_empty()) {
+    // If we have no arcs (maybe we were already removed), quietly do
+    // nothing except to ensure the qpNodePath is clear.
+    (*this) = qpNodePath::removed();
+    return;
+  }
+
+  uncollapse_head();
+  PandaNode::detach(_head);
+
+  (*this) = qpNodePath::removed();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_state
+//       Access: Published
+//  Description: Returns the state changes that must be made to
+//               transition from the render state of this node to the
+//               render state of the other node.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) qpNodePath::
+get_state(const qpNodePath &other) const {
+  if (is_empty()) {
+    return other.get_net_state();
+  }
+  if (other.is_empty()) {
+    return get_net_state()->invert_compose(RenderState::make_empty());
+  }
+    
+  nassertr(verify_complete(), RenderState::make_empty());
+  nassertr(other.verify_complete(), RenderState::make_empty());
+
+  int a_count, b_count;
+  find_common_ancestor(*this, other, a_count, b_count);
+
+  CPT(RenderState) a_state = r_get_partial_state(_head, a_count);
+  CPT(RenderState) b_state = r_get_partial_state(other._head, b_count);
+  return a_state->invert_compose(b_state);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_state
+//       Access: Published
+//  Description: Sets the state object on this node, relative to
+//               the other node.  This computes a new state object
+//               that has the indicated value when seen relative to
+//               the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_state(const qpNodePath &other, const RenderState *state) const {
+  nassertv(_error_type == ET_ok && other._error_type == ET_ok);
+  nassertv_always(!is_empty());
+
+  // First, we perform a wrt to the parent, to get the conversion.
+  qpNodePath parent = get_parent();
+  CPT(RenderState) rel_state = parent.get_state(other);
+
+  CPT(RenderState) new_state = rel_state->compose(state);
+  set_state(new_state);
+}
+
+////////////////////////////////////////////////////////////////////
+//     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.
+////////////////////////////////////////////////////////////////////
+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());
+  }
+    
+  nassertr(verify_complete(), TransformState::make_identity());
+  nassertr(other.verify_complete(), TransformState::make_identity());
+
+  int a_count, b_count;
+  find_common_ancestor(*this, other, a_count, b_count);
+
+  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);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_transform
+//       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.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_transform(const qpNodePath &other, const TransformState *transform) const {
+  nassertv(_error_type == ET_ok && other._error_type == ET_ok);
+  nassertv_always(!is_empty());
+
+  // 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) new_trans = rel_trans->compose(transform);
+  set_transform(new_trans);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos
+//       Access: Published
+//  Description: Sets the translation component of the transform,
+//               leaving rotation and scale untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos(const LVecBase3f &pos) {
+  nassertv(!is_empty());
+  set_transform(get_transform()->set_pos(pos));
+}
+
+void qpNodePath::
+set_x(float x) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos();
+  pos[0] = x;
+  set_pos(pos);
+}
+
+void qpNodePath::
+set_y(float y) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos();
+  pos[1] = y;
+  set_pos(pos);
+}
+
+void qpNodePath::
+set_z(float z) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos();
+  pos[2] = z;
+  set_pos(pos);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_pos
+//       Access: Published
+//  Description: Retrieves the translation component of the transform.
+////////////////////////////////////////////////////////////////////
+LPoint3f qpNodePath::
+get_pos() const {
+  nassertr(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
+  return get_transform()->get_pos();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr
+//       Access: Published
+//  Description: Sets the rotation component of the transform,
+//               leaving translation and scale untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_hpr(const LVecBase3f &hpr) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  set_transform(transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_h(float h) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[0] = h;
+  set_transform(transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_p(float p) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[1] = p;
+  set_transform(transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_r(float r) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[2] = r;
+  set_transform(transform->set_hpr(hpr));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_hpr
+//       Access: Published
+//  Description: Retrieves the rotation component of the transform.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_hpr() const {
+  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  CPT(TransformState) transform = get_transform();
+  nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  return transform->get_hpr();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_hpr
+//       Access: Published
+//  Description: Retrieves the rotation component of the transform.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_hpr(float roll) const {
+  // This function is deprecated.  It used to be a hack to work around
+  // a problem with decomposing Euler angles, but since we no longer
+  // depend on decomposing these, we shouldn't need this any more.
+  return get_hpr();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_scale
+//       Access: Published
+//  Description: Sets the scale component of the transform,
+//               leaving translation and rotation untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_scale(const LVecBase3f &sv3) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  set_transform(transform->set_scale(sv3));
+}
+
+void qpNodePath::
+set_sx(float sx) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[0] = sx;
+  set_transform(transform->set_scale(scale));
+}
+
+void qpNodePath::
+set_sy(float sy) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[1] = sy;
+  set_transform(transform->set_scale(scale));
+}
+
+void qpNodePath::
+set_sz(float sz) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[2] = sz;
+  set_transform(transform->set_scale(scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_scale
+//       Access: Published
+//  Description: Retrieves the scale component of the transform.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_scale() const {
+  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  CPT(TransformState) transform = get_transform();
+  nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  return transform->get_scale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr
+//       Access: Published
+//  Description: Sets the translation and rotation component of the
+//               transform, leaving scale untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos_hpr(const LVecBase3f &pos, const LVecBase3f &hpr) {
+  nassertv_always(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  nassertv(transform->has_components());
+  transform = TransformState::make_pos_hpr_scale
+    (pos, hpr, transform->get_scale());
+  set_transform(transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr_scale
+//       Access: Published
+//  Description: Sets the rotation and scale components of the
+//               transform, leaving translation untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_hpr_scale(const LVecBase3f &hpr, const LVecBase3f &scale) {
+  nassertv_always(!is_empty());
+  CPT(TransformState) transform = get_transform();
+  transform = TransformState::make_pos_hpr_scale
+    (transform->get_pos(), hpr, scale);
+  set_transform(transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr_scale
+//       Access: Published
+//  Description: Completely replaces the transform with new
+//               translation, rotation, and scale components.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos_hpr_scale(const LVecBase3f &pos, const LVecBase3f &hpr,
+                  const LVecBase3f &scale) {
+  nassertv_always(!is_empty());
+  set_transform(TransformState::make_pos_hpr_scale
+                (pos, hpr, scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_mat
+//       Access: Published
+//  Description: Directly sets an arbitrary 4x4 transform matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_mat(const LMatrix4f &mat) {
+  nassertv_always(!is_empty());
+  set_transform(TransformState::make_mat(mat));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_color_scale
+//       Access: Published
+//  Description: Sets the color scale component of the transform,
+//               leaving translation and rotation untouched.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_color_scale(const LVecBase4f &sv4) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_color_scale
+//       Access: Published
+//  Description: Returns the complete transform vector that has been
+//               applied to the bottom node, or the all 1's if no
+//               scale has been applied
+////////////////////////////////////////////////////////////////////
+LVecBase4f qpNodePath::
+get_color_scale() const {
+  nassertr(false, LVecBase4f(1.0f,1.0f,1.0f,1.0f));
+  return LVecBase4f(1.0f,1.0f,1.0f,1.0f);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at
+//       Access: Published
+//  Description: Sets the transform on this qpNodePath so that it
+//               rotates to face the indicated point in space.  This
+//               will overwrite any previously existing scale on the
+//               node, although it will preserve any translation.  See
+//               also look_at_preserve_scale().
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+look_at(const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LPoint3f pos = get_pos();
+
+  LMatrix4f mat;
+  ::look_at(mat, point - pos, up);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up
+//       Access: Published
+//  Description: Behaves like look_at(), but with a strong preference
+//               to keeping the up vector oriented in the indicated
+//               "up" direction.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+heads_up(const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LPoint3f pos = get_pos();
+
+  LMatrix4f mat;
+  ::heads_up(mat, point - pos, up);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at_preserve_scale
+//       Access: Published
+//  Description: Functions like look_at(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, look_at() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+look_at_preserve_scale(const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LMatrix4f mat = get_mat();
+
+  // Extract the axes from the matrix.
+  LVector3f x, y, z;
+
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  // The lengths of the axes defines the scale.
+
+  float scale_0 = length(x);
+  float scale_1 = length(y);
+  float scale_2 = length(z);
+
+  LPoint3f pos;
+  mat.get_row3(pos,3);
+  ::look_at(mat, point - pos, up);
+
+  // Now reapply the scale and position.
+
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  x *= scale_0;
+  y *= scale_1;
+  z *= scale_2;
+
+  mat.set_row(0, x);
+  mat.set_row(1, y);
+  mat.set_row(2, z);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up_preserve_scale
+//       Access: Published
+//  Description: Functions like heads_up(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, heads_up() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+heads_up_preserve_scale(const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LMatrix4f mat = get_mat();
+
+  // Extract the axes from the matrix.
+  LVector3f x, y, z;
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  float scale_0 = length(x);
+  float scale_1 = length(y);
+  float scale_2 = length(z);
+
+  // The lengths of the axes defines the scale.
+  LPoint3f pos;
+  mat.get_row3(pos,3);
+
+  ::heads_up(mat, point - pos, up);
+
+  // Now reapply the scale and position.
+
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  x *= scale_0;
+  y *= scale_1;
+  z *= scale_2;
+
+  mat.set_row(0, x);
+  mat.set_row(1, y);
+  mat.set_row(2, z);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos
+//       Access: Published
+//  Description: Sets the translation component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos(const qpNodePath &other, const LVecBase3f &pos) {
+  nassertv(!is_empty());
+  set_transform(other, get_transform(other)->set_pos(pos));
+}
+
+void qpNodePath::
+set_x(const qpNodePath &other, float x) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos(other);
+  pos[0] = x;
+  set_pos(other, pos);
+}
+
+void qpNodePath::
+set_y(const qpNodePath &other, float y) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos(other);
+  pos[1] = y;
+  set_pos(other, pos);
+}
+
+void qpNodePath::
+set_z(const qpNodePath &other, float z) {
+  nassertv(!is_empty());
+  LPoint3f pos = get_pos(other);
+  pos[2] = z;
+  set_pos(other, pos);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr
+//       Access: Published
+//  Description: Sets the rotation component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_hpr(const qpNodePath &other, const LVecBase3f &hpr) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  set_transform(other, transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_h(const qpNodePath &other, float h) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[0] = h;
+  set_transform(other, transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_p(const qpNodePath &other, float p) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[1] = p;
+  set_transform(other, transform->set_hpr(hpr));
+}
+
+void qpNodePath::
+set_r(const qpNodePath &other, float r) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f hpr = transform->get_hpr();
+  hpr[2] = r;
+  set_transform(other, transform->set_hpr(hpr));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_hpr
+//       Access: Published
+//  Description: Returns the relative orientation of the bottom node
+//               as seen from the other node.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_hpr(const qpNodePath &other) const {
+  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  CPT(TransformState) transform = get_transform(other);
+  nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  return transform->get_hpr();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_hpr
+//       Access: Published
+//  Description: Returns the relative orientation of the bottom node
+//               as seen from the other node.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_hpr(const qpNodePath &other, float roll) const {
+  // This is still doing it the dumb way, with a decomposition.  This
+  // function is deprecated anyway.
+  LMatrix4f mat = get_mat(other);
+  LVector3f scale, hpr, pos;
+  decompose_matrix(mat, scale, hpr, pos, roll);
+  return hpr;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_scale
+//       Access: Published
+//  Description: Sets the scale component of the transform,
+//               relative to the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_scale(const qpNodePath &other, const LVecBase3f &scale) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  set_transform(other, transform->set_scale(scale));
+}
+
+void qpNodePath::
+set_sx(const qpNodePath &other, float sx) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[0] = sx;
+  set_transform(other, transform->set_scale(scale));
+}
+
+void qpNodePath::
+set_sy(const qpNodePath &other, float sy) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[1] = sy;
+  set_transform(other, transform->set_scale(scale));
+}
+
+void qpNodePath::
+set_sz(const qpNodePath &other, float sz) {
+  nassertv(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  LVecBase3f scale = transform->get_scale();
+  scale[2] = sz;
+  set_transform(other, transform->set_scale(scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_scale
+//       Access: Published
+//  Description: Returns the relative scale of the bottom node
+//               as seen from the other node.
+////////////////////////////////////////////////////////////////////
+LVecBase3f qpNodePath::
+get_scale(const qpNodePath &other) const {
+  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  CPT(TransformState) transform = get_transform(other);
+  nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  return transform->get_scale();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr
+//       Access: Published
+//  Description: Sets the translation and rotation component of the
+//               transform, relative to the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos_hpr(const qpNodePath &other, const LVecBase3f &pos,
+            const LVecBase3f &hpr) {
+  nassertv_always(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  nassertv(transform->has_components());
+  transform = TransformState::make_pos_hpr_scale
+    (pos, hpr, transform->get_scale());
+  set_transform(other, transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_hpr_scale
+//       Access: Published
+//  Description: Sets the rotation and scale components of the
+//               transform, leaving translation untouched.  This, or
+//               set_pos_hpr_scale, is the preferred way to update a
+//               transform when both hpr and scale are to be changed.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_hpr_scale(const qpNodePath &other, const LVecBase3f &hpr, const LVecBase3f &scale) {
+  nassertv_always(!is_empty());
+  CPT(TransformState) transform = get_transform(other);
+  transform = TransformState::make_pos_hpr_scale
+    (transform->get_pos(), hpr, scale);
+  set_transform(other, transform);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_pos_hpr_scale
+//       Access: Published
+//  Description: Completely replaces the transform with new
+//               translation, rotation, and scale components, relative
+//               to the other node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_pos_hpr_scale(const qpNodePath &other,
+                  const LVecBase3f &pos, const LVecBase3f &hpr,
+                  const LVecBase3f &scale) {
+  nassertv_always(!is_empty());
+  set_transform(other, TransformState::make_pos_hpr_scale
+                (pos, hpr, scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_mat
+//       Access: Published
+//  Description: Returns the matrix that describes the coordinate
+//               space of the bottom node, relative to the other
+//               path's bottom node's coordinate space.
+////////////////////////////////////////////////////////////////////
+const LMatrix4f &qpNodePath::
+get_mat(const qpNodePath &other) const {
+  CPT(TransformState) transform = get_transform(other);
+  // We can safely assume the transform won't go away when the
+  // function returns, since its reference count is also held in the
+  // cache.  This assumption allows us to return a reference to the
+  // matrix, instead of having to return a matrix on the stack.
+  nassertr(transform->get_ref_count() > 1, LMatrix4f::ident_mat());
+  return transform->get_mat();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_mat
+//       Access: Published
+//  Description: Converts the indicated matrix from the other's
+//               coordinate space to the local coordinate space, and
+//               applies it to the node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_mat(const qpNodePath &other, const LMatrix4f &mat) {
+  nassertv_always(!is_empty());
+  set_transform(other, TransformState::make_mat(mat));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_relative_point
+//       Access: Published
+//  Description: Given that the indicated point is in the coordinate
+//               system of the other node, returns the same point in
+//               this node's coordinate system.
+////////////////////////////////////////////////////////////////////
+LPoint3f qpNodePath::
+get_relative_point(const qpNodePath &other, const LVecBase3f &point) {
+  LPoint3f rel_point = LPoint3f(point) * other.get_mat(*this);
+  return rel_point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at
+//       Access: Published
+//  Description: Sets the transform on this qpNodePath so that it
+//               rotates to face the indicated point in space, which
+//               is relative to the other qpNodePath.  This
+//               will overwrite any previously existing scale on the
+//               node, although it will preserve any translation.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+look_at(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LPoint3f pos = get_pos();
+
+  qpNodePath parent = get_parent();
+  LPoint3f rel_point = point * other.get_mat(parent);
+
+  LMatrix4f mat;
+  ::look_at(mat, rel_point - pos, up);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up
+//       Access: Published
+//  Description: Behaves like look_at(), but with a strong preference
+//               to keeping the up vector oriented in the indicated
+//               "up" direction.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+heads_up(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LPoint3f pos = get_pos();
+
+  qpNodePath parent = get_parent();
+  LPoint3f rel_point = point * other.get_mat(parent);
+
+  LMatrix4f mat;
+  ::heads_up(mat, rel_point - pos, up);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::look_at_preserve_scale
+//       Access: Published
+//  Description: Functions like look_at(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, look_at() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+look_at_preserve_scale(const qpNodePath &other, const LPoint3f &point,
+                       const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LMatrix4f mat = get_mat();
+
+  // Extract the axes from the matrix.
+  LVector3f x, y, z;
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  // The lengths of the axes defines the scale.
+  float scale_0 = length(x);
+  float scale_1 = length(y);
+  float scale_2 = length(z);
+
+  LPoint3f pos;
+  mat.get_row3(pos,3);
+
+  qpNodePath parent = get_parent();
+  LPoint3f rel_point = point * other.get_mat(parent);
+
+  ::look_at(mat, rel_point - pos, up);
+
+  // Now reapply the scale and position.
+
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  x *= scale_0;
+  y *= scale_1;
+  z *= scale_2;
+
+  mat.set_row(0, x);
+  mat.set_row(1, y);
+  mat.set_row(2, z);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::heads_up_preserve_scale
+//       Access: Published
+//  Description: Functions like heads_up(), but preforms additional
+//               work to preserve any scales that may already be
+//               present on the node.  Normally, heads_up() blows away
+//               the scale because scale and rotation are represented
+//               in the same part of the matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+heads_up_preserve_scale(const qpNodePath &other, const LPoint3f &point,
+                        const LVector3f &up) {
+  nassertv(!is_empty());
+
+  LMatrix4f mat = get_mat();
+
+  // Extract the axes from the matrix.
+  LVector3f x, y, z;
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  // The lengths of the axes defines the scale.
+  float scale_0 = length(x);
+  float scale_1 = length(y);
+  float scale_2 = length(z);
+
+  LPoint3f pos;
+  mat.get_row3(pos,3);
+
+  qpNodePath parent = get_parent();
+  LPoint3f rel_point = point * other.get_mat(parent);
+
+  ::heads_up(mat, rel_point - pos, up);
+
+  // Now reapply the scale and position.
+  mat.get_row3(x,0);
+  mat.get_row3(y,1);
+  mat.get_row3(z,2);
+
+  x *= scale_0;
+  y *= scale_1;
+  z *= scale_2;
+
+  mat.set_row(0, x);
+  mat.set_row(1, y);
+  mat.set_row(2, z);
+  mat.set_row(3, pos);
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_color
+//       Access: Published
+//  Description: Applies a scene-graph color to the referenced node.
+//               This color will apply to all geometry at this level
+//               and below (that does not specify a new color or a
+//               set_color_off()).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_color(float r, float g, float b, float a,
+          int priority) {
+  set_color(Colorf(r, g, b, a), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_color
+//       Access: Published
+//  Description: Applies a scene-graph color to the referenced node.
+//               This color will apply to all geometry at this level
+//               and below (that does not specify a new color or a
+//               set_color_off()).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_color(const Colorf &color, int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(ColorAttrib::make_flat(color), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_color_off
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using the geometry color.  This is normally the
+//               default, but it may be useful to use this to
+//               contradict set_color() at a higher node level (or,
+//               with a priority, to override a set_color() at a lower
+//               level).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_color_off(int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(ColorAttrib::make_vertex(), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_color
+//       Access: Published
+//  Description: Completely removes any color adjustment from the node.
+//               This allows the natural color of the geometry, or
+//               whatever color transitions might be otherwise
+//               affecting the geometry, to show instead.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_color() {
+  nassertv(!is_empty());
+  node()->clear_attrib(ColorAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_color
+//       Access: Published
+//  Description: Returns true if a color has been applied to the given
+//               node, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_color() const {
+  nassertr(!is_empty(), false);
+  return node()->has_attrib(ColorAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_color
+//       Access: Published
+//  Description: Returns the color that has been assigned to the node,
+//               or black if no color has been assigned.
+////////////////////////////////////////////////////////////////////
+Colorf qpNodePath::
+get_color() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(ColorAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const ColorAttrib *ca = DCAST(ColorAttrib, attrib);
+    if (ca->get_color_type() == ColorAttrib::T_flat) {
+      return ca->get_color();
+    }
+  }
+
+  pgraph_cat.warning()
+    << "get_color() called on " << *this << " which has no color set.\n";
+
+  return Colorf(0.0f, 0.0f, 0.0f, 0.0f);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_bin
+//       Access: Published
+//  Description: Assigns the geometry at this level and below to the
+//               named rendering bin.  It is the user's responsibility
+//               to ensure that such a bin already exists, either via
+//               the cull-bin Configrc variable, or by explicitly
+//               creating a GeomBin of the appropriate type at
+//               runtime.
+//
+//               There are two default bins created when Panda is
+//               started: "default" and "fixed".  Normally, all
+//               geometry is assigned to "default" unless specified
+//               otherwise.  This bin renders opaque geometry in
+//               state-sorted order, followed by transparent geometry
+//               sorted back-to-front.  If any geometry is assigned to
+//               "fixed", this will be rendered following all the
+//               geometry in "default", in the order specified by
+//               draw_order for each piece of geometry so assigned.
+//
+//               The draw_order parameter is meaningful only for
+//               GeomBinFixed type bins, e.g. "fixed".  Other kinds of
+//               bins ignore it.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_bin(const string &bin_name, int draw_order, int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(CullBinAttrib::make(bin_name, draw_order), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_bin
+//       Access: Published
+//  Description: Completely removes any bin adjustment that may have
+//               been set via set_bin() from this particular node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_bin() {
+  nassertv(!is_empty());
+  node()->clear_attrib(CullBinAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_bin
+//       Access: Published
+//  Description: Returns true if the node has been assigned to the a
+//               particular rendering bin via set_bin(), false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_bin() const {
+  nassertr(!is_empty(), false);
+  return node()->has_attrib(CullBinAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_bin_name
+//       Access: Published
+//  Description: Returns the name of the bin that this particular node
+//               was assigned to via set_bin(), or the empty string if
+//               no bin was assigned.  See set_bin() and has_bin().
+////////////////////////////////////////////////////////////////////
+string qpNodePath::
+get_bin_name() const {
+  nassertr(!is_empty(), string());
+  const RenderAttrib *attrib =
+    node()->get_attrib(ColorAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const CullBinAttrib *ba = DCAST(CullBinAttrib, attrib);
+    return ba->get_bin_name();
+  }
+
+  return string();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_bin_draw_order
+//       Access: Published
+//  Description: Returns the drawing order associated with the bin
+//               that this particular node was assigned to via
+//               set_bin(), or 0 if no bin was assigned.  See
+//               set_bin() and has_bin().
+////////////////////////////////////////////////////////////////////
+int qpNodePath::
+get_bin_draw_order() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(ColorAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const CullBinAttrib *ba = DCAST(CullBinAttrib, attrib);
+    return ba->get_draw_order();
+  }
+
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_texture
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using the indicated texture.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_texture(Texture *tex, int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(TextureAttrib::make(tex), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_texture_off
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using no texture.  This is normally the default, but
+//               it may be useful to use this to contradict
+//               set_texture() at a higher node level (or, with a
+//               priority, to override a set_texture() at a lower
+//               level).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_texture_off(int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(TextureAttrib::make_off(), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_texture
+//       Access: Published
+//  Description: Completely removes any texture adjustment that may
+//               have been set via set_texture() or set_texture_off()
+//               from this particular node.  This allows whatever
+//               textures might be otherwise affecting the geometry to
+//               show instead.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_texture() {
+  nassertv(!is_empty());
+  node()->clear_attrib(TextureAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_texture
+//       Access: Published
+//  Description: Returns true if a texture has been applied to this
+//               particular node via set_texture(), false otherwise.
+//               This is not the same thing as asking whether the
+//               geometry at this node will be rendered with
+//               texturing, as there may be a texture in effect from a
+//               higher or lower level.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_texture() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(TextureAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
+    return !ta->is_off();
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_texture_off
+//       Access: Published
+//  Description: Returns true if a texture has been specifically
+//               disabled on this particular node via
+//               set_texture_off(), false otherwise.  This is not the
+//               same thing as asking whether the geometry at this
+//               node will be rendered untextured, as there may be a
+//               texture in effect from a higher or lower level.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_texture_off() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(ColorAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
+    return ta->is_off();
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_texture
+//       Access: Published
+//  Description: Returns the texture that has been set on this
+//               particular node, or NULL if no texture has been set.
+//               This is not necessarily the texture that will be
+//               applied to the geometry at or below this level, as
+//               another texture at a higher or lower level may
+//               override.
+////////////////////////////////////////////////////////////////////
+Texture *qpNodePath::
+get_texture() const {
+  nassertr(!is_empty(), NULL);
+  const RenderAttrib *attrib =
+    node()->get_attrib(TextureAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
+    return ta->get_texture();
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_material
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using the indicated material.
+//
+//               This operation copies the given material pointer.  If
+//               the material structure is changed later, it must be
+//               reapplied via another call to set_material().
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_material(Material *mat, int priority) {
+  nassertv_always(!is_empty());
+  nassertv(mat != NULL);
+
+  // We create a temporary Material pointer, a copy of the one we are
+  // given, to allow the user to monkey with the material and set it
+  // again later, with the desired effect.  If we stored the user's
+  // pointer directly, it would be bad if the user later modified the
+  // values within the Material.
+  PT(Material) temp = new Material(*mat);
+  const Material *mp = MaterialPool::get_material(temp);
+
+  node()->set_attrib(MaterialAttrib::make(mp), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_material_off
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using no material.  This is normally the default, but
+//               it may be useful to use this to contradict
+//               set_material() at a higher node level (or, with a
+//               priority, to override a set_material() at a lower
+//               level).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_material_off(int priority) {
+  nassertv_always(!is_empty());
+  node()->set_attrib(MaterialAttrib::make_off(), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_material
+//       Access: Published
+//  Description: Completely removes any material adjustment that may
+//               have been set via set_material() from this particular
+//               node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_material() {
+  nassertv(!is_empty());
+  node()->clear_attrib(MaterialAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_material
+//       Access: Published
+//  Description: Returns true if a material has been applied to this
+//               particular node via set_material(), false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_material() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(MaterialAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const MaterialAttrib *ma = DCAST(MaterialAttrib, attrib);
+    return !ma->is_off();
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_material
+//       Access: Published
+//  Description: Returns the material that has been set on this
+//               particular node, or NULL if no material has been set.
+//               This is not necessarily the material that will be
+//               applied to the geometry at or below this level, as
+//               another material at a higher or lower level may
+//               override.
+//
+//               This function returns a copy of the given material,
+//               to allow changes, if desired.  Once changes are made,
+//               they should be reapplied via set_material().
+////////////////////////////////////////////////////////////////////
+PT(Material) qpNodePath::
+get_material() const {
+  nassertr(!is_empty(), NULL);
+  const RenderAttrib *attrib =
+    node()->get_attrib(MaterialAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const MaterialAttrib *ma = DCAST(MaterialAttrib, attrib);
+    return new Material(*ma->get_material());
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_fog
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using the indicated fog.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_fog(Fog *fog, int priority) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_fog_off
+//       Access: Published
+//  Description: Sets the geometry at this level and below to render
+//               using no fog.  This is normally the default, but
+//               it may be useful to use this to contradict
+//               set_fog() at a higher node level (or, with a
+//               priority, to override a set_fog() at a lower
+//               level).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_fog_off(int priority) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_fog
+//       Access: Published
+//  Description: Completely removes any fog adjustment that may
+//               have been set via set_fog() or set_fog_off()
+//               from this particular node.  This allows whatever
+//               fogs might be otherwise affecting the geometry to
+//               show instead.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_fog() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_fog
+//       Access: Published
+//  Description: Returns true if a fog has been applied to this
+//               particular node via set_fog(), false otherwise.
+//               This is not the same thing as asking whether the
+//               geometry at this node will be rendered with
+//               fog, as there may be a fog in effect from a higher or
+//               lower level.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_fog() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_fog_off
+//       Access: Published
+//  Description: Returns true if a fog has been specifically
+//               disabled on this particular node via
+//               set_fog_off(), false otherwise.  This is not the
+//               same thing as asking whether the geometry at this
+//               node will be rendered unfogged, as there may be a
+//               fog in effect from a higher or lower level.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_fog_off() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_fog
+//       Access: Published
+//  Description: Returns the fog that has been set on this
+//               particular node, or NULL if no fog has been set.
+//               This is not necessarily the fog that will be
+//               applied to the geometry at or below this level, as
+//               another fog at a higher or lower level may
+//               override.
+////////////////////////////////////////////////////////////////////
+Fog *qpNodePath::
+get_fog() const {
+  nassertr(false, NULL);
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_render_mode_wireframe
+//       Access: Published
+//  Description: Sets up the geometry at this level and below (unless
+//               overridden) to render in wireframe mode.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_render_mode_wireframe(int priority) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_render_mode_filled
+//       Access: Published
+//  Description: Sets up the geometry at this level and below (unless
+//               overridden) to render in filled (i.e. not wireframe)
+//               mode.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_render_mode_filled(int priority) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_render_mode
+//       Access: Published
+//  Description: Completely removes any render mode adjustment that
+//               may have been set on this node via
+//               set_render_mode_wireframe() or
+//               set_render_mode_filled().
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_render_mode() {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_render_mode
+//       Access: Published
+//  Description: Returns true if a render mode has been explicitly set
+//               on this particular node via
+//               set_render_mode_wireframe() or
+//               set_render_mode_filled(), false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_render_mode() const {
+  nassertr(false, false);
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_two_sided
+//       Access: Published
+//  Description: Specifically sets or disables two-sided rendering
+//               mode on this particular node.  If no other nodes
+//               override, this will cause backfacing polygons to be
+//               drawn (in two-sided mode, true) or culled (in
+//               one-sided mode, false).
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_two_sided(bool two_sided, int priority) {
+  nassertv_always(!is_empty());
+
+  CullFaceAttrib::Mode mode =
+    two_sided ?
+    CullFaceAttrib::M_cull_none :
+    CullFaceAttrib::M_cull_clockwise;
+
+  node()->set_attrib(CullFaceAttrib::make(mode), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_two_sided
+//       Access: Published
+//  Description: Completely removes any two-sided adjustment that
+//               may have been set on this node via set_two_sided().
+//               The geometry at this level and below will
+//               subsequently be rendered either two-sided or
+//               one-sided, according to whatever other nodes may have
+//               had set_two_sided() on it, or according to the
+//               initial state otherwise.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_two_sided() {
+  nassertv(!is_empty());
+  node()->clear_attrib(CullFaceAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_two_sided
+//       Access: Published
+//  Description: Returns true if a two-sided adjustment has been
+//               explicitly set on this particular node via
+//               set_two_sided().  If this returns true, then
+//               get_two_sided() may be called to determine which has
+//               been set.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_two_sided() const {
+  nassertr(!is_empty(), false);
+  return node()->has_attrib(CullFaceAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_two_sided
+//       Access: Published
+//  Description: Returns true if two-sided rendering has been
+//               specifically set on this node via set_two_sided(), or
+//               false if one-sided rendering has been specifically
+//               set, or if nothing has been specifically set.  See
+//               also has_two_sided().  This does not necessarily
+//               imply that the geometry will or will not be rendered
+//               two-sided, as there may be other nodes that override.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+get_two_sided() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(CullFaceAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const CullFaceAttrib *cfa = DCAST(CullFaceAttrib, attrib);
+    return (cfa->get_mode() == CullFaceAttrib::M_cull_none);
+  }
+
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::do_billboard_axis
+//       Access: Published
+//  Description: Performs a billboard-type rotate to the indicated
+//               camera node, one time only, and leaves the object
+//               rotated.  This is similar in principle to heads_up().
+//               However, it does lose both translate and scale
+//               components of the matrix.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+do_billboard_axis(const qpNodePath &camera, float offset) {
+  nassertv(!is_empty());
+
+  qpNodePath parent = get_parent();
+  LMatrix4f rel_mat = camera.get_mat(parent);
+
+  LVector3f up = LVector3f::up();
+  LVector3f rel_pos = -rel_mat.get_row3(3);
+
+  LMatrix4f mat;
+  ::heads_up(mat, rel_pos, up);
+
+  // Also slide the geometry towards the camera according to the
+  // offset factor.
+  if (offset != 0.0f) {
+    LVector3f translate = rel_mat.get_row3(3);
+    translate.normalize();
+    translate *= offset;
+    mat.set_row(3, translate);
+  }
+
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::do_billboard_point_eye
+//       Access: Published
+//  Description: Performs a billboard-type rotate to the indicated
+//               camera node, one time only, and leaves the object
+//               rotated.  This is similar in principle to look_at(),
+//               although the point_eye billboard effect cannot be
+//               achieved using the ordinary look_at() call.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+do_billboard_point_eye(const qpNodePath &camera, float offset) {
+  nassertv(!is_empty());
+
+  qpNodePath parent = get_parent();
+  LMatrix4f rel_mat = camera.get_mat(parent);
+
+  LVector3f up = LVector3f::up() * rel_mat;
+  LVector3f rel_pos = LVector3f::forward() * rel_mat;
+
+  LMatrix4f mat;
+  ::look_at(mat, rel_pos, up);
+
+  // Also slide the geometry towards the camera according to the
+  // offset factor.
+  if (offset != 0.0f) {
+    LVector3f translate = rel_mat.get_row3(3);
+    translate.normalize();
+    translate *= offset;
+    mat.set_row(3, translate);
+  }
+
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::do_billboard_point_world
+//       Access: Published
+//  Description: Performs a billboard-type rotate to the indicated
+//               camera node, one time only, and leaves the object
+//               rotated.  This is similar in principle to look_at().
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+do_billboard_point_world(const qpNodePath &camera, float offset) {
+  nassertv(!is_empty());
+
+  qpNodePath parent = get_parent();
+  LMatrix4f rel_mat = camera.get_mat(parent);
+
+  LVector3f up = LVector3f::up();
+  LVector3f rel_pos = -rel_mat.get_row3(3);
+
+  LMatrix4f mat;
+  ::look_at(mat, rel_pos, up);
+
+  // Also slide the geometry towards the camera according to the
+  // offset factor.
+  if (offset != 0.0f) {
+    LVector3f translate = rel_mat.get_row3(3);
+    translate.normalize();
+    translate *= offset;
+    mat.set_row(3, translate);
+  }
+
+  set_mat(mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_billboard_axis
+//       Access: Published
+//  Description: Puts a billboard transition on the node such that it
+//               will rotate in two dimensions around the up axis.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_billboard_axis(float offset) {
+  nassertv(!is_empty());
+  CPT(RenderAttrib) billboard = BillboardAttrib::make
+    (LVector3f::up(), false, true, 
+     offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
+  node()->set_attrib(billboard);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_billboard_point_eye
+//       Access: Published
+//  Description: Puts a billboard transition on the node such that it
+//               will rotate in three dimensions about the origin,
+//               keeping its up vector oriented to the top of the
+//               camera.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_billboard_point_eye(float offset) {
+  nassertv(!is_empty());
+  CPT(RenderAttrib) billboard = BillboardAttrib::make
+    (LVector3f::up(), true, false,
+     offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
+  node()->set_attrib(billboard);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_billboard_point_world
+//       Access: Published
+//  Description: Puts a billboard transition on the node such that it
+//               will rotate in three dimensions about the origin,
+//               keeping its up vector oriented to the sky.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_billboard_point_world(float offset) {
+  nassertv(!is_empty());
+  CPT(RenderAttrib) billboard = BillboardAttrib::make
+    (LVector3f::up(), false, false,
+     offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
+  node()->set_attrib(billboard);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_billboard
+//       Access: Published
+//  Description: Removes any billboard attributes from the node.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_billboard() {
+  nassertv(!is_empty());
+  node()->clear_attrib(BillboardAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_billboard
+//       Access: Published
+//  Description: Returns true if there is any billboard attribute on
+//               the node.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_billboard() const {
+  nassertr(!is_empty(), false);
+  return node()->has_attrib(BillboardAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_transparency
+//       Access: Published
+//  Description: Specifically sets or disables transparent rendering
+//               mode on this particular node.  If no other nodes
+//               override, this will cause items with a non-1 value
+//               for alpha color to be rendered partially transparent.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+set_transparency(bool transparency, int priority) {
+  nassertv_always(!is_empty());
+
+  TransparencyAttrib::Mode mode =
+    transparency ?
+    TransparencyAttrib::M_alpha :
+    TransparencyAttrib::M_none;
+
+  node()->set_attrib(TransparencyAttrib::make(mode), priority);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::clear_transparency
+//       Access: Published
+//  Description: Completely removes any transparency adjustment that
+//               may have been set on this node via set_transparency().
+//               The geometry at this level and below will
+//               subsequently be rendered either transparent or not,
+//               to whatever other nodes may have had
+//               set_transparency() on them.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+clear_transparency() {
+  nassertv(!is_empty());
+  node()->clear_attrib(TransparencyAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::has_transparency
+//       Access: Published
+//  Description: Returns true if a transparent-rendering adjustment
+//               has been explicitly set on this particular node via
+//               set_transparency().  If this returns true, then
+//               get_transparency() may be called to determine whether
+//               transparency has been explicitly enabled or
+//               explicitly disabled for this node.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+has_transparency() const {
+  nassertr(!is_empty(), false);
+  return node()->has_attrib(TransparencyAttrib::get_class_type());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_transparency
+//       Access: Published
+//  Description: Returns true if transparent rendering has been
+//               specifically set on this node via set_transparency(), or
+//               false if nontransparent rendering has been specifically
+//               set, or if nothing has been specifically set.  See
+//               also has_transparency().  This does not necessarily
+//               imply that the geometry will or will not be rendered
+//               transparent, as there may be other nodes that override.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+get_transparency() const {
+  nassertr(!is_empty(), false);
+  const RenderAttrib *attrib =
+    node()->get_attrib(TransparencyAttrib::get_class_type());
+  if (attrib != (const RenderAttrib *)NULL) {
+    const TransparencyAttrib *cfa = DCAST(TransparencyAttrib, attrib);
+    return (cfa->get_mode() != TransparencyAttrib::M_none);
+  }
+
+  return false;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_hidden_ancestor
+//       Access: Published
+//  Description: Returns the NodePath at or above the referenced node
+//               that is hidden, or an empty NodePath if no ancestor
+//               of the referenced node is hidden (and the node should
+//               be visible).
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePath::
+get_hidden_ancestor() const {
+  nassertr(false, qpNodePath());
+  return qpNodePath();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_stashed_ancestor
+//       Access: Published
+//  Description: Returns the NodePath at or above the referenced node
+//               that is stashed, or an empty NodePath if no ancestor
+//               of the referenced node is stashed (and the node should
+//               be visible).
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePath::
+get_stashed_ancestor() const {
+  nassertr(false, qpNodePath());
+  return qpNodePath();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::verify_complete
+//       Access: Published
+//  Description: Returns true if all of the nodes described in the
+//               qpNodePath are connected *and* the top node is the top
+//               of the graph, or false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePath::
+verify_complete() const {
+  if (is_empty()) {
+    return true;
+  }
+
+  uncollapse_head();
+  const qpNodePathComponent *comp = _head;
+  nassertr(comp != (const qpNodePathComponent *)NULL, false);
+
+  PandaNode *node = comp->get_node();
+  nassertr(node != (const PandaNode *)NULL, false);
+  int length = comp->get_length();
+
+  comp = comp->get_next();
+  length--;
+  while (comp != (const qpNodePathComponent *)NULL) {
+    PandaNode *next_node = comp->get_node();
+    nassertr(next_node != (const PandaNode *)NULL, false);
+
+    if (next_node->find_child(node) < 0) {
+      return false;
+    }
+
+    if (comp->get_length() != length) {
+      return false;
+    }
+
+    node = next_node;
+    comp = comp->get_next();
+    length--;
+  }
+
+  // Now that we've reached the top, we should have no parents.
+  return length == 0 && node->get_num_parents() == 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::uncollapse_head
+//       Access: Private
+//  Description: Quietly and transparently uncollapses the _head
+//               pointer if it needs it.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+uncollapse_head() const {
+  if (_head != (qpNodePathComponent *)NULL && _head->is_collapsed()) {
+    ((qpNodePath *)this)->_head = _head->uncollapse();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::find_common_ancestor
+//       Access: Private, Static
+//  Description: Walks up from both qpNodePaths to find the first node
+//               that both have in common, if any.  Fills a_count and
+//               b_count with the number of nodes below the common
+//               node in each path.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+find_common_ancestor(const qpNodePath &a, const qpNodePath &b,
+                     int &a_count, int &b_count) {
+  nassertv(!a.is_empty() && !b.is_empty());
+  a.uncollapse_head();
+  b.uncollapse_head();
+
+  qpNodePathComponent *ac = a._head;
+  qpNodePathComponent *bc = b._head;
+  a_count = 0;
+  b_count = 0;
+
+  // Shorten up the longer one until they are the same length.
+  while (ac->get_length() > bc->get_length()) {
+    nassertv(ac != (qpNodePathComponent *)NULL);
+    ac = ac->get_next();
+    a_count++;
+  }
+  while (bc->get_length() > ac->get_length()) {
+    nassertv(bc != (qpNodePathComponent *)NULL);
+    bc = bc->get_next();
+    b_count++;
+  }
+
+  // Now shorten them both up until we reach the same component.
+  while (ac != bc) {
+    // These shouldn't go to NULL unless they both go there together. 
+    nassertv(ac != (qpNodePathComponent *)NULL);
+    nassertv(bc != (qpNodePathComponent *)NULL);
+    ac = ac->get_next();
+    a_count++;
+    bc = bc->get_next();
+    b_count++;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_get_net_state
+//       Access: Private
+//  Description: Recursively determines the net state chnages to the
+//               indicated component node from the root of the graph.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) qpNodePath::
+r_get_net_state(qpNodePathComponent *comp) const {
+  if (comp == (qpNodePathComponent *)NULL) {
+    return RenderState::make_empty();
+  } else {
+    CPT(RenderState) state = comp->get_node()->get_state();
+    return r_get_net_state(comp->get_next())->compose(state);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_get_partial_state
+//       Access: Private
+//  Description: Recursively determines the net state changes to the
+//               indicated component node from the nth node above it.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) qpNodePath::
+r_get_partial_state(qpNodePathComponent *comp, int n) const {
+  nassertr(comp != (qpNodePathComponent *)NULL, RenderState::make_empty());
+  if (n == 0) {
+    return RenderState::make_empty();
+  } else {
+    CPT(RenderState) state = comp->get_node()->get_state();
+    return r_get_partial_state(comp->get_next(), n - 1)->compose(state);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_get_net_transform
+//       Access: Private
+//  Description: Recursively determines the net transform to the
+//               indicated component node from the root of the graph.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) qpNodePath::
+r_get_net_transform(qpNodePathComponent *comp) const {
+  if (comp == (qpNodePathComponent *)NULL) {
+    return TransformState::make_identity();
+  } else {
+    CPT(TransformState) transform = comp->get_node()->get_transform();
+    return r_get_net_transform(comp->get_next())->compose(transform);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_get_partial_transform
+//       Access: Private
+//  Description: Recursively determines the net transform to the
+//               indicated component node from the nth node above it.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) qpNodePath::
+r_get_partial_transform(qpNodePathComponent *comp, int n) const {
+  nassertr(comp != (qpNodePathComponent *)NULL, TransformState::make_identity());
+  if (n == 0) {
+    return TransformState::make_identity();
+  } else {
+    CPT(TransformState) transform = comp->get_node()->get_transform();
+    return r_get_partial_transform(comp->get_next(), n - 1)->compose(transform);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_output
+//       Access: Private
+//  Description: The recursive implementation of output(), this writes
+//               the names of each node component in order from
+//               beginning to end, by first walking to the end of the
+//               linked list and then outputting from there.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+r_output(ostream &out, qpNodePathComponent *comp) const {
+  qpNodePathComponent *next = comp->get_next();
+  if (next != (qpNodePathComponent *)NULL) {
+    // This is not the head of the list; keep going up.
+    r_output(out, next);
+    out << "/";
+  }
+
+  // Now output this component.
+  PandaNode *node = comp->get_node();
+  if (node->has_name()) {
+    out << node->get_name();
+  } else {
+    out << "+" << node->get_type();
+  }
+  out << "[" << comp->get_length() << "]";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_compare_to
+//       Access: Private, Static
+//  Description: The recursive implementation of compare_to().  Returns
+//               < 0 if a sorts before b, > 0 if b sorts before a, or
+//               == 0 if they are equivalent.
+////////////////////////////////////////////////////////////////////
+int qpNodePath::
+r_compare_to(const qpNodePathComponent *a, const qpNodePathComponent *b) {
+  if (a == b) {
+    return 0;
+
+  } else if (a == (const qpNodePathComponent *)NULL) {
+    return -1;
+
+  } else if (b == (const qpNodePathComponent *)NULL) {
+    return 1;
+
+  } else if (a->get_node() != b->get_node()) {
+    return a->get_node() - b->get_node();
+
+  } else {
+    return r_compare_to(a->get_next(), b->get_next());
+  }
+}

+ 427 - 0
panda/src/pgraph/qpnodePath.h

@@ -0,0 +1,427 @@
+// Filename: qpnodePath.h
+// Created by:  drose (25Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpNODEPATH_H
+#define qpNODEPATH_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+#include "renderState.h"
+#include "transformState.h"
+#include "qpnodePathComponent.h"
+
+#include "pointerTo.h"
+#include "referenceCount.h"
+#include "notify.h"
+#include "typedObject.h"
+
+class qpNodePathCollection;
+class Texture;
+class Material;
+class Fog;
+
+////////////////////////////////////////////////////////////////////
+//       Class : NodePath
+// Description : NodePath is the fundamental system for disambiguating
+//               instances, and also provides a higher-level interface
+//               for manipulating the scene graph.
+//
+//               A NodePath is a list of connected nodes from the root
+//               of the graph to any sub-node.  Each NodePath
+//               therefore unqiuely describes one instance of a node.
+//
+//               NodePaths themselves are lightweight objects that may
+//               easily be copied and passed by value.  Their data is
+//               stored as a series of NodePathComponents that are
+//               stored on the nodes.  Holding a NodePath will keep a
+//               reference count to all the nodes in the chain.
+//               However, if any node in the chain is removed or
+//               reparented (perhaps through a different NodePath),
+//               the NodePath will automatically be updated to reflect
+//               the changes.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpNodePath {
+PUBLISHED:
+  // This enumeration is returned by get_error_type() for an empty
+  // qpNodePath to report the reason it's empty.
+  enum ErrorType {
+    ET_ok = 0,     // i.e. not empty, or never assigned to anything.
+    ET_not_found,  // returned from a failed find() or similar function.
+    ET_removed,    // remove_node() was previously called on this qpNodePath.
+    ET_fail,       // general failure return from some function.
+  };
+
+  INLINE qpNodePath();
+  INLINE qpNodePath(PandaNode *top_node);
+  INLINE qpNodePath(const qpNodePath &copy);
+  INLINE void operator = (const qpNodePath &copy);
+
+  INLINE static qpNodePath not_found();
+  INLINE static qpNodePath removed();
+  INLINE static qpNodePath fail();
+
+  // Methods to query a qpNodePath's contents.
+  INLINE bool is_empty() const;
+  INLINE bool is_singleton() const;
+  int get_num_nodes() const;
+  PandaNode *get_node(int index) const;
+
+  INLINE ErrorType get_error_type() const;
+
+  PandaNode *get_top_node() const;
+  INLINE PandaNode *node() const;
+
+  // Methods that return collections of NodePaths derived from or
+  // related to this one.
+
+  qpNodePathCollection get_children() const;
+  INLINE int get_num_children() const;
+  INLINE qpNodePath get_child(int n) const;
+
+  INLINE bool has_parent() const;
+  INLINE qpNodePath get_parent() const;
+
+  /*
+  INLINE qpNodePath find(const string &path) const;
+
+  qpNodePathCollection
+  find_all_matches(const string &path) const;
+  */
+
+  // Methods that actually move nodes around in the scene graph.  The
+  // optional "sort" parameter can be used to force a particular
+  // ordering between sibling nodes, useful when dealing with LOD's
+  // and similar switch nodes.  If the sort value is the same, nodes
+  // will be arranged in the order they were added.
+  void reparent_to(const qpNodePath &other, int sort = 0);
+  void wrt_reparent_to(const qpNodePath &other, int sort = 0);
+  qpNodePath instance_to(const qpNodePath &other, int sort = 0) const;
+  qpNodePath copy_to(const qpNodePath &other, int sort = 0) const;
+  qpNodePath attach_new_node(PandaNode *node, int sort = 0) const;
+  INLINE qpNodePath attach_new_node(const string &name, int sort = 0) const;
+  void remove_node();
+
+  // Handy ways to look at what's there, and other miscellaneous
+  // operations.
+
+  INLINE void output(ostream &out) const;
+
+  INLINE void ls() const;
+  INLINE void ls(ostream &out, int indent_level = 0) const;
+  INLINE void ls_transitions() const;
+  INLINE void ls_transforms() const;
+
+
+  // Aggregate transform and state information.
+  INLINE CPT(RenderState) get_state() const;
+  INLINE void set_state(const RenderState *state) const;
+  CPT(RenderState) get_state(const qpNodePath &other) const;
+  void set_state(const qpNodePath &other, const RenderState *state) const;
+  INLINE CPT(RenderState) get_net_state() const;
+
+  INLINE CPT(TransformState) get_transform() const;
+  INLINE void set_transform(const TransformState *transform) const;
+  CPT(TransformState) get_transform(const qpNodePath &other) const;
+  void set_transform(const qpNodePath &other, const TransformState *transform) const;
+  INLINE CPT(TransformState) get_net_transform() const;
+
+
+  // Methods that get and set the matrix transform: pos, hpr, scale,
+  // in the local coordinate system.
+
+  INLINE void set_pos(float x, float y, float z);
+  void set_pos(const LVecBase3f &pos);
+  void set_x(float x);
+  void set_y(float y);
+  void set_z(float z);
+  LPoint3f get_pos() const;
+  INLINE float get_x() const;
+  INLINE float get_y() const;
+  INLINE float get_z() const;
+
+  INLINE void set_hpr(float h, float p, float r);
+  void set_hpr(const LVecBase3f &hpr);
+  void set_h(float h);
+  void set_p(float p);
+  void set_r(float r);
+  LVecBase3f get_hpr() const;
+  LVecBase3f get_hpr(float roll) const;
+  INLINE float get_h() const;
+  INLINE float get_p() const;
+  INLINE float get_r() const;
+
+  INLINE void set_scale(float scale);
+  INLINE void set_scale(float sx, float sy, float sz);
+  void set_scale(const LVecBase3f &sv3);
+  void set_sx(float sx);
+  void set_sy(float sy);
+  void set_sz(float sz);
+  LVecBase3f get_scale() const;
+  INLINE float get_sx() const;
+  INLINE float get_sy() const;
+  INLINE float get_sz() const;
+
+  INLINE void set_pos_hpr(float x, float y, float z,
+                          float h, float p, float r);
+  void set_pos_hpr(const LVecBase3f &pos,
+                   const LVecBase3f &hpr);
+
+  INLINE void set_hpr_scale(float h, float p, float r,
+			    float sx, float sy, float sz);
+  void set_hpr_scale(const LVecBase3f &hpr,
+		     const LVecBase3f &scale);
+  INLINE void set_pos_hpr_scale(float x, float y, float z,
+                                float h, float p, float r,
+                                float sx, float sy, float sz);
+  void set_pos_hpr_scale(const LVecBase3f &pos,
+                         const LVecBase3f &hpr,
+                         const LVecBase3f &scale);
+
+  void set_mat(const LMatrix4f &mat);
+  INLINE void clear_mat();
+  INLINE bool has_mat() const;
+  INLINE const LMatrix4f &get_mat() const;
+
+  INLINE bool has_color_scale() const;
+  INLINE void clear_color_scale();
+
+  void set_color_scale(const LVecBase4f &sv4);
+  INLINE void set_color_scale(float sx, float sy, float sz, float sa);
+  INLINE void set_sr(float sr);
+  INLINE void set_sg(float sg);
+  INLINE void set_sb(float sb);
+  INLINE void set_sa(float sa);
+
+  LVecBase4f get_color_scale() const;
+  INLINE float get_sr() const;
+  INLINE float get_sg() const;
+  INLINE float get_sb() const;
+  INLINE float get_sa() const;
+
+  INLINE void look_at(float x, float y, float z);
+  void look_at(const LPoint3f &point, const LVector3f &up = LVector3f::up());
+  INLINE void heads_up(float x, float y, float z);
+  void heads_up(const LPoint3f &point, const LVector3f &up = LVector3f::up());
+
+  INLINE void look_at_preserve_scale(float x, float y, float z);
+  void look_at_preserve_scale(const LPoint3f &point, const LVector3f &up = LVector3f::up());
+  INLINE void heads_up_preserve_scale(float x, float y, float z);
+  void heads_up_preserve_scale(const LPoint3f &point, const LVector3f &up = LVector3f::up());
+
+  // Methods that get and set the matrix transforms relative to some
+  // other node in the scene graph.  These perform an implicit wrt().
+
+  INLINE void set_pos(const qpNodePath &other, float x, float y, float z);
+  void set_pos(const qpNodePath &other, const LVecBase3f &pos);
+  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;
+  INLINE float get_x(const qpNodePath &other) const;
+  INLINE float get_y(const qpNodePath &other) const;
+  INLINE float get_z(const qpNodePath &other) const;
+
+  INLINE void set_hpr(const qpNodePath &other, float h, float p, float r);
+  void set_hpr(const qpNodePath &other, const LVecBase3f &hpr);
+  void set_h(const qpNodePath &other, float h);
+  void set_p(const qpNodePath &other, float p);
+  void set_r(const qpNodePath &other, float r);
+  LVecBase3f get_hpr(const qpNodePath &other) const;
+  LVecBase3f get_hpr(const qpNodePath &other, float roll) const;
+  INLINE float get_h(const qpNodePath &other) const;
+  INLINE float get_p(const qpNodePath &other) const;
+  INLINE float get_r(const qpNodePath &other) const;
+
+  INLINE void set_scale(const qpNodePath &other, float sx, float sy, float sz);
+  void set_scale(const qpNodePath &other, const LVecBase3f &scale);
+  void set_sx(const qpNodePath &other, float sx);
+  void set_sy(const qpNodePath &other, float sy);
+  void set_sz(const qpNodePath &other, float sz);
+  LVecBase3f get_scale(const qpNodePath &other) const;
+  INLINE float get_sx(const qpNodePath &other) const;
+  INLINE float get_sy(const qpNodePath &other) const;
+  INLINE float get_sz(const qpNodePath &other) const;
+
+  INLINE void set_pos_hpr(const qpNodePath &other,
+                          float x, float y, float z,
+                          float h, float p, float r);
+  void set_pos_hpr(const qpNodePath &other,
+                   const LVecBase3f &pos,
+                   const LVecBase3f &hpr);
+  INLINE void set_hpr_scale(const qpNodePath &other,
+			    float h, float p, float r,
+			    float sx, float sy, float sz);
+  void set_hpr_scale(const qpNodePath &other,
+		     const LVecBase3f &hpr,
+		     const LVecBase3f &scale);
+  INLINE void set_pos_hpr_scale(const qpNodePath &other,
+                                float x, float y, float z,
+                                float h, float p, float r,
+                                float sx, float sy, float sz);
+  void set_pos_hpr_scale(const qpNodePath &other,
+                         const LVecBase3f &pos,
+                         const LVecBase3f &hpr,
+                         const LVecBase3f &scale);
+
+  const LMatrix4f &get_mat(const qpNodePath &other) const;
+  void set_mat(const qpNodePath &other, const LMatrix4f &mat);
+
+  LPoint3f get_relative_point(const qpNodePath &other, const LVecBase3f &point);
+
+  INLINE void look_at(const qpNodePath &other,
+                      float x, float y, float z);
+  void look_at(const qpNodePath &other,
+               const LPoint3f &point = LPoint3f(0.0, 0.0, 0.0),
+               const LVector3f &up = LVector3f::up());
+  INLINE void heads_up(const qpNodePath &other,
+                       float x, float y, float z);
+  void heads_up(const qpNodePath &other,
+                const LPoint3f &point = LPoint3f(0.0, 0.0, 0.0),
+                const LVector3f &up = LVector3f::up());
+
+  INLINE void look_at_preserve_scale(const qpNodePath &other,
+                                     float x, float y, float z);
+  void look_at_preserve_scale(const qpNodePath &other,
+                              const LPoint3f &point = LPoint3f(0.0, 0.0, 0.0),
+                              const LVector3f &up = LVector3f::up());
+  INLINE void heads_up_preserve_scale(const qpNodePath &other,
+                                      float x, float y, float z);
+  void heads_up_preserve_scale(const qpNodePath &other,
+                               const LPoint3f &point = LPoint3f(0.0, 0.0, 0.0),
+                               const LVector3f &up = LVector3f::up());
+
+  INLINE float get_distance(const qpNodePath &other) const;
+
+
+  // Methods that affect appearance of geometry: color, texture, etc.
+  // These affect the state at the bottom level only.
+
+  void set_color(float r, float g, float b, float a = 1.0,
+                 int priority = 0);
+  void set_color(const Colorf &color, int priority = 0);
+  void set_color_off(int priority = 0);
+  void clear_color();
+  bool has_color() const;
+  Colorf get_color() const;
+
+  void set_bin(const string &bin_name, int draw_order, int priority = 0);
+  void clear_bin();
+  bool has_bin() const;
+  string get_bin_name() const;
+  int get_bin_draw_order() const;
+
+  void set_texture(Texture *tex, int priority = 0);
+  void set_texture_off(int priority = 0);
+  void clear_texture();
+  bool has_texture() const;
+  bool has_texture_off() const;
+  Texture *get_texture() const;
+
+  void set_material(Material *tex, int priority = 0);
+  void set_material_off(int priority = 0);
+  void clear_material();
+  bool has_material() const;
+  PT(Material) get_material() const;
+
+  void set_fog(Fog *fog, int priority = 0);
+  void set_fog_off(int priority = 0);
+  void clear_fog();
+  bool has_fog() const;
+  bool has_fog_off() const;
+  Fog *get_fog() const;
+
+  void set_render_mode_wireframe(int priority = 0);
+  void set_render_mode_filled(int priority = 0);
+  void clear_render_mode();
+  bool has_render_mode() const;
+
+  void set_two_sided(bool two_sided, int priority = 0);
+  void clear_two_sided();
+  bool has_two_sided() const;
+  bool get_two_sided() const;
+
+  void do_billboard_axis(const qpNodePath &camera, float offset);
+  void do_billboard_point_eye(const qpNodePath &camera, float offset);
+  void do_billboard_point_world(const qpNodePath &camera, float offset);
+  void set_billboard_axis(float offset = 0.0);
+  void set_billboard_point_eye(float offset = 0.0);
+  void set_billboard_point_world(float offset = 0.0);
+  void clear_billboard();
+  bool has_billboard() const;
+
+  void set_transparency(bool transparency, int priority = 0);
+  void clear_transparency();
+  bool has_transparency() const;
+  bool get_transparency() const;
+
+  INLINE void adjust_all_priorities(int adjustment);
+
+  // Variants on show and hide
+  INLINE void show();
+  INLINE void hide();
+  INLINE void show_collision_solids();
+  INLINE void hide_collision_solids();
+  INLINE bool is_hidden() const;
+  qpNodePath get_hidden_ancestor() const;
+
+  INLINE void stash();
+  INLINE void unstash();
+  INLINE bool is_stashed() const;
+  qpNodePath get_stashed_ancestor() const;
+
+  // Comparison methods
+  INLINE bool operator == (const qpNodePath &other) const;
+  INLINE bool operator != (const qpNodePath &other) const;
+  INLINE bool operator < (const qpNodePath &other) const;
+  INLINE int compare_to(const qpNodePath &other) const;
+
+  bool verify_complete() const;
+
+private:
+  void uncollapse_head() const;
+  static void find_common_ancestor(const qpNodePath &a, const qpNodePath &b,
+                                   int &a_count, int &b_count);
+
+  CPT(RenderState) r_get_net_state(qpNodePathComponent *comp) const;
+  CPT(RenderState) r_get_partial_state(qpNodePathComponent *comp, int n) const;
+  CPT(TransformState) r_get_net_transform(qpNodePathComponent *comp) const;
+  CPT(TransformState) r_get_partial_transform(qpNodePathComponent *comp, int n) const;
+  void r_output(ostream &out, qpNodePathComponent *comp) const;
+  static int r_compare_to(const qpNodePathComponent *a, const qpNodePathComponent *v);
+
+  PT(qpNodePathComponent) _head;
+  ErrorType _error_type;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    register_type(_type_handle, "qpNodePath");
+  }
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const qpNodePath &node_path);
+
+#include "qpnodePath.I"
+
+#endif

+ 38 - 0
panda/src/pgraph/qpnodePathCollection.I

@@ -0,0 +1,38 @@
+// Filename: qpnodePathCollection.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: qpNodePathCollection::Destructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePathCollection::
+~qpNodePathCollection() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::ls
+//       Access: Published
+//  Description: Lists all the nodes at and below each node in the
+//               collection hierarchically.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePathCollection::
+ls() const {
+  ls(nout);
+}

+ 397 - 0
panda/src/pgraph/qpnodePathCollection.cxx

@@ -0,0 +1,397 @@
+// Filename: qpnodePathCollection.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 "qpnodePathCollection.h"
+//#include "findApproxPath.h"
+//#include "findApproxLevel.h"
+
+#include "indent.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpNodePathCollection::
+qpNodePathCollection() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpNodePathCollection::
+qpNodePathCollection(const qpNodePathCollection &copy) :
+  _node_paths(copy._node_paths)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+operator = (const qpNodePathCollection &copy) {
+  _node_paths = copy._node_paths;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::add_path
+//       Access: Published
+//  Description: Adds a new qpNodePath to the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+add_path(const qpNodePath &node_path) {
+  // If the pointer to our internal array is shared by any other
+  // NodePathCollections, we have to copy the array now so we won't
+  // inadvertently modify any of our brethren NodePathCollection
+  // objects.
+
+  if (_node_paths.get_ref_count() > 1) {
+    PTA(qpNodePath) old_node_paths = _node_paths;
+    _node_paths = PTA(qpNodePath)::empty_array(0);
+    _node_paths.v() = old_node_paths.v();
+  }
+
+  _node_paths.push_back(node_path);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::remove_path
+//       Access: Published
+//  Description: Removes the indicated qpNodePath from the collection.
+//               Returns true if the path was removed, false if it was
+//               not a member of the collection.
+////////////////////////////////////////////////////////////////////
+bool qpNodePathCollection::
+remove_path(const qpNodePath &node_path) {
+  int path_index = -1;
+  for (int i = 0; path_index == -1 && i < (int)_node_paths.size(); i++) {
+    if (_node_paths[i] == node_path) {
+      path_index = i;
+    }
+  }
+
+  if (path_index == -1) {
+    // The indicated path was not a member of the collection.
+    return false;
+  }
+
+  // If the pointer to our internal array is shared by any other
+  // NodePathCollections, we have to copy the array now so we won't
+  // inadvertently modify any of our brethren NodePathCollection
+  // objects.
+
+  if (_node_paths.get_ref_count() > 1) {
+    PTA(qpNodePath) old_node_paths = _node_paths;
+    _node_paths = PTA(qpNodePath)::empty_array(0);
+    _node_paths.v() = old_node_paths.v();
+  }
+
+  _node_paths.erase(_node_paths.begin() + path_index);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::add_paths_from
+//       Access: Published
+//  Description: Adds all the qpNodePaths indicated in the other
+//               collection to this path.  The other paths are simply
+//               appended to the end of the paths in this list;
+//               duplicates are not automatically removed.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+add_paths_from(const qpNodePathCollection &other) {
+  int other_num_paths = other.get_num_paths();
+  for (int i = 0; i < other_num_paths; i++) {
+    add_path(other.get_path(i));
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::remove_paths_from
+//       Access: Published
+//  Description: Removes from this collection all of the qpNodePaths
+//               listed in the other collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+remove_paths_from(const qpNodePathCollection &other) {
+  NodePaths new_paths;
+  int num_paths = get_num_paths();
+  for (int i = 0; i < num_paths; i++) {
+    qpNodePath path = get_path(i);
+    if (!other.has_path(path)) {
+      new_paths.push_back(path);
+    }
+  }
+  _node_paths = new_paths;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::remove_duplicate_paths
+//       Access: Published
+//  Description: Removes any duplicate entries of the same NodePaths
+//               on this collection.  If a qpNodePath appears multiple
+//               times, the first appearance is retained; subsequent
+//               appearances are removed.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+remove_duplicate_paths() {
+  NodePaths new_paths;
+
+  int num_paths = get_num_paths();
+  for (int i = 0; i < num_paths; i++) {
+    qpNodePath path = get_path(i);
+    bool duplicated = false;
+
+    for (int j = 0; j < i && !duplicated; j++) {
+      duplicated = (path == get_path(j));
+    }
+
+    if (!duplicated) {
+      new_paths.push_back(path);
+    }
+  }
+
+  _node_paths = new_paths;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::has_path
+//       Access: Published
+//  Description: Returns true if the indicated qpNodePath appears in
+//               this collection, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePathCollection::
+has_path(const qpNodePath &path) const {
+  for (int i = 0; i < get_num_paths(); i++) {
+    if (path == get_path(i)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::clear
+//       Access: Published
+//  Description: Removes all NodePaths from the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+clear() {
+  _node_paths.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::is_empty
+//       Access: Published
+//  Description: Returns true if there are no NodePaths in the
+//               collection, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpNodePathCollection::
+is_empty() const {
+  return _node_paths.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::get_num_paths
+//       Access: Published
+//  Description: Returns the number of NodePaths in the collection.
+////////////////////////////////////////////////////////////////////
+int qpNodePathCollection::
+get_num_paths() const {
+  return _node_paths.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::get_path
+//       Access: Published
+//  Description: Returns the nth qpNodePath in the collection.
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePathCollection::
+get_path(int index) const {
+  nassertr(index >= 0 && index < (int)_node_paths.size(), qpNodePath());
+
+  return _node_paths[index];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::operator []
+//       Access: Published
+//  Description: Returns the nth qpNodePath in the collection.  This is
+//               the same as get_path(), but it may be a more
+//               convenient way to access it.
+////////////////////////////////////////////////////////////////////
+qpNodePath qpNodePathCollection::
+operator [] (int index) const {
+  nassertr(index >= 0 && index < (int)_node_paths.size(), qpNodePath());
+
+  return _node_paths[index];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::ls
+//       Access: Published
+//  Description: Lists all the nodes at and below each node in the
+//               collection hierarchically.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+ls(ostream &out, int indent_level) const {
+  for (int i = 0; i < get_num_paths(); i++) {
+    qpNodePath path = get_path(i);
+    indent(out, indent_level) << path << "\n";
+    path.ls(out, indent_level + 2);
+    out << "\n";
+  }
+}
+
+/*
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::find_all_matches
+//       Access: Published
+//  Description: Returns the complete set of all NodePaths that begin
+//               with any qpNodePath in this collection and can be
+//               extended by path.  The shortest paths will be listed
+//               first.
+////////////////////////////////////////////////////////////////////
+qpNodePathCollection qpNodePathCollection::
+find_all_matches(const string &path) const {
+  qpNodePathCollection result;
+
+  FindApproxPath approx_path;
+  if (approx_path.add_string(path)) {
+    if (!is_empty()) {
+      FindApproxLevel level;
+      for (int i = 0; i < get_num_paths(); i++) {
+        FindApproxLevelEntry start(get_path(i), approx_path);
+        level.add_entry(start);
+      }
+      get_path(0).r_find_matches(result, level, -1,
+                                 qpNodePath::get_max_search_depth());
+    }
+  }
+
+  return result;
+}
+*/
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::reparent_to
+//       Access: Published
+//  Description: Reparents all the NodePaths in the collection to the
+//               indicated node.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+reparent_to(const qpNodePath &other) {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).reparent_to(other);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::wrt_reparent_to
+//       Access: Published
+//  Description: Reparents all the NodePaths in the collection to the
+//               indicated node, adjusting each transform so as not to
+//               move in world coordinates.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+wrt_reparent_to(const qpNodePath &other) {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).wrt_reparent_to(other);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::show
+//       Access: Published
+//  Description: Shows all NodePaths in the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+show() {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).show();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::show
+//       Access: Published
+//  Description: Hides all NodePaths in the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+hide() {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).hide();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::stash
+//       Access: Published
+//  Description: Stashes all NodePaths in the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+stash() {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).stash();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::unstash
+//       Access: Published
+//  Description: Unstashes all NodePaths in the collection.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+unstash() {
+  for (int i = 0; i < get_num_paths(); i++) {
+    get_path(i).unstash();
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::output
+//       Access: Published
+//  Description: Writes a brief one-line description of the
+//               qpNodePathCollection to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+output(ostream &out) const {
+  if (get_num_paths() == 1) {
+    out << "1 NodePath";
+  } else {
+    out << get_num_paths() << " NodePaths";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathCollection::write
+//       Access: Published
+//  Description: Writes a complete multi-line description of the
+//               qpNodePathCollection to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void qpNodePathCollection::
+write(ostream &out, int indent_level) const {
+  for (int i = 0; i < get_num_paths(); i++) {
+    indent(out, indent_level) << get_path(i) << "\n";
+  }
+}

+ 83 - 0
panda/src/pgraph/qpnodePathCollection.h

@@ -0,0 +1,83 @@
+// Filename: qpnodePathCollection.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 qpNODEPATHCOLLECTION_H
+#define qpNODEPATHCOLLECTION_H
+
+#include "pandabase.h"
+#include "qpnodePath.h"
+#include "pointerToArray.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : NodePathCollection
+// Description : This is a set of zero or more NodePaths.  It's handy
+//               for returning from functions that need to return
+//               multiple NodePaths (for instance,
+//               NodePaths::get_children).
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpNodePathCollection {
+PUBLISHED:
+  qpNodePathCollection();
+  qpNodePathCollection(const qpNodePathCollection &copy);
+  void operator = (const qpNodePathCollection &copy);
+  INLINE ~qpNodePathCollection();
+
+  void add_path(const qpNodePath &node_path);
+  bool remove_path(const qpNodePath &node_path);
+  void add_paths_from(const qpNodePathCollection &other);
+  void remove_paths_from(const qpNodePathCollection &other);
+  void remove_duplicate_paths();
+  bool has_path(const qpNodePath &path) const;
+  void clear();
+
+  bool is_empty() const;
+  int get_num_paths() const;
+  qpNodePath get_path(int index) const;
+  qpNodePath operator [] (int index) const;
+
+  // Handy operations on many NodePaths at once.
+  INLINE void ls() const;
+  void ls(ostream &out, int indent_level = 0) const;
+
+  //  qpNodePathCollection find_all_matches(const string &path) const;
+  void reparent_to(const qpNodePath &other);
+  void wrt_reparent_to(const qpNodePath &other);
+
+  void show();
+  void hide();
+  void stash();
+  void unstash();
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  typedef PTA(qpNodePath) NodePaths;
+  NodePaths _node_paths;
+};
+
+INLINE ostream &operator << (ostream &out, const qpNodePathCollection &col) {
+  col.output(out);
+  return out;
+}
+
+#include "qpnodePathCollection.I"
+
+#endif
+
+

+ 46 - 46
panda/src/pgraph/nodeChainComponent.I → panda/src/pgraph/qpnodePathComponent.I

@@ -1,4 +1,4 @@
-// Filename: nodeChainComponent.I
+// Filename: qpnodePathComponent.I
 // Created by:  drose (25Feb02)
 //
 ////////////////////////////////////////////////////////////////////
@@ -18,36 +18,36 @@
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::CData::Constructor
+//     Function: qpNodePathComponent::CData::Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent::CData::
+INLINE qpNodePathComponent::CData::
 CData() {
   _length = 1;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::CData::Copy Constructor
+//     Function: qpNodePathComponent::CData::Copy Constructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent::CData::
-CData(const NodeChainComponent::CData &copy) :
+INLINE qpNodePathComponent::CData::
+CData(const qpNodePathComponent::CData &copy) :
   _next(copy._next),
   _length(copy._length)
 {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::Constructor
+//     Function: qpNodePathComponent::Constructor
 //       Access: Private
-//  Description: Constructs a new NodeChainComponent from the
+//  Description: Constructs a new qpNodePathComponent from the
 //               indicated node.  Don't try to call this directly; ask
 //               the PandaNode to do it for you.
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent::
-NodeChainComponent(PandaNode *node, NodeChainComponent *next) :
+INLINE qpNodePathComponent::
+qpNodePathComponent(PandaNode *node, qpNodePathComponent *next) :
   _node(node)
 {
 #ifdef DO_MEMORY_USAGE
@@ -56,85 +56,85 @@ NodeChainComponent(PandaNode *node, NodeChainComponent *next) :
   CDWriter cdata(_cycler);
   cdata->_next = next;
 
-  if (next != (NodeChainComponent *)NULL) {
+  if (next != (qpNodePathComponent *)NULL) {
     cdata->_length = next->get_length() + 1;
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::Copy Constructor
+//     Function: qpNodePathComponent::Copy Constructor
 //       Access: Private
-//  Description: NodeChainComponents should not be copied.
+//  Description: qpNodePathComponents should not be copied.
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent::
-NodeChainComponent(const NodeChainComponent &copy) {
+INLINE qpNodePathComponent::
+qpNodePathComponent(const qpNodePathComponent &copy) {
   nassertv(false);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::Copy Assignment Operator
+//     Function: qpNodePathComponent::Copy Assignment Operator
 //       Access: Private
-//  Description: NodeChainComponents should not be copied.
+//  Description: qpNodePathComponents should not be copied.
 ////////////////////////////////////////////////////////////////////
-INLINE void NodeChainComponent::
-operator = (const NodeChainComponent &copy) {
+INLINE void qpNodePathComponent::
+operator = (const qpNodePathComponent &copy) {
   nassertv(false);
 }
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::Destructor
+//     Function: qpNodePathComponent::Destructor
 //       Access: Public
 //  Description:
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent::
-~NodeChainComponent() {
+INLINE qpNodePathComponent::
+~qpNodePathComponent() {
   nassertv(_node != (PandaNode *)NULL);
   _node->delete_component(this);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::get_node
+//     Function: qpNodePathComponent::get_node
 //       Access: Public
 //  Description: Returns the node referenced by this component.
 ////////////////////////////////////////////////////////////////////
-INLINE PandaNode *NodeChainComponent::
+INLINE PandaNode *qpNodePathComponent::
 get_node() const {
   return _node;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::is_top_node
+//     Function: qpNodePathComponent::is_top_node
 //       Access: Public
 //  Description: Returns true if this component represents the top
 //               node in the chain.
 ////////////////////////////////////////////////////////////////////
-INLINE bool NodeChainComponent::
+INLINE bool qpNodePathComponent::
 is_top_node() const {
   CDReader cdata(_cycler);
-  return (cdata->_next == (NodeChainComponent *)NULL);
+  return (cdata->_next == (qpNodePathComponent *)NULL);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::is_collapsed
+//     Function: qpNodePathComponent::is_collapsed
 //       Access: Public
 //  Description: Returns true if this component has been collapsed
 //               with another component.  In this case, the component
 //               itself is invalid, and the collapsed component should
 //               be used instead.
 ////////////////////////////////////////////////////////////////////
-INLINE bool NodeChainComponent::
+INLINE bool qpNodePathComponent::
 is_collapsed() const {
   CDReader cdata(_cycler);
   return (cdata->_length == 0);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::get_length
+//     Function: qpNodePathComponent::get_length
 //       Access: Public
 //  Description: Returns the length of the chain.
 ////////////////////////////////////////////////////////////////////
-INLINE int NodeChainComponent::
+INLINE int qpNodePathComponent::
 get_length() const {
   CDReader cdata(_cycler);
   nassertr(!is_collapsed(), 0);
@@ -142,59 +142,59 @@ get_length() const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::get_collapsed
+//     Function: qpNodePathComponent::get_collapsed
 //       Access: Public
 //  Description: If is_collapsed() returns true, this is the component
 //               that this one has been collapsed with, and should be
 //               replaced with.
 ////////////////////////////////////////////////////////////////////
-INLINE NodeChainComponent *NodeChainComponent::
+INLINE qpNodePathComponent *qpNodePathComponent::
 get_collapsed() const {
   CDReader cdata(_cycler);
-  nassertr(is_collapsed(), (NodeChainComponent *)NULL);
+  nassertr(is_collapsed(), (qpNodePathComponent *)NULL);
   return cdata->_next;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::set_next
+//     Function: qpNodePathComponent::set_next
 //       Access: Private
 //  Description: Sets the next pointer in the chain.
 ////////////////////////////////////////////////////////////////////
-INLINE void NodeChainComponent::
-set_next(NodeChainComponent *next) {
+INLINE void qpNodePathComponent::
+set_next(qpNodePathComponent *next) {
   CDWriter cdata(_cycler);
   nassertv(!is_collapsed());
-  nassertv(next != (NodeChainComponent *)NULL);
+  nassertv(next != (qpNodePathComponent *)NULL);
   cdata->_next = next;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::set_top_node
+//     Function: qpNodePathComponent::set_top_node
 //       Access: Private
 //  Description: Severs any connection to the next pointer in the
 //               chain and makes this component a top node.
 ////////////////////////////////////////////////////////////////////
-INLINE void NodeChainComponent::
+INLINE void qpNodePathComponent::
 set_top_node() {
   CDWriter cdata(_cycler);
   nassertv(!is_collapsed());
-  cdata->_next = (NodeChainComponent *)NULL;
+  cdata->_next = (qpNodePathComponent *)NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::collapse_with
+//     Function: qpNodePathComponent::collapse_with
 //       Access: Private
 //  Description: Indicates that this component pointer is no longer
 //               valid, and that the indicated component should be
 //               used instead.  This is done whenever two
-//               NodeChainComponents have been collapsed together due
+//               qpNodePathComponents have been collapsed together due
 //               to an instance being removed higher up in the graph.
 ////////////////////////////////////////////////////////////////////
-INLINE void NodeChainComponent::
-collapse_with(NodeChainComponent *next) {
+INLINE void qpNodePathComponent::
+collapse_with(qpNodePathComponent *next) {
   CDWriter cdata(_cycler);
   nassertv(!is_collapsed());
-  nassertv(next != (NodeChainComponent *)NULL);
+  nassertv(next != (qpNodePathComponent *)NULL);
   cdata->_next = next;
   cdata->_length = 0;
 

+ 16 - 16
panda/src/pgraph/nodeChainComponent.cxx → panda/src/pgraph/qpnodePathComponent.cxx

@@ -1,4 +1,4 @@
-// Filename: nodeChainComponent.cxx
+// Filename: qpnodePathComponent.cxx
 // Created by:  drose (25Feb02)
 //
 ////////////////////////////////////////////////////////////////////
@@ -16,54 +16,54 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#include "nodeChainComponent.h"
+#include "qpnodePathComponent.h"
 
-TypeHandle NodeChainComponent::_type_handle;
+TypeHandle qpNodePathComponent::_type_handle;
 
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::CData::make_copy
+//     Function: qpNodePathComponent::CData::make_copy
 //       Access: Public, Virtual
 //  Description:
 ////////////////////////////////////////////////////////////////////
-CycleData *NodeChainComponent::CData::
+CycleData *qpNodePathComponent::CData::
 make_copy() const {
   return new CData(*this);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::get_next
+//     Function: qpNodePathComponent::get_next
 //       Access: Public
 //  Description: Returns the next component in the chain.
 ////////////////////////////////////////////////////////////////////
-NodeChainComponent *NodeChainComponent::
+qpNodePathComponent *qpNodePathComponent::
 get_next() const {
   CDReader cdata(_cycler);
-  nassertr(!is_collapsed(), (NodeChainComponent *)NULL);
+  nassertr(!is_collapsed(), (qpNodePathComponent *)NULL);
 
-  NodeChainComponent *next = cdata->_next;
+  qpNodePathComponent *next = cdata->_next;
 
   // If the next component has been collapsed, transparently update
   // the pointer to get the actual node, and store the new pointer,
   // before we return.  Collapsing can happen at any time to any
   // component in the chain and we have to deal with it.
-  if (next != (NodeChainComponent *)NULL && next->is_collapsed()) {
+  if (next != (qpNodePathComponent *)NULL && next->is_collapsed()) {
     next = next->uncollapse();
-    ((NodeChainComponent *)this)->set_next(next);
+    ((qpNodePathComponent *)this)->set_next(next);
   }
 
   return next;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::fix_length
+//     Function: qpNodePathComponent::fix_length
 //       Access: Public
 //  Description: Checks that the length indicated by the component is
 //               one more than the length of its predecessor.  If this
 //               is broken, fixes it and returns true indicating the
 //               component has been changed; otherwise, returns false.
 ////////////////////////////////////////////////////////////////////
-bool NodeChainComponent::
+bool qpNodePathComponent::
 fix_length() {
   int length_should_be = 1;
   if (!is_top_node()) {
@@ -79,7 +79,7 @@ fix_length() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: NodeChainComponent::uncollapse
+//     Function: qpNodePathComponent::uncollapse
 //       Access: Public
 //  Description: Returns this component pointer if the component is
 //               not collapsed; or if it has been collapsed, returns
@@ -90,9 +90,9 @@ fix_length() {
 //               when a node is removed further up the chain that
 //               results in two instances becoming the same thing.
 ////////////////////////////////////////////////////////////////////
-NodeChainComponent *NodeChainComponent::
+qpNodePathComponent *qpNodePathComponent::
 uncollapse() {
-  NodeChainComponent *comp = this;
+  qpNodePathComponent *comp = this;
 
   while (comp->is_collapsed()) {
     comp = comp->get_collapsed();

+ 23 - 23
panda/src/pgraph/nodeChainComponent.h → panda/src/pgraph/qpnodePathComponent.h

@@ -1,4 +1,4 @@
-// Filename: nodeChainComponent.h
+// Filename: qpnodePathComponent.h
 // Created by:  drose (25Feb02)
 //
 ////////////////////////////////////////////////////////////////////
@@ -16,8 +16,8 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef NODECHAINCOMPONENT_H
-#define NODECHAINCOMPONENT_H
+#ifndef qpNODEPATHCOMPONENT_H
+#define qpNODEPATHCOMPONENT_H
 
 #include "pandabase.h"
 
@@ -29,45 +29,45 @@
 #include "cycleDataWriter.h"
 
 ////////////////////////////////////////////////////////////////////
-//       Class : NodeChainComponent
-// Description : This is one component of a NodeChain.  These are
+//       Class : qpNodePathComponent
+// Description : This is one component of a NodePath.  These are
 //               stored on each PandaNode, as many as one for each of
 //               the possible instances of the node (but they only
 //               exist when they are requested, to minimize memory
-//               waste).  A NodeChain represents a singly-linked list
+//               waste).  A NodePath represents a singly-linked list
 //               of these from an arbitrary component in the graph to
 //               the root.
 //
-//               This whole NodeChain system is used to disambiguate
+//               This whole NodePath system is used to disambiguate
 //               instances in the scene graph, and the
-//               NodeChainComponents are stored in the nodes
-//               themselves to allow the nodes to keep these up to
-//               date as the scene graph is manipulated.
+//               NodePathComponents are stored in the nodes themselves
+//               to allow the nodes to keep these up to date as the
+//               scene graph is manipulated.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA NodeChainComponent : public ReferenceCount {
+class EXPCL_PANDA qpNodePathComponent : public ReferenceCount {
 private:
-  INLINE NodeChainComponent(PandaNode *node, NodeChainComponent *next = NULL);
-  INLINE NodeChainComponent(const NodeChainComponent &copy);
-  INLINE void operator = (const NodeChainComponent &copy);
+  INLINE qpNodePathComponent(PandaNode *node, qpNodePathComponent *next = NULL);
+  INLINE qpNodePathComponent(const qpNodePathComponent &copy);
+  INLINE void operator = (const qpNodePathComponent &copy);
 
 public:
-  INLINE ~NodeChainComponent();
+  INLINE ~qpNodePathComponent();
   
   INLINE PandaNode *get_node() const;
   INLINE bool is_top_node() const;
   INLINE bool is_collapsed() const;
   
-  NodeChainComponent *get_next() const;
+  qpNodePathComponent *get_next() const;
   INLINE int get_length() const;
-  INLINE NodeChainComponent *get_collapsed() const;
+  INLINE qpNodePathComponent *get_collapsed() const;
 
   bool fix_length();
-  NodeChainComponent *uncollapse();
+  qpNodePathComponent *uncollapse();
   
 private:
-  INLINE void set_next(NodeChainComponent *next);
+  INLINE void set_next(qpNodePathComponent *next);
   INLINE void set_top_node();
-  INLINE void collapse_with(NodeChainComponent *next);
+  INLINE void collapse_with(qpNodePathComponent *next);
 
   PT(PandaNode) _node;
 
@@ -78,7 +78,7 @@ private:
     CData(const CData &copy);
     virtual CycleData *make_copy() const;
 
-    PT(NodeChainComponent) _next;
+    PT(qpNodePathComponent) _next;
     int _length;
   };
 
@@ -92,7 +92,7 @@ public:
   }
   static void init_type() {
     ReferenceCount::init_type();
-    register_type(_type_handle, "NodeChainComponent",
+    register_type(_type_handle, "qpNodePathComponent",
                   ReferenceCount::get_class_type());
   }
   
@@ -101,6 +101,6 @@ private:
   friend class PandaNode;
 };
 
-#include "nodeChainComponent.I"
+#include "qpnodePathComponent.I"
 
 #endif

+ 9 - 9
panda/src/pgraph/test_pgraph.cxx

@@ -17,7 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "pandaNode.h"
-#include "nodeChain.h"
+#include "qpnodePath.h"
 #include "textureAttrib.h"
 #include "colorAttrib.h"
 #include "transformState.h"
@@ -74,20 +74,20 @@ main(int argc, char *argv[]) {
   cerr << "\n";
   list_hierarchy(root, 0);
 
-  NodeChain ch_g1(g1);
+  qpNodePath ch_g1(g1);
   cerr << ch_g1 << "\n";
 
-  NodeChain ch_g2(g2);
+  qpNodePath ch_g2(g2);
   cerr << ch_g2 << "\n";
 
-  cerr << *ch_g1.get_rel_transform(ch_g2) << "\n";
-  cerr << *ch_g2.get_rel_transform(ch_g1) << "\n";
+  cerr << *ch_g1.get_transform(ch_g2) << "\n";
+  cerr << *ch_g2.get_transform(ch_g1) << "\n";
 
-  cerr << *ch_g1.get_rel_state(ch_g2) << "\n";
-  cerr << *ch_g2.get_rel_state(ch_g1) << "\n";
+  cerr << *ch_g1.get_state(ch_g2) << "\n";
+  cerr << *ch_g2.get_state(ch_g1) << "\n";
 
-  cerr << *ch_g2.get_rel_transform(ch_g2) << "\n";
-  cerr << *ch_g2.get_rel_state(ch_g2) << "\n";
+  cerr << *ch_g2.get_transform(ch_g2) << "\n";
+  cerr << *ch_g2.get_state(ch_g2) << "\n";
 
   cerr << "\n";
   return 0;

+ 40 - 5
panda/src/pgraph/transformState.I

@@ -128,17 +128,52 @@ has_components() const {
   return ((_flags & F_has_components) != 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.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+has_pos() const {
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::has_hpr
+//       Access: Published
+//  Description: Returns true if the transform's rotation component
+//               can be extracted out separately and described as a
+//               set of Euler angles.  This is generally true only
+//               when has_components() is true.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+has_hpr() const {
+  return has_components();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::has_scale
+//       Access: Published
+//  Description: Returns true if the transform's scale component
+//               can be extracted out separately.  This is generally
+//               true only when has_components() is true.
+////////////////////////////////////////////////////////////////////
+INLINE bool TransformState::
+has_scale() const {
+  return has_components();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::get_pos
 //       Access: Published
 //  Description: Returns the pos component of the transform.  It is an
-//               error to call this if has_components() returned
-//               false.
+//               error to call this if has_pos() returned false.
 ////////////////////////////////////////////////////////////////////
 INLINE const LVecBase3f &TransformState::
 get_pos() const {
   check_components();
-  nassertr(has_components(), _pos);
   return _pos;
 }
 
@@ -152,7 +187,7 @@ get_pos() const {
 INLINE const LVecBase3f &TransformState::
 get_hpr() const {
   check_components();
-  nassertr(has_components(), _hpr);
+  nassertr(has_hpr(), _hpr);
   return _hpr;
 }
 
@@ -166,7 +201,7 @@ get_hpr() const {
 INLINE const LVecBase3f &TransformState::
 get_scale() const {
   check_components();
-  nassertr(has_components(), _scale);
+  nassertr(has_scale(), _scale);
   return _scale;
 }
 

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

@@ -238,6 +238,54 @@ make_mat(const LMatrix4f &mat) {
   return return_new(attrib);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::set_pos
+//       Access: Published
+//  Description: Returns a new TransformState object that represents the
+//               original TransformState with its pos component
+//               replaced with the indicated value.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+set_pos(const LVecBase3f &pos) const {
+  if ((_flags & F_components_given) != 0) {
+    // If we started with a componentwise transform, we keep it that
+    // way.
+    return make_pos_hpr_scale(pos, get_hpr(), get_scale());
+
+  } else {
+    // Otherwise, we have a matrix transform, and we keep it that way.
+    LMatrix4f mat = get_mat();
+    mat.set_row(3, pos);
+    return make_mat(mat);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::set_hpr
+//       Access: Published
+//  Description: Returns a new TransformState object that represents the
+//               original TransformState with its hpr component
+//               replaced with the indicated value, if possible.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+set_hpr(const LVecBase3f &hpr) const {
+  nassertr(has_components(), this);
+  return make_pos_hpr_scale(get_pos(), hpr, get_scale());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::set_scale
+//       Access: Published
+//  Description: Returns a new TransformState object that represents the
+//               original TransformState with its scale component
+//               replaced with the indicated value, if possible.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+set_scale(const LVecBase3f &scale) const {
+  nassertr(has_components(), this);
+  return make_pos_hpr_scale(get_pos(), get_scale(), get_hpr());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformState::compose
 //       Access: Published
@@ -532,10 +580,14 @@ calc_components() {
     // other explanation is that we were constructed via a matrix.
     nassertv((_flags & F_mat_known) != 0);
 
-    bool possible = decompose_matrix(get_mat(), _scale, _hpr, _pos);
+    const LMatrix4f &mat = get_mat();
+    bool possible = decompose_matrix(mat, _scale, _hpr, _pos);
     if (possible) {
       // Some matrices can't be decomposed into scale, hpr, pos.
       _flags |= F_has_components;
+
+      // However, we can always get at least the pos.
+      mat.get_row3(_pos, 3);
     }
   }
 

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

@@ -80,11 +80,18 @@ PUBLISHED:
   INLINE bool is_identity() const;
   INLINE bool is_singular() const;
   INLINE bool has_components() const;
+  INLINE bool has_pos() const;
+  INLINE bool has_hpr() const;
+  INLINE bool has_scale() const;
   INLINE const LVecBase3f &get_pos() const;
   INLINE const LVecBase3f &get_hpr() const;
   INLINE const LVecBase3f &get_scale() const;
   INLINE const LMatrix4f &get_mat() const;
 
+  CPT(TransformState) set_pos(const LVecBase3f &pos) const;
+  CPT(TransformState) set_hpr(const LVecBase3f &hpr) const;
+  CPT(TransformState) set_scale(const LVecBase3f &scale) const;
+
   CPT(TransformState) compose(const TransformState *other) const;
   CPT(TransformState) invert_compose(const TransformState *other) const;
 

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

@@ -144,7 +144,7 @@ make_camera(GraphicsWindow *window) {
   PT(Lens) lens = new PerspectiveLens;
   lens->set_film_size(win_width, win_height);
   camera->set_lens(lens);
-  dr->set_qpcamera(NodeChain(camera));
+  dr->set_qpcamera(qpNodePath(camera));
 
   return camera;
 }
@@ -216,7 +216,7 @@ get_models(PandaNode *parent, int argc, char *argv[]) {
         cerr << "Unable to load " << filename << "\n";
 
       } else {
-        node->ls();
+        node->ls(cerr, 0);
         parent->add_child(node);
       }
     }
@@ -277,7 +277,7 @@ main(int argc, char *argv[]) {
   // Now we just need to make a scene graph for the camera to render.
   PT(PandaNode) render = new PandaNode("render");
   render->add_child(camera);
-  camera->set_scene(NodeChain(render));
+  camera->set_scene(qpNodePath(render));
 
   // Set up a data graph for tracking user input.  For now, this uses
   // the old-style graph interface.