From 47c83fce1f4a96954a5db33c6be875e17a4675cc Mon Sep 17 00:00:00 2001
From: tobspr <tobias.springer1@googlemail.com>
Date: Sat, 27 Feb 2016 17:35:53 +0100
Subject: [PATCH] Support for p3d_PrevModelViewMatrix, populate previous
 transform during cull stage

---
 panda/src/collide/collisionVisualizer.cxx          |  7 +-
 panda/src/display/callbackGraphicsWindow.cxx       |  3 +-
 panda/src/display/graphicsEngine.cxx               |  4 ++
 panda/src/display/graphicsStateGuardian.I          |  9 +++
 panda/src/display/graphicsStateGuardian.cxx        | 21 +++++-
 panda/src/display/graphicsStateGuardian.h          |  9 +++
 panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx      |  8 ++-
 panda/src/dxgsg9/dxGraphicsStateGuardian9.h        |  4 +-
 panda/src/glstuff/glCgShaderContext_src.cxx        |  8 +++
 panda/src/glstuff/glCgShaderContext_src.h          |  2 +
 panda/src/glstuff/glGraphicsStateGuardian_src.cxx  | 13 ++--
 panda/src/glstuff/glGraphicsStateGuardian_src.h    |  3 +-
 panda/src/glstuff/glShaderContext_src.cxx          | 12 ++++
 panda/src/glstuff/glShaderContext_src.h            |  2 +
 panda/src/gobj/shader.cxx                          |  2 +
 panda/src/gobj/shader.h                            |  4 ++
 panda/src/gobj/shaderContext.h                     |  2 +-
 panda/src/gsgbase/graphicsStateGuardianBase.h      |  4 ++
 panda/src/parametrics/ropeNode.cxx                 | 12 ++--
 panda/src/parametrics/sheetNode.cxx                |  3 +-
 panda/src/pgraph/config_pgraph.cxx                 |  7 ++
 panda/src/pgraph/config_pgraph.h                   |  1 +
 panda/src/pgraph/cullTraverser.I                   |  3 +
 panda/src/pgraph/cullTraverser.cxx                 | 10 ++-
 panda/src/pgraph/cullTraverserData.I               | 22 ++++++
 panda/src/pgraph/cullTraverserData.cxx             |  8 +++
 panda/src/pgraph/cullTraverserData.h               |  5 ++
 panda/src/pgraph/cullableObject.I                  | 29 ++++++--
 panda/src/pgraph/cullableObject.h                  |  5 ++
 panda/src/pgraph/geomNode.cxx                      |  5 +-
 panda/src/pgraph/nodePath.cxx                      | 66 +++++++++++++-----
 panda/src/pgraph/nodePath.h                        | 81 ++++++++++++++--------
 panda/src/pgraph/occluderNode.cxx                  |  6 +-
 panda/src/pgraph/planeNode.cxx                     |  3 +-
 panda/src/pgraph/portalNode.cxx                    |  2 +
 panda/src/pgraph/sceneSetup.I                      | 19 +++++
 panda/src/pgraph/sceneSetup.h                      |  4 ++
 panda/src/pgraphnodes/callbackNode.cxx             |  3 +-
 panda/src/pgraphnodes/computeNode.cxx              |  3 +-
 panda/src/pgraphnodes/lodNode.cxx                  |  2 +
 panda/src/pgraphnodes/nodeCullCallbackData.cxx     |  3 +-
 .../src/tinydisplay/tinyGraphicsStateGuardian.cxx  |  7 +-
 panda/src/tinydisplay/tinyGraphicsStateGuardian.h  |  3 +-
 43 files changed, 349 insertions(+), 80 deletions(-)

diff --git a/panda/src/collide/collisionVisualizer.cxx b/panda/src/collide/collisionVisualizer.cxx
index 19699b9..cceb6c5 100644
--- a/panda/src/collide/collisionVisualizer.cxx
+++ b/panda/src/collide/collisionVisualizer.cxx
@@ -112,6 +112,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     xform_data._net_transform = TransformState::make_identity();
     xform_data._view_frustum = trav->get_view_frustum();
     xform_data.apply_transform_and_state(trav, net_transform,
+                                         TransformState::make_identity(),
                                          RenderState::make_empty(),
                                          RenderEffects::make_empty(),
                                          ClipPlaneAttrib::make());
@@ -187,7 +188,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
           CullableObject *object =
             new CullableObject(geom, point_state,
-                               xform_data.get_internal_transform(trav));
+                               xform_data.get_internal_transform(trav),
+                               xform_data.get_prev_internal_transform(trav));
 
           trav->get_cull_handler()->record_object(object, trav);
         }
@@ -216,7 +218,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
           CullableObject *object =
             new CullableObject(geom, empty_state,
-                               xform_data.get_internal_transform(trav));
+                               xform_data.get_internal_transform(trav),
+                               xform_data.get_prev_internal_transform(trav));
 
           trav->get_cull_handler()->record_object(object, trav);
         }
diff --git a/panda/src/display/callbackGraphicsWindow.cxx b/panda/src/display/callbackGraphicsWindow.cxx
index f9c2362..ebd5b37 100644
--- a/panda/src/display/callbackGraphicsWindow.cxx
+++ b/panda/src/display/callbackGraphicsWindow.cxx
@@ -108,7 +108,8 @@ end_frame(FrameMode mode, Thread *current_thread) {
   if (_render_callback != NULL) {
     // In case the callback or the application hosting the OpenGL context
     // wants to do more rendering, let's give it a blank slate.
-    _gsg->set_state_and_transform(RenderState::make_empty(), _gsg->get_internal_transform());
+    _gsg->set_state_and_transform(RenderState::make_empty(), _gsg->get_internal_transform(),
+                                  _gsg->get_prev_internal_transform());
     _gsg->clear_before_callback();
 
     RenderCallbackData data(this, RCT_end_frame, mode);
diff --git a/panda/src/display/graphicsEngine.cxx b/panda/src/display/graphicsEngine.cxx
index 41f6df5..7eed22f 100644
--- a/panda/src/display/graphicsEngine.cxx
+++ b/panda/src/display/graphicsEngine.cxx
@@ -1805,6 +1805,7 @@ setup_scene(GraphicsStateGuardian *gsg, DisplayRegionPipelineReader *dr) {
   NodePath scene_parent = scene_root.get_parent(current_thread);
   CPT(TransformState) camera_transform = camera.get_transform(scene_parent, current_thread);
   CPT(TransformState) world_transform = scene_parent.get_transform(camera, current_thread);
+  CPT(TransformState) prev_world_transform = scene_parent.get_prev_transform(camera, current_thread);
 
   if (camera_transform->is_invalid()) {
     // There must be a singular transform over the scene.
@@ -1857,6 +1858,9 @@ setup_scene(GraphicsStateGuardian *gsg, DisplayRegionPipelineReader *dr) {
   CPT(TransformState) cs_world_transform = cs_transform->compose(world_transform);
   scene_setup->set_cs_world_transform(cs_world_transform);
 
+  CPT(TransformState) prev_cs_world_transform = cs_transform->compose(prev_world_transform);
+  scene_setup->set_prev_cs_world_transform(prev_cs_world_transform);
+
   // Make sure that the GSG has a ShaderGenerator for the munger to use.  We
   // have to do this here because the ShaderGenerator needs a host window
   // pointer.  Hopefully we'll be able to eliminate that requirement in the
diff --git a/panda/src/display/graphicsStateGuardian.I b/panda/src/display/graphicsStateGuardian.I
index db8e1f1..d330100 100644
--- a/panda/src/display/graphicsStateGuardian.I
+++ b/panda/src/display/graphicsStateGuardian.I
@@ -846,6 +846,15 @@ INLINE CPT(TransformState) GraphicsStateGuardian::
 get_internal_transform() const {
   return _internal_transform;
 }
+/**
+ * Fetches the previous external net transform.  This transform is generally
+ * only set when geometry is about to be rendered.  Therefore, this "get"
+ * function is typically only meaningful during the geometry rendering process.
+ */
+INLINE CPT(TransformState) GraphicsStateGuardian::
+get_prev_internal_transform() const {
+  return _prev_internal_transform;
+}
 
 /**
  * Returns the current display region being rendered to, as set by the last
diff --git a/panda/src/display/graphicsStateGuardian.cxx b/panda/src/display/graphicsStateGuardian.cxx
index 6f5e873..c0b1cab 100644
--- a/panda/src/display/graphicsStateGuardian.cxx
+++ b/panda/src/display/graphicsStateGuardian.cxx
@@ -146,6 +146,7 @@ GraphicsStateGuardian(CoordinateSystem internal_coordinate_system,
 {
   _coordinate_system = CS_invalid;
   _internal_transform = TransformState::make_identity();
+  _prev_internal_transform = _prev_internal_transform;
 
   set_coordinate_system(get_default_coordinate_system());
 
@@ -1189,6 +1190,9 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
   case Shader::SMO_model_to_apiview: {
     return &(get_internal_transform()->get_mat());
   }
+  case Shader::SMO_prev_model_to_apiview: {
+    return &(get_prev_internal_transform()->get_mat());
+  }
   case Shader::SMO_view_to_model: {
     t = get_external_transform()->get_inverse()->get_mat();
     return &t;
@@ -1197,6 +1201,10 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
     t = get_internal_transform()->get_inverse()->get_mat();
     return &t;
   }
+  case Shader::SMO_prev_apiview_to_model: {
+    t = get_prev_internal_transform()->get_inverse()->get_mat();
+    return &t;
+  }
   case Shader::SMO_apiview_to_view: {
     return &(_inv_cs_transform->get_mat());
   }
@@ -2308,6 +2316,15 @@ reset() {
 }
 
 /**
+ * @see set_state_and_transform(const RenderState*, const TransformState*, const TransformState*)
+ */
+void GraphicsStateGuardian::
+set_state_and_transform(const RenderState *state,
+                        const TransformState *trans) {
+  set_state_and_transform(state, trans, trans);
+}
+
+/**
  * Simultaneously resets the render state and the transform state.
  *
  * This transform specified is the "internal" net transform, already converted
@@ -2321,9 +2338,11 @@ reset() {
  */
 void GraphicsStateGuardian::
 set_state_and_transform(const RenderState *state,
-                        const TransformState *trans) {
+                        const TransformState *trans,
+                        const TransformState *prev_transform) {
 }
 
+
 /**
  * Clears the framebuffer within the current DisplayRegion, according to the
  * flags indicated by the given DrawableRegion object.
diff --git a/panda/src/display/graphicsStateGuardian.h b/panda/src/display/graphicsStateGuardian.h
index 401c538..ad81f17 100644
--- a/panda/src/display/graphicsStateGuardian.h
+++ b/panda/src/display/graphicsStateGuardian.h
@@ -320,6 +320,10 @@ public:
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform);
 
+  virtual void set_state_and_transform(const RenderState *state,
+                                       const TransformState *transform,
+                                       const TransformState *prev_transform);
+
   virtual PN_stdfloat compute_distance_to(const LPoint3 &point) const;
 
   virtual void clear(DrawableRegion *clearable);
@@ -384,6 +388,7 @@ public:
 
   INLINE CPT(TransformState) get_external_transform() const;
   INLINE CPT(TransformState) get_internal_transform() const;
+  INLINE CPT(TransformState) get_prev_internal_transform() const;
 
   RenderBuffer get_render_buffer(int buffer_type, const FrameBufferProperties &prop);
 
@@ -473,6 +478,10 @@ protected:
   // The current transform, as of the last call to set_state_and_transform().
   CPT(TransformState) _internal_transform;
 
+  // The previous transform, as of the last call to the 3 parameter overload of
+  // set_state_and_transform()
+  CPT(TransformState) _prev_internal_transform;
+
   // The current TextureAttrib is a special case; we may further restrict it
   // (according to graphics cards limits) or extend it (according to
   // ColorScaleAttribs in effect) beyond what is specifically requested in the
diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
index 5cc7d8d..da726f6 100644
--- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
+++ b/panda/src/dxgsg9/dxGraphicsStateGuardian9.cxx
@@ -758,7 +758,7 @@ clear(DrawableRegion *clearable) {
     return;
   }
 
-  set_state_and_transform(RenderState::make_empty(), _internal_transform);
+  set_state_and_transform(RenderState::make_empty(), _internal_transform, _prev_internal_transform);
 
   D3DCOLOR color_clear_value = LColor_to_D3DCOLOR(clearable->get_clear_color());
   PN_stdfloat depth_clear_value = clearable->get_clear_depth();
@@ -3027,7 +3027,8 @@ do_issue_shade_model() {
  */
 void DXGraphicsStateGuardian9::
 set_state_and_transform(const RenderState *target,
-                        const TransformState *transform) {
+                        const TransformState *transform,
+                        const TransformState *prev_transform) {
 #ifndef NDEBUG
   if (gsg_cat.is_spam()) {
     gsg_cat.spam() << "Setting GSG state to " << (void *)target << ":\n";
@@ -3036,6 +3037,7 @@ set_state_and_transform(const RenderState *target,
 #endif
   _state_pcollector.add_level(1);
   PStatTimer timer1(_draw_set_state_pcollector);
+  _prev_internal_transform = prev_transform;
 
   if (transform != _internal_transform) {
     // PStatTimer timer(_draw_set_state_transform_pcollector);
@@ -3044,6 +3046,7 @@ set_state_and_transform(const RenderState *target,
     do_issue_transform();
   }
 
+
   if (target == _state_rs && (_state_mask | _inv_state_mask).is_all_on()) {
     return;
   }
@@ -3233,6 +3236,7 @@ set_state_and_transform(const RenderState *target,
   _state_rs = _target_rs;
 }
 
+
 /**
  * Called the first time a particular light has been bound to a given id
  * within a frame, this should set up the associated hardware light with the
diff --git a/panda/src/dxgsg9/dxGraphicsStateGuardian9.h b/panda/src/dxgsg9/dxGraphicsStateGuardian9.h
index 4a51d21..b6afb00 100644
--- a/panda/src/dxgsg9/dxGraphicsStateGuardian9.h
+++ b/panda/src/dxgsg9/dxGraphicsStateGuardian9.h
@@ -150,7 +150,9 @@ public:
   INLINE static DWORD LColor_to_D3DCOLOR(const LColor &cLColor);
 
   virtual void set_state_and_transform(const RenderState *state,
-                                       const TransformState *transform);
+                                       const TransformState *transform,
+                                       const TransformState *prev_transform);
+
 
   bool check_dx_allocation (HRESULT result, int allocation_size, int attempts);
 
diff --git a/panda/src/glstuff/glCgShaderContext_src.cxx b/panda/src/glstuff/glCgShaderContext_src.cxx
index c857948..5a0aa4e 100644
--- a/panda/src/glstuff/glCgShaderContext_src.cxx
+++ b/panda/src/glstuff/glCgShaderContext_src.cxx
@@ -384,6 +384,7 @@ unbind() {
 }
 
 /**
+ *
  * This function gets called whenever the RenderState or TransformState has
  * changed, but the Shader itself has not changed.  It loads new values into
  * the shader's parameters.
@@ -391,6 +392,7 @@ unbind() {
 void CLP(CgShaderContext)::
 set_state_and_transform(const RenderState *target_rs,
                         const TransformState *modelview_transform,
+                        const TransformState *prev_modelview_transform,
                         const TransformState *projection_transform) {
 
   if (!valid()) {
@@ -404,6 +406,12 @@ set_state_and_transform(const RenderState *target_rs,
     _modelview_transform = modelview_transform;
     altered |= Shader::SSD_transform;
   }
+
+  if (_prev_modelview_transform != prev_modelview_transform) {
+    _prev_modelview_transform = prev_modelview_transform;
+    altered |= Shader::SSD_transform;
+  }
+
   if (_projection_transform != projection_transform) {
     _projection_transform = projection_transform;
     altered |= Shader::SSD_projection;
diff --git a/panda/src/glstuff/glCgShaderContext_src.h b/panda/src/glstuff/glCgShaderContext_src.h
index ae76989..c229444 100644
--- a/panda/src/glstuff/glCgShaderContext_src.h
+++ b/panda/src/glstuff/glCgShaderContext_src.h
@@ -39,6 +39,7 @@ public:
 
   void set_state_and_transform(const RenderState *state,
                                const TransformState *modelview_transform,
+                               const TransformState *prev_modelview_transform,
                                const TransformState *projection_transform);
 
   void issue_parameters(int altered) OVERRIDE;
@@ -77,6 +78,7 @@ private:
 
   WCPT(RenderState) _state_rs;
   CPT(TransformState) _modelview_transform;
+  CPT(TransformState) _prev_modelview_transform;
   CPT(TransformState) _projection_transform;
   GLint _frame_number;
 
diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
index 2a9ace7..1fb4fcd 100644
--- a/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
+++ b/panda/src/glstuff/glGraphicsStateGuardian_src.cxx
@@ -2869,7 +2869,8 @@ clear(DrawableRegion *clearable) {
 
   // XXX rdb: Is this line really necessary?  Could we perhaps just reset the
   // color write mask and other relevant attributes?
-  set_state_and_transform(RenderState::make_empty(), _internal_transform);
+  set_state_and_transform(RenderState::make_empty(), _internal_transform,
+                          _prev_internal_transform);
 
   int mask = 0;
 
@@ -5889,7 +5890,8 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
   // Bug fix for RE, RE2, and VTX - need to disable texturing in order for
   // glReadPixels() to work NOTE: reading the depth buffer is *much* slower
   // than reading the color buffer
-  set_state_and_transform(RenderState::make_empty(), _internal_transform);
+  set_state_and_transform(RenderState::make_empty(), _internal_transform,
+                          _prev_internal_transform);
 
   int xo, yo, w, h;
   dr->get_region_pixels(xo, yo, w, h);
@@ -9486,7 +9488,8 @@ end_bind_clip_planes() {
  */
 void CLP(GraphicsStateGuardian)::
 set_state_and_transform(const RenderState *target,
-                        const TransformState *transform) {
+                        const TransformState *transform,
+                        const TransformState *prev_transform) {
   report_my_gl_errors();
 #ifndef NDEBUG
   if (gsg_cat.is_spam()) {
@@ -9498,6 +9501,8 @@ set_state_and_transform(const RenderState *target,
   _state_pcollector.add_level(1);
   PStatGPUTimer timer1(this, _draw_set_state_pcollector);
 
+  _prev_internal_transform = prev_transform;
+
   if (transform != _internal_transform) {
     // PStatGPUTimer timer(this, _draw_set_state_transform_pcollector);
     _transform_state_pcollector.add_level(1);
@@ -9529,7 +9534,7 @@ set_state_and_transform(const RenderState *target,
 #endif
 
   if (_current_shader_context != NULL) {
-    _current_shader_context->set_state_and_transform(target, transform, _projection_mat);
+    _current_shader_context->set_state_and_transform(target, transform, prev_transform, _projection_mat);
   }
 #endif
 
diff --git a/panda/src/glstuff/glGraphicsStateGuardian_src.h b/panda/src/glstuff/glGraphicsStateGuardian_src.h
index c2909e3..b1267ba 100644
--- a/panda/src/glstuff/glGraphicsStateGuardian_src.h
+++ b/panda/src/glstuff/glGraphicsStateGuardian_src.h
@@ -375,7 +375,8 @@ public:
   INLINE int get_gl_version_minor() const;
 
   virtual void set_state_and_transform(const RenderState *state,
-                                       const TransformState *transform);
+                                       const TransformState *transform,
+                                       const TransformState *prev_transform);
 
   void bind_fbo(GLuint fbo);
   virtual bool get_supports_cg_profile(const string &name) const;
diff --git a/panda/src/glstuff/glShaderContext_src.cxx b/panda/src/glstuff/glShaderContext_src.cxx
index 708347a..a7759af 100644
--- a/panda/src/glstuff/glShaderContext_src.cxx
+++ b/panda/src/glstuff/glShaderContext_src.cxx
@@ -769,6 +769,12 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
                                 : Shader::SMO_model_to_apiview;
         bind._part[1] = Shader::SMO_identity;
 
+      } else if (matrix_name == "PrevModelViewMatrix") {
+        bind._func = Shader::SMF_first;
+        bind._part[0] = inverse ? Shader::SMO_prev_apiview_to_model
+                                : Shader::SMO_prev_model_to_apiview;
+        bind._part[1] = Shader::SMO_identity;
+
       } else if (matrix_name == "ProjectionMatrix") {
         bind._func = Shader::SMF_first;
         bind._part[0] = inverse ? Shader::SMO_apiclip_to_apiview
@@ -1789,6 +1795,7 @@ unbind() {
 void CLP(ShaderContext)::
 set_state_and_transform(const RenderState *target_rs,
                         const TransformState *modelview_transform,
+                        const TransformState *prev_modelview_transform,
                         const TransformState *projection_transform) {
 
   // Find out which state properties have changed.
@@ -1798,6 +1805,11 @@ set_state_and_transform(const RenderState *target_rs,
     _modelview_transform = modelview_transform;
     altered |= Shader::SSD_transform;
   }
+  if (_prev_modelview_transform != prev_modelview_transform) {
+    _prev_modelview_transform = prev_modelview_transform;
+    altered |= Shader::SSD_transform;
+  }
+
   if (_projection_transform != projection_transform) {
     _projection_transform = projection_transform;
     altered |= Shader::SSD_projection;
diff --git a/panda/src/glstuff/glShaderContext_src.h b/panda/src/glstuff/glShaderContext_src.h
index 4209ec6..b260f1c 100644
--- a/panda/src/glstuff/glShaderContext_src.h
+++ b/panda/src/glstuff/glShaderContext_src.h
@@ -48,6 +48,7 @@ public:
 
   void set_state_and_transform(const RenderState *state,
                                const TransformState *modelview_transform,
+                               const TransformState *prev_modelview_transform,
                                const TransformState *projection_transform);
 
   void issue_parameters(int altered);
@@ -69,6 +70,7 @@ private:
 
   WCPT(RenderState) _state_rs;
   CPT(TransformState) _modelview_transform;
+  CPT(TransformState) _prev_modelview_transform;
   CPT(TransformState) _projection_transform;
 
 /*
diff --git a/panda/src/gobj/shader.cxx b/panda/src/gobj/shader.cxx
index 2905d54..552f665 100644
--- a/panda/src/gobj/shader.cxx
+++ b/panda/src/gobj/shader.cxx
@@ -390,6 +390,8 @@ cp_dependency(ShaderMatInput inp) {
       (inp == SMO_view_to_model) ||
       (inp == SMO_model_to_apiview) ||
       (inp == SMO_apiview_to_model) ||
+      (inp == SMO_prev_model_to_apiview) ||
+      (inp == SMO_prev_apiview_to_model) ||
       (inp == SMO_view_to_world) ||
       (inp == SMO_world_to_view) ||
       (inp == SMO_view_x_to_view) ||
diff --git a/panda/src/gobj/shader.h b/panda/src/gobj/shader.h
index dbdde6f..f517565 100644
--- a/panda/src/gobj/shader.h
+++ b/panda/src/gobj/shader.h
@@ -201,6 +201,10 @@ public:
     // Hack for text rendering.  Don't use in user shaders.
     SMO_tex_is_alpha_i,
 
+    // Previous frame transform
+    SMO_prev_apiview_to_model,
+    SMO_prev_model_to_apiview,
+
     SMO_INVALID
   };
 
diff --git a/panda/src/gobj/shaderContext.h b/panda/src/gobj/shaderContext.h
index 2b356ce..af2c81d 100644
--- a/panda/src/gobj/shaderContext.h
+++ b/panda/src/gobj/shaderContext.h
@@ -32,7 +32,7 @@ class EXPCL_PANDA_GOBJ ShaderContext: public SavedContext {
 public:
   INLINE ShaderContext(Shader *se);
 
-  INLINE virtual void set_state_and_transform(const RenderState *, const TransformState *, const TransformState*) {};
+  INLINE virtual void set_state_and_transform(const RenderState *, const TransformState *, const TransformState *, const TransformState *) {};
 
   INLINE virtual bool valid() { return false; }
   INLINE virtual void bind() {};
diff --git a/panda/src/gsgbase/graphicsStateGuardianBase.h b/panda/src/gsgbase/graphicsStateGuardianBase.h
index 2fecf30..ace7f38 100644
--- a/panda/src/gsgbase/graphicsStateGuardianBase.h
+++ b/panda/src/gsgbase/graphicsStateGuardianBase.h
@@ -170,6 +170,10 @@ public:
   virtual void set_state_and_transform(const RenderState *state,
                                        const TransformState *transform)=0;
 
+  virtual void set_state_and_transform(const RenderState *state,
+                                       const TransformState *transform,
+                                       const TransformState *prev_transform)=0;
+
   // This function may only be called during a render traversal; it will
   // compute the distance to the indicated point, assumed to be in eye
   // coordinates, from the camera plane.  This is a virtual function because
diff --git a/panda/src/parametrics/ropeNode.cxx b/panda/src/parametrics/ropeNode.cxx
index c799595..6705f66 100644
--- a/panda/src/parametrics/ropeNode.cxx
+++ b/panda/src/parametrics/ropeNode.cxx
@@ -334,7 +334,8 @@ render_thread(CullTraverser *trav, CullTraverserData &data,
 
   CullableObject *object =
     new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(object, trav);
 }
 
@@ -380,7 +381,8 @@ render_tape(CullTraverser *trav, CullTraverserData &data,
 
   CullableObject *object =
     new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(object, trav);
 }
 
@@ -433,7 +435,8 @@ render_billboard(CullTraverser *trav, CullTraverserData &data,
 
   CullableObject *object =
     new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(object, trav);
 }
 
@@ -494,7 +497,8 @@ render_tube(CullTraverser *trav, CullTraverserData &data,
 
   CullableObject *object =
     new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(object, trav);
 }
 
diff --git a/panda/src/parametrics/sheetNode.cxx b/panda/src/parametrics/sheetNode.cxx
index 34496bc..f745d6e 100644
--- a/panda/src/parametrics/sheetNode.cxx
+++ b/panda/src/parametrics/sheetNode.cxx
@@ -331,7 +331,8 @@ render_sheet(CullTraverser *trav, CullTraverserData &data,
 
   CullableObject *object =
     new CullableObject(geom, state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(object, trav);
 }
 
diff --git a/panda/src/pgraph/config_pgraph.cxx b/panda/src/pgraph/config_pgraph.cxx
index d5427a6..1360bbf 100644
--- a/panda/src/pgraph/config_pgraph.cxx
+++ b/panda/src/pgraph/config_pgraph.cxx
@@ -369,6 +369,13 @@ ConfigVariableBool allow_live_flatten
           "only has an effect when Panda is not compiled for a release "
           "build."));
 
+
+ConfigVariableBool always_store_prev_transform
+("always-store-prev-transform", false,
+ PRC_DESC("When setting this to true, the previous transform is kept on "
+          "nodes. This makes NodePath::set_pos and all other transform changes "
+          "behave like their fluid variants, e.g. set_fluid_pos."));
+
 /**
  * Initializes the library.  This must be called at least once before any of
  * the functions or classes in this library can be used.  Normally it will be
diff --git a/panda/src/pgraph/config_pgraph.h b/panda/src/pgraph/config_pgraph.h
index 988c9b1..4ba1ad5 100644
--- a/panda/src/pgraph/config_pgraph.h
+++ b/panda/src/pgraph/config_pgraph.h
@@ -72,6 +72,7 @@ extern ConfigVariableList load_file_type;
 extern ConfigVariableString default_model_extension;
 
 extern ConfigVariableBool allow_live_flatten;
+extern ConfigVariableBool always_store_prev_transform;
 
 extern EXPCL_PANDA_PGRAPH void init_libpgraph();
 
diff --git a/panda/src/pgraph/cullTraverser.I b/panda/src/pgraph/cullTraverser.I
index 180b94b..8441ba0 100644
--- a/panda/src/pgraph/cullTraverser.I
+++ b/panda/src/pgraph/cullTraverser.I
@@ -227,6 +227,8 @@ do_traverse(CullTraverserData &data) {
 
       data.apply_transform_and_state(this);
 
+#if defined(HAVE_CG) || defined(SUPPORT_FIXED_FUNCTION)
+      // Fog is only supported with the shader generator or the FFP.
       const FogAttrib *fog = (const FogAttrib *)
         node_reader->get_state()->get_attrib(FogAttrib::get_class_slot());
 
@@ -237,6 +239,7 @@ do_traverse(CullTraverserData &data) {
         // it.
         fog->get_fog()->adjust_to_camera(get_camera_transform());
       }
+#endif
 
       if (fancy_bits & PandaNode::FB_cull_callback) {
         PandaNode *node = data.node();
diff --git a/panda/src/pgraph/cullTraverser.cxx b/panda/src/pgraph/cullTraverser.cxx
index 5d08b4e..df8ae99 100644
--- a/panda/src/pgraph/cullTraverser.cxx
+++ b/panda/src/pgraph/cullTraverser.cxx
@@ -128,7 +128,9 @@ traverse(const NodePath &root) {
     // Store this pointer in this
     set_portal_clipper(&portal_viewer);
 
-    CullTraverserData data(root, TransformState::make_identity(),
+    CullTraverserData data(root,
+                           TransformState::make_identity(),
+                           TransformState::make_identity(),
                            _initial_state, _view_frustum,
                            _current_thread);
 
@@ -142,13 +144,17 @@ traverse(const NodePath &root) {
     // Render the frustum relative to the cull center.
     NodePath cull_center = _scene_setup->get_cull_center();
     CPT(TransformState) transform = cull_center.get_transform(root);
+    CPT(TransformState) prev_transform = cull_center.get_prev_transform(root);
 
     CullTraverserData my_data(data, portal_viewer._previous);
     my_data._net_transform = my_data._net_transform->compose(transform);
+    my_data._prev_net_transform = my_data._prev_net_transform->compose(prev_transform);
     traverse(my_data);
 
   } else {
-    CullTraverserData data(root, TransformState::make_identity(),
+    CullTraverserData data(root,
+                           TransformState::make_identity(),
+                           TransformState::make_identity(),
                            _initial_state, _view_frustum,
                            _current_thread);
 
diff --git a/panda/src/pgraph/cullTraverserData.I b/panda/src/pgraph/cullTraverserData.I
index 4367d16..7c1a79f 100644
--- a/panda/src/pgraph/cullTraverserData.I
+++ b/panda/src/pgraph/cullTraverserData.I
@@ -17,12 +17,14 @@
 INLINE CullTraverserData::
 CullTraverserData(const NodePath &start,
                   const TransformState *net_transform,
+                  const TransformState *prev_net_transform,
                   const RenderState *state,
                   GeometricBoundingVolume *view_frustum,
                   Thread *current_thread) :
   _node_path(start),
   _node_reader(start.node(), current_thread),
   _net_transform(net_transform),
+  _prev_net_transform(prev_net_transform),
   _state(state),
   _view_frustum(view_frustum),
   _cull_planes(CullPlanes::make_empty()),
@@ -42,6 +44,7 @@ CullTraverserData(const CullTraverserData &copy) :
   _node_path(copy._node_path),
   _node_reader(copy._node_reader),
   _net_transform(copy._net_transform),
+  _prev_net_transform(copy._prev_net_transform),
   _state(copy._state),
   _view_frustum(copy._view_frustum),
   _cull_planes(copy._cull_planes),
@@ -58,6 +61,7 @@ operator = (const CullTraverserData &copy) {
   _node_path = copy._node_path;
   _node_reader = copy._node_reader;
   _net_transform = copy._net_transform;
+  _prev_net_transform = copy._prev_net_transform;
   _state = copy._state;
   _view_frustum = copy._view_frustum;
   _cull_planes = copy._cull_planes;
@@ -74,6 +78,7 @@ CullTraverserData(const CullTraverserData &parent, PandaNode *child) :
   _node_path(parent._node_path, child),
   _node_reader(child, parent._node_reader.get_current_thread()),
   _net_transform(parent._net_transform),
+  _prev_net_transform(parent._prev_net_transform),
   _state(parent._state),
   _view_frustum(parent._view_frustum),
   _cull_planes(parent._cull_planes),
@@ -134,6 +139,14 @@ INLINE CPT(TransformState) CullTraverserData::
 get_internal_transform(const CullTraverser *trav) const {
   return trav->get_scene()->get_cs_world_transform()->compose(_net_transform);
 }
+/**
+ * Returns the previous internal transform: the modelview transform in the GSG's
+ * internal coordinate system in the last frame.
+ */
+INLINE CPT(TransformState) CullTraverserData::
+get_prev_internal_transform(const CullTraverser *trav) const {
+  return trav->get_scene()->get_prev_cs_world_transform()->compose(_prev_net_transform);
+}
 
 /**
  * Returns the net transform: the relative transform from root of the scene
@@ -145,6 +158,15 @@ get_net_transform(const CullTraverser *) const {
 }
 
 /**
+ * Returns the previous net transform: the relative transform from root of the scene
+ * graph to the current node in the last frame.
+ */
+INLINE const TransformState *CullTraverserData::
+get_prev_net_transform(const CullTraverser *) const {
+  return _prev_net_transform;
+}
+
+/**
  * Returns true if the current node is within the view frustum, false
  * otherwise.  If the node's bounding volume falls completely within the view
  * frustum, this will also reset the view frustum pointer, saving some work
diff --git a/panda/src/pgraph/cullTraverserData.cxx b/panda/src/pgraph/cullTraverserData.cxx
index 186869c..e67d45d 100644
--- a/panda/src/pgraph/cullTraverserData.cxx
+++ b/panda/src/pgraph/cullTraverserData.cxx
@@ -47,6 +47,7 @@ apply_transform_and_state(CullTraverser *trav) {
   _node_reader.compose_draw_mask(_draw_mask);
 
   apply_transform_and_state(trav, _node_reader.get_transform(),
+                            _node_reader.get_prev_transform(),
                             node_state, _node_reader.get_effects(),
                             _node_reader.get_off_clip_planes());
 }
@@ -58,6 +59,7 @@ apply_transform_and_state(CullTraverser *trav) {
 void CullTraverserData::
 apply_transform_and_state(CullTraverser *trav,
                           CPT(TransformState) node_transform,
+                          CPT(TransformState) prev_node_transform,
                           CPT(RenderState) node_state,
                           CPT(RenderEffects) node_effects,
                           const RenderAttrib *off_clip_planes) {
@@ -96,6 +98,12 @@ apply_transform_and_state(CullTraverser *trav,
     }
   }
 
+  if (_net_transform == _prev_net_transform && node_transform == prev_node_transform) {
+    _prev_net_transform = _net_transform;
+  } else if (!prev_node_transform->is_identity()) {
+    _prev_net_transform = _prev_net_transform->compose(prev_node_transform);
+  }
+
   _state = _state->compose(node_state);
 
   if (clip_plane_cull) {
diff --git a/panda/src/pgraph/cullTraverserData.h b/panda/src/pgraph/cullTraverserData.h
index 80d523f..30b8d68 100644
--- a/panda/src/pgraph/cullTraverserData.h
+++ b/panda/src/pgraph/cullTraverserData.h
@@ -41,6 +41,7 @@ class EXPCL_PANDA_PGRAPH CullTraverserData {
 public:
   INLINE CullTraverserData(const NodePath &start,
                            const TransformState *net_transform,
+                           const TransformState *prev_net_transform,
                            const RenderState *state,
                            GeometricBoundingVolume *view_frustum,
                            Thread *current_thread);
@@ -60,7 +61,9 @@ public:
 PUBLISHED:
   INLINE CPT(TransformState) get_modelview_transform(const CullTraverser *trav) const;
   INLINE CPT(TransformState) get_internal_transform(const CullTraverser *trav) const;
+  INLINE CPT(TransformState) get_prev_internal_transform(const CullTraverser *trav) const;
   INLINE const TransformState *get_net_transform(const CullTraverser *trav) const;
+  INLINE const TransformState *get_prev_net_transform(const CullTraverser *trav) const;
 
   INLINE bool is_in_view(const DrawMask &camera_mask);
   INLINE bool is_this_node_hidden(const DrawMask &camera_mask) const;
@@ -68,6 +71,7 @@ PUBLISHED:
   void apply_transform_and_state(CullTraverser *trav);
   void apply_transform_and_state(CullTraverser *trav,
                                  CPT(TransformState) node_transform,
+                                 CPT(TransformState) prev_node_transform,
                                  CPT(RenderState) node_state,
                                  CPT(RenderEffects) node_effects,
                                  const RenderAttrib *off_clip_planes);
@@ -76,6 +80,7 @@ public:
   WorkingNodePath _node_path;
   PandaNodePipelineReader _node_reader;
   CPT(TransformState) _net_transform;
+  CPT(TransformState) _prev_net_transform;
   CPT(RenderState) _state;
   PT(GeometricBoundingVolume) _view_frustum;
   CPT(CullPlanes) _cull_planes;
diff --git a/panda/src/pgraph/cullableObject.I b/panda/src/pgraph/cullableObject.I
index 2424965..5fbe70c 100644
--- a/panda/src/pgraph/cullableObject.I
+++ b/panda/src/pgraph/cullableObject.I
@@ -23,6 +23,24 @@ CullableObject() {
 
 /**
  * Creates a CullableObject based the indicated geom, with the indicated
+ * render state, transform and previous transform.
+ */
+INLINE CullableObject::
+CullableObject(const Geom *geom, const RenderState *state,
+               const TransformState *internal_transform,
+               const TransformState *prev_internal_transform) :
+  _geom(geom),
+  _state(state),
+  _internal_transform(internal_transform),
+  _prev_internal_transform(prev_internal_transform)
+{
+#ifdef DO_MEMORY_USAGE
+  MemoryUsage::update_type(this, get_class_type());
+#endif
+}
+
+/**
+ * Creates a CullableObject based the indicated geom, with the indicated
  * render state and transform.
  */
 INLINE CullableObject::
@@ -30,7 +48,8 @@ CullableObject(const Geom *geom, const RenderState *state,
                const TransformState *internal_transform) :
   _geom(geom),
   _state(state),
-  _internal_transform(internal_transform)
+  _internal_transform(internal_transform),
+  _prev_internal_transform(internal_transform)
 {
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::update_type(this, get_class_type());
@@ -46,7 +65,8 @@ CullableObject(const CullableObject &copy) :
   _munger(copy._munger),
   _munged_data(copy._munged_data),
   _state(copy._state),
-  _internal_transform(copy._internal_transform)
+  _internal_transform(copy._internal_transform),
+  _prev_internal_transform(copy._prev_internal_transform)
 {
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::update_type(this, get_class_type());
@@ -63,6 +83,7 @@ operator = (const CullableObject &copy) {
   _munged_data = copy._munged_data;
   _state = copy._state;
   _internal_transform = copy._internal_transform;
+  _prev_internal_transform = copy._prev_internal_transform;
   _draw_callback = copy._draw_callback;
 }
 
@@ -75,7 +96,7 @@ draw(GraphicsStateGuardianBase *gsg, bool force, Thread *current_thread) {
   if (_draw_callback != (CallbackObject *)NULL) {
     // It has a callback associated.
     gsg->clear_before_callback();
-    gsg->set_state_and_transform(_state, _internal_transform);
+    gsg->set_state_and_transform(_state, _internal_transform, _prev_internal_transform);
     GeomDrawCallbackData cbdata(this, gsg, force);
     _draw_callback->do_callback(&cbdata);
     if (cbdata.get_lost_state()) {
@@ -85,7 +106,7 @@ draw(GraphicsStateGuardianBase *gsg, bool force, Thread *current_thread) {
     // Now the callback has taken care of drawing.
   } else {
     nassertv(_geom != (Geom *)NULL);
-    gsg->set_state_and_transform(_state, _internal_transform);
+    gsg->set_state_and_transform(_state, _internal_transform, _prev_internal_transform);
     draw_inline(gsg, force, current_thread);
   }
 }
diff --git a/panda/src/pgraph/cullableObject.h b/panda/src/pgraph/cullableObject.h
index 82fd0df..f450cbc 100644
--- a/panda/src/pgraph/cullableObject.h
+++ b/panda/src/pgraph/cullableObject.h
@@ -49,6 +49,10 @@ public:
   INLINE CullableObject(const Geom *geom, const RenderState *state,
                         const TransformState *internal_transform);
 
+  INLINE CullableObject(const Geom *geom, const RenderState *state,
+                        const TransformState *internal_transform,
+                        const TransformState *prev_internal_transform);
+
   INLINE CullableObject(const CullableObject &copy);
   INLINE void operator = (const CullableObject &copy);
 
@@ -74,6 +78,7 @@ public:
   CPT(GeomVertexData) _munged_data;
   CPT(RenderState) _state;
   CPT(TransformState) _internal_transform;
+  CPT(TransformState) _prev_internal_transform;
   PT(CallbackObject) _draw_callback;
 
 private:
diff --git a/panda/src/pgraph/geomNode.cxx b/panda/src/pgraph/geomNode.cxx
index bfbd24f..5ea321e 100644
--- a/panda/src/pgraph/geomNode.cxx
+++ b/panda/src/pgraph/geomNode.cxx
@@ -512,7 +512,6 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
   Geoms geoms = get_geoms(trav->get_current_thread());
   int num_geoms = geoms.get_num_geoms();
   trav->_geoms_pcollector.add_level(num_geoms);
-  CPT(TransformState) internal_transform = data.get_internal_transform(trav);
 
   for (int i = 0; i < num_geoms; i++) {
     const Geom *geom = geoms.get_geom(i);
@@ -558,7 +557,9 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
     }
 
     CullableObject *object =
-      new CullableObject(geom, state, internal_transform);
+      new CullableObject(geom, state,
+        data.get_internal_transform(trav),
+        data.get_prev_internal_transform(trav));
     trav->get_cull_handler()->record_object(object, trav);
   }
 }
diff --git a/panda/src/pgraph/nodePath.cxx b/panda/src/pgraph/nodePath.cxx
index c6bfff1..ecb8936 100644
--- a/panda/src/pgraph/nodePath.cxx
+++ b/panda/src/pgraph/nodePath.cxx
@@ -906,7 +906,9 @@ void NodePath::
 set_pos(const LVecBase3 &pos) {
   nassertv_always(!is_empty());
   set_transform(get_transform()->set_pos(pos));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 void NodePath::
@@ -1173,7 +1175,10 @@ set_pos_hpr(const LVecBase3 &pos, const LVecBase3 &hpr) {
   transform = TransformState::make_pos_hpr_scale_shear
     (pos, hpr, transform->get_scale(), transform->get_shear());
   set_transform(transform);
-  node()->reset_prev_transform();
+
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1187,7 +1192,10 @@ set_pos_quat(const LVecBase3 &pos, const LQuaternion &quat) {
   transform = TransformState::make_pos_quat_scale_shear
     (pos, quat, transform->get_scale(), transform->get_shear());
   set_transform(transform);
-  node()->reset_prev_transform();
+
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1226,7 +1234,9 @@ set_pos_hpr_scale(const LVecBase3 &pos, const LVecBase3 &hpr,
   nassertv_always(!is_empty());
   set_transform(TransformState::make_pos_hpr_scale
                 (pos, hpr, scale));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1239,7 +1249,9 @@ set_pos_quat_scale(const LVecBase3 &pos, const LQuaternion &quat,
   nassertv_always(!is_empty());
   set_transform(TransformState::make_pos_quat_scale
                 (pos, quat, scale));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1252,7 +1264,9 @@ set_pos_hpr_scale_shear(const LVecBase3 &pos, const LVecBase3 &hpr,
   nassertv_always(!is_empty());
   set_transform(TransformState::make_pos_hpr_scale_shear
                 (pos, hpr, scale, shear));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1265,7 +1279,9 @@ set_pos_quat_scale_shear(const LVecBase3 &pos, const LQuaternion &quat,
   nassertv_always(!is_empty());
   set_transform(TransformState::make_pos_quat_scale_shear
                 (pos, quat, scale, shear));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1275,7 +1291,9 @@ void NodePath::
 set_mat(const LMatrix4 &mat) {
   nassertv_always(!is_empty());
   set_transform(TransformState::make_mat(mat));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1334,7 +1352,9 @@ set_pos(const NodePath &other, const LVecBase3 &pos) {
     // If we didn't have a componentwise transform already, never mind.
     set_transform(other, rel_transform->set_pos(pos));
   }
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 void NodePath::
@@ -1712,7 +1732,9 @@ set_pos_hpr(const NodePath &other, const LVecBase3 &pos,
     // If we didn't have a componentwise transform already, never mind.
     set_transform(other, TransformState::make_pos_hpr_scale_shear
                   (pos, hpr, rel_transform->get_scale(), rel_transform->get_shear()));
-    node()->reset_prev_transform();
+    if (!always_store_prev_transform) {
+      node()->reset_prev_transform();
+    }
   }
 }
 
@@ -1747,7 +1769,9 @@ set_pos_quat(const NodePath &other, const LVecBase3 &pos,
     // If we didn't have a componentwise transform already, never mind.
     set_transform(other, TransformState::make_pos_quat_scale_shear
                   (pos, quat, rel_transform->get_scale(), rel_transform->get_shear()));
-    node()->reset_prev_transform();
+    if (!always_store_prev_transform) {
+      node()->reset_prev_transform();
+    }
   }
 }
 
@@ -1797,7 +1821,9 @@ set_pos_hpr_scale(const NodePath &other,
   nassertv_always(!is_empty());
   set_transform(other, TransformState::make_pos_hpr_scale
                 (pos, hpr, scale));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1811,7 +1837,9 @@ set_pos_quat_scale(const NodePath &other,
   nassertv_always(!is_empty());
   set_transform(other, TransformState::make_pos_quat_scale
                 (pos, quat, scale));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1825,7 +1853,9 @@ set_pos_hpr_scale_shear(const NodePath &other,
   nassertv_always(!is_empty());
   set_transform(other, TransformState::make_pos_hpr_scale_shear
                 (pos, hpr, scale, shear));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1839,7 +1869,9 @@ set_pos_quat_scale_shear(const NodePath &other,
   nassertv_always(!is_empty());
   set_transform(other, TransformState::make_pos_quat_scale_shear
                 (pos, quat, scale, shear));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
@@ -1864,7 +1896,9 @@ void NodePath::
 set_mat(const NodePath &other, const LMatrix4 &mat) {
   nassertv_always(!is_empty());
   set_transform(other, TransformState::make_mat(mat));
-  node()->reset_prev_transform();
+  if (!always_store_prev_transform) {
+    node()->reset_prev_transform();
+  }
 }
 
 /**
diff --git a/panda/src/pgraph/nodePath.h b/panda/src/pgraph/nodePath.h
index 729bcf3..a9a8233 100644
--- a/panda/src/pgraph/nodePath.h
+++ b/panda/src/pgraph/nodePath.h
@@ -66,50 +66,73 @@ class ShaderInput;
  * A NodePath is the fundamental unit of high-level interaction with the scene
  * graph.  It encapsulates the complete path down to a node from some other
  * node, usually the root of the scene graph.  This is used to resolve
- * ambiguities associated with instancing.  NodePath also contains a number of
- * handy high-level methods for common scene-graph manipulations, such as
- * reparenting, and common state changes, such as repositioning.  There are
- * also a number of NodePath methods for finding nodes deep within the tree by
- * name or by type.  These take a path string, which at its simplest consists
- * of a series of node names separated by slashes, like a directory pathname.
+ * ambiguities associated with instancing.
+ *
+ * NodePath also contains a number of handy high-level methods for common
+ * scene-graph manipulations, such as  reparenting, and common state changes,
+ * such as repositioning.  There are also a number of NodePath methods for
+ * finding nodes deep within the tree by name or by type.  These take a path
+ * string, which at its simplest consists  of a series of node names separated
+ * by slashes, like a directory pathname.
+ *
  * Each component of the path string may optionally consist of one of the
- * following special names, instead of a node name: *          -- matches
- * exactly one node, with any name.  **         -- matches any sequence of
- * zero or more nodes.  +typename  -- matches any node that is or derives from
- * the given type.  -typename  -- matches any node that is the given type
- * exactly.  =tag       -- matches any node that has the indicated tag.
- * =tag=value -- matches any node whose tag matches the indicated value.
+ * following special names, instead of a node name:
+ *
+ *    *          -- matches exactly one node, with any name.
+ *    **         -- matches any sequence of zero or more nodes.
+ *    +typename  -- matches any node that is or derives from the given type.
+ *    -typename  -- matches any node that is the given type exactly.
+ *    =tag       -- matches any node that has the indicated tag.
+ *    =tag=value -- matches any node whose tag matches the indicated value.
+ *
  * Furthermore, a node name may itself contain standard filename globbing
  * characters, like *, ?, and [a-z], that will be accepted as a partial match.
  * (In fact, the '*' special name may be seen as just a special case of this.)
  * The globbing characters may not be used with the typename matches or with
  * tag matches, but they may be used to match a tag's value in the =tag=value
- * syntax.  The special characters "@@", appearing at the beginning of a node
+ * syntax.
+ *
+ * The special characters "@@", appearing at the beginning of a node
  * name, indicate a stashed node.  Normally, stashed nodes are not returned by
  * a find (but see the special flags, below), but a stashed node may be found
  * if it is explicitly named with its leading @@ characters.  By extension,
- * "@@*" may be used to identify any stashed node.  Examples: "roomgraph" will
- * look for a node named "graph", which is a child of an unnamed node, which
- * is a child of a node named "room", which is a child of the starting path.
- * "**red*" will look for any node anywhere in the tree (below the starting
- * path) with a name that begins with "red". "**+PartBundleNode**head" will
- * look for a node named "head", somewhere below a PartBundleNode anywhere in
- * the tree.  The search is always potentially ambiguous, even if the special
+ * "@@*" may be used to identify any stashed node.
+ *
+ * Examples:
+ *
+ *   "roomgraph" will look for a node named "graph", which is a child
+ *   of an unnamed node, which is a child of a node named "room", which is a
+ *   child of the starting path.
+ *
+ *   "**red*" will look for any node anywhere in the tree (below the starting
+ *   path) with a name that begins with "red".
+ *
+ *   "**+PartBundleNode**head" will  look for a node named "head", somewhere
+ *   below a PartBundleNode anywhere in the tree.
+ *
+ * The search is always potentially ambiguous, even if the special
  * wildcard operators are not used, because there may be multiple nodes in the
  * tree with the same name.  In general, in the case of an ambiguity, the
  * shortest path is preferred; when a method (such as extend_by) must choose
  * only only one of several possible paths, it will choose the shortest
  * available; on the other hand, when a method (such as find_all_matches) is
  * to return all of the matching paths, it will sort them so that the shortest
- * paths appear first in the output.  Special flags.  The entire string may
- * optionally be followed by the ";" character, followed by one or more of the
- * following special control flags, with no intervening spaces or punctuation:
- * -h    Do not return hidden nodes.  +h    Do return hidden nodes.  -s    Do
- * not return stashed nodes unless explicitly referenced with @@. +s    Return
- * stashed nodes even without any explicit @@ characters.  -i    Node name
- * comparisons are not case insensitive: case must match exactly.  +i    Node
- * name comparisons are case insensitive: case is not important.  This affects
- * matches against the node name only; node type and tag strings are always
+ * paths appear first in the output.
+ *
+ * Special flags:
+ *
+ * The entire string may optionally be followed by the ";" character, followed
+ * by one or more of the following special control flags, with no intervening
+ * spaces or punctuation:
+ *
+ *    -h   Do not return hidden nodes.
+ *    +h   Do return hidden nodes.
+ *    -s   Do not return stashed nodes unless explicitly referenced with @@.
+ *    +s   Return stashed nodes even without any explicit @@ characters.
+ *    -i   Node name comparisons are not case insensitive: case must match exactly.
+ *    +i   Node name comparisons are case insensitive: case is not important.
+ *
+ * This affects matches against the node name only; node type and tag strings are always
  * case sensitive.  The default flags are +h-s-i.
  */
 
diff --git a/panda/src/pgraph/occluderNode.cxx b/panda/src/pgraph/occluderNode.cxx
index c1a4c4d..83695fb 100644
--- a/panda/src/pgraph/occluderNode.cxx
+++ b/panda/src/pgraph/occluderNode.cxx
@@ -142,14 +142,16 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
   // draw a visualization, a checkerboard-textured polygon.
   CullableObject *occluder_viz =
     new CullableObject(get_occluder_viz(trav, data), get_occluder_viz_state(trav, data),
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(occluder_viz, trav);
 
   // Also get the frame.
   nassertr(_frame_viz != (Geom *)NULL, false);
   CullableObject *frame_viz =
     new CullableObject(_frame_viz, get_frame_viz_state(trav, data),
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(frame_viz, trav);
 
   // Now carry on to render our child nodes.
diff --git a/panda/src/pgraph/planeNode.cxx b/panda/src/pgraph/planeNode.cxx
index c096eae..a643862 100644
--- a/panda/src/pgraph/planeNode.cxx
+++ b/panda/src/pgraph/planeNode.cxx
@@ -141,7 +141,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
   CullableObject *plane_viz =
     new CullableObject(get_viz(trav, data), data._state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   trav->get_cull_handler()->record_object(plane_viz, trav);
 
   // Now carry on to render our child nodes.
diff --git a/panda/src/pgraph/portalNode.cxx b/panda/src/pgraph/portalNode.cxx
index d70eacd..3e360fd 100644
--- a/panda/src/pgraph/portalNode.cxx
+++ b/panda/src/pgraph/portalNode.cxx
@@ -228,6 +228,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
         // Get the net trasform of the _cell_out as seen from the camera.
         CPT(TransformState) cell_transform = _cell_out.get_net_transform();
+        CPT(TransformState) prev_cell_transform = _cell_out.get_net_prev_transform();
         CPT(TransformState) frustum_transform = cell_transform ->invert_compose(portal_viewer->_scene_setup->get_cull_center().get_net_transform());
 
         // transform to _cell_out space
@@ -283,6 +284,7 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
 
         CullTraverserData next_data(_cell_out,
                                     cell_transform,
+                                    prev_cell_transform,
                                     next_state, new_bh,
                                     current_thread);
         next_data._portal_depth = data._portal_depth + 1;
diff --git a/panda/src/pgraph/sceneSetup.I b/panda/src/pgraph/sceneSetup.I
index 425c538..1b150ff 100644
--- a/panda/src/pgraph/sceneSetup.I
+++ b/panda/src/pgraph/sceneSetup.I
@@ -25,6 +25,7 @@ SceneSetup() {
   _world_transform = TransformState::make_identity();
   _cs_transform = TransformState::make_identity();
   _cs_world_transform = TransformState::make_identity();
+  _prev_cs_world_transform = TransformState::make_identity();
 }
 
 /**
@@ -268,3 +269,21 @@ INLINE const TransformState *SceneSetup::
 get_cs_world_transform() const {
   return _cs_world_transform;
 }
+
+/**
+ * Specifies the position from the starting node relative to the camera, in
+ * the GSG's internal coordinate system.
+ */
+INLINE void SceneSetup::
+set_prev_cs_world_transform(const TransformState *prev_cs_world_transform) {
+  _prev_cs_world_transform = prev_cs_world_transform;
+}
+
+/**
+ * Returns the position from the starting node relative to the camera, in the
+ * GSG's internal coordinate system.
+ */
+INLINE const TransformState *SceneSetup::
+get_prev_cs_world_transform() const {
+  return _prev_cs_world_transform;
+}
diff --git a/panda/src/pgraph/sceneSetup.h b/panda/src/pgraph/sceneSetup.h
index d2ad0c0..72c0599 100644
--- a/panda/src/pgraph/sceneSetup.h
+++ b/panda/src/pgraph/sceneSetup.h
@@ -74,6 +74,9 @@ PUBLISHED:
   INLINE void set_cs_world_transform(const TransformState *cs_world_transform);
   INLINE const TransformState *get_cs_world_transform() const;
 
+  INLINE void set_prev_cs_world_transform(const TransformState *prev_cs_world_transform);
+  INLINE const TransformState *get_prev_cs_world_transform() const;
+
 private:
   DisplayRegion *_display_region;
   int _viewport_width;
@@ -88,6 +91,7 @@ private:
   CPT(TransformState) _world_transform;
   CPT(TransformState) _cs_transform;
   CPT(TransformState) _cs_world_transform;
+  CPT(TransformState) _prev_cs_world_transform;
 
 public:
   static TypeHandle get_class_type() {
diff --git a/panda/src/pgraphnodes/callbackNode.cxx b/panda/src/pgraphnodes/callbackNode.cxx
index 17424d5..2ced441 100644
--- a/panda/src/pgraphnodes/callbackNode.cxx
+++ b/panda/src/pgraphnodes/callbackNode.cxx
@@ -133,7 +133,8 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
   if (cbobj != (CallbackObject *)NULL) {
     CullableObject *object =
       new CullableObject(NULL, data._state,
-                         data.get_internal_transform(trav));
+                         data.get_internal_transform(trav),
+                         data.get_prev_internal_transform(trav));
     object->set_draw_callback(cbobj);
     trav->get_cull_handler()->record_object(object, trav);
   }
diff --git a/panda/src/pgraphnodes/computeNode.cxx b/panda/src/pgraphnodes/computeNode.cxx
index 7962378..2f40dd6 100644
--- a/panda/src/pgraphnodes/computeNode.cxx
+++ b/panda/src/pgraphnodes/computeNode.cxx
@@ -94,7 +94,8 @@ add_for_draw(CullTraverser *trav, CullTraverserData &data) {
   // however.
   CullableObject *object =
     new CullableObject(NULL, data._state,
-                       data.get_internal_transform(trav));
+                       data.get_internal_transform(trav),
+                       data.get_prev_internal_transform(trav));
   object->set_draw_callback(_dispatcher);
   trav->get_cull_handler()->record_object(object, trav);
 }
diff --git a/panda/src/pgraphnodes/lodNode.cxx b/panda/src/pgraphnodes/lodNode.cxx
index 5936b06..2d52b41 100644
--- a/panda/src/pgraphnodes/lodNode.cxx
+++ b/panda/src/pgraphnodes/lodNode.cxx
@@ -394,6 +394,7 @@ show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
         // And draw the spindle in this color.
         CullTraverserData next_data2(data, sw.get_spindle_viz());
         next_data2.apply_transform_and_state(trav, viz_transform,
+                                             TransformState::make_identity(),
                                              RenderState::make_empty(),
                                              RenderEffects::make_empty(),
                                              ClipPlaneAttrib::make());
@@ -404,6 +405,7 @@ show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
       // the geometry and the spindle.
       CullTraverserData next_data(data, sw.get_ring_viz());
       next_data.apply_transform_and_state(trav, viz_transform,
+                                          TransformState::make_identity(),
                                           RenderState::make_empty(),
                                           RenderEffects::make_empty(),
                                           ClipPlaneAttrib::make());
diff --git a/panda/src/pgraphnodes/nodeCullCallbackData.cxx b/panda/src/pgraphnodes/nodeCullCallbackData.cxx
index d0bf763..b89ac5f 100644
--- a/panda/src/pgraphnodes/nodeCullCallbackData.cxx
+++ b/panda/src/pgraphnodes/nodeCullCallbackData.cxx
@@ -50,7 +50,8 @@ upcall() {
     if (cbobj != (CallbackObject *)NULL) {
       CullableObject *object =
         new CullableObject(NULL, _data._state,
-                           _data.get_internal_transform(_trav));
+                           _data.get_internal_transform(_trav),
+                           _data.get_prev_internal_transform(_trav));
       object->set_draw_callback(cbobj);
       _trav->get_cull_handler()->record_object(object, _trav);
     }
diff --git a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
index ba33377..af0a608 100644
--- a/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
+++ b/panda/src/tinydisplay/tinyGraphicsStateGuardian.cxx
@@ -206,7 +206,7 @@ clear(DrawableRegion *clearable) {
     return;
   }
 
-  set_state_and_transform(RenderState::make_empty(), _internal_transform);
+  set_state_and_transform(RenderState::make_empty(), _internal_transform, _prev_internal_transform);
 
   bool clear_color = false;
   PIXEL color = 0;
@@ -1467,7 +1467,8 @@ framebuffer_copy_to_ram(Texture *tex, int view, int z,
  */
 void TinyGraphicsStateGuardian::
 set_state_and_transform(const RenderState *target,
-                        const TransformState *transform) {
+                        const TransformState *transform,
+                        const TransformState *prev_transform) {
 #ifndef NDEBUG
   if (tinydisplay_cat.is_spam()) {
     tinydisplay_cat.spam()
@@ -1480,6 +1481,8 @@ set_state_and_transform(const RenderState *target,
   _state_pcollector.add_level(1);
   PStatTimer timer1(_draw_set_state_pcollector);
 
+  _prev_internal_transform = prev_transform;
+
   if (transform != _internal_transform) {
     PStatTimer timer(_draw_set_state_transform_pcollector);
     _state_pcollector.add_level(1);
diff --git a/panda/src/tinydisplay/tinyGraphicsStateGuardian.h b/panda/src/tinydisplay/tinyGraphicsStateGuardian.h
index 65e01d7..69f4ed9 100644
--- a/panda/src/tinydisplay/tinyGraphicsStateGuardian.h
+++ b/panda/src/tinydisplay/tinyGraphicsStateGuardian.h
@@ -82,7 +82,8 @@ public:
   (Texture *tex, int view, int z, const DisplayRegion *dr, const RenderBuffer &rb);
 
   virtual void set_state_and_transform(const RenderState *state,
-                                       const TransformState *transform);
+                                       const TransformState *transform,
+                                       const TransformState *prev_transform);
 
   virtual TextureContext *prepare_texture(Texture *tex, int view);
   virtual bool update_texture(TextureContext *tc, bool force);
-- 
1.9.5.msysgit.0

