Browse Source

new scene graph, transforms and cameras

David Rose 24 years ago
parent
commit
063c2cafff
55 changed files with 3925 additions and 625 deletions
  1. 11 0
      panda/src/display/displayRegion.I
  2. 49 12
      panda/src/display/displayRegion.cxx
  3. 17 15
      panda/src/display/displayRegion.h
  4. 4 1
      panda/src/display/drawCullHandler.cxx
  5. 2 1
      panda/src/display/drawCullHandler.h
  6. 14 7
      panda/src/display/graphicsEngine.cxx
  7. 16 0
      panda/src/display/graphicsStateGuardian.I
  8. 10 0
      panda/src/display/graphicsStateGuardian.cxx
  9. 4 0
      panda/src/display/graphicsStateGuardian.h
  10. 18 0
      panda/src/glgsg/glGraphicsStateGuardian.cxx
  11. 1 0
      panda/src/glgsg/glGraphicsStateGuardian.h
  12. 3 0
      panda/src/glxdisplay/glxGraphicsWindow.cxx
  13. 16 4
      panda/src/pgraph/Sources.pp
  14. 14 3
      panda/src/pgraph/config_pgraph.cxx
  15. 4 2
      panda/src/pgraph/cullHandler.cxx
  16. 3 1
      panda/src/pgraph/cullHandler.h
  17. 1 1
      panda/src/pgraph/cycleDataReader.I
  18. 1 1
      panda/src/pgraph/cycleDataWriter.I
  19. 323 0
      panda/src/pgraph/nodeChain.I
  20. 514 0
      panda/src/pgraph/nodeChain.cxx
  21. 176 0
      panda/src/pgraph/nodeChain.h
  22. 203 0
      panda/src/pgraph/nodeChainComponent.I
  23. 102 0
      panda/src/pgraph/nodeChainComponent.cxx
  24. 106 0
      panda/src/pgraph/nodeChainComponent.h
  25. 86 10
      panda/src/pgraph/pandaNode.I
  26. 409 28
      panda/src/pgraph/pandaNode.cxx
  27. 49 9
      panda/src/pgraph/pandaNode.h
  28. 3 1
      panda/src/pgraph/pgraph_composite1.cxx
  29. 2 2
      panda/src/pgraph/pgraph_composite2.cxx
  30. 33 0
      panda/src/pgraph/pipelineCycler.I
  31. 4 0
      panda/src/pgraph/pipelineCycler.h
  32. 53 0
      panda/src/pgraph/pipelineCyclerBase.I
  33. 3 2
      panda/src/pgraph/pipelineCyclerBase.cxx
  34. 6 1
      panda/src/pgraph/pipelineCyclerBase.h
  35. 87 0
      panda/src/pgraph/qpcamera.I
  36. 144 0
      panda/src/pgraph/qpcamera.cxx
  37. 90 0
      panda/src/pgraph/qpcamera.h
  38. 12 15
      panda/src/pgraph/qpcullTraverser.cxx
  39. 5 4
      panda/src/pgraph/qpcullTraverser.h
  40. 89 0
      panda/src/pgraph/qplensNode.I
  41. 82 0
      panda/src/pgraph/qplensNode.cxx
  42. 78 0
      panda/src/pgraph/qplensNode.h
  43. 2 1
      panda/src/pgraph/renderAttrib.cxx
  44. 4 9
      panda/src/pgraph/renderState.cxx
  45. 2 1
      panda/src/pgraph/renderState.h
  46. 16 9
      panda/src/pgraph/test_pgraph.cxx
  47. 0 308
      panda/src/pgraph/transformAttrib.cxx
  48. 0 117
      panda/src/pgraph/transformAttrib.h
  49. 85 32
      panda/src/pgraph/transformState.I
  50. 650 0
      panda/src/pgraph/transformState.cxx
  51. 187 0
      panda/src/pgraph/transformState.h
  52. 96 25
      panda/src/testbed/pview.cxx
  53. 1 1
      panda/src/tform/Sources.pp
  54. 30 2
      panda/src/tform/transform2sg.cxx
  55. 5 0
      panda/src/tform/transform2sg.h

+ 11 - 0
panda/src/display/displayRegion.I

@@ -102,6 +102,17 @@ get_camera() const {
   return _camera;
   return _camera;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::get_qpcamera
+//       Access: Public
+//  Description: Returns the camera associated with this
+//               DisplayRegion, or NULL if no camera is associated.
+////////////////////////////////////////////////////////////////////
+INLINE const NodeChain &DisplayRegion::
+get_qpcamera() const {
+  return _qpcamera;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::set_cull_frustum
 //     Function: DisplayRegion::set_cull_frustum
 //       Access: Public
 //       Access: Public

+ 49 - 12
panda/src/display/displayRegion.cxx

@@ -22,6 +22,7 @@
 #include "graphicsWindow.h"
 #include "graphicsWindow.h"
 #include "config_display.h"
 #include "config_display.h"
 #include "displayRegion.h"
 #include "displayRegion.h"
+#include "qpcamera.h"
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -33,6 +34,7 @@ DisplayRegion::
 DisplayRegion(GraphicsLayer *layer) :
 DisplayRegion(GraphicsLayer *layer) :
   _l(0.), _r(1.), _b(0.), _t(1.),
   _l(0.), _r(1.), _b(0.), _t(1.),
   _layer(layer),
   _layer(layer),
+  _camera_node((qpCamera *)NULL),
   _active(true)
   _active(true)
 {
 {
 }
 }
@@ -44,10 +46,11 @@ DisplayRegion(GraphicsLayer *layer) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DisplayRegion::
 DisplayRegion::
 DisplayRegion(GraphicsLayer *layer, const float l,
 DisplayRegion(GraphicsLayer *layer, const float l,
-              const float r, const float b, const float t)
-  : _l(l), _r(r), _b(b), _t(t),
-    _layer(layer),
-    _active(true)
+              const float r, const float b, const float t) :
+  _l(l), _r(r), _b(b), _t(t),
+  _layer(layer),
+  _camera_node((qpCamera *)NULL),
+  _active(true)
 {
 {
 }
 }
 
 
@@ -62,7 +65,8 @@ DisplayRegion::
 DisplayRegion(int xsize, int ysize) :
 DisplayRegion(int xsize, int ysize) :
   _l(0.), _r(1.), _b(0.), _t(1.),
   _l(0.), _r(1.), _b(0.), _t(1.),
   _pl(0), _pr(xsize), _pb(0), _pt(ysize),
   _pl(0), _pr(xsize), _pb(0), _pt(ysize),
-  _layer((GraphicsLayer *)0L),
+  _layer((GraphicsLayer *)NULL),
+  _camera_node((qpCamera *)NULL),
   _active(true)
   _active(true)
 {
 {
 }
 }
@@ -74,8 +78,7 @@ DisplayRegion(int xsize, int ysize) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DisplayRegion::
 DisplayRegion::
 DisplayRegion(const DisplayRegion&) {
 DisplayRegion(const DisplayRegion&) {
-  display_cat.error()
-    << "DisplayRegions should not be copied" << endl;
+  nassertv(false);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -83,11 +86,9 @@ DisplayRegion(const DisplayRegion&) {
 //       Access: Private
 //       Access: Private
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-DisplayRegion &DisplayRegion::
-operator=(const DisplayRegion&) {
-  display_cat.error()
-  << "DisplayRegions should not be assigned" << endl;
-  return *this;
+void DisplayRegion::
+operator = (const DisplayRegion&) {
+  nassertv(false);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -97,6 +98,7 @@ operator=(const DisplayRegion&) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DisplayRegion::
 DisplayRegion::
 ~DisplayRegion() {
 ~DisplayRegion() {
+  set_qpcamera(NodeChain());
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -182,6 +184,41 @@ set_camera(Camera *camera) {
   set_cull_frustum(camera);
   set_cull_frustum(camera);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DisplayRegion::set_qpcamera
+//       Access: Public
+//  Description: Sets the camera that is associated with this
+//               DisplayRegion.  There is a one-to-one association
+//               between cameras and DisplayRegions; if this camera
+//               was already associated with a different
+//               DisplayRegion, that association is removed.
+//
+//               The camera is actually set via a NodeChain, which
+//               clarifies which instance of the camera (if there
+//               happen to be multiple instances) we should use.
+////////////////////////////////////////////////////////////////////
+void DisplayRegion::
+set_qpcamera(const NodeChain &camera) {
+  qpCamera *camera_node = (qpCamera *)NULL;
+  if (!camera.is_empty()) {
+    DCAST_INTO_V(camera_node, camera.node());
+  }
+
+  if (camera_node != _camera_node) {
+    if (_camera_node != (qpCamera *)NULL) {
+      // We need to tell the old camera we're not using him anymore.
+      _camera_node->remove_display_region(this);
+    }
+    _camera_node = camera_node;
+    if (_camera_node != (qpCamera *)NULL) {
+      // Now tell the new camera we are using him.
+      _camera_node->add_display_region(this);
+    }
+  }
+
+  _qpcamera = camera;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DisplayRegion::output
 //     Function: DisplayRegion::output
 //       Access: Public
 //       Access: Public

+ 17 - 15
panda/src/display/displayRegion.h

@@ -17,25 +17,21 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 #ifndef DISPLAYREGION_H
 #ifndef DISPLAYREGION_H
 #define DISPLAYREGION_H
 #define DISPLAYREGION_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include <pandabase.h>
 
 
-#include <referenceCount.h>
-#include <camera.h>
+#include "pandabase.h"
+
+#include "referenceCount.h"
+#include "camera.h"
+#include "nodeChain.h"
 
 
 #include "plist.h"
 #include "plist.h"
 
 
-////////////////////////////////////////////////////////////////////
-// Defines
-////////////////////////////////////////////////////////////////////
 class GraphicsLayer;
 class GraphicsLayer;
 class GraphicsChannel;
 class GraphicsChannel;
 class GraphicsWindow;
 class GraphicsWindow;
 class GraphicsPipe;
 class GraphicsPipe;
 class CullHandler;
 class CullHandler;
+class qpCamera;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : DisplayRegion
 //       Class : DisplayRegion
@@ -48,7 +44,12 @@ public:
                 const float l, const float r,
                 const float l, const float r,
                 const float b, const float t);
                 const float b, const float t);
   DisplayRegion(int xsize, int ysize);
   DisplayRegion(int xsize, int ysize);
-  virtual ~DisplayRegion();
+private:
+  DisplayRegion(const DisplayRegion &);
+  void operator = (const DisplayRegion &);
+
+public:
+  ~DisplayRegion();
 
 
 PUBLISHED:
 PUBLISHED:
   INLINE void get_dimensions(float &l, float &r, float &b, float &t) const;
   INLINE void get_dimensions(float &l, float &r, float &b, float &t) const;
@@ -66,6 +67,9 @@ PUBLISHED:
   void set_camera(Camera *camera);
   void set_camera(Camera *camera);
   INLINE Camera *get_camera() const;
   INLINE Camera *get_camera() const;
 
 
+  void set_qpcamera(const NodeChain &camera);
+  INLINE const NodeChain &get_qpcamera() const;
+
   INLINE void set_cull_frustum(LensNode *cull_frustum);
   INLINE void set_cull_frustum(LensNode *cull_frustum);
   INLINE LensNode *get_cull_frustum() const;
   INLINE LensNode *get_cull_frustum() const;
 
 
@@ -98,14 +102,12 @@ protected:
 
 
   GraphicsLayer *_layer;
   GraphicsLayer *_layer;
   PT(Camera) _camera;
   PT(Camera) _camera;
+  NodeChain _qpcamera;
+  qpCamera *_camera_node;
   PT(LensNode) _cull_frustum;
   PT(LensNode) _cull_frustum;
 
 
   bool _active;
   bool _active;
 
 
-private:
-  DisplayRegion(const DisplayRegion &);
-  DisplayRegion& operator=(const DisplayRegion &);
-
   friend class GraphicsLayer;
   friend class GraphicsLayer;
 };
 };
 
 

+ 4 - 1
panda/src/display/drawCullHandler.cxx

@@ -18,6 +18,7 @@
 
 
 #include "drawCullHandler.h"
 #include "drawCullHandler.h"
 #include "geom.h"
 #include "geom.h"
+#include "transformState.h"
 #include "renderState.h"
 #include "renderState.h"
 #include "graphicsStateGuardian.h"
 #include "graphicsStateGuardian.h"
 
 
@@ -30,7 +31,9 @@
 //               discovered by the CullTraverser.
 //               discovered by the CullTraverser.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void DrawCullHandler::
 void DrawCullHandler::
-record_geom(Geom *geom, const RenderState *state) {
+record_geom(Geom *geom, const TransformState *transform,
+            const RenderState *state) {
+  _gsg->set_transform(transform);
   _gsg->set_state(state);
   _gsg->set_state(state);
   geom->draw(_gsg);
   geom->draw(_gsg);
 }
 }

+ 2 - 1
panda/src/display/drawCullHandler.h

@@ -40,7 +40,8 @@ public:
   INLINE DrawCullHandler(GraphicsStateGuardian *gsg);
   INLINE DrawCullHandler(GraphicsStateGuardian *gsg);
 
 
   //  virtual void begin_decal();
   //  virtual void begin_decal();
-  virtual void record_geom(Geom *geom, const RenderState *state);
+  virtual void record_geom(Geom *geom, const TransformState *transform,
+                           const RenderState *state);
   //  virtual void push_decal();
   //  virtual void push_decal();
   //  virtual void pop_decal();
   //  virtual void pop_decal();
 
 

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

@@ -99,6 +99,7 @@ cull_and_draw_together() {
       cull_and_draw_together(win, dr);
       cull_and_draw_together(win, dr);
     }
     }
     win->flip();
     win->flip();
+    win->process_events();
   }
   }
 }
 }
 
 
@@ -111,19 +112,27 @@ cull_and_draw_together() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void GraphicsEngine::
 void GraphicsEngine::
 cull_and_draw_together(GraphicsWindow *win, DisplayRegion *dr) {
 cull_and_draw_together(GraphicsWindow *win, DisplayRegion *dr) {
-  Camera *camera = dr->get_camera();
-  if (camera == (Camera *)NULL || !camera->is_active()) {
+  const NodeChain &camera = dr->get_qpcamera();
+  if (camera.is_empty()) {
     // No camera, no draw.
     // No camera, no draw.
     return;
     return;
   }
   }
 
 
-  Lens *lens = camera->get_lens();
+  qpCamera *camera_node;
+  DCAST_INTO_V(camera_node, camera.node());
+
+  if (!camera_node->is_active()) {
+    // Camera inactive, no draw.
+    return;
+  }
+
+  Lens *lens = camera_node->get_lens();
   if (lens == (Lens *)NULL) {
   if (lens == (Lens *)NULL) {
     // No lens, no draw.
     // No lens, no draw.
     return;
     return;
   }
   }
 
 
-  PandaNode *scene = camera->get_qpscene();
+  PandaNode *scene = camera_node->get_scene();
   if (scene == (PandaNode *)NULL) {
   if (scene == (PandaNode *)NULL) {
     // No scene, no draw.
     // No scene, no draw.
     return;
     return;
@@ -143,9 +152,7 @@ cull_and_draw_together(GraphicsWindow *win, DisplayRegion *dr) {
   DrawCullHandler cull_handler(gsg);
   DrawCullHandler cull_handler(gsg);
   qpCullTraverser trav;
   qpCullTraverser trav;
   trav.set_cull_handler(&cull_handler);
   trav.set_cull_handler(&cull_handler);
-  
-  // Here we should figure out the world transform: the camera's
-  // inverse transform.
+  trav.set_world_transform(camera.get_rel_transform(NodeChain()));
   
   
   DisplayRegionStack old_dr = gsg->push_display_region(dr);
   DisplayRegionStack old_dr = gsg->push_display_region(dr);
   gsg->prepare_display_region();
   gsg->prepare_display_region();

+ 16 - 0
panda/src/display/graphicsStateGuardian.I

@@ -144,6 +144,22 @@ set_state(const RenderState *state) {
   _qpstate = _qpstate->issue_delta_set(state, this);
   _qpstate = _qpstate->issue_delta_set(state, this);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::set_transform
+//       Access: Public
+//  Description: Sets the world transform that will be applied to
+//               subsequent geometry.  This is normally called only
+//               during the draw process, immediately before issuing
+//               geometry commands.
+////////////////////////////////////////////////////////////////////
+INLINE void GraphicsStateGuardian::
+set_transform(const TransformState *transform) {
+  if (transform != _transform) {
+    _transform = transform;
+    issue_transform(transform);
+  }
+}
+
 /*
 /*
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_state
 //     Function: GraphicsStateGuardian::get_state

+ 10 - 0
panda/src/display/graphicsStateGuardian.cxx

@@ -879,6 +879,16 @@ void GraphicsStateGuardian::
 end_decal(GeomNode *) {
 end_decal(GeomNode *) {
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::issue_transform
+//       Access: Public, Virtual
+//  Description: Sends the indicated transform matrix to the graphics
+//               API to be applied to future vertices.
+////////////////////////////////////////////////////////////////////
+void GraphicsStateGuardian::
+issue_transform(const TransformState *) {
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::issue_color
 //     Function: GraphicsStateGuardian::issue_color
 //       Access: Public, Virtual
 //       Access: Public, Virtual

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

@@ -35,6 +35,7 @@
 #include "renderTraverser.h"
 #include "renderTraverser.h"
 #include "pStatCollector.h"
 #include "pStatCollector.h"
 #include "allTransitionsWrapper.h"
 #include "allTransitionsWrapper.h"
+#include "transformState.h"
 #include "renderState.h"
 #include "renderState.h"
 
 
 #include "notify.h"
 #include "notify.h"
@@ -142,6 +143,7 @@ public:
 
 
   INLINE void modify_state(const RenderState *state);
   INLINE void modify_state(const RenderState *state);
   INLINE void set_state(const RenderState *state);
   INLINE void set_state(const RenderState *state);
+  INLINE void set_transform(const TransformState *transform);
 
 
   RenderBuffer get_render_buffer(int buffer_type);
   RenderBuffer get_render_buffer(int buffer_type);
 
 
@@ -173,6 +175,7 @@ public:
 
 
   INLINE void clear_cached_state(void) { _state.clear(); };  
   INLINE void clear_cached_state(void) { _state.clear(); };  
 
 
+  virtual void issue_transform(const TransformState *transform);
   virtual void issue_color(const ColorAttrib *attrib);
   virtual void issue_color(const ColorAttrib *attrib);
 
 
 protected:
 protected:
@@ -224,6 +227,7 @@ protected:
   State _state;
   State _state;
 
 
   CPT(RenderState) _qpstate;
   CPT(RenderState) _qpstate;
+  CPT(TransformState) _transform;
 
 
   int _buffer_mask;
   int _buffer_mask;
   Colorf _color_clear_value;
   Colorf _color_clear_value;

+ 18 - 0
panda/src/glgsg/glGraphicsStateGuardian.cxx

@@ -3415,6 +3415,24 @@ issue_polygon_offset(const PolygonOffsetTransition *attrib) {
   report_errors();
   report_errors();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GLGraphicsStateGuardian::issue_transform
+//       Access: Public, Virtual
+//  Description: Sends the indicated transform matrix to the graphics
+//               API to be applied to future vertices.
+////////////////////////////////////////////////////////////////////
+void GLGraphicsStateGuardian::
+issue_transform(const TransformState *transform) {
+#ifdef GSG_VERBOSE
+  glgsg_cat.debug()
+    << "glLoadMatrix(GL_MODELVIEW): " << transform->get_mat() << endl;
+#endif
+  glMatrixMode(GL_MODELVIEW);
+  glLoadMatrixf(transform->get_mat().get_data());
+
+  report_errors();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GLGraphicsStateGuardian::issue_texture
 //     Function: GLGraphicsStateGuardian::issue_texture
 //       Access: Public, Virtual
 //       Access: Public, Virtual

+ 1 - 0
panda/src/glgsg/glGraphicsStateGuardian.h

@@ -156,6 +156,7 @@ public:
   virtual void issue_point_shape(const PointShapeTransition *attrib);
   virtual void issue_point_shape(const PointShapeTransition *attrib);
   virtual void issue_polygon_offset(const PolygonOffsetTransition *attrib);
   virtual void issue_polygon_offset(const PolygonOffsetTransition *attrib);
 
 
+  virtual void issue_transform(const TransformState *transform);
   virtual void issue_texture(const TextureAttrib *attrib);
   virtual void issue_texture(const TextureAttrib *attrib);
 
 
   virtual bool wants_normals(void) const;
   virtual bool wants_normals(void) const;

+ 3 - 0
panda/src/glxdisplay/glxGraphicsWindow.cxx

@@ -1217,6 +1217,9 @@ void glxGraphicsWindow::process_events(void)
   int got_event;
   int got_event;
   glxGraphicsWindow* window;
   glxGraphicsWindow* window;
 
 
+  if (_change_mask)
+    handle_changes();
+
   glxDisplay *glx = _pipe->get_glx_display();
   glxDisplay *glx = _pipe->get_glx_display();
   nassertv(glx != (glxDisplay *)NULL);
   nassertv(glx != (glxDisplay *)NULL);
 
 

+ 16 - 4
panda/src/pgraph/Sources.pp

@@ -1,11 +1,12 @@
 #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
 #define OTHER_LIBS interrogatedb:c dconfig:c dtoolconfig:m \
                    dtoolutil:c dtoolbase:c dtool:m
                    dtoolutil:c dtoolbase:c dtool:m
-#define LOCAL_LIBS gsgbase gobj putil graph linmath express pandabase
+#define LOCAL_LIBS event gsgbase gobj putil graph linmath express pandabase
 
 
 #begin lib_target
 #begin lib_target
   #define TARGET pgraph
   #define TARGET pgraph
   
   
   #define SOURCES \
   #define SOURCES \
+    qpcamera.h qpcamera.I \
     colorAttrib.h colorAttrib.I \
     colorAttrib.h colorAttrib.I \
     config_pgraph.h \
     config_pgraph.h \
     cullHandler.h \
     cullHandler.h \
@@ -14,6 +15,9 @@
     cycleDataReader.h cycleDataReader.I \
     cycleDataReader.h cycleDataReader.I \
     cycleDataWriter.h cycleDataWriter.I \
     cycleDataWriter.h cycleDataWriter.I \
     qpgeomNode.h qpgeomNode.I \
     qpgeomNode.h qpgeomNode.I \
+    qplensNode.h qplensNode.I \
+    nodeChain.h nodeChain.I \
+    nodeChainComponent.h nodeChainComponent.I \
     pandaNode.h pandaNode.I \
     pandaNode.h pandaNode.I \
     pipeline.h pipeline.I \
     pipeline.h pipeline.I \
     pipelineCycler.h pipelineCycler.I \
     pipelineCycler.h pipelineCycler.I \
@@ -21,10 +25,11 @@
     renderAttrib.h renderAttrib.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \
     renderState.h renderState.I \
     textureAttrib.h textureAttrib.I \
     textureAttrib.h textureAttrib.I \
-    transformAttrib.h transformAttrib.I
+    transformState.h transformState.I
 
 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
+    qpcamera.cxx \
     colorAttrib.cxx \
     colorAttrib.cxx \
     config_pgraph.cxx \
     config_pgraph.cxx \
     cullHandler.cxx \
     cullHandler.cxx \
@@ -33,6 +38,9 @@
     cycleDataReader.cxx \
     cycleDataReader.cxx \
     cycleDataWriter.cxx \
     cycleDataWriter.cxx \
     qpgeomNode.cxx \
     qpgeomNode.cxx \
+    qplensNode.cxx \
+    nodeChain.cxx \
+    nodeChainComponent.cxx \
     pandaNode.cxx \
     pandaNode.cxx \
     pipeline.cxx \
     pipeline.cxx \
     pipelineCycler.cxx \
     pipelineCycler.cxx \
@@ -40,7 +48,7 @@
     renderAttrib.cxx \
     renderAttrib.cxx \
     renderState.cxx \
     renderState.cxx \
     textureAttrib.cxx \
     textureAttrib.cxx \
-    transformAttrib.cxx
+    transformState.cxx
 
 
   #if $[DONT_COMBINE_PGRAPH]    
   #if $[DONT_COMBINE_PGRAPH]    
     #define SOURCES $[SOURCES] $[INCLUDED_SOURCES]
     #define SOURCES $[SOURCES] $[INCLUDED_SOURCES]
@@ -49,6 +57,7 @@
   #endif
   #endif
 
 
   #define INSTALL_HEADERS \
   #define INSTALL_HEADERS \
+    qpcamera.h qpcamera.I \
     colorAttrib.h colorAttrib.I \
     colorAttrib.h colorAttrib.I \
     config_pgraph.h \
     config_pgraph.h \
     cullHandler.h \
     cullHandler.h \
@@ -57,6 +66,9 @@
     cycleDataReader.h cycleDataReader.I \
     cycleDataReader.h cycleDataReader.I \
     cycleDataWriter.h cycleDataWriter.I \
     cycleDataWriter.h cycleDataWriter.I \
     qpgeomNode.h qpgeomNode.I \
     qpgeomNode.h qpgeomNode.I \
+    qplensNode.h qplensNode.I \
+    nodeChain.h nodeChain.I \
+    nodeChainComponent.h nodeChainComponent.I \
     pandaNode.h pandaNode.I \
     pandaNode.h pandaNode.I \
     pipeline.h pipeline.I \
     pipeline.h pipeline.I \
     pipelineCycler.h pipelineCycler.I \
     pipelineCycler.h pipelineCycler.I \
@@ -64,7 +76,7 @@
     renderAttrib.h renderAttrib.I \
     renderAttrib.h renderAttrib.I \
     renderState.h renderState.I \
     renderState.h renderState.I \
     textureAttrib.h textureAttrib.I \
     textureAttrib.h textureAttrib.I \
-    transformAttrib.h transformAttrib.I
+    transformState.h transformState.I
 
 
   #define IGATESCAN all
   #define IGATESCAN all
 
 

+ 14 - 3
panda/src/pgraph/config_pgraph.cxx

@@ -18,13 +18,18 @@
 
 
 #include "config_pgraph.h"
 #include "config_pgraph.h"
 
 
+#include "qpcamera.h"
 #include "colorAttrib.h"
 #include "colorAttrib.h"
 #include "qpgeomNode.h"
 #include "qpgeomNode.h"
+#include "qplensNode.h"
+#include "nodeChain.h"
+#include "nodeChainComponent.h"
 #include "pandaNode.h"
 #include "pandaNode.h"
 #include "renderAttrib.h"
 #include "renderAttrib.h"
 #include "renderState.h"
 #include "renderState.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
-#include "transformAttrib.h"
+#include "colorAttrib.h"
+#include "transformState.h"
 
 
 #include "dconfig.h"
 #include "dconfig.h"
 
 
@@ -52,18 +57,24 @@ init_libpgraph() {
   }
   }
   initialized = true;
   initialized = true;
 
 
+  qpCamera::init_type();
   ColorAttrib::init_type();
   ColorAttrib::init_type();
   qpGeomNode::init_type();
   qpGeomNode::init_type();
+  qpLensNode::init_type();
+  NodeChain::init_type();
+  NodeChainComponent::init_type();
   PandaNode::init_type();
   PandaNode::init_type();
   RenderAttrib::init_type();
   RenderAttrib::init_type();
   RenderState::init_type();
   RenderState::init_type();
   TextureAttrib::init_type();
   TextureAttrib::init_type();
-  TransformAttrib::init_type();
+  ColorAttrib::init_type();
+  TransformState::init_type();
 
 
   ColorAttrib::register_with_read_factory();
   ColorAttrib::register_with_read_factory();
   qpGeomNode::register_with_read_factory();
   qpGeomNode::register_with_read_factory();
   PandaNode::register_with_read_factory();
   PandaNode::register_with_read_factory();
   RenderState::register_with_read_factory();
   RenderState::register_with_read_factory();
   TextureAttrib::register_with_read_factory();
   TextureAttrib::register_with_read_factory();
-  TransformAttrib::register_with_read_factory();
+  ColorAttrib::register_with_read_factory();
+  TransformState::register_with_read_factory();
 }
 }

+ 4 - 2
panda/src/pgraph/cullHandler.cxx

@@ -18,6 +18,7 @@
 
 
 #include "cullHandler.h"
 #include "cullHandler.h"
 #include "geom.h"
 #include "geom.h"
+#include "transformState.h"
 #include "renderState.h"
 #include "renderState.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -31,6 +32,7 @@
 //               it's not intended to be used except for debugging.
 //               it's not intended to be used except for debugging.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void CullHandler::
 void CullHandler::
-record_geom(Geom *geom, const RenderState *state) {
-  cerr << *geom << " " << *state << "\n";
+record_geom(Geom *geom, const TransformState *transform,
+            const RenderState *state) {
+  cerr << *geom << " " << *transform << " " << *state << "\n";
 }
 }

+ 3 - 1
panda/src/pgraph/cullHandler.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 class Geom;
 class Geom;
+class TransformState;
 class RenderState;
 class RenderState;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -34,7 +35,8 @@ class RenderState;
 class EXPCL_PANDA CullHandler {
 class EXPCL_PANDA CullHandler {
 public:
 public:
   //  virtual void begin_decal();
   //  virtual void begin_decal();
-  virtual void record_geom(Geom *geom, const RenderState *state);
+  virtual void record_geom(Geom *geom, const TransformState *transform,
+                           const RenderState *state);
   //  virtual void push_decal();
   //  virtual void push_decal();
   //  virtual void pop_decal();
   //  virtual void pop_decal();
 };
 };

+ 1 - 1
panda/src/pgraph/cycleDataReader.I

@@ -27,7 +27,7 @@ INLINE CycleDataReader<CycleDataType>::
 CycleDataReader(const PipelineCycler<CycleDataType> &cycler) :
 CycleDataReader(const PipelineCycler<CycleDataType> &cycler) :
   _cycler(cycler)
   _cycler(cycler)
 {
 {
-  _pointer = (const CycleDataType *)_cycler.read();
+  _pointer = _cycler.read();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 1 - 1
panda/src/pgraph/cycleDataWriter.I

@@ -27,7 +27,7 @@ INLINE CycleDataWriter<CycleDataType>::
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler) :
 CycleDataWriter(PipelineCycler<CycleDataType> &cycler) :
   _cycler(cycler)
   _cycler(cycler)
 {
 {
-  _pointer = (CycleDataType *)_cycler.write();
+  _pointer = _cycler.write();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

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

@@ -0,0 +1,323 @@
+// 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::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(*this);
+  parent._head = parent._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;
+}
+

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

@@ -0,0 +1,514 @@
+// 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());
+  }
+}

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

@@ -0,0 +1,176 @@
+// 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

+ 203 - 0
panda/src/pgraph/nodeChainComponent.I

@@ -0,0 +1,203 @@
+// Filename: nodeChainComponent.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: NodeChainComponent::CData::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE NodeChainComponent::CData::
+CData() {
+  _length = 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::CData::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE NodeChainComponent::CData::
+CData(const NodeChainComponent::CData &copy) :
+  _next(copy._next),
+  _length(copy._length)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::Constructor
+//       Access: Private
+//  Description: Constructs a new NodeChainComponent 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) :
+  _node(node)
+{
+#ifdef DO_MEMORY_USAGE
+  MemoryUsage::update_type(this, get_class_type());
+#endif
+  CDWriter cdata(_cycler);
+  cdata->_next = next;
+
+  if (next != (NodeChainComponent *)NULL) {
+    cdata->_length = next->get_length() + 1;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::Copy Constructor
+//       Access: Private
+//  Description: NodeChainComponents should not be copied.
+////////////////////////////////////////////////////////////////////
+INLINE NodeChainComponent::
+NodeChainComponent(const NodeChainComponent &copy) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::Copy Assignment Operator
+//       Access: Private
+//  Description: NodeChainComponents should not be copied.
+////////////////////////////////////////////////////////////////////
+INLINE void NodeChainComponent::
+operator = (const NodeChainComponent &copy) {
+  nassertv(false);
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE NodeChainComponent::
+~NodeChainComponent() {
+  nassertv(_node != (PandaNode *)NULL);
+  _node->delete_component(this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::get_node
+//       Access: Public
+//  Description: Returns the node referenced by this component.
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *NodeChainComponent::
+get_node() const {
+  return _node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::is_top_node
+//       Access: Public
+//  Description: Returns true if this component represents the top
+//               node in the chain.
+////////////////////////////////////////////////////////////////////
+INLINE bool NodeChainComponent::
+is_top_node() const {
+  CDReader cdata(_cycler);
+  return (cdata->_next == (NodeChainComponent *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::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::
+is_collapsed() const {
+  CDReader cdata(_cycler);
+  return (cdata->_length == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::get_length
+//       Access: Public
+//  Description: Returns the length of the chain.
+////////////////////////////////////////////////////////////////////
+INLINE int NodeChainComponent::
+get_length() const {
+  CDReader cdata(_cycler);
+  nassertr(!is_collapsed(), 0);
+  return cdata->_length;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::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::
+get_collapsed() const {
+  CDReader cdata(_cycler);
+  nassertr(is_collapsed(), (NodeChainComponent *)NULL);
+  return cdata->_next;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::set_next
+//       Access: Private
+//  Description: Sets the next pointer in the chain.
+////////////////////////////////////////////////////////////////////
+INLINE void NodeChainComponent::
+set_next(NodeChainComponent *next) {
+  CDWriter cdata(_cycler);
+  nassertv(!is_collapsed());
+  nassertv(next != (NodeChainComponent *)NULL);
+  cdata->_next = next;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::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::
+set_top_node() {
+  CDWriter cdata(_cycler);
+  nassertv(!is_collapsed());
+  cdata->_next = (NodeChainComponent *)NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::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
+//               to an instance being removed higher up in the graph.
+////////////////////////////////////////////////////////////////////
+INLINE void NodeChainComponent::
+collapse_with(NodeChainComponent *next) {
+  CDWriter cdata(_cycler);
+  nassertv(!is_collapsed());
+  nassertv(next != (NodeChainComponent *)NULL);
+  cdata->_next = next;
+  cdata->_length = 0;
+
+  // We indicate a component has been collapsed by setting its length
+  // to zero.
+}

+ 102 - 0
panda/src/pgraph/nodeChainComponent.cxx

@@ -0,0 +1,102 @@
+// Filename: nodeChainComponent.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 "nodeChainComponent.h"
+
+TypeHandle NodeChainComponent::_type_handle;
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::CData::make_copy
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+CycleData *NodeChainComponent::CData::
+make_copy() const {
+  return new CData(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::get_next
+//       Access: Public
+//  Description: Returns the next component in the chain.
+////////////////////////////////////////////////////////////////////
+NodeChainComponent *NodeChainComponent::
+get_next() const {
+  CDReader cdata(_cycler);
+  nassertr(!is_collapsed(), (NodeChainComponent *)NULL);
+
+  NodeChainComponent *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()) {
+    next = next->uncollapse();
+    ((NodeChainComponent *)this)->set_next(next);
+  }
+
+  return next;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::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::
+fix_length() {
+  int length_should_be = 1;
+  if (!is_top_node()) {
+    length_should_be = get_next()->get_length() + 1;
+  }
+  if (get_length() == length_should_be) {
+    return false;
+  }
+
+  CDWriter cdata(_cycler);
+  cdata->_length = length_should_be;
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: NodeChainComponent::uncollapse
+//       Access: Public
+//  Description: Returns this component pointer if the component is
+//               not collapsed; or if it has been collapsed, returns
+//               the pointer it has been collapsed into.
+//
+//               Collapsing can happen at any time to any component in
+//               the chain and we have to deal with it.  It happens
+//               when a node is removed further up the chain that
+//               results in two instances becoming the same thing.
+////////////////////////////////////////////////////////////////////
+NodeChainComponent *NodeChainComponent::
+uncollapse() {
+  NodeChainComponent *comp = this;
+
+  while (comp->is_collapsed()) {
+    comp = comp->get_collapsed();
+  }
+
+  return comp;
+}

+ 106 - 0
panda/src/pgraph/nodeChainComponent.h

@@ -0,0 +1,106 @@
+// Filename: nodeChainComponent.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 NODECHAINCOMPONENT_H
+#define NODECHAINCOMPONENT_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+#include "pointerTo.h"
+#include "referenceCount.h"
+#include "cycleData.h"
+#include "cycleDataReader.h"
+#include "cycleDataWriter.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : NodeChainComponent
+// Description : This is one component of a NodeChain.  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
+//               of these from an arbitrary component in the graph to
+//               the root.
+//
+//               This whole NodeChain 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.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA NodeChainComponent : public ReferenceCount {
+private:
+  INLINE NodeChainComponent(PandaNode *node, NodeChainComponent *next = NULL);
+  INLINE NodeChainComponent(const NodeChainComponent &copy);
+  INLINE void operator = (const NodeChainComponent &copy);
+
+public:
+  INLINE ~NodeChainComponent();
+  
+  INLINE PandaNode *get_node() const;
+  INLINE bool is_top_node() const;
+  INLINE bool is_collapsed() const;
+  
+  NodeChainComponent *get_next() const;
+  INLINE int get_length() const;
+  INLINE NodeChainComponent *get_collapsed() const;
+
+  bool fix_length();
+  NodeChainComponent *uncollapse();
+  
+private:
+  INLINE void set_next(NodeChainComponent *next);
+  INLINE void set_top_node();
+  INLINE void collapse_with(NodeChainComponent *next);
+
+  PT(PandaNode) _node;
+
+  // This is the data that must be cycled between pipeline stages.
+  class EXPCL_PANDA CData : public CycleData {
+  public:
+    INLINE CData();
+    CData(const CData &copy);
+    virtual CycleData *make_copy() const;
+
+    PT(NodeChainComponent) _next;
+    int _length;
+  };
+
+  PipelineCycler<CData> _cycler;
+  typedef CycleDataReader<CData> CDReader;
+  typedef CycleDataWriter<CData> CDWriter;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    ReferenceCount::init_type();
+    register_type(_type_handle, "NodeChainComponent",
+                  ReferenceCount::get_class_type());
+  }
+  
+private:
+  static TypeHandle _type_handle;
+  friend class PandaNode;
+};
+
+#include "nodeChainComponent.I"
+
+#endif

+ 86 - 10
panda/src/pgraph/pandaNode.I

@@ -62,6 +62,42 @@ get_sort() const {
   return _sort;
   return _sort;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::UpConnection::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode::UpConnection::
+UpConnection(PandaNode *parent) :
+  _parent(parent)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::UpConnection::operator <
+//       Access: Public
+//  Description: Sorts the up connections of a node by pointer.  This
+//               is different from the down connections of a node,
+//               which are sorted by the specified _sort number.  This
+//               makes it easy to locate a particular parent of a node
+//               by pointer, or to test for a parent-child
+//               relationship given two node pointers.
+////////////////////////////////////////////////////////////////////
+INLINE bool PandaNode::UpConnection::
+operator < (const UpConnection &other) const {
+  return _parent < other._parent;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::UpConnection::get_parent
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *PandaNode::UpConnection::
+get_parent() const {
+  return _parent;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::CData::Constructor
 //     Function: PandaNode::CData::Constructor
 //       Access: Public
 //       Access: Public
@@ -69,7 +105,8 @@ get_sort() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE PandaNode::CData::
 INLINE PandaNode::CData::
 CData() {
 CData() {
-  _state_changes = RenderState::make_empty();
+  _state = RenderState::make_empty();
+  _transform = TransformState::make_identity();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -140,7 +177,7 @@ INLINE PandaNode *PandaNode::
 get_parent(int n) const {
 get_parent(int n) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
   nassertr(n >= 0 && n < (int)cdata->_up.size(), NULL);
   nassertr(n >= 0 && n < (int)cdata->_up.size(), NULL);
-  return cdata->_up[n];
+  return cdata->_up[n].get_parent();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -152,7 +189,7 @@ get_parent(int n) const {
 INLINE int PandaNode::
 INLINE int PandaNode::
 find_parent(PandaNode *node) const {
 find_parent(PandaNode *node) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  Up::const_iterator ui = cdata->_up.find(node);
+  Up::const_iterator ui = cdata->_up.find(UpConnection(node));
   if (ui == cdata->_up.end()) {
   if (ui == cdata->_up.end()) {
     return -1;
     return -1;
   }
   }
@@ -212,7 +249,7 @@ get_child_sort(int n) const {
 INLINE void PandaNode::
 INLINE void PandaNode::
 set_attrib(const RenderAttrib *attrib, int override) {
 set_attrib(const RenderAttrib *attrib, int override) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
-  cdata->_state_changes = cdata->_state_changes->add(attrib, override);
+  cdata->_state = cdata->_state->add(attrib, override);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -227,9 +264,9 @@ set_attrib(const RenderAttrib *attrib, int override) {
 INLINE const RenderAttrib *PandaNode::
 INLINE const RenderAttrib *PandaNode::
 get_attrib(TypeHandle type) const {
 get_attrib(TypeHandle type) const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  int index = cdata->_state_changes->find_attrib(type);
+  int index = cdata->_state->find_attrib(type);
   if (index >= 0) {
   if (index >= 0) {
-    return cdata->_state_changes->get_attrib(index);
+    return cdata->_state->get_attrib(index);
   }
   }
   return NULL;
   return NULL;
 }
 }
@@ -245,7 +282,7 @@ get_attrib(TypeHandle type) const {
 INLINE void PandaNode::
 INLINE void PandaNode::
 clear_attrib(TypeHandle type) {
 clear_attrib(TypeHandle type) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
-  cdata->_state_changes = cdata->_state_changes->remove(type);
+  cdata->_state = cdata->_state->remove(type);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -261,7 +298,7 @@ clear_attrib(TypeHandle type) {
 INLINE void PandaNode::
 INLINE void PandaNode::
 set_state(const RenderState *state) {
 set_state(const RenderState *state) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
-  cdata->_state_changes = state;
+  cdata->_state = state;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -276,7 +313,7 @@ set_state(const RenderState *state) {
 INLINE const RenderState *PandaNode::
 INLINE const RenderState *PandaNode::
 get_state() const {
 get_state() const {
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  return cdata->_state_changes;
+  return cdata->_state;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -290,7 +327,46 @@ get_state() const {
 INLINE void PandaNode::
 INLINE void PandaNode::
 clear_state() {
 clear_state() {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
-  cdata->_state_changes = RenderState::make_empty();
+  cdata->_state = RenderState::make_empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::set_transform
+//       Access: Published
+//  Description: Sets the transform that will be applied to this node
+//               and below.  This defines a new coordinate space at
+//               this point in the scene graph and below.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+set_transform(const TransformState *transform) {
+  CDWriter cdata(_cycler);
+  cdata->_transform = transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_transform
+//       Access: Published
+//  Description: Returns the transform that has been set on this
+//               particular node.  This is not the net transform from
+//               the root, but simply the transform on this particular
+//               node.
+////////////////////////////////////////////////////////////////////
+INLINE const TransformState *PandaNode::
+get_transform() const {
+  CDReader cdata(_cycler);
+  return cdata->_transform;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::clear_transform
+//       Access: Published
+//  Description: Resets the transform on this node to the identity
+//               transform.
+////////////////////////////////////////////////////////////////////
+INLINE void PandaNode::
+clear_transform() {
+  CDWriter cdata(_cycler);
+  cdata->_transform = TransformState::make_identity();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 409 - 28
panda/src/pgraph/pandaNode.cxx

@@ -17,6 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "pandaNode.h"
 #include "pandaNode.h"
+#include "config_pgraph.h"
+#include "nodeChainComponent.h"
 #include "bamReader.h"
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "bamWriter.h"
 #include "indent.h"
 #include "indent.h"
@@ -34,9 +36,11 @@ PandaNode::CData::
 CData(const PandaNode::CData &copy) :
 CData(const PandaNode::CData &copy) :
   _down(copy._down),
   _down(copy._down),
   _up(copy._up),
   _up(copy._up),
+  _chains(copy._chains),
   _node_bounds(copy._node_bounds),
   _node_bounds(copy._node_bounds),
   _subgraph_bounds(copy._subgraph_bounds),
   _subgraph_bounds(copy._subgraph_bounds),
-  _state_changes(copy._state_changes)
+  _state(copy._state),
+  _transform(copy._transform)
 {
 {
 }
 }
 
 
@@ -74,9 +78,11 @@ PandaNode(const PandaNode &copy) :
 {
 {
   // Copying a node does not copy its children.
   // Copying a node does not copy its children.
 
 
-  // Copy the other node's bounding volume.
+  // Copy the other node's state and bounding volume.
   CDReader copy_cdata(copy._cycler);
   CDReader copy_cdata(copy._cycler);
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
+  cdata->_state = copy_cdata->_state;
+  cdata->_transform = copy_cdata->_transform;
   cdata->_node_bounds = copy_cdata->_node_bounds;
   cdata->_node_bounds = copy_cdata->_node_bounds;
 }
 }
 
 
@@ -91,9 +97,11 @@ operator = (const PandaNode &copy) {
   Namable::operator = (copy);
   Namable::operator = (copy);
   ReferenceCount::operator = (copy);
   ReferenceCount::operator = (copy);
 
 
-  // Copy the other node's bounding volume.
+  // Copy the other node's state and bounding volume.
   CDReader copy_cdata(copy._cycler);
   CDReader copy_cdata(copy._cycler);
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
+  cdata->_state = copy_cdata->_state;
+  cdata->_transform = copy_cdata->_transform;
   cdata->_node_bounds = copy_cdata->_node_bounds;
   cdata->_node_bounds = copy_cdata->_node_bounds;
 }
 }
 
 
@@ -144,19 +152,32 @@ find_child(PandaNode *node) const {
 //
 //
 //               If the same child is added to a node more than once,
 //               If the same child is added to a node more than once,
 //               the previous instance is first removed.
 //               the previous instance is first removed.
-//
-//               The return value is the index of the new child.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-int PandaNode::
-add_child(PandaNode *child, int sort) {
-  remove_child(child);
+void PandaNode::
+add_child(PandaNode *child_node, int sort) {
+  // Ensure the child_node is not deleted while we do this.
+  PT(PandaNode) keep_child = child_node;
+  remove_child(child_node);
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
-  CDWriter cdata_child(child->_cycler);
-
-  Down::iterator ci = cdata->_down.insert(DownConnection(child, sort));
-  cdata_child->_up.insert(this);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata->_down.insert(DownConnection(child_node, sort));
+  cdata_child->_up.insert(UpConnection(this));
+
+  // We also have to adjust any NodeChainComponents the child might
+  // have that reference the child as a top node.  Any other
+  // components we can leave alone, because we are making a new
+  // instance of the child.
+  Chains::iterator ci;
+  for (ci = cdata_child->_chains.begin();
+       ci != cdata_child->_chains.end();
+       ++ci) {
+    if ((*ci)->is_top_node()) {
+      (*ci)->set_next(get_generic_component());
+    }
+  }
 
 
-  return ci - cdata->_down.begin();
+  child_node->fix_chain_lengths();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -169,12 +190,41 @@ remove_child(int n) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
   nassertv(n >= 0 && n < (int)cdata->_down.size());
   nassertv(n >= 0 && n < (int)cdata->_down.size());
 
 
-  PandaNode *child = cdata->_down[n].get_child();
-  CDWriter cdata_child(child->_cycler);
+  PT(PandaNode) child_node = cdata->_down[n].get_child();
+  CDWriter cdata_child(child_node->_cycler);
 
 
   cdata->_down.erase(cdata->_down.begin() + n);
   cdata->_down.erase(cdata->_down.begin() + n);
-  int num_erased = cdata_child->_up.erase(this);
+  int num_erased = cdata_child->_up.erase(UpConnection(this));
   nassertv(num_erased == 1);
   nassertv(num_erased == 1);
+
+  // Now sever any NodeChainComponents on the child that reference
+  // this node.  If we have multiple of these, we have to collapse
+  // them together.
+  NodeChainComponent *collapsed = (NodeChainComponent *)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) {
+        (*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
+        // still keep a pointer to this one, so we can't remove it
+        // altogether.
+        (*ci)->collapse_with(collapsed);
+        cdata_child->_chains.erase(ci);
+      }
+    }
+    ci = cnext;
+  }
+
+  child_node->fix_chain_lengths();
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -185,24 +235,49 @@ remove_child(int n) {
 //               already a child of the node.
 //               already a child of the node.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool PandaNode::
 bool PandaNode::
-remove_child(PandaNode *child) {
-  CDWriter cdata_child(child->_cycler);
+remove_child(PandaNode *child_node) {
+  // Ensure the child_node is not deleted while we do this.
+  PT(PandaNode) keep_child = child_node;
+  CDWriter cdata_child(child_node->_cycler);
 
 
   // First, look for and remove this node from the child's parent
   // First, look for and remove this node from the child's parent
   // list.
   // list.
-  int num_erased = cdata_child->_up.erase(this);
+  int num_erased = cdata_child->_up.erase(UpConnection(this));
   if (num_erased == 0) {
   if (num_erased == 0) {
     // No such node; it wasn't our child to begin with.
     // No such node; it wasn't our child to begin with.
     return false;
     return false;
   }
   }
 
 
+  // Now sever any NodeChainComponents 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;
+  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) {
+        (*ci)->set_top_node();
+        collapsed = (*ci);
+      } else {
+        (*ci)->collapse_with(collapsed);
+        cdata_child->_chains.erase(ci);
+      }
+    }
+    ci = cnext;
+  }
+
+  child_node->fix_chain_lengths();
+
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
 
 
   // Now, look for and remove the child node from our down list.
   // Now, look for and remove the child node from our down list.
-  Down::iterator ci;
-  for (ci = cdata->_down.begin(); ci != cdata->_down.end(); ++ci) {
-    if ((*ci).get_child() == child) {
-      cdata->_down.erase(ci);
+  Down::iterator di;
+  for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+    if ((*di).get_child() == child_node) {
+      cdata->_down.erase(di);
       return true;
       return true;
     }
     }
   }
   }
@@ -222,9 +297,32 @@ remove_all_children() {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
   Down::iterator ci;
   Down::iterator ci;
   for (ci = cdata->_down.begin(); ci != cdata->_down.end(); ++ci) {
   for (ci = cdata->_down.begin(); ci != cdata->_down.end(); ++ci) {
-    PandaNode *child = (*ci).get_child();
-    CDWriter child_cdata(child->_cycler);
-    child_cdata->_up.erase(this);
+    PT(PandaNode) child_node = (*ci).get_child();
+    CDWriter cdata_child(child_node->_cycler);
+    cdata_child->_up.erase(UpConnection(this));
+
+    // Now sever any NodeChainComponents 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;
+    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) {
+          (*ci)->set_top_node();
+          collapsed = (*ci);
+        } else {
+          (*ci)->collapse_with(collapsed);
+          cdata_child->_chains.erase(ci);
+        }
+      }
+      ci = cnext;
+    }
+
+    child_node->fix_chain_lengths();
   }
   }
 }
 }
 
 
@@ -247,8 +345,11 @@ void PandaNode::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
   indent(out, indent_level) << *this;
   indent(out, indent_level) << *this;
   CDReader cdata(_cycler);
   CDReader cdata(_cycler);
-  if (!cdata->_state_changes->is_empty()) {
-    out << " (" << *cdata->_state_changes << ")";
+  if (!cdata->_transform->is_identity()) {
+    out << " T";
+  }
+  if (!cdata->_state->is_empty()) {
+    out << " (" << *cdata->_state << ")";
   }
   }
   out << "\n";
   out << "\n";
 }
 }
@@ -269,6 +370,286 @@ is_geom_node() const {
   return false;
   return false;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::attach
+//       Access: Private, Static
+//  Description: Creates a new parent-child relationship, and returns
+//               the new NodeChainComponent.  If the child was already
+//               attached to the indicated parent, repositions it and
+//               returns the original NodeChainComponent.
+////////////////////////////////////////////////////////////////////
+PT(NodeChainComponent) PandaNode::
+attach(NodeChainComponent *parent, PandaNode *child_node, int sort) {
+  nassertr(parent != (NodeChainComponent *)NULL, (NodeChainComponent *)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);
+
+  if (child == (NodeChainComponent *)NULL) {
+    // The child was not already attached to the parent, so get a new
+    // component.
+    child = get_top_component(child_node);
+  }
+
+  reparent(parent, child, sort);
+  return child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::detach
+//       Access: Private, Static
+//  Description: Breaks a parent-child relationship.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+detach(NodeChainComponent *child) {
+  nassertv(child != (NodeChainComponent *)NULL);
+  nassertv(!child->is_top_node());
+  PandaNode *child_node = child->get_node();
+  PandaNode *parent_node = child->get_next()->get_node();
+
+  // Break the NodeChainComponent connection.
+  child->set_top_node();
+
+  CDWriter cdata_child(child_node->_cycler);
+
+  // Any other components in the same child_node that previously
+  // referenced the same parent has now become invalid and must be
+  // collapsed into this one and removed from the chains set.
+  Chains::iterator ci;
+  ci = cdata_child->_chains.begin();
+  while (ci != cdata_child->_chains.end()) {
+    Chains::iterator cnext = ci;
+    ++cnext;
+    if ((*ci) != child && !(*ci)->is_top_node() && 
+        (*ci)->get_next()->get_node() == parent_node) {
+      (*ci)->collapse_with(child);
+      cdata_child->_chains.erase(ci);
+    }
+    ci = cnext;
+  }
+
+  child_node->fix_chain_lengths();
+
+  // Now look for the child and break the actual connection.
+
+  // First, look for and remove the parent node from the child's up
+  // list.
+  int num_erased = cdata_child->_up.erase(UpConnection(parent_node));
+  nassertv(num_erased == 1);
+
+  CDWriter cdata_parent(parent_node->_cycler);
+
+  // Now, look for and remove the child node from the parent's down list.
+  Down::iterator di;
+  for (di = cdata_parent->_down.begin(); 
+       di != cdata_parent->_down.end(); 
+       ++di) {
+    if ((*di).get_child() == child_node) {
+      cdata_parent->_down.erase(di);
+      return;
+    }
+  }
+
+  // We shouldn't get here unless there was a parent-child mismatch.
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::reparent
+//       Access: Private, Static
+//  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);
+
+  if (!child->is_top_node()) {
+    detach(child);
+  }
+
+  // Adjust the NodeChainComponents.
+  child->set_next(new_parent);
+
+  PandaNode *child_node = child->get_node();
+  PandaNode *parent_node = new_parent->get_node();
+
+  // Now reattach at the indicated sort position.
+  CDWriter cdata_parent(parent_node->_cycler);
+  CDWriter cdata_child(child_node->_cycler);
+  
+  cdata_parent->_down.insert(DownConnection(child_node, sort));
+  cdata_child->_up.insert(UpConnection(parent_node));
+
+  cdata_child->_chains.insert(child);
+  child_node->fix_chain_lengths();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_component
+//       Access: Private, Static
+//  Description: Returns the NodeChainComponent 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);
+  PandaNode *parent_node = parent->get_node();
+
+  {
+    CDReader cdata_child(child_node->_cycler);
+
+    // First, walk through the list of NodeChainComponents we already
+    // have on the child, looking for one that already exists,
+    // referencing the indicated parent component.
+    Chains::iterator ci;
+    for (ci = cdata_child->_chains.begin(); 
+         ci != cdata_child->_chains.end(); 
+         ++ci) {
+      if ((*ci)->get_next() == parent) {
+        // If we already have such a component, just return it.
+        return (*ci);
+      }
+    }
+  }
+    
+  // We don't already have a NodeChainComponent 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);
+    CDWriter cdata_child(child_node->_cycler);
+    cdata_child->_chains.insert(child);
+    return child;
+  } else {
+    // They aren't related.  Return NULL.
+    return NULL;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_top_component
+//       Access: Private, Static
+//  Description: Returns a NodeChainComponent 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).
+////////////////////////////////////////////////////////////////////
+PT(NodeChainComponent) PandaNode::
+get_top_component(PandaNode *child_node) {
+  {
+    CDReader cdata_child(child_node->_cycler);
+
+    // Walk through the list of NodeChainComponents we already have on
+    // the child, looking for one that already exists as a top node.
+    Chains::const_iterator ci;
+    for (ci = cdata_child->_chains.begin(); 
+         ci != cdata_child->_chains.end(); 
+         ++ci) {
+      if ((*ci)->is_top_node()) {
+        // If we already have such a component, just return it.
+        return (*ci);
+      }
+    }
+  }
+
+  // We don't already have such a NodeChainComponent; create and
+  // return a new one.
+  PT(NodeChainComponent) child = 
+    new NodeChainComponent(child_node, (NodeChainComponent *)NULL);
+  CDWriter cdata_child(child_node->_cycler);
+  cdata_child->_chains.insert(child);
+
+  return child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::get_generic_component
+//       Access: Private
+//  Description: Returns a NodeChainComponent 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::
+get_generic_component() {
+  int num_parents = get_num_parents();
+  if (num_parents == 0) {
+    return get_top_component(this);
+
+  } else {
+    if (num_parents != 1) {
+      pgraph_cat.warning()
+        << *this << " has " << num_parents
+        << " parents; choosing arbitrary path to root.\n";
+    }
+    PT(NodeChainComponent) parent = get_parent(0)->get_generic_component();
+    return get_component(parent, this);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::delete_component
+//       Access: Private
+//  Description: Removes a NodeChainComponent from the set prior to
+//               its deletion.  This should only be called by the
+//               NodeChainComponent destructor.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+delete_component(NodeChainComponent *component) {
+  // We have to remove the component from all of the pipeline stages,
+  // not just the current one.
+  int max_num_erased = 0;
+
+  int num_stages = _cycler.get_num_stages();
+  for (int i = 0; i < num_stages; i++) {
+    if (_cycler.is_stage_unique(i)) {
+      CData *cdata = _cycler.write_stage(i);
+      int num_erased = cdata->_chains.erase(component);
+      max_num_erased = max(max_num_erased, num_erased);
+      _cycler.release_write_stage(i, cdata);
+    }
+  }
+  nassertv(max_num_erased == 1);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::fix_chain_lengths
+//       Access: Private
+//  Description: Recursively fixes the _length member of each
+//               NodeChainComponent at this level and below, after an
+//               add or delete child operation that might have messed
+//               these up.
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+fix_chain_lengths() {
+  CDReader cdata(_cycler);
+  bool any_wrong = false;
+
+  Chains::const_iterator ci;
+  for (ci = cdata->_chains.begin(); ci != cdata->_chains.end(); ++ci) {
+    if ((*ci)->fix_length()) {
+      any_wrong = true;
+    }
+  }
+  
+  // If any chains were updated, we have to recurse on all of our
+  // children, since any one of those chains might be shared by any of
+  // our child nodes.
+  if (any_wrong) {
+    Down::const_iterator di;
+    for (di = cdata->_down.begin(); di != cdata->_down.end(); ++di) {
+      (*di).get_child()->fix_chain_lengths();
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::register_with_read_factory
 //     Function: PandaNode::register_with_read_factory
 //       Access: Public, Static
 //       Access: Public, Static

+ 49 - 9
panda/src/pgraph/pandaNode.h

@@ -26,6 +26,7 @@
 #include "cycleDataWriter.h"
 #include "cycleDataWriter.h"
 #include "pipelineCycler.h"
 #include "pipelineCycler.h"
 #include "renderState.h"
 #include "renderState.h"
+#include "transformState.h"
 
 
 #include "typedWritable.h"
 #include "typedWritable.h"
 #include "boundedObject.h"
 #include "boundedObject.h"
@@ -35,6 +36,8 @@
 #include "ordered_vector.h"
 #include "ordered_vector.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 
 
+class NodeChainComponent;
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : PandaNode
 //       Class : PandaNode
 // Description : A basic node of the scene graph or data graph.  This
 // Description : A basic node of the scene graph or data graph.  This
@@ -58,9 +61,9 @@ PUBLISHED:
   INLINE int get_child_sort(int n) const;
   INLINE int get_child_sort(int n) const;
   int find_child(PandaNode *node) const;
   int find_child(PandaNode *node) const;
 
 
-  int add_child(PandaNode *child, int sort = 0);
+  void add_child(PandaNode *child_node, int sort = 0);
   void remove_child(int n);
   void remove_child(int n);
-  bool remove_child(PandaNode *child);
+  bool remove_child(PandaNode *child_node);
 
 
   /*
   /*
   bool stash_child(PandaNode *child);
   bool stash_child(PandaNode *child);
@@ -80,12 +83,31 @@ PUBLISHED:
   INLINE const RenderState *get_state() const;
   INLINE const RenderState *get_state() const;
   INLINE void clear_state();
   INLINE void clear_state();
 
 
+  INLINE void set_transform(const TransformState *transform);
+  INLINE const TransformState *get_transform() const;
+  INLINE void clear_transform();
+
   virtual void output(ostream &out) const;
   virtual void output(ostream &out) const;
   virtual void write(ostream &out, int indent_level) const;
   virtual void write(ostream &out, int indent_level) const;
 
 
 public:
 public:
   virtual bool is_geom_node() const;
   virtual bool is_geom_node() const;
 
 
+private:
+  // parent-child manipulation for NodeChain support.  Don't try to
+  // call these directly.
+  static PT(NodeChainComponent) attach(NodeChainComponent *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,
+                                              PandaNode *child);
+  static PT(NodeChainComponent) get_top_component(PandaNode *child);
+  PT(NodeChainComponent) get_generic_component();
+  void delete_component(NodeChainComponent *component);
+  void fix_chain_lengths();
+
 private:
 private:
   class EXPCL_PANDA DownConnection {
   class EXPCL_PANDA DownConnection {
   public:
   public:
@@ -102,13 +124,27 @@ private:
     int _sort;
     int _sort;
   };
   };
   typedef ov_multiset<DownConnection> Down;
   typedef ov_multiset<DownConnection> Down;
-  // Parent pointers are not reference counted.  That way, parents and
-  // children do not circularly reference each other.  In fact, parent
-  // pointers are just simple pointers, with no additional data.  We
-  // don't really need to keep the parent pointers around, but it's
-  // nice to be able to walk up the graph.
-  typedef ov_set<PandaNode *> Up;
 
 
+  class EXPCL_PANDA UpConnection {
+  public:
+    INLINE UpConnection(PandaNode *child);
+    INLINE bool operator < (const UpConnection &other) const;
+    INLINE PandaNode *get_parent() const;
+
+  private:
+    // Parent pointers are not reference counted.  That way, parents and
+    // children do not circularly reference each other.
+    PandaNode *_parent;
+  };
+  typedef ov_set<UpConnection> Up;
+
+  // We also maintain a set of NodeChainComponents 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
+  // set.
+  typedef pset<NodeChainComponent *> Chains;
+  
   // This is the data that must be cycled between pipeline stages.
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA CData : public CycleData {
   class EXPCL_PANDA CData : public CycleData {
   public:
   public:
@@ -118,10 +154,12 @@ private:
 
 
     Down _down;
     Down _down;
     Up _up;
     Up _up;
+    Chains _chains;
 
 
     BoundedObject _node_bounds;
     BoundedObject _node_bounds;
     BoundedObject _subgraph_bounds;
     BoundedObject _subgraph_bounds;
-    CPT(RenderState) _state_changes;
+    CPT(RenderState) _state;
+    CPT(TransformState) _transform;
   };
   };
 
 
   PipelineCycler<CData> _cycler;
   PipelineCycler<CData> _cycler;
@@ -175,6 +213,8 @@ private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
 
 
   friend class PandaNode::Children;
   friend class PandaNode::Children;
+  friend class NodeChain;
+  friend class NodeChainComponent;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const PandaNode &node) {
 INLINE ostream &operator << (ostream &out, const PandaNode &node) {

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

@@ -6,4 +6,6 @@
 #include "cycleDataReader.cxx"
 #include "cycleDataReader.cxx"
 #include "cycleDataWriter.cxx"
 #include "cycleDataWriter.cxx"
 #include "qpgeomNode.cxx"
 #include "qpgeomNode.cxx"
-#include "pandaNode.cxx"
+#include "qplensNode.cxx"
+#include "nodeChain.cxx"
+#include "nodeChainComponent.cxx"

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

@@ -1,3 +1,4 @@
+#include "pandaNode.cxx"
 #include "pipeline.cxx"
 #include "pipeline.cxx"
 #include "pipelineCycler.cxx"
 #include "pipelineCycler.cxx"
 #include "pipelineCyclerBase.cxx"
 #include "pipelineCyclerBase.cxx"
@@ -5,5 +6,4 @@
 #include "renderState.cxx"
 #include "renderState.cxx"
 #include "test_pgraph.cxx"
 #include "test_pgraph.cxx"
 #include "textureAttrib.cxx"
 #include "textureAttrib.cxx"
-#include "transformAttrib.cxx"
-
+#include "transformState.cxx"

+ 33 - 0
panda/src/pgraph/pipelineCycler.I

@@ -28,3 +28,36 @@ PipelineCycler(Pipeline *pipeline) :
   PipelineCyclerBase(new CycleDataType, pipeline)
   PipelineCyclerBase(new CycleDataType, pipeline)
 {
 {
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCycler::read
+//       Access: Public
+//  Description: See PipelineCyclerBase::read().
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE const CycleDataType *PipelineCycler<CycleDataType>::
+read() const {
+  return (const CycleDataType *)PipelineCyclerBase::read();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCycler::write
+//       Access: Public
+//  Description: See PipelineCyclerBase::write().
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE CycleDataType *PipelineCycler<CycleDataType>::
+write() {
+  return (CycleDataType *)PipelineCyclerBase::write();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCycler::write_stage
+//       Access: Public
+//  Description: See PipelineCyclerBase::write_stage().
+////////////////////////////////////////////////////////////////////
+template<class CycleDataType>
+INLINE CycleDataType *PipelineCycler<CycleDataType>::
+write_stage(int n) {
+  return (CycleDataType *)PipelineCyclerBase::write_stage(n);
+}

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

@@ -53,6 +53,10 @@ template<class CycleDataType>
 class PipelineCycler : public PipelineCyclerBase {
 class PipelineCycler : public PipelineCyclerBase {
 public:
 public:
   INLINE PipelineCycler(Pipeline *pipeline = NULL);
   INLINE PipelineCycler(Pipeline *pipeline = NULL);
+
+  INLINE const CycleDataType *read() const;
+  INLINE CycleDataType *write();
+  INLINE CycleDataType *write_stage(int n);
 };
 };
 
 
 #include "pipelineCycler.I"
 #include "pipelineCycler.I"

+ 53 - 0
panda/src/pgraph/pipelineCyclerBase.I

@@ -107,3 +107,56 @@ release_write(CycleData *pointer) {
   nassertv(_write_count > 0);
   nassertv(_write_count > 0);
   _write_count--;
   _write_count--;
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCyclerBase::get_num_stages
+//       Access: Public
+//  Description: Returns the number of stages in the pipeline.
+////////////////////////////////////////////////////////////////////
+INLINE int PipelineCyclerBase::
+get_num_stages() {
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCyclerBase::is_stage_unique
+//       Access: Public
+//  Description: Returns true if the nth stage is a different pointer
+//               than the previous stage, or false if its pointer is
+//               shared with the previous one.
+////////////////////////////////////////////////////////////////////
+INLINE bool PipelineCyclerBase::
+is_stage_unique(int n) const {
+  nassertr(n == 0, false);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCyclerBase::write_stage
+//       Access: Public
+//  Description: Returns a pointer suitable for writing to the nth
+//               stage of the pipeline.  This is for special
+//               applications that need to update the entire pipeline
+//               at once (for instance, to remove an invalid pointer).
+//               This pointer should later be released with
+//               release_write_stage().
+////////////////////////////////////////////////////////////////////
+INLINE CycleData *PipelineCyclerBase::
+write_stage(int n) {
+  nassertr(n == 0, (CycleData *)NULL);
+  _stage_count++;
+  return _data;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: PipelineCyclerBase::release_write_stage
+//       Access: Public
+//  Description: Releases a pointer previously obtained via a call to
+//               write_stage().
+////////////////////////////////////////////////////////////////////
+INLINE void PipelineCyclerBase::
+release_write_stage(int n, CycleData *pointer) {
+  nassertv(n == 0 && pointer == _data);
+  nassertv(_stage_count > 0);
+  _stage_count--;
+}

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

@@ -29,7 +29,8 @@ PipelineCyclerBase(CycleData *initial_data, Pipeline *pipeline) :
   _data(initial_data),
   _data(initial_data),
   _pipeline(pipeline),
   _pipeline(pipeline),
   _read_count(0),
   _read_count(0),
-  _write_count(0)
+  _write_count(0),
+  _stage_count(0)
 {
 {
   if (_pipeline == (Pipeline *)NULL) {
   if (_pipeline == (Pipeline *)NULL) {
     _pipeline = Pipeline::get_render_pipeline();
     _pipeline = Pipeline::get_render_pipeline();
@@ -43,6 +44,6 @@ PipelineCyclerBase(CycleData *initial_data, Pipeline *pipeline) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PipelineCyclerBase::
 PipelineCyclerBase::
 ~PipelineCyclerBase() {
 ~PipelineCyclerBase() {
-  nassertv(_read_count == 0 && _write_count == 0);
+  nassertv(_read_count == 0 && _write_count == 0 && _stage_count == 0);
 }
 }
 
 

+ 6 - 1
panda/src/pgraph/pipelineCyclerBase.h

@@ -43,10 +43,15 @@ public:
   INLINE void increment_write(CycleData *pointer);
   INLINE void increment_write(CycleData *pointer);
   INLINE void release_write(CycleData *pointer);
   INLINE void release_write(CycleData *pointer);
 
 
+  INLINE int get_num_stages();
+  INLINE bool is_stage_unique(int n) const;
+  INLINE CycleData *write_stage(int n);
+  INLINE void release_write_stage(int n, CycleData *pointer);
+
 private:
 private:
   PT(CycleData) _data;
   PT(CycleData) _data;
   Pipeline *_pipeline;
   Pipeline *_pipeline;
-  short _read_count, _write_count;
+  short _read_count, _write_count, _stage_count;
 };
 };
 
 
 #include "pipelineCyclerBase.I"
 #include "pipelineCyclerBase.I"

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

@@ -0,0 +1,87 @@
+// Filename: qpcamera.I
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCamera::set_active
+//       Access: Published
+//  Description: Sets the active flag on the camera.  When the camera
+//               is not active, nothing will be rendered.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCamera::
+set_active(bool active) {
+  _active = active;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::is_active
+//       Access: Published
+//  Description: Returns the current setting of the active flag on the
+//               camera.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCamera::
+is_active() const {
+  return _active;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::set_scene
+//       Access: Published
+//  Description: Sets the scene that will be rendered by the camera.
+//               This is normally the root node of a scene graph,
+//               typically a node called 'render', although it could
+//               represent the root of any subgraph.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCamera::
+set_scene(PandaNode *scene) {
+  _scene = scene;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::get_scene
+//       Access: Published
+//  Description: Returns the scene that will be rendered by the
+//               camera.  See set_scene().
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *qpCamera::
+get_scene() const {
+  return _scene;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::get_num_display_regions
+//       Access: Published
+//  Description: Returns the number of display regions associated with
+//               the camera.
+////////////////////////////////////////////////////////////////////
+INLINE int qpCamera::
+get_num_display_regions() const {
+  return _display_regions.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::get_display_region
+//       Access: Published
+//  Description: Returns the nth display region associated with the
+//               camera.
+////////////////////////////////////////////////////////////////////
+INLINE DisplayRegion *qpCamera::
+get_display_region(int n) const {
+  nassertr(n >= 0 && n < (int)_display_regions.size(), (DisplayRegion *)NULL);
+  return _display_regions[n];
+}

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

@@ -0,0 +1,144 @@
+// Filename: qpcamera.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://www.panda3d.org/license.txt .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "pandabase.h"
+#include "qpcamera.h"
+#include "lens.h"
+#include "throw_event.h"
+
+TypeHandle qpCamera::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCamera::
+qpCamera(const string &name) :
+  qpLensNode(name),
+  _active(true),
+  _scene((PandaNode *)NULL)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCamera::
+qpCamera(const qpCamera &copy) :
+  qpLensNode(copy),
+  _active(copy._active),
+  _scene(copy._scene)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+operator = (const qpCamera &copy) {
+  qpLensNode::operator = (copy);
+  _active = copy._active;
+  _scene = copy._scene;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCamera::
+~qpCamera() {
+  // We don't have to destroy the display region(s) associated with
+  // the camera; they're responsible for themselves.  However, they
+  // should have removed themselves before we destruct, or something
+  // went wrong.
+  nassertv(_display_regions.empty());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpCamera::
+make_copy() const {
+  return new qpCamera(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::safe_to_flatten
+//       Access: Public, Virtual
+//  Description: Returns true if it is generally safe to flatten out
+//               this particular kind of Node by duplicating
+//               instances, false otherwise (for instance, a qpCamera
+//               cannot be safely flattened, because the qpCamera
+//               pointer itself is meaningful).
+////////////////////////////////////////////////////////////////////
+bool qpCamera::
+safe_to_flatten() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::safe_to_transform
+//       Access: Public, Virtual
+//  Description: Returns true if it is generally safe to transform
+//               this particular kind of Node by calling the xform()
+//               method, false otherwise.  For instance, it's usually
+//               a bad idea to attempt to xform a Character.
+////////////////////////////////////////////////////////////////////
+bool qpCamera::
+safe_to_transform() const {
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::add_display_region
+//       Access: Private
+//  Description: Adds the indicated DisplayRegion to the set of
+//               DisplayRegions shared by the camera.  This is only
+//               intended to be called from the DisplayRegion.
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+add_display_region(DisplayRegion *display_region) {
+  _display_regions.push_back(display_region);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCamera::remove_display_region
+//       Access: Private
+//  Description: Removes the indicated DisplayRegion from the set of
+//               DisplayRegions shared by the camera.  This is only
+//               intended to be called from the DisplayRegion.
+////////////////////////////////////////////////////////////////////
+void qpCamera::
+remove_display_region(DisplayRegion *display_region) {
+  DisplayRegions::iterator dri =
+    find(_display_regions.begin(), _display_regions.end(), display_region);
+  if (dri != _display_regions.end()) {
+    _display_regions.erase(dri);
+  }
+}

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

@@ -0,0 +1,90 @@
+// Filename: qpcamera.h
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 QPCAMERA_H
+#define QPCAMERA_H
+
+#include "pandabase.h"
+
+#include "qplensNode.h"
+
+class DisplayRegion;
+class PandaNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpCamera
+// Description : A node that can be positioned around in the scene
+//               graph to represent a point of view for rendering a
+//               scene.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCamera : public qpLensNode {
+PUBLISHED:
+  qpCamera(const string &name);
+
+public:
+  qpCamera(const qpCamera &copy);
+  void operator = (const qpCamera &copy);
+  virtual ~qpCamera();
+
+  virtual PandaNode *make_copy() const;
+  virtual bool safe_to_flatten() const;
+  virtual bool safe_to_transform() const;
+
+PUBLISHED:
+  INLINE void set_active(bool active);
+  INLINE bool is_active() const;
+
+  INLINE void set_scene(PandaNode *scene);
+  INLINE PandaNode *get_scene() const;
+
+  INLINE int get_num_display_regions() const;
+  INLINE DisplayRegion *get_display_region(int n) const;
+
+private:
+  void add_display_region(DisplayRegion *display_region);
+  void remove_display_region(DisplayRegion *display_region);
+
+  bool _active;
+  PandaNode *_scene;
+
+  typedef pvector<DisplayRegion *> DisplayRegions;
+  DisplayRegions _display_regions;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpLensNode::init_type();
+    register_type(_type_handle, "qpCamera",
+                  qpLensNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+
+  friend class DisplayRegion;
+};
+
+#include "qpcamera.I"
+
+#endif

+ 12 - 15
panda/src/pgraph/qpcullTraverser.cxx

@@ -17,6 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "qpcullTraverser.h"
 #include "qpcullTraverser.h"
+#include "transformState.h"
 #include "renderState.h"
 #include "renderState.h"
 #include "cullHandler.h"
 #include "cullHandler.h"
 #include "dcast.h"
 #include "dcast.h"
@@ -30,7 +31,7 @@
 qpCullTraverser::
 qpCullTraverser::
 qpCullTraverser() {
 qpCullTraverser() {
   _initial_state = RenderState::make_empty();
   _initial_state = RenderState::make_empty();
-  _world_transform = DCAST(TransformAttrib, TransformAttrib::make_identity());
+  _world_transform = DCAST(TransformState, TransformState::make_identity());
   _cull_handler = (CullHandler *)NULL;
   _cull_handler = (CullHandler *)NULL;
 }
 }
 
 
@@ -49,17 +50,11 @@ set_initial_state(const RenderState *initial_state) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpCullTraverser::set_world_transform
 //     Function: qpCullTraverser::set_world_transform
 //       Access: Public
 //       Access: Public
-//  Description: Specifies the net inverse transform of the camera
-//               viewing the scene.  Normally, this is the same as the
-//               transform specified in the initial state, but it
-//               might be different if we are culling a subgraph,
-//               for instance.
-//
-//               This is used to evaluate camera-dependent attributes
-//               like billboards and LOD nodes.
+//  Description: Specifies the position of the world relative to the
+//               camera.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCullTraverser::
 void qpCullTraverser::
-set_world_transform(const TransformAttrib *world_transform) {
+set_world_transform(const TransformState *world_transform) {
   _world_transform = world_transform;
   _world_transform = world_transform;
 }
 }
 
 
@@ -67,7 +62,7 @@ set_world_transform(const TransformAttrib *world_transform) {
 //     Function: qpCullTraverser::set_cull_handler
 //     Function: qpCullTraverser::set_cull_handler
 //       Access: Public
 //       Access: Public
 //  Description: Specifies the object that will receive the culled
 //  Description: Specifies the object that will receive the culled
-//               Geoms.  This must be called before traverse().
+//               Geoms.  This must be set before calling traverse().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCullTraverser::
 void qpCullTraverser::
 set_cull_handler(CullHandler *cull_handler) {
 set_cull_handler(CullHandler *cull_handler) {
@@ -83,7 +78,7 @@ void qpCullTraverser::
 traverse(PandaNode *root) {
 traverse(PandaNode *root) {
   nassertv(_cull_handler != (CullHandler *)NULL);
   nassertv(_cull_handler != (CullHandler *)NULL);
 
 
-  r_traverse(root, _initial_state, 0);
+  r_traverse(root, _world_transform, _initial_state, 0);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -92,7 +87,9 @@ traverse(PandaNode *root) {
 //  Description: The recursive traversal implementation.
 //  Description: The recursive traversal implementation.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCullTraverser::
 void qpCullTraverser::
-r_traverse(PandaNode *node, const RenderState *state, int flags) {
+r_traverse(PandaNode *node, const TransformState *transform,
+           const RenderState *state, int flags) {
+  CPT(TransformState) next_transform = transform->compose(node->get_transform());
   CPT(RenderState) next_state = state->compose(node->get_state());
   CPT(RenderState) next_state = state->compose(node->get_state());
 
 
   if (node->is_geom_node()) {
   if (node->is_geom_node()) {
@@ -104,7 +101,7 @@ r_traverse(PandaNode *node, const RenderState *state, int flags) {
       Geom *geom = geom_node->get_geom(i);
       Geom *geom = geom_node->get_geom(i);
       CPT(RenderState) geom_state = 
       CPT(RenderState) geom_state = 
         next_state->compose(geom_node->get_geom_state(i));
         next_state->compose(geom_node->get_geom_state(i));
-      _cull_handler->record_geom(geom, geom_state);
+      _cull_handler->record_geom(geom, next_transform, geom_state);
     }
     }
   }
   }
 
 
@@ -112,6 +109,6 @@ r_traverse(PandaNode *node, const RenderState *state, int flags) {
   PandaNode::Children cr = node->get_children();
   PandaNode::Children cr = node->get_children();
   int num_children = cr.get_num_children();
   int num_children = cr.get_num_children();
   for (int i = 0; i < num_children; i++) {
   for (int i = 0; i < num_children; i++) {
-    r_traverse(cr.get_child(i), next_state, flags);
+    r_traverse(cr.get_child(i), next_transform, next_state, flags);
   }
   }
 }
 }

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

@@ -22,7 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 #include "renderState.h"
 #include "renderState.h"
-#include "transformAttrib.h"
+#include "transformState.h"
 #include "pointerTo.h"
 #include "pointerTo.h"
 
 
 class PandaNode;
 class PandaNode;
@@ -41,16 +41,17 @@ public:
   qpCullTraverser();
   qpCullTraverser();
 
 
   void set_initial_state(const RenderState *initial_state);
   void set_initial_state(const RenderState *initial_state);
-  void set_world_transform(const TransformAttrib *world_transform);
+  void set_world_transform(const TransformState *world_transform);
   void set_cull_handler(CullHandler *cull_handler);
   void set_cull_handler(CullHandler *cull_handler);
 
 
   void traverse(PandaNode *root);
   void traverse(PandaNode *root);
 
 
 private:
 private:
-  void r_traverse(PandaNode *node, const RenderState *state, int flags);
+  void r_traverse(PandaNode *node, const TransformState *transform,
+                  const RenderState *state, int flags);
 
 
   CPT(RenderState) _initial_state;
   CPT(RenderState) _initial_state;
-  CPT(TransformAttrib) _world_transform;
+  CPT(TransformState) _world_transform;
   CullHandler *_cull_handler;
   CullHandler *_cull_handler;
 };
 };
 
 

+ 89 - 0
panda/src/pgraph/qplensNode.I

@@ -0,0 +1,89 @@
+// Filename: qplensNode.I
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpLensNode::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpLensNode::
+qpLensNode(const string &name) :
+  PandaNode(name)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpLensNode::
+qpLensNode(const qpLensNode &copy) :
+  PandaNode(copy),
+  _lens(copy._lens)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpLensNode::
+operator = (const qpLensNode &copy) {
+  PandaNode::operator = (copy);
+  _lens = copy._lens;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: copy_lens
+//       Access: Public
+//  Description: Sets up the qpLensNode using a copy of the
+//               indicated Lens.  If the original Lens is
+//               changed or destroyed, this qpLensNode is not
+//               affected.
+////////////////////////////////////////////////////////////////////
+INLINE void qpLensNode::
+copy_lens(const Lens &lens) {
+  _lens = lens.make_copy();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: set_lens
+//       Access: Public
+//  Description: Sets up the qpLensNode using this particular Lens
+//               pointer.  If the lens is subsequently modified, the
+//               qpLensNode properties immediately reflect the change.
+////////////////////////////////////////////////////////////////////
+INLINE void qpLensNode::
+set_lens(Lens *lens) {
+  _lens = lens;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: get_lens
+//       Access: Public
+//  Description: Returns a pointer to the particular Lens
+//               associated with this qpLensNode, or NULL if there is
+//               not yet a Lens associated.
+////////////////////////////////////////////////////////////////////
+INLINE Lens *qpLensNode::
+get_lens() {
+  return _lens;
+}

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

@@ -0,0 +1,82 @@
+// Filename: qplensNode.cxx
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qplensNode.h"
+#include "geometricBoundingVolume.h"
+
+TypeHandle qpLensNode::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::make_copy
+//       Access: Public, Virtual
+//  Description: Returns a newly-allocated Node that is a shallow copy
+//               of this one.  It will be a different Node pointer,
+//               but its internal data may or may not be shared with
+//               that of the original Node.
+////////////////////////////////////////////////////////////////////
+PandaNode *qpLensNode::
+make_copy() const {
+  return new qpLensNode(*this);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::is_in_view
+//       Access: Public
+//  Description: Returns true if the given point is within the bounds
+//               of the lens of the qpLensNode (i.e. if the camera can
+//               see the point).
+////////////////////////////////////////////////////////////////////
+bool qpLensNode::
+is_in_view(const LPoint3f &pos) {
+  PT(BoundingVolume) bv = _lens->make_bounds();
+  if (bv == NULL) {
+    return false;
+  }
+  GeometricBoundingVolume *gbv = DCAST(GeometricBoundingVolume, bv);
+  int ret = gbv->contains(pos);
+  return (ret != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::output
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpLensNode::
+output(ostream &out) const {
+  PandaNode::output(out);
+  if (_lens != (Lens *)NULL) {
+    out << " (";
+    _lens->output(out);
+    out << ")";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpLensNode::write
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpLensNode::
+write(ostream &out, int indent_level) const {
+  PandaNode::write(out, indent_level);
+  if (_lens != (Lens *)NULL) {
+    _lens->write(out, indent_level + 2);
+  }
+}
+

+ 78 - 0
panda/src/pgraph/qplensNode.h

@@ -0,0 +1,78 @@
+// Filename: qplensNode.h
+// Created by:  drose (26Feb02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 QPLENSNODE_H
+#define QPLENSNODE_H
+
+#include "pandabase.h"
+
+#include "pandaNode.h"
+#include "lens.h"
+#include "pointerTo.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpLensNode
+// Description : A node that contains a Lens.  The most important
+//               example of this kind of node is a Camera, but other
+//               kinds of nodes also contain a lens (for instance, a
+//               Spotlight).
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpLensNode : public PandaNode {
+PUBLISHED:
+  INLINE qpLensNode(const string &name);
+
+public:
+  INLINE qpLensNode(const qpLensNode &copy);
+  INLINE void operator = (const qpLensNode &copy);
+
+  virtual void output(ostream &out) const;
+  virtual void write(ostream &out, int indent_level = 0) const;
+
+  virtual PandaNode *make_copy() const;
+
+PUBLISHED:
+  INLINE void copy_lens(const Lens &lens);
+  INLINE void set_lens(Lens *lens);
+  INLINE Lens *get_lens();
+
+  bool is_in_view(const LPoint3f &pos);
+
+protected:
+  PT(Lens) _lens;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    PandaNode::init_type();
+    register_type(_type_handle, "qpLensNode",
+                  PandaNode::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+#include "qplensNode.I"
+
+#endif

+ 2 - 1
panda/src/pgraph/renderAttrib.cxx

@@ -173,7 +173,8 @@ compare_to_impl(const RenderAttrib *other) const {
 //               RenderAttrib (that is, a subsequent RenderAttrib
 //               RenderAttrib (that is, a subsequent RenderAttrib
 //               completely replaces the preceding one).  On the other
 //               completely replaces the preceding one).  On the other
 //               hand, some kinds of RenderAttrib (for instance,
 //               hand, some kinds of RenderAttrib (for instance,
-//               TransformAttrib) might combine in meaningful ways.
+//               ColorTransformAttrib) might combine in meaningful
+//               ways.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 CPT(RenderAttrib) RenderAttrib::
 CPT(RenderAttrib) RenderAttrib::
 compose_impl(const RenderAttrib *other) const {
 compose_impl(const RenderAttrib *other) const {

+ 4 - 9
panda/src/pgraph/renderState.cxx

@@ -72,9 +72,6 @@ RenderState::
   if (_saved_entry != _states.end()) {
   if (_saved_entry != _states.end()) {
     _states.erase(_saved_entry);
     _states.erase(_saved_entry);
     _saved_entry = _states.end();
     _saved_entry = _states.end();
-    
-    cerr << "Removing " << (void *)this << ", " << _states.size()
-         << " remaining.\n";
   }
   }
 
 
   // Now make sure we clean up all other floating pointers to the
   // Now make sure we clean up all other floating pointers to the
@@ -359,8 +356,6 @@ invert_compose(const RenderState *other) const {
   // but we pretend that it is because it's only a cache which is
   // but we pretend that it is because it's only a cache which is
   // transparent to the rest of the interface.
   // transparent to the rest of the interface.
 
 
-  cerr << "invert composing " << *this << " with " << *other << "\n";
-
   // We handle empty state (identity) as a trivial special case.
   // We handle empty state (identity) as a trivial special case.
   if (is_empty()) {
   if (is_empty()) {
     return other;
     return other;
@@ -384,7 +379,6 @@ invert_compose(const RenderState *other) const {
       ((Composition &)comp)._result = do_invert_compose(other);
       ((Composition &)comp)._result = do_invert_compose(other);
     }
     }
     // Here's the cache!
     // Here's the cache!
-    cerr << "  returning cached result " << (void *)comp._result.p() << "\n";
     return comp._result;
     return comp._result;
   }
   }
 
 
@@ -401,7 +395,6 @@ invert_compose(const RenderState *other) const {
   ((RenderState *)other)->_invert_composition_cache[this]._result = NULL;
   ((RenderState *)other)->_invert_composition_cache[this]._result = NULL;
   ((RenderState *)this)->_invert_composition_cache[other]._result = result;
   ((RenderState *)this)->_invert_composition_cache[other]._result = result;
 
 
-  cerr << "  returning new result " << (void *)result.p() << "\n";
   return result;
   return result;
 }
 }
 
 
@@ -472,17 +465,19 @@ remove(TypeHandle type) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void RenderState::
 void RenderState::
 output(ostream &out) const {
 output(ostream &out) const {
+  out << get_type() << ":";
   if (_attributes.empty()) {
   if (_attributes.empty()) {
-    out << "empty";
+    out << "(empty)";
 
 
   } else {
   } else {
     Attributes::const_iterator ai = _attributes.begin();
     Attributes::const_iterator ai = _attributes.begin();
-    out << (*ai)._type;
+    out << "(" << (*ai)._type;
     ++ai;
     ++ai;
     while (ai != _attributes.end()) {
     while (ai != _attributes.end()) {
       out << " " << (*ai)._type;
       out << " " << (*ai)._type;
       ++ai;
       ++ai;
     }
     }
+    out << ")";
   }
   }
 }
 }
 
 

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

@@ -126,7 +126,8 @@ private:
   // should be).
   // should be).
   const RenderState *_self_compose;
   const RenderState *_self_compose;
 
 
-  // This is the actual set of data within the RenderState: a set of
+private:
+  // This is the actual data within the RenderState: a set of
   // RenderAttribs.
   // RenderAttribs.
   class Attribute {
   class Attribute {
   public:
   public:

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

@@ -17,9 +17,10 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "pandaNode.h"
 #include "pandaNode.h"
+#include "nodeChain.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
 #include "colorAttrib.h"
 #include "colorAttrib.h"
-#include "transformAttrib.h"
+#include "transformState.h"
 #include "texture.h"
 #include "texture.h"
 #include "qpgeomNode.h"
 #include "qpgeomNode.h"
 #include "geomTristrip.h"
 #include "geomTristrip.h"
@@ -68,19 +69,25 @@ main(int argc, char *argv[]) {
   qpGeomNode *g2 = new qpGeomNode("g2");
   qpGeomNode *g2 = new qpGeomNode("g2");
   b->add_child(g2);
   b->add_child(g2);
   g2->add_geom(geom1, b->get_state());
   g2->add_geom(geom1, b->get_state());
-  g2->set_attrib(TransformAttrib::make_mat(LMatrix4f::translate_mat(10, 0, 0)));
+  g2->set_transform(TransformState::make_mat(LMatrix4f::translate_mat(10, 0, 0)));
 
 
   cerr << "\n";
   cerr << "\n";
   list_hierarchy(root, 0);
   list_hierarchy(root, 0);
 
 
-  qpCullTraverser trav;
-  CullHandler cull_handler;
-  trav.set_cull_handler(&cull_handler);
-  cerr << "\n";
-  trav.traverse(root);
+  NodeChain ch_g1(g1);
+  cerr << ch_g1 << "\n";
 
 
-  cerr << "\n";
-  trav.traverse(root);
+  NodeChain 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_rel_state(ch_g2) << "\n";
+  cerr << *ch_g2.get_rel_state(ch_g1) << "\n";
+
+  cerr << *ch_g2.get_rel_transform(ch_g2) << "\n";
+  cerr << *ch_g2.get_rel_state(ch_g2) << "\n";
 
 
   cerr << "\n";
   cerr << "\n";
   return 0;
   return 0;

+ 0 - 308
panda/src/pgraph/transformAttrib.cxx

@@ -1,308 +0,0 @@
-// Filename: transformAttrib.cxx
-// Created by:  drose (23Feb02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 "transformAttrib.h"
-#include "bamReader.h"
-#include "bamWriter.h"
-#include "datagram.h"
-#include "datagramIterator.h"
-#include "compose_matrix.h"
-
-TypeHandle TransformAttrib::_type_handle;
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::make_identity
-//       Access: Published, Static
-//  Description: Constructs an identity transform.
-////////////////////////////////////////////////////////////////////
-CPT(RenderAttrib) TransformAttrib::
-make_identity() {
-  TransformAttrib *attrib = new TransformAttrib;
-  return return_new(attrib);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::make_components
-//       Access: Published, Static
-//  Description: Makes a new TransformAttrib with the specified
-//               components.
-////////////////////////////////////////////////////////////////////
-CPT(RenderAttrib) TransformAttrib::
-make_components(const LVecBase3f &pos, const LVecBase3f &hpr, 
-                const LVecBase3f &scale) {
-  // Make a special-case check for the identity transform.
-  if (pos == LVecBase3f(0.0f, 0.0f, 0.0f) &&
-      hpr == LVecBase3f(0.0f, 0.0f, 0.0f) &&
-      scale == LVecBase3f(1.0f, 1.0f, 1.0f)) {
-    return make_identity();
-  }
-
-  TransformAttrib *attrib = new TransformAttrib;
-  attrib->_pos = pos;
-  attrib->_hpr = hpr;
-  attrib->_scale = scale;
-  attrib->_flags = F_components_given | F_components_known | F_has_components;
-  return return_new(attrib);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::make_components
-//       Access: Published, Static
-//  Description: Makes a new TransformAttrib with the specified
-//               transformation matrix.
-////////////////////////////////////////////////////////////////////
-CPT(RenderAttrib) TransformAttrib::
-make_mat(const LMatrix4f &mat) {
-  // Make a special-case check for the identity matrix.
-  if (mat == LMatrix4f::ident_mat()) {
-    return make_identity();
-  }
-
-  TransformAttrib *attrib = new TransformAttrib;
-  attrib->_mat = mat;
-  attrib->_flags = F_mat_known;
-  return return_new(attrib);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::output
-//       Access: Public, Virtual
-//  Description: 
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-output(ostream &out) const {
-  out << get_type() << ":";
-  if (is_identity()) {
-    out << "(identity)";
-
-  } else if (has_components()) {
-    out << "(";
-    if (get_pos() != LVecBase3f(0.0f, 0.0f, 0.0f)) {
-      out << "pos " << get_pos();
-    }
-    if (get_hpr() != LVecBase3f(0.0f, 0.0f, 0.0f)) {
-      out << "hpr " << get_hpr();
-    }
-    if (get_scale() != LVecBase3f(1.0f, 1.0f, 1.0f)) {
-      out << "scale " << get_scale();
-    }
-    out << ")";
-
-  } else {
-    out << get_mat();
-  }
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::compare_to_impl
-//       Access: Protected, Virtual
-//  Description: Intended to be overridden by derived TransformAttrib
-//               types to return a unique number indicating whether
-//               this TransformAttrib is equivalent to the other one.
-//
-//               This should return 0 if the two TransformAttrib objects
-//               are equivalent, a number less than zero if this one
-//               should be sorted before the other one, and a number
-//               greater than zero otherwise.
-//
-//               This will only be called with two TransformAttrib
-//               objects whose get_type() functions return the same.
-////////////////////////////////////////////////////////////////////
-int TransformAttrib::
-compare_to_impl(const RenderAttrib *other) const {
-  const TransformAttrib *ta;
-  DCAST_INTO_R(ta, other, 0);
-
-  if (is_identity() != ta->is_identity()) {
-    return is_identity() < ta->is_identity();
-  }
-  if (is_identity()) {
-    // All identity transforms are equivalent to each other.
-    return 0;
-  }
-
-  bool components_given = (_flags & F_components_given) != 0;
-  bool ta_components_given = (ta->_flags & F_components_given) != 0;
-  if (components_given != ta_components_given) {
-    return components_given < ta_components_given;
-  }
-  if (components_given) {
-    // If the transform was specified componentwise, compare them
-    // componentwise.
-    int c = _pos.compare_to(ta->_pos);
-    if (c != 0) {
-      return c;
-    }
-    c = _hpr.compare_to(ta->_hpr);
-    if (c != 0) {
-      return c;
-    }
-    c = _scale.compare_to(ta->_hpr);
-    return c;
-    
-  }
-
-  // Otherwise, compare the matrices.
-  return get_mat().compare_to(ta->get_mat());
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::compose_impl
-//       Access: Protected, Virtual
-//  Description: Intended to be overridden by derived RenderAttrib
-//               types to specify how two consecutive RenderAttrib
-//               objects of the same type interact.
-////////////////////////////////////////////////////////////////////
-CPT(RenderAttrib) TransformAttrib::
-compose_impl(const RenderAttrib *other) const {
-  const TransformAttrib *ta;
-  DCAST_INTO_R(ta, other, other);
-
-  // Identity times anything, duh.
-  if (is_identity()) {
-    return ta;
-  } else if (ta->is_identity()) {
-    return this;
-  }
-
-  // Perhaps we should be smarter here if both transforms are
-  // componentwise, and multiply them componentwise.
-  LMatrix4f new_mat = get_mat() * ta->get_mat();
-  return make_mat(new_mat);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::make_default_impl
-//       Access: Protected, Virtual
-//  Description: Intended to be overridden by derived TransformAttrib
-//               types to specify what the default property for a
-//               TransformAttrib of this type should be.
-//
-//               This should return a newly-allocated TransformAttrib of
-//               the same type that corresponds to whatever the
-//               standard default for this kind of TransformAttrib is.
-////////////////////////////////////////////////////////////////////
-RenderAttrib *TransformAttrib::
-make_default_impl() const {
-  return new TransformAttrib;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::calc_singular
-//       Access: Private
-//  Description: Determines whether the transform is singular (i.e. it
-//               scales to zero, and has no inverse).
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-calc_singular() {
-  bool singular = false;
-
-  if (has_components()) {
-    // The matrix is singular if any component of its scale is 0.
-    singular = (_scale[0] == 0.0f || _scale[1] == 0.0f || _scale[2] == 0.0f);
-  } else {
-    // The matrix is singular if its determinant is zero.
-    const LMatrix4f &mat = get_mat();
-    singular = (mat.get_upper_3().determinant() == 0.0f);
-  }
-
-  if (singular) {
-    _flags |= F_is_singular;
-  }
-  _flags |= F_singular_known;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::calc_components
-//       Access: Private
-//  Description: Derives the components from the matrix, if possible.
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-calc_components() {
-  bool possible = decompose_matrix(get_mat(), _scale, _hpr, _pos);
-  if (possible) {
-    // Some matrices can't be decomposed into scale, hpr, pos.
-    _flags |= F_has_components;
-  }
-  _flags |= F_components_known;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::calc_mat
-//       Access: Private
-//  Description: Computes the matrix from the components.
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-calc_mat() {
-  compose_matrix(_mat, _scale, _hpr, _pos);
-  _flags |= F_mat_known;
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::register_with_read_factory
-//       Access: Public, Static
-//  Description: Tells the BamReader how to create objects of type
-//               TransformAttrib.
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-register_with_read_factory() {
-  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::write_datagram
-//       Access: Public, Virtual
-//  Description: Writes the contents of this object to the datagram
-//               for shipping out to a Bam file.
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-write_datagram(BamWriter *manager, Datagram &dg) {
-  RenderAttrib::write_datagram(manager, dg);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::make_from_bam
-//       Access: Protected, Static
-//  Description: This function is called by the BamReader's factory
-//               when a new object of type TransformAttrib is encountered
-//               in the Bam file.  It should create the TransformAttrib
-//               and extract its information from the file.
-////////////////////////////////////////////////////////////////////
-TypedWritable *TransformAttrib::
-make_from_bam(const FactoryParams &params) {
-  TransformAttrib *attrib = new TransformAttrib;
-  DatagramIterator scan;
-  BamReader *manager;
-
-  parse_params(params, scan, manager);
-  attrib->fillin(scan, manager);
-
-  return new_from_bam(attrib, manager);
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::fillin
-//       Access: Protected
-//  Description: This internal function is called by make_from_bam to
-//               read in all of the relevant data from the BamFile for
-//               the new TransformAttrib.
-////////////////////////////////////////////////////////////////////
-void TransformAttrib::
-fillin(DatagramIterator &scan, BamReader *manager) {
-  RenderAttrib::fillin(scan, manager);
-}

+ 0 - 117
panda/src/pgraph/transformAttrib.h

@@ -1,117 +0,0 @@
-// Filename: transformAttrib.h
-// Created by:  drose (23Feb02)
-//
-////////////////////////////////////////////////////////////////////
-//
-// 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 TRANSFORMATTRIB_H
-#define TRANSFORMATTRIB_H
-
-#include "pandabase.h"
-
-#include "renderAttrib.h"
-#include "luse.h"
-
-////////////////////////////////////////////////////////////////////
-//       Class : TransformAttrib
-// Description : Indicates a coordinate-system transform on vertices.
-//               TransformAttribs are the primary means for storing
-//               transformations on the scene graph.
-//
-//               Transforms may be specified in one of two ways:
-//               componentwise, with a pos-hpr-scale, or with an
-//               arbitrary transform matrix.  If you specify a
-//               transform componentwise, it will remember its
-//               original components.
-////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA TransformAttrib : public RenderAttrib {
-private:
-  INLINE TransformAttrib();
-
-PUBLISHED:
-  static CPT(RenderAttrib) make_identity();
-  static CPT(RenderAttrib) make_components(const LVecBase3f &pos,
-                                           const LVecBase3f &hpr, 
-                                           const LVecBase3f &scale);
-  static CPT(RenderAttrib) make_mat(const LMatrix4f &mat);
-
-  INLINE bool is_identity() const;
-  INLINE bool is_singular() const;
-  INLINE bool has_components() 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;
-
-public:
-  virtual void output(ostream &out) const;
-
-protected:
-  virtual int compare_to_impl(const RenderAttrib *other) const;
-  virtual CPT(RenderAttrib) compose_impl(const RenderAttrib *other) const;
-  virtual RenderAttrib *make_default_impl() const;
-
-private:
-  INLINE void check_singular() const;
-  INLINE void check_components() const;
-  INLINE void check_mat() const;
-  void calc_singular();
-  void calc_components();
-  void calc_mat();
-
-  enum Flags {
-    F_is_identity      =  0x0001,
-    F_is_singular      =  0x0002,
-    F_singular_known   =  0x0004,
-    F_components_given =  0x0008,
-    F_components_known =  0x0010,
-    F_has_components   =  0x0020,
-    F_mat_known        =  0x0040,
-  };
-  LVecBase3f _pos, _hpr, _scale;
-  LMatrix4f _mat;
-  
-  short _flags;
-
-public:
-  static void register_with_read_factory();
-  virtual void write_datagram(BamWriter *manager, Datagram &dg);
-
-protected:
-  static TypedWritable *make_from_bam(const FactoryParams &params);
-  void fillin(DatagramIterator &scan, BamReader *manager);
-  
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    RenderAttrib::init_type();
-    register_type(_type_handle, "TransformAttrib",
-                  RenderAttrib::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-};
-
-#include "transformAttrib.I"
-
-#endif
-

+ 85 - 32
panda/src/pgraph/transformAttrib.I → panda/src/pgraph/transformState.I

@@ -1,5 +1,5 @@
-// Filename: transformAttrib.I
-// Created by:  drose (23Feb02)
+// Filename: transformState.I
+// Created by:  drose (25Feb02)
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //
 //
@@ -18,42 +18,95 @@
 
 
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::Constructor
-//       Access: Private
-//  Description: Use TransformAttrib::make() to construct a new
-//               TransformAttrib object.
+//     Function: TransformState::make_pos
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) TransformState::
+make_pos(const LVecBase3f &pos) {
+  return make_pos_hpr_scale(pos, 
+                            LVecBase3f(0.0f, 0.0f, 0.0f),
+                            LVecBase3f(1.0f, 1.0f, 1.0f));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_hpr
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) TransformState::
+make_hpr(const LVecBase3f &hpr) {
+  return make_pos_hpr_scale(LVecBase3f(0.0f, 0.0f, 0.0f),
+                            hpr, 
+                            LVecBase3f(1.0f, 1.0f, 1.0f));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_pos_hpr
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) TransformState::
+make_pos_hpr(const LVecBase3f &pos, const LVecBase3f &hpr) {
+  return make_pos_hpr_scale(pos, hpr,
+                            LVecBase3f(1.0, 1.0f, 1.0f));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_scale
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
+////////////////////////////////////////////////////////////////////
+INLINE CPT(TransformState) TransformState::
+make_scale(float scale) {
+  return make_pos_hpr_scale(LVecBase3f(0.0f, 0.0f, 0.0f),
+                            LVecBase3f(0.0f, 0.0f, 0.0f),
+                            LVecBase3f(scale, scale, scale));
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_scale
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE TransformAttrib::
-TransformAttrib() {
-  _flags = F_is_identity | F_singular_known;
+INLINE CPT(TransformState) TransformState::
+make_scale(const LVecBase3f &scale) {
+  return make_pos_hpr_scale(LVecBase3f(0.0f, 0.0f, 0.0f),
+                            LVecBase3f(0.0f, 0.0f, 0.0f),
+                            scale);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::is_identity
+//     Function: TransformState::is_identity
 //       Access: Published
 //       Access: Published
 //  Description: Returns true if the transform represents the identity
 //  Description: Returns true if the transform represents the identity
 //               matrix, false otherwise.
 //               matrix, false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool TransformAttrib::
+INLINE bool TransformState::
 is_identity() const {
 is_identity() const {
   return ((_flags & F_is_identity) != 0);
   return ((_flags & F_is_identity) != 0);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::is_singular
+//     Function: TransformState::is_singular
 //       Access: Published
 //       Access: Published
 //  Description: Returns true if the transform represents a singular
 //  Description: Returns true if the transform represents a singular
 //               transform (that is, it has a zero scale, and it
 //               transform (that is, it has a zero scale, and it
 //               cannot be inverted), or false otherwise.
 //               cannot be inverted), or false otherwise.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool TransformAttrib::
+INLINE bool TransformState::
 is_singular() const {
 is_singular() const {
   check_singular();
   check_singular();
   return ((_flags & F_is_singular) != 0);
   return ((_flags & F_is_singular) != 0);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::has_components
+//     Function: TransformState::has_components
 //       Access: Published
 //       Access: Published
 //  Description: Returns true if the transform can be described by
 //  Description: Returns true if the transform can be described by
 //               separate pos, hpr, and scale components.  Most
 //               separate pos, hpr, and scale components.  Most
@@ -69,20 +122,20 @@ is_singular() const {
 //               If this returns true, you may safely call get_pos(),
 //               If this returns true, you may safely call get_pos(),
 //               etc., to retrieve the components.
 //               etc., to retrieve the components.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool TransformAttrib::
+INLINE bool TransformState::
 has_components() const {
 has_components() const {
   check_components();
   check_components();
   return ((_flags & F_has_components) != 0);
   return ((_flags & F_has_components) != 0);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::get_pos
+//     Function: TransformState::get_pos
 //       Access: Published
 //       Access: Published
 //  Description: Returns the pos component of the transform.  It is an
 //  Description: Returns the pos component of the transform.  It is an
 //               error to call this if has_components() returned
 //               error to call this if has_components() returned
 //               false.
 //               false.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const LVecBase3f &TransformAttrib::
+INLINE const LVecBase3f &TransformState::
 get_pos() const {
 get_pos() const {
   check_components();
   check_components();
   nassertr(has_components(), _pos);
   nassertr(has_components(), _pos);
@@ -90,13 +143,13 @@ get_pos() const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::get_hpr
+//     Function: TransformState::get_hpr
 //       Access: Published
 //       Access: Published
 //  Description: Returns the hpr component of the transform.  It is an
 //  Description: Returns the hpr component of the transform.  It is an
 //               error to call this if has_components() returned
 //               error to call this if has_components() returned
 //               false.
 //               false.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const LVecBase3f &TransformAttrib::
+INLINE const LVecBase3f &TransformState::
 get_hpr() const {
 get_hpr() const {
   check_components();
   check_components();
   nassertr(has_components(), _hpr);
   nassertr(has_components(), _hpr);
@@ -104,13 +157,13 @@ get_hpr() const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::get_scale
+//     Function: TransformState::get_scale
 //       Access: Published
 //       Access: Published
 //  Description: Returns the scale component of the transform.  It is an
 //  Description: Returns the scale component of the transform.  It is an
 //               error to call this if has_components() returned
 //               error to call this if has_components() returned
 //               false.
 //               false.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const LVecBase3f &TransformAttrib::
+INLINE const LVecBase3f &TransformState::
 get_scale() const {
 get_scale() const {
   check_components();
   check_components();
   nassertr(has_components(), _scale);
   nassertr(has_components(), _scale);
@@ -118,55 +171,55 @@ get_scale() const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::get_mat
+//     Function: TransformState::get_mat
 //       Access: Published
 //       Access: Published
 //  Description: Returns the matrix that describes the transform.
 //  Description: Returns the matrix that describes the transform.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const LMatrix4f &TransformAttrib::
+INLINE const LMatrix4f &TransformState::
 get_mat() const {
 get_mat() const {
   check_mat();
   check_mat();
   return _mat;
   return _mat;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::check_singular
+//     Function: TransformState::check_singular
 //       Access: Private
 //       Access: Private
 //  Description: Ensures that we know whether the matrix is singular.
 //  Description: Ensures that we know whether the matrix is singular.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE void TransformAttrib::
+INLINE void TransformState::
 check_singular() const {
 check_singular() const {
   // This pretends to be a const function, even though it's not,
   // This pretends to be a const function, even though it's not,
   // because it only updates a transparent cache value.
   // because it only updates a transparent cache value.
   if ((_flags & F_singular_known) == 0) {
   if ((_flags & F_singular_known) == 0) {
-    ((TransformAttrib *)this)->calc_singular();
+    ((TransformState *)this)->calc_singular();
   }
   }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::check_components
+//     Function: TransformState::check_components
 //       Access: Private
 //       Access: Private
 //  Description: Ensures that we know the components of the transform
 //  Description: Ensures that we know the components of the transform
 //               (or that we know they cannot be derived).
 //               (or that we know they cannot be derived).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE void TransformAttrib::
+INLINE void TransformState::
 check_components() const {
 check_components() const {
   // This pretends to be a const function, even though it's not,
   // This pretends to be a const function, even though it's not,
   // because it only updates a transparent cache value.
   // because it only updates a transparent cache value.
   if ((_flags & F_components_known) == 0) {
   if ((_flags & F_components_known) == 0) {
-    ((TransformAttrib *)this)->calc_components();
+    ((TransformState *)this)->calc_components();
   }
   }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: TransformAttrib::check_mat
+//     Function: TransformState::check_mat
 //       Access: Private
 //       Access: Private
 //  Description: Ensures that we know the overall matrix.
 //  Description: Ensures that we know the overall matrix.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE void TransformAttrib::
+INLINE void TransformState::
 check_mat() const {
 check_mat() const {
   // This pretends to be a const function, even though it's not,
   // This pretends to be a const function, even though it's not,
   // because it only updates a transparent cache value.
   // because it only updates a transparent cache value.
   if ((_flags & F_mat_known) == 0) {
   if ((_flags & F_mat_known) == 0) {
-    ((TransformAttrib *)this)->calc_mat();
+    ((TransformState *)this)->calc_mat();
   }
   }
 }
 }

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

@@ -0,0 +1,650 @@
+// Filename: transformState.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 "transformState.h"
+#include "compose_matrix.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "datagramIterator.h"
+#include "indent.h"
+#include "compareTo.h"
+
+TransformState::States TransformState::_states;
+CPT(TransformState) TransformState::_identity_state;
+TypeHandle TransformState::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::Constructor
+//       Access: Protected
+//  Description: Actually, this could be a private constructor, since
+//               no one inherits from TransformState, but gcc gives us a
+//               spurious warning if all constructors are private.
+////////////////////////////////////////////////////////////////////
+TransformState::
+TransformState() {
+  _saved_entry = _states.end();
+  _self_compose = (TransformState *)NULL;
+  _flags = F_is_identity | F_singular_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::Copy Constructor
+//       Access: Private
+//  Description: TransformStates are not meant to be copied.
+////////////////////////////////////////////////////////////////////
+TransformState::
+TransformState(const TransformState &) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::Copy Assignment Operator
+//       Access: Private
+//  Description: TransformStates are not meant to be copied.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+operator = (const TransformState &) {
+  nassertv(false);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::Destructor
+//       Access: Public, Virtual
+//  Description: The destructor is responsible for removing the
+//               TransformState from the global set if it is there.
+////////////////////////////////////////////////////////////////////
+TransformState::
+~TransformState() {
+  // Remove the deleted TransformState object from the global pool.
+  if (_saved_entry != _states.end()) {
+    _states.erase(_saved_entry);
+    _saved_entry = _states.end();
+  }
+
+  // Now make sure we clean up all other floating pointers to the
+  // TransformState.  These may be scattered around in the various
+  // CompositionCaches from other TransformState objects.
+
+  // Fortunately, since we added CompositionCache records in pairs, we
+  // know exactly the set of TransformState objects that have us in their
+  // cache: it's the same set of TransformState objects that we have in
+  // our own cache.
+
+  // We do need to put some thought into this loop, because as we
+  // clear out cache entries we'll cause other TransformState objects to
+  // destruct, which could cause things to get pulled out of our own
+  // _composition_cache map.  We don't want to get bitten by this
+  // cascading effect.
+  CompositionCache::iterator ci;
+  ci = _composition_cache.begin();
+  while (ci != _composition_cache.end()) {
+    {
+      PT(TransformState) other = (TransformState *)(*ci).first;
+      Composition comp = (*ci).second;
+
+      // We should never have a reflexive entry in this map.  If we
+      // do, something got screwed up elsewhere.
+      nassertv(other != (const TransformState *)this);
+      
+      // Now we're holding a reference count to the other state, as well
+      // as to the computed result (if any), so neither object will be
+      // tempted to destruct.  Go ahead and remove ourselves from the
+      // other cache.
+      other->_composition_cache.erase(this);
+
+      // It's all right if the other state destructs now, since it
+      // won't try to remove itself from our own composition cache any
+      // more.  Someone might conceivably delete the *next* entry,
+      // though, so we should be sure to let all that deleting finish
+      // up before we attempt to increment ci, by closing the scope
+      // here.
+    }
+    // Now it's safe to increment ci, because the current cache entry
+    // has not gone away, and if the next one has, by now it's safely
+    // gone.
+    ++ci;
+  }
+
+  // A similar bit of code for the invert cache.
+  ci = _invert_composition_cache.begin();
+  while (ci != _invert_composition_cache.end()) {
+    {
+      PT(TransformState) other = (TransformState *)(*ci).first;
+      Composition comp = (*ci).second;
+      nassertv(other != (const TransformState *)this);
+      other->_invert_composition_cache.erase(this);
+    }
+    ++ci;
+  }
+
+  // Also, if we called compose(this) at some point and the return
+  // value was something other than this, we need to decrement the
+  // associated reference count.
+  if (_self_compose != (TransformState *)NULL && _self_compose != this) {
+    unref_delete((TransformState *)_self_compose);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::operator <
+//       Access: Public
+//  Description: Provides an arbitrary ordering among all unique
+//               TransformStates, so we can store the essentially
+//               different ones in a big set and throw away the rest.
+//
+//               This method is not needed outside of the TransformState
+//               class because all equivalent TransformState objects are
+//               guaranteed to share the same pointer; thus, a pointer
+//               comparison is always sufficient.
+////////////////////////////////////////////////////////////////////
+bool TransformState::
+operator < (const TransformState &other) const {
+  bool components_given = (_flags & F_components_given) != 0;
+  bool other_components_given = (other._flags & F_components_given) != 0;
+  if (components_given != other_components_given) {
+    return components_given < other_components_given;
+  }
+  if (components_given) {
+    // If the transform was specified componentwise, compare them
+    // componentwise.
+    int c = _pos.compare_to(other._pos);
+    if (c != 0) {
+      return c < 0;
+    }
+    c = _hpr.compare_to(other._hpr);
+    if (c != 0) {
+      return c < 0;
+    }
+    c = _scale.compare_to(other._hpr);
+    return c < 0;
+  }
+
+  // Otherwise, compare the matrices.
+  return get_mat() < other.get_mat();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_identity
+//       Access: Published, Static
+//  Description: Constructs an identity transform.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+make_identity() {
+  // The identity state is asked for so often, we make it a special case
+  // and store a pointer forever once we find it the first time.
+  if (_identity_state == (TransformState *)NULL) {
+    TransformState *state = new TransformState;
+    _identity_state = return_new(state);
+  }
+
+  return _identity_state;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_pos_hpr_scale
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               components.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+make_pos_hpr_scale(const LVecBase3f &pos, const LVecBase3f &hpr, 
+                   const LVecBase3f &scale) {
+  // Make a special-case check for the identity transform.
+  if (pos == LVecBase3f(0.0f, 0.0f, 0.0f) &&
+      hpr == LVecBase3f(0.0f, 0.0f, 0.0f) &&
+      scale == LVecBase3f(1.0f, 1.0f, 1.0f)) {
+    return make_identity();
+  }
+
+  TransformState *attrib = new TransformState;
+  attrib->_pos = pos;
+  attrib->_hpr = hpr;
+  attrib->_scale = scale;
+  attrib->_flags = F_components_given | F_components_known | F_has_components;
+  return return_new(attrib);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_mat
+//       Access: Published, Static
+//  Description: Makes a new TransformState with the specified
+//               transformation matrix.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+make_mat(const LMatrix4f &mat) {
+  // Make a special-case check for the identity matrix.
+  if (mat == LMatrix4f::ident_mat()) {
+    return make_identity();
+  }
+
+  TransformState *attrib = new TransformState;
+  attrib->_mat = mat;
+  attrib->_flags = F_mat_known;
+  return return_new(attrib);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::compose
+//       Access: Published
+//  Description: Returns a new TransformState object that represents the
+//               composition of this state with the other state.
+//
+//               The result of this operation is cached, and will be
+//               retained as long as both this TransformState object and
+//               the other TransformState object continue to exist.
+//               Should one of them destruct, the cached entry will be
+//               removed, and its pointer will be allowed to destruct
+//               as well.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+compose(const TransformState *other) const {
+  // This method isn't strictly const, because it updates the cache,
+  // but we pretend that it is because it's only a cache which is
+  // transparent to the rest of the interface.
+
+  // We handle identity as a trivial special case.
+  if (is_identity()) {
+    return other;
+  }
+  if (other->is_identity()) {
+    return this;
+  }
+
+  if (other == this) {
+    // compose(this) has to be handled as a special case, because the
+    // caching problem is so different.
+    if (_self_compose != (TransformState *)NULL) {
+      return _self_compose;
+    }
+    CPT(TransformState) result = do_compose(this);
+    ((TransformState *)this)->_self_compose = result;
+
+    if (result != (const TransformState *)this) {
+      // If the result of compose(this) is something other than this,
+      // explicitly increment the reference count.  We have to be sure
+      // to decrement it again later, in our destructor.
+      _self_compose->ref();
+
+      // (If the result was just this again, we still store the
+      // result, but we don't increment the reference count, since
+      // that would be a self-referential leak.  What a mess this is.)
+    }
+    return _self_compose;
+  }
+
+  // Is this composition already cached?
+  CompositionCache::const_iterator ci = _composition_cache.find(other);
+  if (ci != _composition_cache.end()) {
+    const Composition &comp = (*ci).second;
+    if (comp._result == (const TransformState *)NULL) {
+      // Well, it wasn't cached already, but we already had an entry
+      // (probably created for the reverse direction), so use the same
+      // entry to store the new result.
+      ((Composition &)comp)._result = do_compose(other);
+    }
+    // Here's the cache!
+    return comp._result;
+  }
+
+  // We need to make a new cache entry, both in this object and in the
+  // other object.  We make both records so the other TransformState
+  // object will know to delete the entry from this object when it
+  // destructs, and vice-versa.
+
+  // The cache entry in this object is the only one that indicates the
+  // result; the other will be NULL for now.
+  CPT(TransformState) result = do_compose(other);
+  // We store them in this order, on the off-chance that other is the
+  // same as this, a degenerate case which is still worth supporting.
+  ((TransformState *)other)->_composition_cache[this]._result = NULL;
+  ((TransformState *)this)->_composition_cache[other]._result = result;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::invert_compose
+//       Access: Published
+//  Description: Returns a new TransformState object that represents the
+//               composition of this state's inverse with the other
+//               state.
+//
+//               This is similar to compose(), but is particularly
+//               useful for computing the relative state of a node as
+//               viewed from some other node.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+invert_compose(const TransformState *other) const {
+  // This method isn't strictly const, because it updates the cache,
+  // but we pretend that it is because it's only a cache which is
+  // transparent to the rest of the interface.
+
+  // We handle identity as a trivial special case.
+  if (is_identity()) {
+    return other;
+  }
+  // Unlike compose(), the case of other->is_identity() is not quite as
+  // trivial for invert_compose().
+
+  if (other == this) {
+    // a->invert_compose(a) always produces identity.
+    return make_identity();
+  }
+
+  // Is this composition already cached?
+  CompositionCache::const_iterator ci = _invert_composition_cache.find(other);
+  if (ci != _invert_composition_cache.end()) {
+    const Composition &comp = (*ci).second;
+    if (comp._result == (const TransformState *)NULL) {
+      // Well, it wasn't cached already, but we already had an entry
+      // (probably created for the reverse direction), so use the same
+      // entry to store the new result.
+      ((Composition &)comp)._result = do_invert_compose(other);
+    }
+    // Here's the cache!
+    return comp._result;
+  }
+
+  // We need to make a new cache entry, both in this object and in the
+  // other object.  We make both records so the other TransformState
+  // object will know to delete the entry from this object when it
+  // destructs, and vice-versa.
+
+  // The cache entry in this object is the only one that indicates the
+  // result; the other will be NULL for now.
+  CPT(TransformState) result = do_invert_compose(other);
+  // We store them in this order, on the off-chance that other is the
+  // same as this, a degenerate case which is still worth supporting.
+  ((TransformState *)other)->_invert_composition_cache[this]._result = NULL;
+  ((TransformState *)this)->_invert_composition_cache[other]._result = result;
+
+  return result;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::output
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TransformState::
+output(ostream &out) const {
+  out << get_type() << ":";
+  if (is_identity()) {
+    out << "(identity)";
+
+  } else if (has_components()) {
+    out << "(";
+    if (get_pos() != LVecBase3f(0.0f, 0.0f, 0.0f)) {
+      out << "pos " << get_pos();
+    }
+    if (get_hpr() != LVecBase3f(0.0f, 0.0f, 0.0f)) {
+      out << "hpr " << get_hpr();
+    }
+    if (get_scale() != LVecBase3f(1.0f, 1.0f, 1.0f)) {
+      out << "scale " << get_scale();
+    }
+    out << ")";
+
+  } else {
+    out << get_mat();
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::write
+//       Access: Published, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void TransformState::
+write(ostream &out, int indent_level) const {
+  indent(out, indent_level) << *this << "\n";
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::return_new
+//       Access: Private, Static
+//  Description: This function is used to share a common TransformState
+//               pointer for all equivalent TransformState objects.
+//
+//               See the similar logic in RenderAttrib.  The idea is
+//               to create a new TransformState object and pass it
+//               through this function, which will share the pointer
+//               with a previously-created TransformState object if it is
+//               equivalent.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+return_new(TransformState *state) {
+  nassertr(state != (TransformState *)NULL, state);
+
+  // This should be a newly allocated pointer, not one that was used
+  // for anything else.
+  nassertr(state->_saved_entry == _states.end(), state);
+
+  // Save the state in a local PointerTo so that it will be freed at
+  // the end of this function if no one else uses it.
+  CPT(TransformState) pt_state = state;
+
+  pair<States::iterator, bool> result = _states.insert(state);
+  if (result.second) {
+    // The state was inserted; save the iterator and return the
+    // input state.
+    state->_saved_entry = result.first;
+    return pt_state;
+  }
+
+  // The state was not inserted; there must be an equivalent one
+  // already in the set.  Return that one.
+  return *(result.first);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::do_compose
+//       Access: Private
+//  Description: The private implemention of compose(); this actually
+//               composes two TransformStates, without bothering with the
+//               cache.
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+do_compose(const TransformState *other) const {
+  LMatrix4f new_mat = get_mat() * other->get_mat();
+  return make_mat(new_mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::do_invert_compose
+//       Access: Private
+//  Description: The private implemention of invert_compose().
+////////////////////////////////////////////////////////////////////
+CPT(TransformState) TransformState::
+do_invert_compose(const TransformState *other) const {
+  LMatrix4f new_mat;
+  new_mat.invert_from(get_mat());
+  new_mat = new_mat * other->get_mat();
+  return make_mat(new_mat);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::calc_singular
+//       Access: Private
+//  Description: Determines whether the transform is singular (i.e. it
+//               scales to zero, and has no inverse).
+////////////////////////////////////////////////////////////////////
+void TransformState::
+calc_singular() {
+  bool singular = false;
+
+  if (has_components()) {
+    // The matrix is singular if any component of its scale is 0.
+    singular = (_scale[0] == 0.0f || _scale[1] == 0.0f || _scale[2] == 0.0f);
+  } else {
+    // The matrix is singular if its determinant is zero.
+    const LMatrix4f &mat = get_mat();
+    singular = (mat.get_upper_3().determinant() == 0.0f);
+  }
+
+  if (singular) {
+    _flags |= F_is_singular;
+  }
+  _flags |= F_singular_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::calc_components
+//       Access: Private
+//  Description: Derives the components from the matrix, if possible.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+calc_components() {
+  if ((_flags & F_is_identity) != 0) {
+    _scale.set(1.0f, 1.0f, 1.0f);
+    _hpr.set(0.0f, 0.0f, 0.0f);
+    _pos.set(0.0f, 0.0f, 0.0f);
+    _flags |= F_has_components;
+
+  } else {
+    // If we don't have components and we're not identity, the only
+    // 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);
+    if (possible) {
+      // Some matrices can't be decomposed into scale, hpr, pos.
+      _flags |= F_has_components;
+    }
+  }
+
+  _flags |= F_components_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::calc_mat
+//       Access: Private
+//  Description: Computes the matrix from the components.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+calc_mat() {
+  if ((_flags & F_is_identity) != 0) {
+    _mat = LMatrix4f::ident_mat();
+
+  } else {
+    // If we don't have a matrix and we're not identity, the only
+    // other explanation is that we were constructed via components.
+    nassertv((_flags & F_components_known) != 0);
+    compose_matrix(_mat, _scale, _hpr, _pos);
+  }
+  _flags |= F_mat_known;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::register_with_read_factory
+//       Access: Public, Static
+//  Description: Tells the BamReader how to create objects of type
+//               TransformState.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+register_with_read_factory() {
+  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::write_datagram
+//       Access: Public, Virtual
+//  Description: Writes the contents of this object to the datagram
+//               for shipping out to a Bam file.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+write_datagram(BamWriter *manager, Datagram &dg) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::finalize
+//       Access: Public, Virtual
+//  Description: Method to ensure that any necessary clean up tasks
+//               that have to be performed by this object are performed
+////////////////////////////////////////////////////////////////////
+void TransformState::
+finalize() {
+  // Unref the pointer that we explicitly reffed in make_from_bam().
+  unref();
+
+  // We should never get back to zero after unreffing our own count,
+  // because we expect to have been stored in a pointer somewhere.  If
+  // we do get to zero, it's a memory leak; the way to avoid this is
+  // to call unref_delete() above instead of unref(), but this is
+  // dangerous to do from within a virtual function.
+  nassertv(get_ref_count() != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::make_from_bam
+//       Access: Protected, Static
+//  Description: This function is called by the BamReader's factory
+//               when a new object of type TransformState is encountered
+//               in the Bam file.  It should create the TransformState
+//               and extract its information from the file.
+////////////////////////////////////////////////////////////////////
+TypedWritable *TransformState::
+make_from_bam(const FactoryParams &params) {
+  TransformState *state = new TransformState;
+  DatagramIterator scan;
+  BamReader *manager;
+
+  parse_params(params, scan, manager);
+  state->fillin(scan, manager);
+
+  return new_from_bam(state, manager);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::new_from_bam
+//       Access: Protected, Static
+//  Description: Uniquifies the pointer for a TransformState object just
+//               created from a bam file, and preserves its reference
+//               count correctly.
+////////////////////////////////////////////////////////////////////
+TypedWritable *TransformState::
+new_from_bam(TransformState *state, BamReader *manager) {
+  // First, uniquify the pointer.
+  CPT(TransformState) pointer = return_new(state);
+
+  // But now we have a problem, since we have to hold the reference
+  // count and there's no way to return a TypedWritable while still
+  // holding the reference count!  We work around this by explicitly
+  // upping the count, and also setting a finalize() callback to down
+  // it later.
+  if (pointer == state) {
+    pointer->ref();
+    manager->register_finalize(state);
+  }
+  
+  // We have to cast the pointer back to non-const, because the bam
+  // reader expects that.
+  return (TransformState *)pointer.p();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformState::fillin
+//       Access: Protected
+//  Description: This internal function is called by make_from_bam to
+//               read in all of the relevant data from the BamFile for
+//               the new TransformState.
+////////////////////////////////////////////////////////////////////
+void TransformState::
+fillin(DatagramIterator &scan, BamReader *manager) {
+}

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

@@ -0,0 +1,187 @@
+// Filename: transformState.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 TRANSFORMSTATE_H
+#define TRANSFORMSTATE_H
+
+#include "pandabase.h"
+
+#include "typedWritableReferenceCount.h"
+#include "pointerTo.h"
+#include "indirectLess.h"
+#include "luse.h"
+#include "pset.h"
+
+class GraphicsStateGuardianBase;
+
+////////////////////////////////////////////////////////////////////
+//       Class : TransformState
+// Description : Indicates a coordinate-system transform on vertices.
+//               TransformStates are the primary means for storing
+//               transformations on the scene graph.
+//
+//               Transforms may be specified in one of two ways:
+//               componentwise, with a pos-hpr-scale, or with an
+//               arbitrary transform matrix.  If you specify a
+//               transform componentwise, it will remember its
+//               original components.
+//
+//               TransformState objects are managed very much like
+//               RenderState objects.  They are immutable and
+//               reference-counted automatically.
+//
+//               You should not attempt to create or modify a
+//               TransformState object directly.  Instead, call one of
+//               the make() functions to create one for you.  And
+//               instead of modifying a TransformState object, create a
+//               new one.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TransformState : public TypedWritableReferenceCount {
+protected:
+  TransformState();
+
+private:
+  TransformState(const TransformState &copy);
+  void operator = (const TransformState &copy);
+
+public:
+  virtual ~TransformState();
+
+  bool operator < (const TransformState &other) const;
+
+PUBLISHED:
+  static CPT(TransformState) make_identity();
+  INLINE static CPT(TransformState) make_pos(const LVecBase3f &pos);
+  INLINE static CPT(TransformState) make_hpr(const LVecBase3f &hpr);
+  INLINE static CPT(TransformState) make_pos_hpr(const LVecBase3f &pos,
+                                                 const LVecBase3f &hpr);
+  INLINE static CPT(TransformState) make_scale(float scale);
+  INLINE static CPT(TransformState) make_scale(const LVecBase3f &scale);
+  static CPT(TransformState) make_pos_hpr_scale(const LVecBase3f &pos,
+                                                const LVecBase3f &hpr, 
+                                                const LVecBase3f &scale);
+  static CPT(TransformState) make_mat(const LMatrix4f &mat);
+
+  INLINE bool is_identity() const;
+  INLINE bool is_singular() const;
+  INLINE bool has_components() 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) compose(const TransformState *other) const;
+  CPT(TransformState) invert_compose(const TransformState *other) const;
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level) const;
+
+private:
+  static CPT(TransformState) return_new(TransformState *state);
+  CPT(TransformState) do_compose(const TransformState *other) const;
+  CPT(TransformState) do_invert_compose(const TransformState *other) const;
+
+private:
+  typedef pset<const TransformState *, IndirectLess<TransformState> > States;
+  static States _states;
+  static CPT(TransformState) _identity_state;
+
+  // This iterator records the entry corresponding to this TransformState
+  // object in the above global set.  We keep the iterator around so
+  // we can remove it when the TransformState destructs.
+  States::iterator _saved_entry;
+
+  // This data structure manages the job of caching the composition of
+  // two TransformStates.  It's complicated because we have to be sure to
+  // remove the entry if *either* of the input TransformStates destructs.
+  // To implement this, we always record Composition entries in pairs,
+  // one in each of the two involved TransformState objects.
+  class Composition {
+  public:
+    CPT(TransformState) _result;
+  };
+    
+  typedef pmap<const TransformState *, Composition> CompositionCache;
+  CompositionCache _composition_cache;
+  CompositionCache _invert_composition_cache;
+
+  // Thise pointer is used to cache the result of compose(this).  This
+  // has to be a special case, because we have to handle the reference
+  // counts carefully so that we don't leak.
+  const TransformState *_self_compose;
+
+private:
+  // This is the actual data within the TransformState.
+  INLINE void check_singular() const;
+  INLINE void check_components() const;
+  INLINE void check_mat() const;
+  void calc_singular();
+  void calc_components();
+  void calc_mat();
+
+  enum Flags {
+    F_is_identity      =  0x0001,
+    F_is_singular      =  0x0002,
+    F_singular_known   =  0x0004,
+    F_components_given =  0x0008,
+    F_components_known =  0x0010,
+    F_has_components   =  0x0020,
+    F_mat_known        =  0x0040,
+  };
+  LVecBase3f _pos, _hpr, _scale;
+  LMatrix4f _mat;
+  
+  short _flags;
+
+public:
+  static void register_with_read_factory();
+  virtual void write_datagram(BamWriter *manager, Datagram &dg);
+  virtual void finalize();
+
+protected:
+  static TypedWritable *make_from_bam(const FactoryParams &params);
+  static TypedWritable *new_from_bam(TransformState *state, BamReader *manager);
+  void fillin(DatagramIterator &scan, BamReader *manager);
+  
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedWritableReferenceCount::init_type();
+    register_type(_type_handle, "TransformState",
+                  TypedWritableReferenceCount::get_class_type());
+  }
+  virtual TypeHandle get_type() const {
+    return get_class_type();
+  }
+  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
+
+private:
+  static TypeHandle _type_handle;
+};
+
+INLINE ostream &operator << (ostream &out, const TransformState &state) {
+  state.output(out);
+  return out;
+}
+
+#include "transformState.I"
+
+#endif
+

+ 96 - 25
panda/src/testbed/pview.cxx

@@ -23,7 +23,7 @@
 #include "graphicsPipe.h"
 #include "graphicsPipe.h"
 #include "interactiveGraphicsPipe.h"
 #include "interactiveGraphicsPipe.h"
 #include "displayRegion.h"
 #include "displayRegion.h"
-#include "camera.h"
+#include "qpcamera.h"
 #include "perspectiveLens.h"
 #include "perspectiveLens.h"
 #include "pandaNode.h"
 #include "pandaNode.h"
 #include "textureAttrib.h"
 #include "textureAttrib.h"
@@ -33,6 +33,32 @@
 #include "texture.h"
 #include "texture.h"
 #include "texturePool.h"
 #include "texturePool.h"
 
 
+// These are in support of legacy data graph operations.
+#include "namedNode.h"
+#include "mouse.h"
+#include "mouseWatcher.h"
+#include "trackball.h"
+#include "transform2sg.h"
+#include "dataRelation.h"
+#include "dataGraphTraversal.h"
+#include "buttonThrower.h"
+#include "modifierButtons.h"
+#include "keyboardButton.h"
+#include "event.h"
+#include "eventQueue.h"
+#include "eventHandler.h"
+
+// Use dconfig to read a few Configrc variables.
+Configure(config_pview);
+ConfigureFn(config_pview) {
+}
+
+static const int win_width = config_pview.GetInt("win-width", 640);
+static const int win_height = config_pview.GetInt("win-height", 480);
+
+// As long as this is true, the main loop will continue running.
+bool run_flag = true;
+
 PT(GraphicsPipe)
 PT(GraphicsPipe)
 make_pipe() {
 make_pipe() {
   // We use the GraphicsPipe factory to make us a renderable pipe
   // We use the GraphicsPipe factory to make us a renderable pipe
@@ -59,20 +85,25 @@ make_pipe() {
   return pipe;
   return pipe;
 }
 }
 
 
-PT(Camera)
-setup_window(GraphicsPipe *pipe, GraphicsEngine *engine) {
+PT(GraphicsWindow)
+make_window(GraphicsPipe *pipe, GraphicsEngine *engine) {
   // Now create a window on that pipe.
   // Now create a window on that pipe.
   GraphicsWindow::Properties window_prop;
   GraphicsWindow::Properties window_prop;
   window_prop._xorg = 0;
   window_prop._xorg = 0;
   window_prop._yorg = 0;
   window_prop._yorg = 0;
-  window_prop._xsize = 640;
-  window_prop._ysize = 480;
+  window_prop._xsize = win_width;
+  window_prop._ysize = win_height;
   window_prop._title = "Panda Viewer";
   window_prop._title = "Panda Viewer";
   //  window_prop._fullscreen = true;
   //  window_prop._fullscreen = true;
 
 
   PT(GraphicsWindow) window = pipe->make_window(window_prop);
   PT(GraphicsWindow) window = pipe->make_window(window_prop);
   engine->add_window(window);
   engine->add_window(window);
+  
+  return window;
+}
 
 
+PT(qpCamera)
+make_camera(GraphicsWindow *window) {
   // Get the first channel on the window.  This will be the only
   // Get the first channel on the window.  This will be the only
   // channel on non-SGI hardware.
   // channel on non-SGI hardware.
   PT(GraphicsChannel) channel = window->get_channel(0);
   PT(GraphicsChannel) channel = window->get_channel(0);
@@ -84,11 +115,11 @@ setup_window(GraphicsPipe *pipe, GraphicsEngine *engine) {
   PT(DisplayRegion) dr = layer->make_display_region();
   PT(DisplayRegion) dr = layer->make_display_region();
 
 
   // Finally, we need a camera to associate with the display region.
   // Finally, we need a camera to associate with the display region.
-  PT(Camera) camera = new Camera;
+  PT(qpCamera) camera = new qpCamera("camera");
   PT(Lens) lens = new PerspectiveLens;
   PT(Lens) lens = new PerspectiveLens;
-  lens->set_film_size(640, 480);
+  lens->set_film_size(win_width, win_height);
   camera->set_lens(lens);
   camera->set_lens(lens);
-  dr->set_camera(camera);
+  dr->set_qpcamera(NodeChain(camera));
 
 
   return camera;
   return camera;
 }
 }
@@ -102,9 +133,9 @@ make_default_geometry(PandaNode *parent) {
   PTA_Colorf colors;
   PTA_Colorf colors;
   PTA_ushort cindex;
   PTA_ushort cindex;
   
   
-  coords.push_back(Vertexf::rfu(0.0, 20.0, 0.0));
-  coords.push_back(Vertexf::rfu(1.0, 20.0, 0.0));
-  coords.push_back(Vertexf::rfu(0.0, 20.0, 1.0));
+  coords.push_back(Vertexf::rfu(0.0, 0.0, 0.0));
+  coords.push_back(Vertexf::rfu(1.0, 0.0, 0.0));
+  coords.push_back(Vertexf::rfu(0.0, 0.0, 1.0));
   uvs.push_back(TexCoordf(0.0, 0.0));
   uvs.push_back(TexCoordf(0.0, 0.0));
   uvs.push_back(TexCoordf(1.0, 0.0));
   uvs.push_back(TexCoordf(1.0, 0.0));
   uvs.push_back(TexCoordf(0.0, 1.0));
   uvs.push_back(TexCoordf(0.0, 1.0));
@@ -153,6 +184,39 @@ get_models(PandaNode *parent, int argc, char *argv[]) {
   */
   */
 }
 }
 
 
+NamedNode * 
+setup_mouse(NamedNode *data_root, GraphicsWindow *window) {
+  MouseAndKeyboard *mouse = new MouseAndKeyboard(window, 0);
+  new DataRelation(data_root, mouse);
+
+  // Create a ButtonThrower to throw events from the keyboard.
+  PT(ButtonThrower) bt = new ButtonThrower("kb-events");
+  ModifierButtons mods;
+  mods.add_button(KeyboardButton::shift());
+  mods.add_button(KeyboardButton::control());
+  mods.add_button(KeyboardButton::alt());
+  bt->set_modifier_buttons(mods);
+  new DataRelation(mouse, bt);
+
+  return mouse;
+}
+
+void 
+setup_trackball(NamedNode *mouse, qpCamera *camera) {
+  PT(Trackball) trackball = new Trackball("trackball");
+  trackball->set_pos(LVector3f::forward() * 50.0);
+  new DataRelation(mouse, trackball);
+
+  PT(Transform2SG) tball2cam = new Transform2SG("tball2cam");
+  tball2cam->set_node(camera);
+  new DataRelation(trackball, tball2cam);
+}
+
+void
+event_esc(CPT_Event) {
+  // The Escape or q key was pressed.  Exit the application.
+  run_flag = false;
+}
 
 
 int
 int
 main(int argc, char *argv[]) {
 main(int argc, char *argv[]) {
@@ -163,33 +227,40 @@ main(int argc, char *argv[]) {
   GraphicsEngine *engine = new GraphicsEngine;
   GraphicsEngine *engine = new GraphicsEngine;
 
 
   // Now open a window and get a camera.
   // Now open a window and get a camera.
-  PT(Camera) camera = setup_window(pipe, engine);
+  PT(GraphicsWindow) window = make_window(pipe, engine);
+  PT(qpCamera) camera = make_camera(window);
 
 
   // Now we just need to make a scene graph for the camera to render.
   // Now we just need to make a scene graph for the camera to render.
   PT(PandaNode) render = new PandaNode("render");
   PT(PandaNode) render = new PandaNode("render");
-  camera->set_qpscene(render);
+  render->add_child(camera);
+  camera->set_scene(render);
+
+  // Set up a data graph for tracking user input.  For now, this uses
+  // the old-style graph interface.
+  PT(NamedNode) data_root = new NamedNode("data_root");
+  NamedNode *mouse = setup_mouse(data_root, window);
+  setup_trackball(mouse, camera);
+
+  // Use an event handler to manage keyboard events.
+  EventHandler event_handler(EventQueue::get_global_event_queue());
+  event_handler.add_hook("escape", event_esc);
+  event_handler.add_hook("q", event_esc);
+
 
 
   // Put something in the scene graph to look at.
   // Put something in the scene graph to look at.
   get_models(render, argc, argv);
   get_models(render, argc, argv);
 
 
-  /*
-  // Put the scene a distance in front of our face so we can see it.
-  LMatrix4f mat = LMatrix4f::translate_mat(0, 20, 0);
-  TransformTransition *tt = new TransformTransition(mat);
-  render_arc->set_transition(tt);
-  */
-
 
 
-  // This is our main update loop.  Run for 5 seconds, then shut down.
+  // This is our main update loop.  Loop here until someone
+  // (e.g. event_esc) sets run_flag to false.
   ClockObject *clock = ClockObject::get_global_clock();
   ClockObject *clock = ClockObject::get_global_clock();
-  clock->tick();
-  double now = clock->get_frame_time();
-  while (clock->get_frame_time() - now < 5.0) {
+  while (run_flag) {
     clock->tick();
     clock->tick();
+    traverse_data_graph(data_root);
+    event_handler.process_events();
     engine->render_frame();
     engine->render_frame();
   } 
   } 
 
 
-  cerr << "Exiting.\n";
   delete engine;
   delete engine;
   return (0);
   return (0);
 }
 }

+ 1 - 1
panda/src/tform/Sources.pp

@@ -4,7 +4,7 @@
 #begin lib_target
 #begin lib_target
   #define TARGET tform
   #define TARGET tform
   #define LOCAL_LIBS \
   #define LOCAL_LIBS \
-    dgraph graph linmath sgattrib display event putil gobj gsgbase \
+    dgraph pgraph graph linmath sgattrib display event putil gobj gsgbase \
     mathutil sgraph device sgraphutil
     mathutil sgraph device sgraphutil
 
 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 

+ 30 - 2
panda/src/tform/transform2sg.cxx

@@ -20,6 +20,7 @@
 
 
 #include "nodeRelation.h"
 #include "nodeRelation.h"
 #include "transformTransition.h"
 #include "transformTransition.h"
+#include "transformState.h"
 #include "matrixDataTransition.h"
 #include "matrixDataTransition.h"
 #include "allTransitionsWrapper.h"
 #include "allTransitionsWrapper.h"
 
 
@@ -36,6 +37,7 @@ TypeHandle Transform2SG::_transform_type;
 Transform2SG::
 Transform2SG::
 Transform2SG(const string &name) : DataNode(name) {
 Transform2SG(const string &name) : DataNode(name) {
   _arc = NULL;
   _arc = NULL;
+  _node = NULL;
 }
 }
 
 
 
 
@@ -60,6 +62,27 @@ get_arc() const {
   return _arc;
   return _arc;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Transform2SG::set_node
+//       Access: Public
+//  Description: Sets the node that this node will adjust.
+////////////////////////////////////////////////////////////////////
+void Transform2SG::
+set_node(PandaNode *node) {
+  _node = node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Transform2SG::get_node
+//       Access: Public
+//  Description: Returns the node that this node will adjust, or NULL
+//               if the node has not yet been set.
+////////////////////////////////////////////////////////////////////
+PandaNode *Transform2SG::
+get_node() const {
+  return _node;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Transform2SG::transmit_data
 //     Function: Transform2SG::transmit_data
@@ -70,9 +93,14 @@ void Transform2SG::
 transmit_data(AllTransitionsWrapper &data) {
 transmit_data(AllTransitionsWrapper &data) {
   const NodeTransition *transform = data.get_transition(_transform_type);
   const NodeTransition *transform = data.get_transition(_transform_type);
 
 
-  if (transform != (NodeTransition *)NULL && _arc != (NodeRelation *)NULL) {
+  if (transform != (NodeTransition *)NULL) {
     const LMatrix4f &mat = DCAST(MatrixDataTransition, transform)->get_value();
     const LMatrix4f &mat = DCAST(MatrixDataTransition, transform)->get_value();
-    _arc->set_transition(new TransformTransition(mat));
+    if (_arc != (NodeRelation *)NULL) {
+      _arc->set_transition(new TransformTransition(mat));
+    }
+    if (_node != (PandaNode *)NULL) {
+      _node->set_transform(TransformState::make_mat(mat));
+    }
   }
   }
 
 
   // Clear the data below us.
   // Clear the data below us.

+ 5 - 0
panda/src/tform/transform2sg.h

@@ -22,6 +22,7 @@
 #include "pandabase.h"
 #include "pandabase.h"
 
 
 #include "dataNode.h"
 #include "dataNode.h"
+#include "pandaNode.h"
 
 
 class NodeRelation;
 class NodeRelation;
 
 
@@ -39,8 +40,12 @@ PUBLISHED:
   void set_arc(NodeRelation *arc);
   void set_arc(NodeRelation *arc);
   NodeRelation *get_arc() const;
   NodeRelation *get_arc() const;
 
 
+  void set_node(PandaNode *node);
+  PandaNode *get_node() const;
+
 private:
 private:
   NodeRelation *_arc;
   NodeRelation *_arc;
+  PandaNode *_node;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // From parent class DataNode
 // From parent class DataNode