Просмотр исходного кода

support velocity for collisions

David Rose 23 лет назад
Родитель
Сommit
b39fa23b8e

+ 37 - 0
panda/src/collide/collisionEntry.I

@@ -156,6 +156,43 @@ get_inv_wrt_space() const {
   return _inv_wrt_space;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionEntry::set_from_velocity
+//       Access: Public
+//  Description: Indicates the velocity associated with the "from"
+//               object, in the object's coordinate space.
+////////////////////////////////////////////////////////////////////
+INLINE void CollisionEntry::
+set_from_velocity(const LVector3f &vel) {
+  _from_velocity = vel;
+  _flags |= F_has_from_velocity;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionEntry::has_from_velocity
+//       Access: Public
+//  Description: Returns true if the "from" object had an indicated
+//               velocity, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool CollisionEntry::
+has_from_velocity() const {
+  return (_flags & F_has_from_velocity) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionEntry::get_from_velocity
+//       Access: Public
+//  Description: Returns the instantaneous velocity of the "from"
+//               object, in its own coordinate space.  This represents
+//               the delta between its current position and its
+//               position last frame.
+////////////////////////////////////////////////////////////////////
+INLINE const LVector3f &CollisionEntry::
+get_from_velocity() const {
+  nassertr(has_from_velocity(), _from_velocity);
+  return _from_velocity;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionEntry::set_into_intersection_point
 //       Access: Public

+ 6 - 0
panda/src/collide/collisionEntry.h

@@ -63,6 +63,10 @@ PUBLISHED:
   INLINE const LMatrix4f &get_wrt_space() const;
   INLINE const LMatrix4f &get_inv_wrt_space() const;
 
+  INLINE void set_from_velocity(const LVector3f &vel);
+  INLINE bool has_from_velocity() const;
+  INLINE const LVector3f &get_from_velocity() const;
+
   INLINE void set_into_intersection_point(const LPoint3f &point);
   INLINE bool has_into_intersection_point() const;
   INLINE const LPoint3f &get_into_intersection_point() const;
@@ -94,10 +98,12 @@ private:
     F_has_into_intersection_point = 0x0001,
     F_has_into_surface_normal     = 0x0002,
     F_has_into_depth              = 0x0004,
+    F_has_from_velocity           = 0x0008,
   };
 
   int _flags;
 
+  LVector3f _from_velocity;
   LPoint3f _into_intersection_point;
   LVector3f _into_surface_normal;
   float _into_depth;

+ 1 - 1
panda/src/collide/collisionHandlerPusher.cxx

@@ -132,7 +132,7 @@ handle_entries() {
                 normal[2] = 0.0f;
               }
               // Just to be on the safe size, we normalize the normal
-              // vector, even though it really out to be unit-length
+              // vector, even though it really ought to be unit-length
               // already (unless we just forced it horizontal, above).
               normal.normalize();
 

+ 14 - 0
panda/src/collide/collisionLevelState.cxx

@@ -63,6 +63,20 @@ prepare_collider(const ColliderDef &def) {
   } else {
     GeometricBoundingVolume *gbv;
     DCAST_INTO_V(gbv, bv.make_copy());
+
+    if (def._node->has_velocity()) {
+      // If the node has a velocity, we have to include the starting
+      // point in the volume as well.
+
+      // Strictly speaking, we should actually transform gbv backward
+      // by get_velocity(), and extend by *that* volume, instead of
+      // just extending by the single point at -get_velocity().
+      // However, assuming the solids within a moving CollisionNode
+      // tend to be near the origin, this will generally produce the
+      // same results, and is much easier to compute.
+      gbv->extend_by(LPoint3f(-def._node->get_velocity()));
+    }
+
     gbv->xform(def._space);
     _local_bounds.push_back(gbv);
   }

+ 53 - 13
panda/src/collide/collisionNode.I

@@ -19,7 +19,7 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::set_collide_mask
-//       Access: Public
+//       Access: Published
 //  Description: Simultaneously sets both the "from" and "into"
 //               CollideMask values to the same thing.
 ////////////////////////////////////////////////////////////////////
@@ -31,7 +31,7 @@ set_collide_mask(CollideMask mask) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::set_from_collide_mask
-//       Access: Public
+//       Access: Published
 //  Description: Sets the "from" CollideMask.  In order for a
 //               collision to be detected from this object into
 //               another object, the intersection of this object's
@@ -45,7 +45,7 @@ set_from_collide_mask(CollideMask mask) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::set_into_collide_mask
-//       Access: Public
+//       Access: Published
 //  Description: Sets the "into" CollideMask.  In order for a
 //               collision to be detected from another object into
 //               this object, the intersection of the other object's
@@ -64,7 +64,7 @@ set_into_collide_mask(CollideMask mask) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::get_from_collide_mask
-//       Access: Public
+//       Access: Published
 //  Description: Returns the current "from" CollideMask.  In order for
 //               a collision to be detected from this object into
 //               another object, the intersection of this object's
@@ -78,7 +78,7 @@ get_from_collide_mask() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::get_into_collide_mask
-//       Access: Public
+//       Access: Published
 //  Description: Returns the current "into" CollideMask.  In order for
 //               a collision to be detected from another object into
 //               this object, the intersection of the other object's
@@ -92,7 +92,7 @@ get_into_collide_mask() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::set_collide_geom
-//       Access: Public
+//       Access: Published
 //  Description: Sets the state of the "collide geom" flag for this
 //               CollisionNode.  Normally, this is false; when this is
 //               set true, the CollisionSolids in this node will test
@@ -107,23 +107,27 @@ get_into_collide_mask() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void CollisionNode::
 set_collide_geom(bool flag) {
-  _collide_geom = flag;
+  if (flag) {
+    _flags |= F_collide_geom;
+  } else {
+    _flags &= ~F_collide_geom;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::get_collide_geom
-//       Access: Public
+//       Access: Published
 //  Description: Returns the current state of the collide_geom flag.
 //               See set_collide_geom().
 ////////////////////////////////////////////////////////////////////
 INLINE bool CollisionNode::
 get_collide_geom() const {
-  return _collide_geom;
+  return (_flags & F_collide_geom) != 0;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::get_num_solids
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE int CollisionNode::
@@ -133,7 +137,7 @@ get_num_solids() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::get_solid
-//       Access: Public
+//       Access: Published
 //  Description:
 ////////////////////////////////////////////////////////////////////
 INLINE CollisionSolid *CollisionNode::
@@ -144,7 +148,7 @@ get_solid(int n) const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::remove_solid
-//       Access: Public
+//       Access: Published
 //  Description: Removes the solid with the indicated index.  This
 //               will shift all subsequent indices down by one.
 ////////////////////////////////////////////////////////////////////
@@ -157,7 +161,7 @@ remove_solid(int n) {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::add_solid
-//       Access: Public
+//       Access: Published
 //  Description: Adds the indicated solid to the node.  Returns the
 //               index of the new solid within the node's list of
 //               solids.
@@ -168,3 +172,39 @@ add_solid(CollisionSolid *solid) {
   mark_bound_stale();
   return _solids.size() - 1;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionNode::clear_velocity
+//       Access: Published
+//  Description: Removes the velocity information associated with the
+//               node.  See set_velocity().
+////////////////////////////////////////////////////////////////////
+INLINE void CollisionNode::
+clear_velocity() {
+  _flags &= ~F_has_velocity;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionNode::has_velocity
+//       Access: Published
+//  Description: Returns true if the node has an associated velocity,
+//               false otherwise.  See set_velocity().
+////////////////////////////////////////////////////////////////////
+INLINE bool CollisionNode::
+has_velocity() const {
+  return (_flags & F_has_velocity) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionNode::get_velocity
+//       Access: Published
+//  Description: Returns the instantaneous velocity of the node, in
+//               its own coordinate space.  This represents the delta
+//               between its current position and its position last
+//               frame.  See set_velocity().
+////////////////////////////////////////////////////////////////////
+INLINE const LVector3f &CollisionNode::
+get_velocity() const {
+  nassertr(has_velocity(), _velocity);
+  return _velocity;
+}

+ 74 - 4
panda/src/collide/collisionNode.cxx

@@ -22,6 +22,10 @@
 #include "geomNode.h"
 #include "cullTraverserData.h"
 #include "cullTraverser.h"
+#include "renderState.h"
+#include "transformState.h"
+#include "colorScaleAttrib.h"
+#include "transparencyAttrib.h"
 #include "datagram.h"
 #include "datagramIterator.h"
 #include "bamReader.h"
@@ -40,7 +44,8 @@ CollisionNode(const string &name) :
   PandaNode(name),
   _from_collide_mask(CollideMask::all_on()),
   _into_collide_mask(CollideMask::all_on()),
-  _collide_geom(false)
+  _velocity(0.0f, 0.0f, 0.0f),
+  _flags(0)
 {
   // CollisionNodes are hidden by default.
   set_draw_mask(DrawMask::all_off());
@@ -56,7 +61,8 @@ CollisionNode(const CollisionNode &copy) :
   PandaNode(copy),
   _from_collide_mask(copy._from_collide_mask),
   _into_collide_mask(copy._into_collide_mask),
-  _collide_geom(copy._collide_geom),
+  _velocity(copy._velocity),
+  _flags(copy._flags),
   _solids(copy._solids)
 {
 }
@@ -212,6 +218,22 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
     trav->traverse(next_data);
   }
 
+  if (has_velocity()) {
+    // If we have a velocity, also draw the previous frame's position,
+    // ghosted.
+    CPT(TransformState) transform = TransformState::make_pos(-get_velocity());
+    for (si = _solids.begin(); si != _solids.end(); ++si) {
+      CollisionSolid *solid = (*si);
+      PandaNode *node = solid->get_viz();
+      CullTraverserData next_data(data, node);
+
+      next_data._render_transform = 
+        next_data._render_transform->compose(transform);
+      next_data._state = get_last_pos_state();
+      trav->traverse(next_data);
+    }
+  }
+
   // Now carry on to render our child nodes.
   return true;
 }
@@ -232,6 +254,31 @@ output(ostream &out) const {
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionNode::set_velocity
+//       Access: Published, Virtual
+//  Description: Indicates the instantaneous velocity of the node.
+//               This is only meaningful for nodes that represent
+//               "colliders", that is, nodes added to a
+//               CollisionTraverser to be tested for collision into
+//               other objects.
+//
+//               The velocity vector represents the delta from this
+//               node's position last frame to its current position.
+//               The collision traverser automatically clears it after
+//               it has performed the traversal.
+//
+//               This velocity information is optional and, if
+//               available, is used by the collision traverser to help
+//               determine which walls the collider is actually
+//               intersecting with, and which it is simply passing by.
+////////////////////////////////////////////////////////////////////
+void CollisionNode::
+set_velocity(const LVector3f &vel) {
+  _velocity = vel;
+  _flags |= F_has_velocity;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::recompute_bound
 //       Access: Protected, Virtual
@@ -293,6 +340,29 @@ recompute_internal_bound() {
   return bound;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionNode::get_last_pos_state
+//       Access: Protected
+//  Description: Returns a RenderState for rendering the ghosted
+//               collision solid that represents the previous frame's
+//               position, for those collision nodes that indicate a
+//               velocity.
+////////////////////////////////////////////////////////////////////
+CPT(RenderState) CollisionNode::
+get_last_pos_state() {
+  // Once someone asks for this pointer, we hold its reference count
+  // and never free it.
+  static CPT(RenderState) state = (const RenderState *)NULL;
+  if (state == (const RenderState *)NULL) {
+    state = RenderState::make
+      (ColorScaleAttrib::make(LVecBase4f(1.0f, 1.0f, 1.0f, 0.5f)),
+       TransparencyAttrib::make(TransparencyAttrib::M_alpha));
+  }
+
+  return state;
+}
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionNode::register_with_read_factory
 //       Access: Public, Static
@@ -322,7 +392,7 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 
   dg.add_uint32(_from_collide_mask.get_word());
   dg.add_uint32(_into_collide_mask.get_word());
-  dg.add_bool(_collide_geom);
+  dg.add_uint8(_flags);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -387,5 +457,5 @@ fillin(DatagramIterator &scan, BamReader *manager) {
 
   _from_collide_mask.set_word(scan.get_uint32());
   _into_collide_mask.set_word(scan.get_uint32());
-  _collide_geom = scan.get_bool();
+  _flags = scan.get_uint8();
 }

+ 15 - 1
panda/src/collide/collisionNode.h

@@ -68,17 +68,31 @@ PUBLISHED:
   INLINE void remove_solid(int n);
   INLINE int add_solid(CollisionSolid *solid);
 
+  virtual void set_velocity(const LVector3f &vel);
+  INLINE void clear_velocity();
+  INLINE bool has_velocity() const;
+  INLINE const LVector3f &get_velocity() const;
+
 protected:
   virtual BoundingVolume *recompute_bound();
   virtual BoundingVolume *recompute_internal_bound();
 
 private:
+  CPT(RenderState) get_last_pos_state();
+
   // This data is not cycled, for now.  We assume the collision
   // traversal will take place in App only.  Perhaps we will revisit
   // this later.
   CollideMask _from_collide_mask;
   CollideMask _into_collide_mask;
-  bool _collide_geom;
+  LVector3f _velocity;
+
+  enum Flags {
+    F_collide_geom = 0x0001,
+    F_has_velocity = 0x0002,
+    // Presently only 8 bits are written to the bam file.
+  };
+  int _flags;
 
   typedef pvector< PT(CollisionSolid) > Solids;
   Solids _solids;

+ 49 - 1
panda/src/collide/collisionPolygon.cxx

@@ -240,7 +240,48 @@ test_intersection_from_sphere(CollisionHandler *record,
   const CollisionSphere *sphere;
   DCAST_INTO_R(sphere, entry.get_from(), 0);
 
-  LPoint3f from_center = sphere->get_center() * entry.get_wrt_space();
+  LPoint3f orig_center = sphere->get_center() * entry.get_wrt_space();
+  LPoint3f from_center = orig_center;
+  bool moved_from_center = false;
+
+  if (entry.has_from_velocity()) {
+    // If we have a velocity indication, we use that to determine some
+    // more properties of the collision.
+    LPoint3f b = from_center;
+    LPoint3f a = (sphere->get_center() - entry.get_from_velocity()) * entry.get_wrt_space();
+
+    LVector3f delta = b - a;
+    // First, there is no collision if the "from" object is moving in
+    // the same direction as the plane's normal.
+    float dot = delta.dot(get_normal());
+    if (dot > 0.0f) {
+      return 0;
+    }
+
+    if (IS_NEARLY_ZERO(dot)) {
+      // If we're moving parallel to the plane, the sphere is tested
+      // at its final point.  Leave it as it is.
+
+    } else {
+      // Otherwise, we're moving into the plane; the sphere is tested
+      // at the point along its path that is closest to intersecting
+      // the plane.  This may be the actual intersection point, or it
+      // may be the starting point or the final point.
+      float t = -(dist_to_plane(a) / dot);
+      if (t >= 1.0f) {
+        // Leave it where it is.
+
+      } else if (t < 0.0f) {
+        from_center = a;
+        moved_from_center = true;
+
+      } else {
+        from_center = a + t * delta;
+        moved_from_center = true;
+      }
+    }
+  }
+
   LVector3f from_radius_v =
     LVector3f(sphere->get_radius(), 0.0f, 0.0f) * entry.get_wrt_space();
   float from_radius = length(from_radius_v);
@@ -325,9 +366,16 @@ test_intersection_from_sphere(CollisionHandler *record,
 
   LVector3f into_normal = get_normal() * entry.get_inv_wrt_space();
   float into_depth = from_radius - dist;
+  if (moved_from_center) {
+    // We have to base the depth of intersection on the sphere's final
+    // resting point, not the point from which we tested the
+    // intersection.
+    into_depth = from_radius - dist_to_plane(orig_center);
+  }
 
   new_entry->set_into_surface_normal(into_normal);
   new_entry->set_into_depth(into_depth);
+  new_entry->set_into_intersection_point(from_center);
 
   record->add_entry(new_entry);
   return 1;

+ 15 - 0
panda/src/collide/collisionTraverser.cxx

@@ -251,6 +251,17 @@ traverse(const NodePath &root) {
       ++hi;
     }
   }
+
+  if (auto_clear_velocity) {
+    // Clear all the velocities for next time.
+    OrderedColliders::iterator ci;
+    for (ci = _ordered_colliders.begin(); 
+         ci != _ordered_colliders.end(); 
+         ++ci) {
+      CollisionNode *node = (*ci);
+      node->clear_velocity();
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -373,6 +384,10 @@ r_traverse(CollisionLevelState &level_state) {
           entry._from = level_state.get_collider(c);
           entry._from_space = level_state.get_space(c);
 
+          if (entry._from_node->has_velocity()) {
+            entry.set_from_velocity(entry._from_node->get_velocity());
+          }
+
           NodePath root;
           const LMatrix4f &into_space_inv = 
             root.get_mat(entry._into_node_path);

+ 5 - 0
panda/src/collide/config_collide.cxx

@@ -40,6 +40,11 @@ ConfigureFn(config_collide) {
   init_libcollide();
 }
 
+// Set this false to stop the CollisionTraverser from automatically
+// clearing the velocity associated with its CollisionNodes after it
+// has traversed them.  Mainly useful for visualizing the velocities.
+const bool auto_clear_velocity = config_collide.GetBool("auto-clear-velocity", true);
+
 ////////////////////////////////////////////////////////////////////
 //     Function: init_libcollide
 //  Description: Initializes the library.  This must be called at

+ 2 - 0
panda/src/collide/config_collide.h

@@ -24,6 +24,8 @@
 
 NotifyCategoryDecl(collide, EXPCL_PANDA, EXPTP_PANDA);
 
+extern const bool auto_clear_velocity;
+
 extern EXPCL_PANDA void init_libcollide();
 
 #endif

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

@@ -1167,6 +1167,25 @@ as_light() {
   return NULL;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: PandaNode::set_velocity
+//       Access: Public, Virtual
+//  Description: Indicates the instantaneous velocity of this node.
+//               This function is meaningless to most kinds of nodes;
+//               it is implemented only for CollisionNodes and is
+//               intended to inform the collision system of velocity.
+//
+//               It is defined at this level only as an abstract
+//               interface to allow setting the velocity of a
+//               collision node without having to link with, or know
+//               anything about, the collision system.
+//
+//               See CollisionNode::set_velocity().
+////////////////////////////////////////////////////////////////////
+void PandaNode::
+set_velocity(const LVector3f &) {
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: PandaNode::propagate_stale_bound
 //       Access: Protected, Virtual

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

@@ -166,6 +166,7 @@ PUBLISHED:
 public:
   virtual bool is_geom_node() const;
   virtual Light *as_light();
+  virtual void set_velocity(const LVector3f &vel);
 
 protected:
   // Inherited from BoundedObject

+ 9 - 1
panda/src/tform/driveInterface.cxx

@@ -112,8 +112,10 @@ DriveInterface(const string &name) :
   _button_events_input = define_input("button_events", ButtonEventList::get_class_type());
 
   _transform_output = define_output("transform", EventStoreMat4::get_class_type());
+  _velocity_output = define_output("velocity", EventStoreVec3::get_class_type());
 
   _transform = new EventStoreMat4(LMatrix4f::ident_mat());
+  _velocity = new EventStoreVec3(LVector3f::zero());
 
   _forward_speed = drive_forward_speed;
   _reverse_speed = drive_reverse_speed;
@@ -239,10 +241,12 @@ get_mat() const {
 void DriveInterface::
 force_dgraph() {
   _transform->set_value(_mat);
+  _velocity->set_value(_vel);
 
   DataNodeTransmit output;
   output.reserve(get_num_outputs());
   output.set_data(_transform_output, EventParameter(_transform));
+  output.set_data(_velocity_output, EventParameter(_velocity));
 
   DataGraphTraverser dg_trav;
   dg_trav.traverse_below(this, output);
@@ -356,6 +360,7 @@ apply(double x, double y, bool any_button) {
   }
 
   if (_speed == 0.0f && _rot_speed == 0.0f) {
+    _vel.set(0.0f, 0.0f, 0.0f);
     return;
   }
 
@@ -371,7 +376,8 @@ apply(double x, double y, bool any_button) {
     LMatrix3f::rotate_mat_normaxis(_hpr[0], LVector3f::up(_cs), _cs);
 
   // Take a step in the direction of our previous heading.
-  LVector3f step = distance * (LVector3f::forward(_cs) * rot_mat);
+  _vel = LVector3f::forward(_cs) * distance;
+  LVector3f step = (_vel * rot_mat);
 
   // To prevent upward drift due to numerical errors, force the
   // vertical component of our step to zero (it should be pretty near
@@ -503,5 +509,7 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &output) {
 
   apply(x, y, _mods.is_any_down());
   _transform->set_value(_mat);
+  _velocity->set_value(_vel);
   output.set_data(_transform_output, EventParameter(_transform));
+  output.set_data(_velocity_output, EventParameter(_velocity));
 }

+ 3 - 0
panda/src/tform/driveInterface.h

@@ -133,6 +133,7 @@ private:
   LPoint3f _xyz;
   LVecBase3f _hpr;
   LMatrix4f _mat;
+  LVector3f _vel;
   float _force_roll;
   bool _is_force_roll;
   CoordinateSystem _cs;
@@ -173,8 +174,10 @@ private:
 
   // outputs
   int _transform_output;
+  int _velocity_output;
 
   PT(EventStoreMat4) _transform;
+  PT(EventStoreVec3) _velocity;
 
 public:
   static TypeHandle get_class_type() {

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

@@ -32,14 +32,16 @@ Transform2SG(const string &name) :
   DataNode(name)
 {
   _transform_input = define_input("transform", EventStoreMat4::get_class_type());
+  _velocity_input = define_input("velocity", EventStoreVec3::get_class_type());
 
   _node = NULL;
+  _velocity_node = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Transform2SG::set_node
 //       Access: Public
-//  Description: Sets the node that this node will adjust.
+//  Description: Sets the node that this object will adjust.
 ////////////////////////////////////////////////////////////////////
 void Transform2SG::
 set_node(PandaNode *node) {
@@ -49,7 +51,7 @@ set_node(PandaNode *node) {
 ////////////////////////////////////////////////////////////////////
 //     Function: Transform2SG::get_node
 //       Access: Public
-//  Description: Returns the node that this node will adjust, or NULL
+//  Description: Returns the node that this object will adjust, or NULL
 //               if the node has not yet been set.
 ////////////////////////////////////////////////////////////////////
 PandaNode *Transform2SG::
@@ -57,6 +59,33 @@ get_node() const {
   return _node;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: Transform2SG::set_velocity_node
+//       Access: Public
+//  Description: Sets the node that this object will assign the
+//               computed velocity to.  Normally this is a
+//               CollisionNode parented below the node indicated by
+//               set_node().  Setting this node allows the collision
+//               system to track the velocity imparted to the
+//               CollisionNode by the data graph object that set its
+//               transform, if that data is available.
+////////////////////////////////////////////////////////////////////
+void Transform2SG::
+set_velocity_node(PandaNode *node) {
+  _velocity_node = node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Transform2SG::get_velocity_node
+//       Access: Public
+//  Description: Returns the node that this object will assign the
+//               computed velocity to.  See set_velocity_node().
+////////////////////////////////////////////////////////////////////
+PandaNode *Transform2SG::
+get_velocity_node() const {
+  return _velocity_node;
+}
+
 
 ////////////////////////////////////////////////////////////////////
 //     Function: Transform2SG::do_transmit_data
@@ -81,4 +110,13 @@ do_transmit_data(const DataNodeTransmit &input, DataNodeTransmit &) {
       _node->set_transform(TransformState::make_mat(mat));
     }
   }
+
+  if (input.has_data(_velocity_input)) {
+    const EventStoreVec3 *velocity;
+    DCAST_INTO_V(velocity, input.get_data(_velocity_input).get_ptr());
+    LVector3f vel = velocity->get_value();
+    if (_velocity_node != (PandaNode *)NULL) {
+      _velocity_node->set_velocity(vel);
+    }
+  }
 }

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

@@ -38,8 +38,12 @@ PUBLISHED:
   void set_node(PandaNode *node);
   PandaNode *get_node() const;
 
+  void set_velocity_node(PandaNode *node);
+  PandaNode *get_velocity_node() const;
+
 private:
   PandaNode *_node;
+  PandaNode *_velocity_node;
 
 protected:
   // Inherited from DataNode
@@ -49,6 +53,7 @@ protected:
 private:
   // inputs
   int _transform_input;
+  int _velocity_input;
 
 public:
   static TypeHandle get_class_type() {