Browse Source

pgraph collisions, textures

David Rose 24 years ago
parent
commit
e46050a69e
72 changed files with 4754 additions and 356 deletions
  1. 6 4
      direct/src/extensions/NodePath-extensions.py
  2. 14 0
      direct/src/ffi/FFIRename.py
  3. 39 8
      panda/src/collide/Sources.pp
  4. 8 0
      panda/src/collide/collide_composite1.cxx
  5. 0 1
      panda/src/collide/collisionLevelState.N
  6. 103 9
      panda/src/collide/collisionPlane.cxx
  7. 11 0
      panda/src/collide/collisionPlane.h
  8. 231 9
      panda/src/collide/collisionPolygon.cxx
  9. 15 0
      panda/src/collide/collisionPolygon.h
  10. 14 0
      panda/src/collide/collisionRay.I
  11. 53 6
      panda/src/collide/collisionRay.cxx
  12. 10 1
      panda/src/collide/collisionRay.h
  13. 14 0
      panda/src/collide/collisionSegment.I
  14. 49 6
      panda/src/collide/collisionSegment.cxx
  15. 10 1
      panda/src/collide/collisionSegment.h
  16. 39 0
      panda/src/collide/collisionSolid.cxx
  17. 24 8
      panda/src/collide/collisionSolid.h
  18. 161 8
      panda/src/collide/collisionSphere.cxx
  19. 14 0
      panda/src/collide/collisionSphere.h
  20. 14 0
      panda/src/collide/config_collide.cxx
  21. 298 0
      panda/src/collide/qpcollisionEntry.I
  22. 66 0
      panda/src/collide/qpcollisionEntry.cxx
  23. 130 0
      panda/src/collide/qpcollisionEntry.h
  24. 61 0
      panda/src/collide/qpcollisionHandler.cxx
  25. 68 0
      panda/src/collide/qpcollisionHandler.h
  26. 162 0
      panda/src/collide/qpcollisionHandlerEvent.I
  27. 213 0
      panda/src/collide/qpcollisionHandlerEvent.cxx
  28. 102 0
      panda/src/collide/qpcollisionHandlerEvent.h
  29. 67 0
      panda/src/collide/qpcollisionHandlerFloor.I
  30. 143 0
      panda/src/collide/qpcollisionHandlerFloor.cxx
  31. 77 0
      panda/src/collide/qpcollisionHandlerFloor.h
  32. 52 0
      panda/src/collide/qpcollisionHandlerPhysical.I
  33. 197 0
      panda/src/collide/qpcollisionHandlerPhysical.cxx
  34. 102 0
      panda/src/collide/qpcollisionHandlerPhysical.h
  35. 38 0
      panda/src/collide/qpcollisionHandlerPusher.I
  36. 208 0
      panda/src/collide/qpcollisionHandlerPusher.cxx
  37. 71 0
      panda/src/collide/qpcollisionHandlerPusher.h
  38. 144 0
      panda/src/collide/qpcollisionHandlerQueue.cxx
  39. 76 0
      panda/src/collide/qpcollisionHandlerQueue.h
  40. 220 0
      panda/src/collide/qpcollisionLevelState.I
  41. 100 0
      panda/src/collide/qpcollisionLevelState.cxx
  42. 98 0
      panda/src/collide/qpcollisionLevelState.h
  43. 47 54
      panda/src/collide/qpcollisionTraverser.cxx
  44. 15 12
      panda/src/collide/qpcollisionTraverser.h
  45. 3 5
      panda/src/egg2pg/qpeggLoader.cxx
  46. 3 3
      panda/src/egg2sg/eggLoader.cxx
  47. 87 32
      panda/src/gobj/imageBuffer.I
  48. 14 6
      panda/src/gobj/imageBuffer.cxx
  49. 22 21
      panda/src/gobj/imageBuffer.h
  50. 9 8
      panda/src/gobj/pixelBuffer.cxx
  51. 3 2
      panda/src/gobj/pixelBuffer.h
  52. 48 18
      panda/src/gobj/texture.cxx
  53. 3 3
      panda/src/gobj/texture.h
  54. 2 2
      panda/src/gobj/texturePool.cxx
  55. 9 3
      panda/src/pgraph/Sources.pp
  56. 1 0
      panda/src/pgraph/pandaNode.h
  57. 2 0
      panda/src/pgraph/pgraph_composite2.cxx
  58. 78 30
      panda/src/pgraph/qpnodePath.I
  59. 261 81
      panda/src/pgraph/qpnodePath.cxx
  60. 24 1
      panda/src/pgraph/qpnodePath.h
  61. 9 11
      panda/src/pgraph/qpnodePathCollection.cxx
  62. 1 1
      panda/src/pgraph/qpnodePathCollection.h
  63. 23 1
      panda/src/pgraph/qpnodePathComponent.I
  64. 4 0
      panda/src/pgraph/qpnodePathComponent.cxx
  65. 4 0
      panda/src/pgraph/qpnodePathComponent.h
  66. 27 0
      panda/src/pgraph/textureCollection.I
  67. 282 0
      panda/src/pgraph/textureCollection.cxx
  68. 67 0
      panda/src/pgraph/textureCollection.h
  69. 70 0
      panda/src/pgraph/workingNodePath.I
  70. 41 0
      panda/src/pgraph/workingNodePath.cxx
  71. 71 0
      panda/src/pgraph/workingNodePath.h
  72. 2 1
      panda/src/putil/bam.h

+ 6 - 4
direct/src/extensions/NodePath-extensions.py

@@ -6,10 +6,12 @@
 
 
     def id(self):
     def id(self):
         """Returns a unique id identifying the NodePath instance"""
         """Returns a unique id identifying the NodePath instance"""
-        # Nowadays, the NodePath itself serves as that unique id.
-        # This function is therefore deprecated and should be removed
-        # soon.
-        return self
+        try:
+            # Old style scene graph
+            return self.arc()
+        except:
+            # New style scene graph
+            return self.getKey()
 
 
     def getName(self):
     def getName(self):
         """Returns the name of the bottom node if it exists, or <noname>"""
         """Returns the name of the bottom node if it exists, or <noname>"""

+ 14 - 0
direct/src/ffi/FFIRename.py

@@ -101,6 +101,13 @@ pgraphClassRenameDictionary = {
     'CardMaker'                 : 'SpCardMaker',
     'CardMaker'                 : 'SpCardMaker',
     'ChanConfig'                : 'SpChanConfig',
     'ChanConfig'                : 'SpChanConfig',
     'Character'                 : 'SpCharacter',
     'Character'                 : 'SpCharacter',
+    'CollisionEntry'            : 'SpCollisionEntry',
+    'CollisionHandler'          : 'SpCollisionHandler',
+    'CollisionHandlerEvent'     : 'SpCollisionHandlerEvent',
+    'CollisionHandlerFloor'     : 'SpCollisionHandlerFloor',
+    'CollisionHandlerPhysical'  : 'SpCollisionHandlerPhysical',
+    'CollisionHandlerPusher'    : 'SpCollisionHandlerPusher',
+    'CollisionHandlerQueue'     : 'SpCollisionHandlerQueue',
     'CollisionNode'             : 'SpCollisionNode',
     'CollisionNode'             : 'SpCollisionNode',
     'CollisionTraverser'        : 'SpCollisionTraverser',
     'CollisionTraverser'        : 'SpCollisionTraverser',
     'DataGraphTraverser'        : 'SpDataGraphTraverser',
     'DataGraphTraverser'        : 'SpDataGraphTraverser',
@@ -140,6 +147,13 @@ pgraphClassRenameDictionary = {
     'QpCardMaker'               : 'CardMaker',
     'QpCardMaker'               : 'CardMaker',
     'QpChanConfig'              : 'ChanConfig',
     'QpChanConfig'              : 'ChanConfig',
     'QpCharacter'               : 'Character',
     'QpCharacter'               : 'Character',
+    'QpCollisionEntry'          : 'CollisionEntry',
+    'QpCollisionHandler'        : 'CollisionHandler',
+    'QpCollisionHandlerEvent'   : 'CollisionHandlerEvent',
+    'QpCollisionHandlerFloor'   : 'CollisionHandlerFloor',
+    'QpCollisionHandlerPhysical': 'CollisionHandlerPhysical',
+    'QpCollisionHandlerPusher'  : 'CollisionHandlerPusher',
+    'QpCollisionHandlerQueue'   : 'CollisionHandlerQueue',
     'QpCollisionNode'           : 'CollisionNode',
     'QpCollisionNode'           : 'CollisionNode',
     'QpCollisionTraverser'      : 'CollisionTraverser',
     'QpCollisionTraverser'      : 'CollisionTraverser',
     'QpDataGraphTraverser'      : 'DataGraphTraverser',
     'QpDataGraphTraverser'      : 'DataGraphTraverser',

+ 39 - 8
panda/src/collide/Sources.pp

@@ -10,13 +10,22 @@
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
 
 
   #define SOURCES \
   #define SOURCES \
-    collisionEntry.I collisionEntry.h collisionHandler.h  \
+    collisionEntry.I collisionEntry.h \
+    qpcollisionEntry.I qpcollisionEntry.h \
+    collisionHandler.h  \
+    qpcollisionHandler.h  \
     collisionHandlerEvent.I collisionHandlerEvent.h  \
     collisionHandlerEvent.I collisionHandlerEvent.h  \
+    qpcollisionHandlerEvent.I qpcollisionHandlerEvent.h  \
     collisionHandlerFloor.I collisionHandlerFloor.h  \
     collisionHandlerFloor.I collisionHandlerFloor.h  \
+    qpcollisionHandlerFloor.I qpcollisionHandlerFloor.h  \
     collisionHandlerPhysical.I collisionHandlerPhysical.h  \
     collisionHandlerPhysical.I collisionHandlerPhysical.h  \
+    qpcollisionHandlerPhysical.I qpcollisionHandlerPhysical.h  \
     collisionHandlerPusher.I collisionHandlerPusher.h  \
     collisionHandlerPusher.I collisionHandlerPusher.h  \
-    collisionHandlerQueue.h collisionLevelState.I  \
-    collisionLevelState.N collisionLevelState.h \
+    qpcollisionHandlerPusher.I qpcollisionHandlerPusher.h  \
+    collisionHandlerQueue.h \
+    qpcollisionHandlerQueue.h \
+    collisionLevelState.I collisionLevelState.h \
+    qpcollisionLevelState.I qpcollisionLevelState.h \
     collisionNode.I collisionNode.h \
     collisionNode.I collisionNode.h \
     qpcollisionNode.I qpcollisionNode.h \
     qpcollisionNode.I qpcollisionNode.h \
     collisionPlane.I collisionPlane.h  \
     collisionPlane.I collisionPlane.h  \
@@ -29,10 +38,22 @@
     config_collide.h
     config_collide.h
     
     
  #define INCLUDED_SOURCES \
  #define INCLUDED_SOURCES \
-    collisionEntry.cxx collisionHandler.cxx collisionHandlerEvent.cxx  \
-    collisionHandlerFloor.cxx collisionHandlerPhysical.cxx  \
-    collisionHandlerPusher.cxx collisionHandlerQueue.cxx  \
+    collisionEntry.cxx \
+    qpcollisionEntry.cxx \
+    collisionHandler.cxx \
+    qpcollisionHandler.cxx \
+    collisionHandlerEvent.cxx  \
+    qpcollisionHandlerEvent.cxx  \
+    collisionHandlerFloor.cxx \
+    qpcollisionHandlerFloor.cxx \
+    collisionHandlerPhysical.cxx  \
+    qpcollisionHandlerPhysical.cxx  \
+    collisionHandlerPusher.cxx \
+    qpcollisionHandlerPusher.cxx \
+    collisionHandlerQueue.cxx  \
+    qpcollisionHandlerQueue.cxx  \
     collisionLevelState.cxx \
     collisionLevelState.cxx \
+    qpcollisionLevelState.cxx \
     collisionNode.cxx \
     collisionNode.cxx \
     qpcollisionNode.cxx \
     qpcollisionNode.cxx \
     collisionPlane.cxx  \
     collisionPlane.cxx  \
@@ -43,12 +64,22 @@
     config_collide.cxx 
     config_collide.cxx 
 
 
   #define INSTALL_HEADERS \
   #define INSTALL_HEADERS \
-    collisionEntry.I collisionEntry.h collisionHandler.h \
+    collisionEntry.I collisionEntry.h \
+    qpcollisionEntry.I qpcollisionEntry.h \
+    collisionHandler.h \
+    qpcollisionHandler.h \
     collisionHandlerEvent.I collisionHandlerEvent.h \
     collisionHandlerEvent.I collisionHandlerEvent.h \
+    qpcollisionHandlerEvent.I qpcollisionHandlerEvent.h \
     collisionHandlerFloor.I collisionHandlerFloor.h \
     collisionHandlerFloor.I collisionHandlerFloor.h \
+    qpcollisionHandlerFloor.I qpcollisionHandlerFloor.h \
     collisionHandlerPhysical.I collisionHandlerPhysical.h \
     collisionHandlerPhysical.I collisionHandlerPhysical.h \
+    qpcollisionHandlerPhysical.I qpcollisionHandlerPhysical.h \
     collisionHandlerPusher.I collisionHandlerPusher.h \
     collisionHandlerPusher.I collisionHandlerPusher.h \
-    collisionHandlerQueue.h collisionLevelState.I collisionLevelState.h \
+    qpcollisionHandlerPusher.I qpcollisionHandlerPusher.h \
+    collisionHandlerQueue.h \
+    qpcollisionHandlerQueue.h \
+    collisionLevelState.I collisionLevelState.h \
+    qpcollisionLevelState.I qpcollisionLevelState.h \
     collisionNode.I collisionNode.h \
     collisionNode.I collisionNode.h \
     qpcollisionNode.I qpcollisionNode.h \
     qpcollisionNode.I qpcollisionNode.h \
     collisionPlane.I collisionPlane.h \
     collisionPlane.I collisionPlane.h \

+ 8 - 0
panda/src/collide/collide_composite1.cxx

@@ -1,13 +1,21 @@
 
 
 #include "config_collide.cxx"
 #include "config_collide.cxx"
 #include "collisionEntry.cxx"
 #include "collisionEntry.cxx"
+#include "qpcollisionEntry.cxx"
 #include "collisionHandler.cxx"
 #include "collisionHandler.cxx"
+#include "qpcollisionHandler.cxx"
 #include "collisionHandlerEvent.cxx"
 #include "collisionHandlerEvent.cxx"
+#include "qpcollisionHandlerEvent.cxx"
 #include "collisionHandlerFloor.cxx"
 #include "collisionHandlerFloor.cxx"
+#include "qpcollisionHandlerFloor.cxx"
 #include "collisionHandlerPhysical.cxx"
 #include "collisionHandlerPhysical.cxx"
+#include "qpcollisionHandlerPhysical.cxx"
 #include "collisionHandlerPusher.cxx"
 #include "collisionHandlerPusher.cxx"
+#include "qpcollisionHandlerPusher.cxx"
 #include "collisionHandlerQueue.cxx"
 #include "collisionHandlerQueue.cxx"
+#include "qpcollisionHandlerQueue.cxx"
 #include "collisionLevelState.cxx"
 #include "collisionLevelState.cxx"
+#include "qpcollisionLevelState.cxx"
 #include "collisionNode.cxx"
 #include "collisionNode.cxx"
 #include "qpcollisionNode.cxx"
 #include "qpcollisionNode.cxx"
 
 

+ 0 - 1
panda/src/collide/collisionLevelState.N

@@ -1 +0,0 @@
-ignoreinvolved ColliderDef

+ 103 - 9
panda/src/collide/collisionPlane.cxx

@@ -19,21 +19,23 @@
 
 
 #include "collisionPlane.h"
 #include "collisionPlane.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "collisionSphere.h"
 #include "collisionSphere.h"
 #include "collisionRay.h"
 #include "collisionRay.h"
 #include "config_collide.h"
 #include "config_collide.h"
 
 
-#include <pointerToArray.h>
-#include <geomNode.h>
-#include <geom.h>
-#include <datagram.h>
-#include <datagramIterator.h>
-#include <bamReader.h>
-#include <bamWriter.h>
+#include "pointerToArray.h"
+#include "geomNode.h"
+#include "geom.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "bamReader.h"
+#include "bamWriter.h"
 
 
-#include <omniBoundingVolume.h>
-#include <geomQuad.h>
+#include "omniBoundingVolume.h"
+#include "geomQuad.h"
 
 
 TypeHandle CollisionPlane::_type_handle;
 TypeHandle CollisionPlane::_type_handle;
 
 
@@ -61,6 +63,20 @@ test_intersection(CollisionHandler *, const CollisionEntry &,
   return 0;
   return 0;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPlane::test_intersection
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPlane::
+test_intersection(qpCollisionHandler *, const qpCollisionEntry &,
+                  const CollisionSolid *) const {
+  // Planes cannot currently be intersected from, only into.  Do not
+  // add a CollisionPlane to a CollisionTraverser.
+  nassertr(false, 0);
+  return 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionPlane::xform
 //     Function: CollisionPlane::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -191,6 +207,84 @@ test_intersection_from_ray(CollisionHandler *record,
   return 1;
   return 1;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPlane::test_intersection_from_sphere
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPlane::
+test_intersection_from_sphere(qpCollisionHandler *record,
+                              const qpCollisionEntry &entry) const {
+  const CollisionSphere *sphere;
+  DCAST_INTO_R(sphere, entry.get_from(), 0);
+
+  LPoint3f from_center = sphere->get_center() * entry.get_wrt_space();
+  LVector3f from_radius_v =
+    LVector3f(sphere->get_radius(), 0.0f, 0.0f) * entry.get_wrt_space();
+  float from_radius = length(from_radius_v);
+
+  float dist = dist_to_plane(from_center);
+  if (dist > from_radius) {
+    // No intersection.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  LVector3f into_normal = get_normal() * entry.get_inv_wrt_space();
+  float into_depth = from_radius - dist;
+
+  new_entry->set_into_surface_normal(into_normal);
+  new_entry->set_into_depth(into_depth);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPlane::test_intersection_from_ray
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPlane::
+test_intersection_from_ray(qpCollisionHandler *record,
+                           const qpCollisionEntry &entry) const {
+  const CollisionRay *ray;
+  DCAST_INTO_R(ray, entry.get_from(), 0);
+
+  LPoint3f from_origin = ray->get_origin() * entry.get_wrt_space();
+  LVector3f from_direction = ray->get_direction() * entry.get_wrt_space();
+
+  float t;
+  if (!_plane.intersects_line(t, from_origin, from_direction)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (t < 0.0f) {
+    // The intersection point is before the start of the ray.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  LPoint3f into_intersection_point = from_origin + t * from_direction;
+  new_entry->set_into_intersection_point(into_intersection_point);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionPlane::recompute_viz
 //     Function: CollisionPlane::recompute_viz
 //       Access: Public, Virtual
 //       Access: Public, Virtual

+ 11 - 0
panda/src/collide/collisionPlane.h

@@ -46,6 +46,11 @@ public:
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const;
                     const CollisionSolid *into) const;
 
 
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const;
+
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual LPoint3f get_collision_origin() const;
   virtual LPoint3f get_collision_origin() const;
 
 
@@ -68,6 +73,12 @@ protected:
   virtual int
   virtual int
   test_intersection_from_ray(CollisionHandler *record,
   test_intersection_from_ray(CollisionHandler *record,
                              const CollisionEntry &entry) const;
                              const CollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_sphere(qpCollisionHandler *record,
+                                const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_ray(qpCollisionHandler *record,
+                             const qpCollisionEntry &entry) const;
 
 
   virtual void recompute_viz(Node *parent);
   virtual void recompute_viz(Node *parent);
 
 

+ 231 - 9
panda/src/collide/collisionPolygon.cxx

@@ -19,23 +19,25 @@
 
 
 #include "collisionPolygon.h"
 #include "collisionPolygon.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "collisionSphere.h"
 #include "collisionSphere.h"
 #include "collisionRay.h"
 #include "collisionRay.h"
 #include "collisionSegment.h"
 #include "collisionSegment.h"
 #include "config_collide.h"
 #include "config_collide.h"
 
 
-#include <boundingSphere.h>
-#include <pointerToArray.h>
-#include <geomNode.h>
-#include <geom.h>
-#include <datagram.h>
-#include <datagramIterator.h>
-#include <bamReader.h>
-#include <bamWriter.h>
+#include "boundingSphere.h"
+#include "pointerToArray.h"
+#include "geomNode.h"
+#include "geom.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "bamReader.h"
+#include "bamWriter.h"
+#include "geomPolygon.h"
 
 
 #include <algorithm>
 #include <algorithm>
-#include <geomPolygon.h>
 
 
 TypeHandle CollisionPolygon::_type_handle;
 TypeHandle CollisionPolygon::_type_handle;
 
 
@@ -134,6 +136,20 @@ test_intersection(CollisionHandler *, const CollisionEntry &,
   return 0;
   return 0;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPolygon::test_intersection
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPolygon::
+test_intersection(qpCollisionHandler *, const qpCollisionEntry &,
+                  const CollisionSolid *into) const {
+  // Polygons cannot currently be intersected from, only into.  Do not
+  // add a CollisionPolygon to a CollisionTraverser.
+  nassertr(false, 0);
+  return 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionPolygon::xform
 //     Function: CollisionPolygon::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -431,6 +447,212 @@ test_intersection_from_segment(CollisionHandler *record,
   return 1;
   return 1;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPolygon::test_intersection_from_sphere
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPolygon::
+test_intersection_from_sphere(qpCollisionHandler *record,
+                              const qpCollisionEntry &entry) const {
+  if (_points.size() < 3) {
+    return 0;
+  }
+
+  const CollisionSphere *sphere;
+  DCAST_INTO_R(sphere, entry.get_from(), 0);
+
+  LPoint3f from_center = sphere->get_center() * entry.get_wrt_space();
+  LVector3f from_radius_v =
+    LVector3f(sphere->get_radius(), 0.0f, 0.0f) * entry.get_wrt_space();
+  float from_radius = length(from_radius_v);
+
+  float dist = dist_to_plane(from_center);
+  if (dist > from_radius || dist < -from_radius) {
+    // No intersection.
+    return 0;
+  }
+
+  // Ok, we intersected the plane, but did we intersect the polygon?
+
+  // The nearest point within the plane to our center is the
+  // intersection of the line (center, center+normal) with the plane.
+  LPoint3f plane_point;
+  bool really_intersects =
+    get_plane().intersects_line(plane_point,
+                                from_center, from_center + get_normal());
+  nassertr(really_intersects, 0);
+
+  LPoint2f p = to_2d(plane_point);
+
+  // Now we have found a point on the polygon's plane that corresponds
+  // to the point tangent to our collision sphere where it first
+  // touches the plane.  We want to decide whether the sphere itself
+  // will intersect the polygon.  We can approximate this by testing
+  // whether a circle of the given radius centered around this tangent
+  // point, in the plane of the polygon, would intersect.
+
+  // But even this approximate test is too difficult.  To approximate
+  // the approximation, we'll test two points: (1) the center itself.
+  // If this is inside the polygon, then certainly the circle
+  // intersects the polygon, and the sphere collides.  (2) a point on
+  // the outside of the circle, nearest to the center of the polygon.
+  // If _this_ point is inside the polygon, then again the circle, and
+  // hence the sphere, intersects.  If neither point is inside the
+  // polygon, chances are reasonably good the sphere doesn't intersect
+  // the polygon after all.
+
+  if (is_inside(p)) {
+    // The circle's center is inside the polygon; we have a collision!
+
+  } else {
+
+    if (from_radius > 0.0f) {
+      // Now find the point on the rim of the circle nearest the
+      // polygon's center.
+
+      // First, get a vector from the center of the circle to the center
+      // of the polygon.
+      LVector2f rim = _median - p;
+      float rim_length = length(rim);
+
+      if (rim_length <= from_radius) {
+        // Here's a surprise: the center of the polygon is within the
+        // circle!  Since the center is guaranteed to be interior to the
+        // polygon (the polygon is convex), it follows that the circle
+        // intersects the polygon.
+
+      } else {
+        // Now scale this vector to length radius, and get the new point.
+        rim = (rim * from_radius / rim_length) + p;
+
+        // Is the new point within the polygon?
+        if (is_inside(rim)) {
+          // It sure is!  The circle intersects!
+
+        } else {
+          // No intersection.
+          return 0;
+        }
+      }
+    }
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  LVector3f into_normal = get_normal() * entry.get_inv_wrt_space();
+  float into_depth = from_radius - dist;
+
+  new_entry->set_into_surface_normal(into_normal);
+  new_entry->set_into_depth(into_depth);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPolygon::test_intersection_from_ray
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPolygon::
+test_intersection_from_ray(qpCollisionHandler *record,
+                           const qpCollisionEntry &entry) const {
+  if (_points.size() < 3) {
+    return 0;
+  }
+
+  const CollisionRay *ray;
+  DCAST_INTO_R(ray, entry.get_from(), 0);
+
+  LPoint3f from_origin = ray->get_origin() * entry.get_wrt_space();
+  LVector3f from_direction = ray->get_direction() * entry.get_wrt_space();
+
+  float t;
+  if (!get_plane().intersects_line(t, from_origin, from_direction)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (t < 0.0f) {
+    // The intersection point is before the start of the ray.
+    return 0;
+  }
+
+  LPoint3f plane_point = from_origin + t * from_direction;
+  if (!is_inside(to_2d(plane_point))) {
+    // Outside the polygon's perimeter.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  new_entry->set_into_intersection_point(plane_point);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionPolygon::test_intersection_from_segment
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionPolygon::
+test_intersection_from_segment(qpCollisionHandler *record,
+                               const qpCollisionEntry &entry) const {
+  if (_points.size() < 3) {
+    return 0;
+  }
+
+  const CollisionSegment *segment;
+  DCAST_INTO_R(segment, entry.get_from(), 0);
+
+  LPoint3f from_a = segment->get_point_a() * entry.get_wrt_space();
+  LPoint3f from_b = segment->get_point_b() * entry.get_wrt_space();
+  LPoint3f from_direction = from_b - from_a;
+
+  float t;
+  if (!get_plane().intersects_line(t, from_a, from_direction)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (t < 0.0f || t > 1.0f) {
+    // The intersection point is before the start of the segment or
+    // after the end of the segment.
+    return 0;
+  }
+
+  LPoint3f plane_point = from_a + t * from_direction;
+  if (!is_inside(to_2d(plane_point))) {
+    // Outside the polygon's perimeter.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  new_entry->set_into_intersection_point(plane_point);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionPolygon::recompute_viz
 //     Function: CollisionPolygon::recompute_viz
 //       Access: Public, Virtual
 //       Access: Public, Virtual

+ 15 - 0
panda/src/collide/collisionPolygon.h

@@ -54,6 +54,11 @@ public:
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const;
                     const CollisionSolid *into) const;
 
 
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const;
+
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual LPoint3f get_collision_origin() const;
   virtual LPoint3f get_collision_origin() const;
 
 
@@ -74,6 +79,16 @@ protected:
   test_intersection_from_segment(CollisionHandler *record,
   test_intersection_from_segment(CollisionHandler *record,
                                  const CollisionEntry &entry) const;
                                  const CollisionEntry &entry) const;
 
 
+  virtual int
+  test_intersection_from_sphere(qpCollisionHandler *record,
+                                const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_ray(qpCollisionHandler *record,
+                             const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_segment(qpCollisionHandler *record,
+                                 const qpCollisionEntry &entry) const;
+
   virtual void recompute_viz(Node *parent);
   virtual void recompute_viz(Node *parent);
 
 
 private:
 private:

+ 14 - 0
panda/src/collide/collisionRay.I

@@ -147,3 +147,17 @@ INLINE bool CollisionRay::
 set_from_lens(LensNode *camera, float px, float py) {
 set_from_lens(LensNode *camera, float px, float py) {
   return set_from_lens(camera, LPoint2f(px, py));
   return set_from_lens(camera, LPoint2f(px, py));
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionRay::set_from_lens
+//       Access: Public
+//  Description: Accepts a qpLensNode and a 2-d point in the range
+//               [-1,1].  Sets the CollisionRay so that it begins at
+//               the qpLensNode's near plane and extends to
+//               infinity, making it suitable for picking objects from
+//               the screen given a camera and a mouse location.
+////////////////////////////////////////////////////////////////////
+INLINE bool CollisionRay::
+set_from_lens(qpLensNode *camera, float px, float py) {
+  return set_from_lens(camera, LPoint2f(px, py));
+}

+ 53 - 6
panda/src/collide/collisionRay.cxx

@@ -18,14 +18,17 @@
 
 
 #include "collisionRay.h"
 #include "collisionRay.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "config_collide.h"
 #include "config_collide.h"
-#include <geom.h>
-#include <geomNode.h>
-#include <geomLinestrip.h>
-#include <boundingLine.h>
-#include <lensNode.h>
-#include <lens.h>
+#include "geom.h"
+#include "geomNode.h"
+#include "geomLinestrip.h"
+#include "boundingLine.h"
+#include "lensNode.h"
+#include "qplensNode.h"
+#include "lens.h"
 
 
 TypeHandle CollisionRay::_type_handle;
 TypeHandle CollisionRay::_type_handle;
 
 
@@ -51,6 +54,17 @@ test_intersection(CollisionHandler *record, const CollisionEntry &entry,
   return into->test_intersection_from_ray(record, entry);
   return into->test_intersection_from_ray(record, entry);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionRay::test_intersection
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionRay::
+test_intersection(qpCollisionHandler *record, const qpCollisionEntry &entry,
+                  const CollisionSolid *into) const {
+  return into->test_intersection_from_ray(record, entry);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionRay::xform
 //     Function: CollisionRay::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -120,6 +134,39 @@ set_from_lens(LensNode *camera, const LPoint2f &point) {
   return success;
   return success;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionRay::set_from_lens
+//       Access: Public
+//  Description: Accepts a qpLensNode and a 2-d point in the range
+//               [-1,1].  Sets the CollisionRay so that it begins at
+//               the qpLensNode's near plane and extends to
+//               infinity, making it suitable for picking objects from
+//               the screen given a camera and a mouse location.
+//
+//               Returns true if the point was acceptable, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool CollisionRay::
+set_from_lens(qpLensNode *camera, const LPoint2f &point) {
+  Lens *lens = camera->get_lens();
+
+  bool success = true;
+  LPoint3f near_point, far_point;
+  if (!lens->extrude(point, near_point, far_point)) {
+    _origin = LPoint3f::origin();
+    _direction = LVector3f::forward();
+    success = false;
+  } else {
+    _origin = near_point;
+    _direction = far_point - near_point;
+  }
+
+  mark_bound_stale();
+  mark_viz_stale();
+
+  return success;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionRay::recompute_bound
 //     Function: CollisionRay::recompute_bound
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual

+ 10 - 1
panda/src/collide/collisionRay.h

@@ -19,11 +19,12 @@
 #ifndef COLLISIONRAY_H
 #ifndef COLLISIONRAY_H
 #define COLLISIONRAY_H
 #define COLLISIONRAY_H
 
 
-#include <pandabase.h>
+#include "pandabase.h"
 
 
 #include "collisionSolid.h"
 #include "collisionSolid.h"
 
 
 class LensNode;
 class LensNode;
+class qpLensNode;
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
 //       Class : CollisionRay
 //       Class : CollisionRay
@@ -49,6 +50,11 @@ public:
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const;
                     const CollisionSolid *into) const;
 
 
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const;
+
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual LPoint3f get_collision_origin() const;
   virtual LPoint3f get_collision_origin() const;
 
 
@@ -66,6 +72,9 @@ PUBLISHED:
   bool set_from_lens(LensNode *camera, const LPoint2f &point);
   bool set_from_lens(LensNode *camera, const LPoint2f &point);
   INLINE bool set_from_lens(LensNode *camera, float px, float py);
   INLINE bool set_from_lens(LensNode *camera, float px, float py);
 
 
+  bool set_from_lens(qpLensNode *camera, const LPoint2f &point);
+  INLINE bool set_from_lens(qpLensNode *camera, float px, float py);
+
 protected:
 protected:
   virtual BoundingVolume *recompute_bound();
   virtual BoundingVolume *recompute_bound();
 
 

+ 14 - 0
panda/src/collide/collisionSegment.I

@@ -150,3 +150,17 @@ INLINE bool CollisionSegment::
 set_from_lens(LensNode *camera, float px, float py) {
 set_from_lens(LensNode *camera, float px, float py) {
   return set_from_lens(camera, LPoint2f(px, py));
   return set_from_lens(camera, LPoint2f(px, py));
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSegment::set_from_lens
+//       Access: Public
+//  Description: Accepts a qpLensNode and a 2-d point in the range
+//               [-1,1].  Sets the CollisionSegment so that it begins at
+//               the qpLensNode's near plane and extends to the
+//               far plane, making it suitable for picking objects
+//               from the screen given a camera and a mouse location.
+////////////////////////////////////////////////////////////////////
+INLINE bool CollisionSegment::
+set_from_lens(qpLensNode *camera, float px, float py) {
+  return set_from_lens(camera, LPoint2f(px, py));
+}

+ 49 - 6
panda/src/collide/collisionSegment.cxx

@@ -19,16 +19,19 @@
 
 
 #include "collisionSegment.h"
 #include "collisionSegment.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "config_collide.h"
 #include "config_collide.h"
 
 
-#include <geom.h>
-#include <geomNode.h>
-#include <lensNode.h>
-#include <lens.h>
+#include "geom.h"
+#include "geomNode.h"
+#include "lensNode.h"
+#include "qplensNode.h"
+#include "lens.h"
 
 
-#include <geomLine.h>
-#include <geometricBoundingVolume.h>
+#include "geomLine.h"
+#include "geometricBoundingVolume.h"
 
 
 TypeHandle CollisionSegment::_type_handle;
 TypeHandle CollisionSegment::_type_handle;
 
 
@@ -54,6 +57,17 @@ test_intersection(CollisionHandler *record, const CollisionEntry &entry,
   return into->test_intersection_from_segment(record, entry);
   return into->test_intersection_from_segment(record, entry);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSegment::test_intersection
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSegment::
+test_intersection(qpCollisionHandler *record, const qpCollisionEntry &entry,
+                  const CollisionSolid *into) const {
+  return into->test_intersection_from_segment(record, entry);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionSegment::xform
 //     Function: CollisionSegment::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -119,6 +133,35 @@ set_from_lens(LensNode *camera, const LPoint2f &point) {
   return success;
   return success;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSegment::set_from_lens
+//       Access: Public
+//  Description: Accepts a qpLensNode and a 2-d point in the range
+//               [-1,1].  Sets the CollisionSegment so that it begins at
+//               the qpLensNode's near plane and extends to the
+//               far plane, making it suitable for picking objects
+//               from the screen given a camera and a mouse location.
+//
+//               Returns true if the point was acceptable, false
+//               otherwise.
+////////////////////////////////////////////////////////////////////
+bool CollisionSegment::
+set_from_lens(qpLensNode *camera, const LPoint2f &point) {
+  Lens *proj = camera->get_lens();
+
+  bool success = true;
+  if (!proj->extrude(point, _a, _b)) {
+    _a = LPoint3f::origin();
+    _b = _a + LVector3f::forward();
+    success = false;
+  }
+
+  mark_bound_stale();
+  mark_viz_stale();
+
+  return success;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionSegment::recompute_bound
 //     Function: CollisionSegment::recompute_bound
 //       Access: Protected, Virtual
 //       Access: Protected, Virtual

+ 10 - 1
panda/src/collide/collisionSegment.h

@@ -19,11 +19,12 @@
 #ifndef COLLISIONSEGMENT_H
 #ifndef COLLISIONSEGMENT_H
 #define COLLISIONSEGMENT_H
 #define COLLISIONSEGMENT_H
 
 
-#include <pandabase.h>
+#include "pandabase.h"
 
 
 #include "collisionSolid.h"
 #include "collisionSolid.h"
 
 
 class LensNode;
 class LensNode;
+class qpLensNode;
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
 //       Class : CollisionSegment
 //       Class : CollisionSegment
@@ -52,6 +53,11 @@ public:
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const;
                     const CollisionSolid *into) const;
 
 
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const;
+
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual LPoint3f get_collision_origin() const;
   virtual LPoint3f get_collision_origin() const;
 
 
@@ -69,6 +75,9 @@ PUBLISHED:
   bool set_from_lens(LensNode *camera, const LPoint2f &point);
   bool set_from_lens(LensNode *camera, const LPoint2f &point);
   INLINE bool set_from_lens(LensNode *camera, float px, float py);
   INLINE bool set_from_lens(LensNode *camera, float px, float py);
 
 
+  bool set_from_lens(qpLensNode *camera, const LPoint2f &point);
+  INLINE bool set_from_lens(qpLensNode *camera, float px, float py);
+
 protected:
 protected:
   virtual BoundingVolume *recompute_bound();
   virtual BoundingVolume *recompute_bound();
 
 

+ 39 - 0
panda/src/collide/collisionSolid.cxx

@@ -148,6 +148,45 @@ test_intersection_from_segment(CollisionHandler *,
   return 0;
   return 0;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSolid::test_intersection_from_sphere
+//       Access: Protected, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSolid::
+test_intersection_from_sphere(qpCollisionHandler *,
+                              const qpCollisionEntry &) const {
+  report_undefined_intersection_test(CollisionSphere::get_class_type(),
+                                     get_type());
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSolid::test_intersection_from_ray
+//       Access: Protected, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSolid::
+test_intersection_from_ray(qpCollisionHandler *,
+                           const qpCollisionEntry &) const {
+  report_undefined_intersection_test(CollisionRay::get_class_type(),
+                                     get_type());
+  return 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSolid::test_intersection_from_segment
+//       Access: Protected, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSolid::
+test_intersection_from_segment(qpCollisionHandler *,
+                               const qpCollisionEntry &) const {
+  report_undefined_intersection_test(CollisionSegment::get_class_type(),
+                                     get_type());
+  return 0;
+}
+
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionSolid::report_undefined_intersection_test
 //     Function: CollisionSolid::report_undefined_intersection_test

+ 24 - 8
panda/src/collide/collisionSolid.h

@@ -19,22 +19,25 @@
 #ifndef COLLISIONSOLID_H
 #ifndef COLLISIONSOLID_H
 #define COLLISIONSOLID_H
 #define COLLISIONSOLID_H
 
 
-#include <pandabase.h>
+#include "pandabase.h"
 
 
-#include <typedWritableReferenceCount.h>
-#include <boundedObject.h>
-#include <luse.h>
-#include <nodeRelation.h>
-#include <pointerTo.h>
-#include <node.h>
-#include <vector_PT_NodeRelation.h>
+#include "typedWritableReferenceCount.h"
+#include "boundedObject.h"
+#include "luse.h"
+#include "nodeRelation.h"
+#include "pointerTo.h"
+#include "node.h"
+#include "vector_PT_NodeRelation.h"
 
 
 class CollisionHandler;
 class CollisionHandler;
+class qpCollisionHandler;
 class CollisionEntry;
 class CollisionEntry;
+class qpCollisionEntry;
 class CollisionSphere;
 class CollisionSphere;
 class Node;
 class Node;
 class GeomNode;
 class GeomNode;
 class CollisionNode;
 class CollisionNode;
+class qpCollisionNode;
 
 
 ///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////////////////////////
 //       Class : CollisionSolid
 //       Class : CollisionSolid
@@ -68,6 +71,10 @@ public:
   test_intersection(CollisionHandler *record,
   test_intersection(CollisionHandler *record,
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const=0;
                     const CollisionSolid *into) const=0;
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const=0;
 
 
   virtual void xform(const LMatrix4f &mat)=0;
   virtual void xform(const LMatrix4f &mat)=0;
 
 
@@ -87,6 +94,15 @@ protected:
   virtual int
   virtual int
   test_intersection_from_segment(CollisionHandler *record,
   test_intersection_from_segment(CollisionHandler *record,
                                  const CollisionEntry &entry) const;
                                  const CollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_sphere(qpCollisionHandler *record,
+                                const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_ray(qpCollisionHandler *record,
+                             const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_segment(qpCollisionHandler *record,
+                                 const qpCollisionEntry &entry) const;
 
 
   static void
   static void
   report_undefined_intersection_test(TypeHandle from_type,
   report_undefined_intersection_test(TypeHandle from_type,

+ 161 - 8
panda/src/collide/collisionSphere.cxx

@@ -21,18 +21,20 @@
 #include "collisionRay.h"
 #include "collisionRay.h"
 #include "collisionSegment.h"
 #include "collisionSegment.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "config_collide.h"
 #include "config_collide.h"
 
 
-#include <boundingSphere.h>
-#include <geomNode.h>
-#include <datagram.h>
-#include <datagramIterator.h>
-#include <bamReader.h>
-#include <bamWriter.h>
+#include "boundingSphere.h"
+#include "geomNode.h"
+#include "datagram.h"
+#include "datagramIterator.h"
+#include "bamReader.h"
+#include "bamWriter.h"
 
 
-#include <geomSphere.h>
-#include <nearly_zero.h>
+#include "geomSphere.h"
+#include "nearly_zero.h"
 
 
 TypeHandle CollisionSphere::_type_handle;
 TypeHandle CollisionSphere::_type_handle;
 
 
@@ -57,6 +59,17 @@ test_intersection(CollisionHandler *record, const CollisionEntry &entry,
   return into->test_intersection_from_sphere(record, entry);
   return into->test_intersection_from_sphere(record, entry);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSphere::test_intersection
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSphere::
+test_intersection(qpCollisionHandler *record, const qpCollisionEntry &entry,
+                  const CollisionSolid *into) const {
+  return into->test_intersection_from_sphere(record, entry);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionSphere::xform
 //     Function: CollisionSphere::xform
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -254,6 +267,146 @@ test_intersection_from_segment(CollisionHandler *record,
   return 1;
   return 1;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSphere::test_intersection_from_sphere
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSphere::
+test_intersection_from_sphere(qpCollisionHandler *record,
+                              const qpCollisionEntry &entry) const {
+  const CollisionSphere *sphere;
+  DCAST_INTO_R(sphere, entry.get_from(), 0);
+
+  LPoint3f from_center = sphere->get_center() * entry.get_wrt_space();
+  LVector3f from_radius_v =
+    LVector3f(sphere->get_radius(), 0.0f, 0.0f) * entry.get_wrt_space();
+  float from_radius = length(from_radius_v);
+
+  LPoint3f into_center = _center;
+  float into_radius = _radius;
+
+  LVector3f vec = from_center - into_center;
+  float dist2 = dot(vec, vec);
+  if (dist2 > (into_radius + from_radius) * (into_radius + from_radius)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  float dist = sqrtf(dist2);
+  LVector3f into_normal = normalize(vec);
+  LPoint3f into_intersection_point = into_normal * (dist - from_radius);
+  float into_depth = into_radius + from_radius - dist;
+
+  new_entry->set_into_surface_normal(into_normal * entry.get_inv_wrt_space());
+  new_entry->set_into_intersection_point(into_intersection_point * entry.get_inv_wrt_space());
+  new_entry->set_into_depth(into_depth);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSphere::test_intersection_from_ray
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSphere::
+test_intersection_from_ray(qpCollisionHandler *record,
+                           const qpCollisionEntry &entry) const {
+  const CollisionRay *ray;
+  DCAST_INTO_R(ray, entry.get_from(), 0);
+
+  LPoint3f from_origin = ray->get_origin() * entry.get_wrt_space();
+  LVector3f from_direction = ray->get_direction() * entry.get_wrt_space();
+
+  double t1, t2;
+  if (!intersects_line(t1, t2, from_origin, from_direction)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (t2 < 0.0) {
+    // Both intersection points are before the start of the ray.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  LPoint3f into_intersection_point;
+  if (t1 < 0.0) {
+    into_intersection_point = from_origin + t2 * from_direction;
+  } else {
+    into_intersection_point = from_origin + t1 * from_direction;
+  }
+  new_entry->set_into_intersection_point(into_intersection_point);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CollisionSphere::test_intersection_from_segment
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+int CollisionSphere::
+test_intersection_from_segment(qpCollisionHandler *record,
+                               const qpCollisionEntry &entry) const {
+  const CollisionSegment *segment;
+  DCAST_INTO_R(segment, entry.get_from(), 0);
+
+  LPoint3f from_a = segment->get_point_a() * entry.get_wrt_space();
+  LPoint3f from_b = segment->get_point_b() * entry.get_wrt_space();
+  LVector3f from_direction = from_b - from_a;
+
+  double t1, t2;
+  if (!intersects_line(t1, t2, from_a, from_direction)) {
+    // No intersection.
+    return 0;
+  }
+
+  if (t2 < 0.0 || t1 > 1.0) {
+    // Both intersection points are before the start of the segment or
+    // after the end of the segment.
+    return 0;
+  }
+
+  if (collide_cat.is_debug()) {
+    collide_cat.debug()
+      << "intersection detected from " << *entry.get_from_node() << " into "
+      << entry.get_into_node_path() << "\n";
+  }
+  PT(qpCollisionEntry) new_entry = new qpCollisionEntry(entry);
+
+  LPoint3f into_intersection_point;
+  if (t1 < 0.0) {
+    // Point a is within the sphere.  The first intersection point is
+    // point a itself.
+    into_intersection_point = from_a;
+  } else {
+    // Point a is outside the sphere, and point b is either inside the
+    // sphere or beyond it.  The first intersection point is at t1.
+    into_intersection_point = from_a + t1 * from_direction;
+  }
+  new_entry->set_into_intersection_point(into_intersection_point);
+
+  record->add_entry(new_entry);
+  return 1;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: CollisionSphere::recompute_viz
 //     Function: CollisionSphere::recompute_viz
 //       Access: Public, Virtual
 //       Access: Public, Virtual

+ 14 - 0
panda/src/collide/collisionSphere.h

@@ -41,6 +41,11 @@ public:
                     const CollisionEntry &entry,
                     const CollisionEntry &entry,
                     const CollisionSolid *into) const;
                     const CollisionSolid *into) const;
 
 
+  virtual int
+  test_intersection(qpCollisionHandler *record,
+                    const qpCollisionEntry &entry,
+                    const CollisionSolid *into) const;
+
   virtual void xform(const LMatrix4f &mat);
   virtual void xform(const LMatrix4f &mat);
   virtual LPoint3f get_collision_origin() const;
   virtual LPoint3f get_collision_origin() const;
 
 
@@ -68,6 +73,15 @@ protected:
   virtual int
   virtual int
   test_intersection_from_segment(CollisionHandler *record,
   test_intersection_from_segment(CollisionHandler *record,
                                  const CollisionEntry &entry) const;
                                  const CollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_sphere(qpCollisionHandler *record,
+                                const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_ray(qpCollisionHandler *record,
+                             const qpCollisionEntry &entry) const;
+  virtual int
+  test_intersection_from_segment(qpCollisionHandler *record,
+                                 const qpCollisionEntry &entry) const;
 
 
   virtual void recompute_viz(Node *parent);
   virtual void recompute_viz(Node *parent);
 
 

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

@@ -18,12 +18,19 @@
 
 
 #include "config_collide.h"
 #include "config_collide.h"
 #include "collisionEntry.h"
 #include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "collisionHandler.h"
 #include "collisionHandler.h"
+#include "qpcollisionHandler.h"
 #include "collisionHandlerEvent.h"
 #include "collisionHandlerEvent.h"
+#include "qpcollisionHandlerEvent.h"
 #include "collisionHandlerFloor.h"
 #include "collisionHandlerFloor.h"
+#include "qpcollisionHandlerFloor.h"
 #include "collisionHandlerPhysical.h"
 #include "collisionHandlerPhysical.h"
+#include "qpcollisionHandlerPhysical.h"
 #include "collisionHandlerPusher.h"
 #include "collisionHandlerPusher.h"
+#include "qpcollisionHandlerPusher.h"
 #include "collisionHandlerQueue.h"
 #include "collisionHandlerQueue.h"
+#include "qpcollisionHandlerQueue.h"
 #include "collisionNode.h"
 #include "collisionNode.h"
 #include "qpcollisionNode.h"
 #include "qpcollisionNode.h"
 #include "collisionPlane.h"
 #include "collisionPlane.h"
@@ -58,12 +65,19 @@ init_libcollide() {
   initialized = true;
   initialized = true;
 
 
   CollisionEntry::init_type();
   CollisionEntry::init_type();
+  qpCollisionEntry::init_type();
   CollisionHandler::init_type();
   CollisionHandler::init_type();
+  qpCollisionHandler::init_type();
   CollisionHandlerEvent::init_type();
   CollisionHandlerEvent::init_type();
+  qpCollisionHandlerEvent::init_type();
   CollisionHandlerFloor::init_type();
   CollisionHandlerFloor::init_type();
+  qpCollisionHandlerFloor::init_type();
   CollisionHandlerPhysical::init_type();
   CollisionHandlerPhysical::init_type();
+  qpCollisionHandlerPhysical::init_type();
   CollisionHandlerPusher::init_type();
   CollisionHandlerPusher::init_type();
+  qpCollisionHandlerPusher::init_type();
   CollisionHandlerQueue::init_type();
   CollisionHandlerQueue::init_type();
+  qpCollisionHandlerQueue::init_type();
   CollisionNode::init_type();
   CollisionNode::init_type();
   qpCollisionNode::init_type();
   qpCollisionNode::init_type();
   CollisionPlane::init_type();
   CollisionPlane::init_type();

+ 298 - 0
panda/src/collide/qpcollisionEntry.I

@@ -0,0 +1,298 @@
+// Filename: qpcollisionEntry.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionEntry::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionEntry::
+qpCollisionEntry() {
+  _flags = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_from
+//       Access: Public
+//  Description: Returns the CollisionSolid pointer for the particular
+//               solid that triggered this collision.
+////////////////////////////////////////////////////////////////////
+INLINE const CollisionSolid *qpCollisionEntry::
+get_from() const {
+  return _from;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::has_into
+//       Access: Public
+//  Description: Returns true if the "into" solid is, in fact, a
+//               CollisionSolid, and its pointer is known (in which
+//               case get_into() may be called to retrieve it).  If
+//               this returns false, the collision was detected into a
+//               GeomNode, and there is no CollisionSolid pointer to
+//               be retrieved.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionEntry::
+has_into() const {
+  return (_into != (CollisionSolid *)NULL);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into
+//       Access: Public
+//  Description: Returns the CollisionSolid pointer for the particular
+//               solid was collided into.  This pointer might be NULL
+//               if the collision was into a piece of visible
+//               geometry, instead of a normal CollisionSolid
+//               collision; see has_into().
+////////////////////////////////////////////////////////////////////
+INLINE const CollisionSolid *qpCollisionEntry::
+get_into() const {
+  return _into;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_from_node
+//       Access: Public
+//  Description: Returns the node that contains the CollisionSolid
+//               that triggered this collision.  This will be a node
+//               that has been added to a CollisionTraverser via
+//               add_collider().
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionNode *qpCollisionEntry::
+get_from_node() const {
+  return _from_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_from_space
+//       Access: Public
+//  Description: Returns the global coordinate space of the
+//               qpCollisionNode returned by get_from_node(), as of the
+//               time of the collision.  This will be equivalent to a
+//               wrt() from the node to render.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionEntry::
+get_from_space() const {
+  return _from_space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_node
+//       Access: Public
+//  Description: Returns the node that contains the CollisionSolid
+//               that was collided into.  This returns a PandaNode
+//               pointer instead of something more specific, because
+//               it might be either a qpCollisionNode or a GeomNode.
+//
+//               Also see get_into_node_path().
+////////////////////////////////////////////////////////////////////
+INLINE PandaNode *qpCollisionEntry::
+get_into_node() const {
+  return _into_node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_node_path
+//       Access: Public
+//  Description: Returns the qpNodePath that represents the specific
+//               qpCollisionNode or GeomNode instance that was collided
+//               into.  This is the same node returned by
+//               get_into_node(), represented as a qpNodePath; however,
+//               it may be more useful because the qpNodePath can
+//               resolve the particular instance of the node, if there
+//               is more than one.
+////////////////////////////////////////////////////////////////////
+INLINE const qpNodePath &qpCollisionEntry::
+get_into_node_path() const {
+  return _into_node_path;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_space
+//       Access: Public
+//  Description: Returns the global coordinate space of the
+//               qpCollisionNode or GeomNode returned by
+//               get_into_node(), as of the time of the collision.
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionEntry::
+get_into_space() const {
+  return _into_space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_wrt_space
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionEntry::
+get_wrt_space() const {
+  return _wrt_space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_inv_wrt_space
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionEntry::
+get_inv_wrt_space() const {
+  return _inv_wrt_space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::set_into_intersection_point
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionEntry::
+set_into_intersection_point(const LPoint3f &point) {
+  _into_intersection_point = point;
+  _flags |= F_has_into_intersection_point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::has_into_intersection_point
+//       Access: Public
+//  Description: Returns true if the detected collision knows its
+//               intersection point in the coordinate space of the
+//               collided-into object, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionEntry::
+has_into_intersection_point() const {
+  return (_flags & F_has_into_intersection_point) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_intersection_point
+//       Access: Public
+//  Description: Returns the intersection point in the coordinate
+//               space of the collided-into object.  It is an error to
+//               call this if has_into_intersection_point() returns
+//               false.
+////////////////////////////////////////////////////////////////////
+INLINE const LPoint3f &qpCollisionEntry::
+get_into_intersection_point() const {
+  nassertr(has_into_intersection_point(), _into_intersection_point);
+  return _into_intersection_point;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::has_from_intersection_point
+//       Access: Public
+//  Description: Returns true if the detected collision knows its
+//               intersection point in the coordinate space of the
+//               colliding object, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionEntry::
+has_from_intersection_point() const {
+  // Since we derive the from_intersection_point from the
+  // into_intersection_point, this is really the same question.
+  return has_into_intersection_point();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_from_intersection_point
+//       Access: Public
+//  Description: Returns the intersection point in the coordinate
+//               space of the colliding object.  It is an error to
+//               call this if has_from_intersection_point() returns
+//               false.
+////////////////////////////////////////////////////////////////////
+INLINE LPoint3f qpCollisionEntry::
+get_from_intersection_point() const {
+  return get_into_intersection_point() * get_inv_wrt_space();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::set_into_surface_normal
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionEntry::
+set_into_surface_normal(const LVector3f &normal) {
+  _into_surface_normal = normal;
+  _flags |= F_has_into_surface_normal;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::has_into_surface_normal
+//       Access: Public
+//  Description: Returns true if the detected collision knows the
+//               surface normal of the collided-into object at the
+//               point of the collision, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionEntry::
+has_into_surface_normal() const {
+  return (_flags & F_has_into_surface_normal) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_surface_normal
+//       Access: Public
+//  Description: Returns the surface normal of the collided-into
+//               object at the point of the collision.  It is an error
+//               to call this if has_into_surface_normal() returns
+//               false.
+////////////////////////////////////////////////////////////////////
+INLINE const LVector3f &qpCollisionEntry::
+get_into_surface_normal() const {
+  nassertr(has_into_surface_normal(), _into_surface_normal);
+  return _into_surface_normal;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::set_into_depth
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionEntry::
+set_into_depth(float depth) {
+  _into_depth = depth;
+  _flags |= F_has_into_depth;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::has_into_depth
+//       Access: Public
+//  Description: Returns true if the collision entry knows how "deep"
+//               the collision was into the collided-into object; that
+//               is, how far into the surface of the collided-into
+//               object the colliding object has penetrated.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionEntry::
+has_into_depth() const {
+  return (_flags & F_has_into_depth) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::get_into_depth
+//       Access: Public
+//  Description: Returns how "deep" the collision was into the
+//               collided-into object; that is, how far into the
+//               surface of the collided-into object the colliding
+//               object has penetrated.  It is an error to call this
+//               if has_into_depth() returns false.
+////////////////////////////////////////////////////////////////////
+INLINE float qpCollisionEntry::
+get_into_depth() const {
+  nassertr(has_into_depth(), 0.0);
+  return _into_depth;
+}

+ 66 - 0
panda/src/collide/qpcollisionEntry.cxx

@@ -0,0 +1,66 @@
+// Filename: qpcollisionEntry.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionEntry.h"
+
+TypeHandle qpCollisionEntry::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::Copy Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionEntry::
+qpCollisionEntry(const qpCollisionEntry &copy) :
+  _from(copy._from),
+  _into(copy._into),
+  _from_node(copy._from_node),
+  _into_node(copy._into_node),
+  _into_node_path(copy._into_node_path),
+  _from_space(copy._from_space),
+  _into_space(copy._into_space),
+  _wrt_space(copy._wrt_space),
+  _inv_wrt_space(copy._inv_wrt_space),
+  _flags(copy._flags),
+  _into_intersection_point(copy._into_intersection_point),
+  _into_surface_normal(copy._into_surface_normal),
+  _into_depth(copy._into_depth)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionEntry::Copy Assignment Operator
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpCollisionEntry::
+operator = (const qpCollisionEntry &copy) {
+  _from = copy._from;
+  _into = copy._into;
+  _from_node = copy._from_node;
+  _into_node = copy._into_node;
+  _into_node_path = copy._into_node_path;
+  _from_space = copy._from_space;
+  _into_space = copy._into_space;
+  _wrt_space = copy._wrt_space;
+  _inv_wrt_space = copy._inv_wrt_space;
+  _flags = copy._flags;
+  _into_intersection_point = copy._into_intersection_point;
+  _into_surface_normal = copy._into_surface_normal;
+  _into_depth = copy._into_depth;
+}

+ 130 - 0
panda/src/collide/qpcollisionEntry.h

@@ -0,0 +1,130 @@
+// Filename: qpcollisionEntry.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONENTRY_H
+#define qpCOLLISIONENTRY_H
+
+#include "pandabase.h"
+
+#include "collisionSolid.h"
+#include "qpcollisionNode.h"
+
+#include "typedReferenceCount.h"
+#include "luse.h"
+#include "pointerTo.h"
+#include "pandaNode.h"
+#include "qpnodePath.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionEntry
+// Description : Defines a single collision event.  One of these is
+//               created for each collision detected by a
+//               CollisionTraverser, to be dealt with by the
+//               CollisionHandler.
+//
+//               A qpCollisionEntry provides slots for a number of data
+//               values (such as intersection point and normal) that
+//               might or might not be known for each collision.  It
+//               is up to the handler to determine what information is
+//               known and to do the right thing with it.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionEntry : public TypedReferenceCount {
+public:
+  INLINE qpCollisionEntry();
+  qpCollisionEntry(const qpCollisionEntry &copy);
+  void operator = (const qpCollisionEntry &copy);
+
+PUBLISHED:
+  INLINE const CollisionSolid *get_from() const;
+  INLINE bool has_into() const;
+  INLINE const CollisionSolid *get_into() const;
+
+  INLINE qpCollisionNode *get_from_node() const;
+  INLINE PandaNode *get_into_node() const;
+  INLINE const qpNodePath &get_into_node_path() const;
+
+  INLINE const LMatrix4f &get_from_space() const;
+  INLINE const LMatrix4f &get_into_space() const;
+  INLINE const LMatrix4f &get_wrt_space() const;
+  INLINE const LMatrix4f &get_inv_wrt_space() 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;
+
+  INLINE bool has_from_intersection_point() const;
+  INLINE LPoint3f get_from_intersection_point() const;
+
+  INLINE void set_into_surface_normal(const LVector3f &normal);
+  INLINE bool has_into_surface_normal() const;
+  INLINE const LVector3f &get_into_surface_normal() const;
+
+  INLINE void set_into_depth(float depth);
+  INLINE bool has_into_depth() const;
+  INLINE float get_into_depth() const;
+
+private:
+  CPT(CollisionSolid) _from;
+  CPT(CollisionSolid) _into;
+
+  PT(qpCollisionNode) _from_node;
+  PT(PandaNode) _into_node;
+  qpNodePath _into_node_path;
+  LMatrix4f _from_space;
+  LMatrix4f _into_space;
+  LMatrix4f _wrt_space;
+  LMatrix4f _inv_wrt_space;
+
+  enum Flags {
+    F_has_into_intersection_point = 0x0001,
+    F_has_into_surface_normal     = 0x0002,
+    F_has_into_depth              = 0x0004,
+  };
+
+  int _flags;
+
+  LPoint3f _into_intersection_point;
+  LVector3f _into_surface_normal;
+  float _into_depth;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "qpCollisionEntry",
+                  TypedReferenceCount::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 qpCollisionTraverser;
+};
+
+#include "qpcollisionEntry.I"
+
+#endif
+
+
+

+ 61 - 0
panda/src/collide/qpcollisionHandler.cxx

@@ -0,0 +1,61 @@
+// Filename: qpcollisionHandler.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandler.h"
+
+TypeHandle qpCollisionHandler::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandler::begin_group
+//       Access: Public, Virtual
+//  Description: Will be called by the CollisionTraverser before a new
+//               traversal is begun.  It instructs the handler to
+//               reset itself in preparation for a number of
+//               CollisionEntries to be sent.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandler::
+begin_group() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandler::add_entry
+//       Access: Public, Virtual
+//  Description: Called between a begin_group() .. end_group()
+//               sequence for each collision that is detected.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandler::
+add_entry(qpCollisionEntry *) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandler::end_group
+//       Access: Public, Virtual
+//  Description: Called by the CollisionTraverser at the completion of
+//               all collision detections for this traversal.  It
+//               should do whatever finalization is required for the
+//               handler.
+//
+//               The return value is normally true, but if this
+//               returns value, the CollisionTraverser will remove the
+//               handler from its list, allowing the qpCollisionHandler
+//               itself to determine when it is no longer needed.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandler::
+end_group() {
+  return true;
+}

+ 68 - 0
panda/src/collide/qpcollisionHandler.h

@@ -0,0 +1,68 @@
+// Filename: qpcollisionHandler.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLER_H
+#define qpCOLLISIONHANDLER_H
+
+#include "pandabase.h"
+
+#include "typedReferenceCount.h"
+
+class qpCollisionEntry;
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandler
+// Description : The abstract interface to a number of classes that
+//               decide what to do what a collision is detected.  One
+//               of these must be assigned to the CollisionTraverser
+//               that is processing collisions in order to specify how
+//               to dispatch detected collisions.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandler : public TypedReferenceCount {
+public:
+  virtual void begin_group();
+  virtual void add_entry(qpCollisionEntry *entry);
+  virtual bool end_group();
+
+
+PUBLISHED:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+
+public:
+  static void init_type() {
+    TypedReferenceCount::init_type();
+    register_type(_type_handle, "qpCollisionHandler",
+                  TypedReferenceCount::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 CollisionTraverser;
+};
+
+#endif
+
+
+

+ 162 - 0
panda/src/collide/qpcollisionHandlerEvent.I

@@ -0,0 +1,162 @@
+// Filename: qpcollisionHandlerEvent.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionHandlerEvent::SortEntries::operator ()
+//       Access: Public
+//  Description: Orders the CollisionEntries in the set so that there
+//               is one entry for each node/node intersection
+//               detected.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionHandlerEvent::SortEntries::
+operator () (const PT(qpCollisionEntry) &a,
+             const PT(qpCollisionEntry) &b) const {
+  if (a->get_from_node() != b->get_from_node()) {
+    return a->get_from_node() < b->get_from_node();
+  }
+  if (a->get_into_node() != b->get_into_node()) {
+    return a->get_into_node() < b->get_into_node();
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::SortEntries::operator =
+//       Access: Public
+//  Description: The assignment operator does absolutely nothing,
+//               since this is just a function object class that
+//               stores no data.  We define it just to quiet up g++ in
+//               -Wall mode.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerEvent::SortEntries::
+operator = (const qpCollisionHandlerEvent::SortEntries &) {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::set_in_pattern
+//       Access: Public
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated for each collision detected.
+//               This is a string that may contain any of the
+//               following:
+//
+//                  %fn  - the name of the "from" object's node
+//                  %in  - the name of the "into" object's node
+//                  %ft  - 't' if "from" is tangible, 'i' if intangible
+//                  %it  - 't' if "into" is tangible, 'i' if intangible
+//
+//               The event name will be based on the in_pattern
+//               string specified here, with all occurrences of the
+//               above strings replaced with the corresponding values.
+//
+//               In general, the in_pattern event is thrown on the
+//               first detection of a collision between two particular
+//               nodes.  In subsequent passes, as long as a collision
+//               between those two nodes continues to be detected each
+//               frame, the again_pattern is thrown.  The first frame
+//               in which the collision is no longer detected, the
+//               out_pattern event is thrown.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerEvent::
+set_in_pattern(const string &in_pattern) {
+  _in_pattern = in_pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::get_in_pattern
+//       Access: Public
+//  Description: Returns the pattern string that indicates how the
+//               event names are generated for each collision
+//               detected.  See set_in_pattern().
+////////////////////////////////////////////////////////////////////
+INLINE string qpCollisionHandlerEvent::
+get_in_pattern() const {
+  return _in_pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::set_again_pattern
+//       Access: Public
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated when a collision between two
+//               particular nodes is *still* detected.  This event is
+//               thrown each consecutive time a collision between two
+//               particular nodes is detected, starting with the
+//               second time.
+//
+//               In general, the in_pattern event is thrown on the
+//               first detection of a collision between two particular
+//               nodes.  In subsequent passes, as long as a collision
+//               between those two nodes continues to be detected each
+//               frame, the again_pattern is thrown.  The first frame
+//               in which the collision is no longer detected, the
+//               out_pattern event is thrown.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerEvent::
+set_again_pattern(const string &again_pattern) {
+  _again_pattern = again_pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::get_again_pattern
+//       Access: Public
+//  Description: Returns the pattern string that indicates how the
+//               event names are generated when a collision between
+//               two particular nodes is *still* detected.  See
+//               set_again_pattern() and set_in_pattern().
+////////////////////////////////////////////////////////////////////
+INLINE string qpCollisionHandlerEvent::
+get_again_pattern() const {
+  return _again_pattern;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::set_out_pattern
+//       Access: Public
+//  Description: Sets the pattern string that indicates how the event
+//               names are generated when a collision between two
+//               particular nodes is *no longer* detected.
+//
+//               In general, the in_pattern event is thrown on the
+//               first detection of a collision between two particular
+//               nodes.  In subsequent passes, as long as a collision
+//               between those two nodes continues to be detected each
+//               frame, the again_pattern is thrown.  The first frame
+//               in which the collision is no longer detected, the
+//               out_pattern event is thrown.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerEvent::
+set_out_pattern(const string &out_pattern) {
+  _out_pattern = out_pattern;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::get_out_pattern
+//       Access: Public
+//  Description: Returns the pattern string that indicates how the
+//               event names are generated when a collision between
+//               two particular nodes is *no longer* detected.  See
+//               set_out_pattern() and set_in_pattern().
+////////////////////////////////////////////////////////////////////
+INLINE string qpCollisionHandlerEvent::
+get_out_pattern() const {
+  return _out_pattern;
+}
+

+ 213 - 0
panda/src/collide/qpcollisionHandlerEvent.cxx

@@ -0,0 +1,213 @@
+// Filename: qpcollisionHandlerEvent.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandlerEvent.h"
+#include "config_collide.h"
+
+#include "eventParameter.h"
+#include "throw_event.h"
+
+
+TypeHandle qpCollisionHandlerEvent::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::Constructor
+//       Access: Public
+//  Description: The default qpCollisionHandlerEvent will throw no
+//               events.  Its pattern strings must first be set via a
+//               call to set_in_pattern() and/or set_out_pattern().
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerEvent::
+qpCollisionHandlerEvent() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::begin_group
+//       Access: Public, Virtual
+//  Description: Will be called by the CollisionTraverser before a new
+//               traversal is begun.  It instructs the handler to
+//               reset itself in preparation for a number of
+//               CollisionEntries to be sent.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerEvent::
+begin_group() {
+  if (collide_cat.is_spam()) {
+    collide_cat.spam()
+      << "begin_group.\n";
+  }
+  _last_colliding.swap(_current_colliding);
+  _current_colliding.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::add_entry
+//       Access: Public, Virtual
+//  Description: Called between a begin_group() .. end_group()
+//               sequence for each collision that is detected.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerEvent::
+add_entry(qpCollisionEntry *entry) {
+  nassertv(entry != (qpCollisionEntry *)NULL);
+
+  // Record this particular entry for later.  This will keep track of
+  // all the unique pairs of node/node intersections.
+  bool inserted = _current_colliding.insert(entry).second;
+
+  if (collide_cat.is_spam()) {
+    collide_cat.spam()
+      << "Detected collision from " << (void *)entry->get_from_node()
+      << " to " << (void *)entry->get_into_node()
+      << ", inserted = " << inserted << "\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::end_group
+//       Access: Public, Virtual
+//  Description: Called by the CollisionTraverser at the completion of
+//               all collision detections for this traversal.  It
+//               should do whatever finalization is required for the
+//               handler.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerEvent::
+end_group() {
+  // Now compare the list of entries we collected this frame with
+  // those we kept from the last time.  Each new entry represents a
+  // new 'in' event; each missing entry represents a new 'out' event.
+
+  if (collide_cat.is_spam()) {
+    collide_cat.spam()
+      << "end_group.\n"
+      << "current_colliding has " << _current_colliding.size()
+      << " entries, last_colliding has " << _last_colliding.size()
+      << "\n";
+  }
+
+  Colliding::iterator ca, cb;
+
+  ca = _current_colliding.begin();
+  cb = _last_colliding.begin();
+
+  SortEntries order;
+  while (ca != _current_colliding.end() && cb != _last_colliding.end()) {
+    if (order(*ca, *cb)) {
+      // Here's an element in a but not in b.  That's a newly entered
+      // intersection.
+      throw_event_pattern(_in_pattern, *ca);
+      ++ca;
+
+    } else if (order(*cb, *ca)) {
+      // Here's an element in b but not in a.  That's a newly exited
+      // intersection.
+      throw_event_pattern(_out_pattern, *cb);
+      ++cb;
+
+    } else {
+      // This element is in both b and a.  It hasn't changed.
+      throw_event_pattern(_again_pattern, *cb);
+      ++ca;
+      ++cb;
+    }
+  }
+
+  while (ca != _current_colliding.end()) {
+    // Here's an element in a but not in b.  That's a newly entered
+    // intersection.
+    throw_event_pattern(_in_pattern, *ca);
+    ++ca;
+  }
+
+  while (cb != _last_colliding.end()) {
+    // Here's an element in b but not in a.  That's a newly exited
+    // intersection.
+    throw_event_pattern(_out_pattern, *cb);
+    ++cb;
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::clear
+//       Access: Public
+//  Description: Empties the list of elements that all colliders are
+//               known to be colliding with.  No "out" events will be
+//               thrown; if the same collision is detected next frame,
+//               a new "in" event will be thrown for each collision.
+//
+//               This can be called each frame to defeat the
+//               persistent "in" event mechanism, which prevents the
+//               same "in" event from being thrown repeatedly.
+//               However, also see set_again_pattern(), which can be
+//               used to set the event that is thrown when a collision
+//               is detected for two or more consecutive frames.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerEvent::
+clear() {
+  _last_colliding.clear();
+  _current_colliding.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerEvent::throw_event_pattern
+//       Access: Private
+//  Description: Throws an event matching the indicated pattern.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerEvent::
+throw_event_pattern(const string &pattern, qpCollisionEntry *entry) {
+  if (pattern.empty()) {
+    return;
+  }
+
+  string event;
+  for (size_t p = 0; p < pattern.size(); ++p) {
+    if (pattern[p] == '%') {
+      string cmd = pattern.substr(p + 1, 2);
+      p += 2;
+      if (cmd == "fn") {
+        event += entry->get_from_node()->get_name();
+
+      } else if (cmd == "in") {
+        event += entry->get_into_node()->get_name();
+
+      } else if (cmd == "ft") {
+        event +=
+          (!entry->get_from()->is_tangible() ? 'i' : 't');
+
+      } else if (cmd == "it") {
+        event +=
+          (entry->has_into() && !entry->get_into()->is_tangible() ? 'i' : 't');
+
+      } else if (cmd == "ig") {
+        event +=
+          (entry->has_into() ? 'c' : 'g');
+
+      } else {
+        collide_cat.error()
+          << "Invalid symbol in event_pattern: %" << cmd << "\n";
+      }
+    } else {
+      event += pattern[p];
+    }
+  }
+
+  if (!event.empty()) {
+    throw_event(event, EventParameter(entry));
+  }
+}

+ 102 - 0
panda/src/collide/qpcollisionHandlerEvent.h

@@ -0,0 +1,102 @@
+// Filename: qpcollisionHandlerEvent.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLEREVENT_H
+#define qpCOLLISIONHANDLEREVENT_H
+
+#include "pandabase.h"
+
+#include "qpcollisionHandler.h"
+#include "qpcollisionNode.h"
+#include "qpcollisionEntry.h"
+
+#include "pointerTo.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandlerEvent
+// Description : A specialized kind of qpCollisionHandler that throws an
+//               event for each collision detected.  The event thrown
+//               may be based on the name of the moving object or the
+//               struck object, or both.  The first parameter of the
+//               event will be a pointer to the qpCollisionEntry that
+//               triggered it.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandlerEvent : public qpCollisionHandler {
+PUBLISHED:
+  qpCollisionHandlerEvent();
+
+public:
+  virtual void begin_group();
+  virtual void add_entry(qpCollisionEntry *entry);
+  virtual bool end_group();
+
+PUBLISHED:
+  INLINE void set_in_pattern(const string &pattern);
+  INLINE string get_in_pattern() const;
+  INLINE void set_again_pattern(const string &pattern);
+  INLINE string get_again_pattern() const;
+  INLINE void set_out_pattern(const string &pattern);
+  INLINE string get_out_pattern() const;
+
+  void clear();
+
+private:
+  void throw_event_pattern(const string &pattern, qpCollisionEntry *entry);
+
+  string _in_pattern;
+  string _again_pattern;
+  string _out_pattern;
+
+  int _index;
+
+  class SortEntries {
+  public:
+    INLINE bool
+    operator () (const PT(qpCollisionEntry) &a,
+                 const PT(qpCollisionEntry) &b) const;
+    INLINE void operator = (const SortEntries &other);
+  };
+
+  typedef pset<PT(qpCollisionEntry), SortEntries> Colliding;
+  Colliding _current_colliding;
+  Colliding _last_colliding;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCollisionHandler::init_type();
+    register_type(_type_handle, "qpCollisionHandlerEvent",
+                  qpCollisionHandler::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 "qpcollisionHandlerEvent.I"
+
+#endif
+
+
+

+ 67 - 0
panda/src/collide/qpcollisionHandlerFloor.I

@@ -0,0 +1,67 @@
+// Filename: qpcollisionHandlerFloor.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionHandlerFloor::set_offset
+//       Access: Public
+//  Description: Sets the linear offset to add to (or subtract from)
+//               the highest detected collision point to determine the
+//               actual height at which to set the collider.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerFloor::
+set_offset(float offset) {
+  _offset = offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::get_offset
+//       Access: Public
+//  Description: Returns the linear offset to add to (or subtract from)
+//               the highest detected collision point to determine the
+//               actual height at which to set the collider.
+////////////////////////////////////////////////////////////////////
+INLINE float qpCollisionHandlerFloor::
+get_offset() const {
+  return _offset;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::set_max_velocity
+//       Access: Public
+//  Description: Sets the maximum speed at which the object will be
+//               allowed to descend towards a floor below it, in units
+//               per second.  Set this to zero to allow it to
+//               instantly teleport any distance.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerFloor::
+set_max_velocity(float max_velocity) {
+  _max_velocity = max_velocity;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::get_max_velocity
+//       Access: Public
+//  Description: Retrieves the maximum speed at which the object will
+//               be allowed to descend towards a floor below it, in
+//               units per second.  See set_max_velocity().
+////////////////////////////////////////////////////////////////////
+INLINE float qpCollisionHandlerFloor::
+get_max_velocity() const {
+  return _max_velocity;
+}

+ 143 - 0
panda/src/collide/qpcollisionHandlerFloor.cxx

@@ -0,0 +1,143 @@
+// Filename: qpcollisionHandlerFloor.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandlerFloor.h"
+#include "qpcollisionNode.h"
+#include "qpcollisionEntry.h"
+#include "config_collide.h"
+
+#include "clockObject.h"
+
+TypeHandle qpCollisionHandlerFloor::_type_handle;
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerFloor::
+qpCollisionHandlerFloor() {
+  _offset = 0.0f;
+  _max_velocity = 0.0f;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerFloor::
+~qpCollisionHandlerFloor() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerFloor::handle_entries
+//       Access: Protected, Virtual
+//  Description: Called by the parent class after all collisions have
+//               been detected, this manages the various collisions
+//               and moves around the nodes as necessary.
+//
+//               The return value is normally true, but it may be
+//               false to indicate the CollisionTraverser should
+//               disable this handler from being called in the future.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerFloor::
+handle_entries() {
+  bool okflag = true;
+
+  FromEntries::const_iterator fi;
+  for (fi = _from_entries.begin(); fi != _from_entries.end(); ++fi) {
+    qpCollisionNode *from_node = (*fi).first;
+    nassertr(from_node != (qpCollisionNode *)NULL, false);
+    const Entries &entries = (*fi).second;
+
+    Colliders::iterator ci;
+    ci = _colliders.find(from_node);
+    if (ci == _colliders.end()) {
+      // Hmm, someone added a qpCollisionNode to a traverser and gave
+      // it this qpCollisionHandler pointer--but they didn't tell us
+      // about the node.
+      collide_cat.error()
+        << get_type() << " doesn't know about "
+        << *from_node << ", disabling.\n";
+      okflag = false;
+
+    } else {
+      ColliderDef &def = (*ci).second;
+      if (!def.is_valid()) {
+        collide_cat.error()
+          << "Removing invalid collider " << *from_node << " from "
+          << get_type() << "\n";
+        _colliders.erase(ci);
+
+      } else {
+        // Get the maximum height for all collisions with this node.
+        bool got_max = false;
+        float max_height = 0.0f;
+        
+        Entries::const_iterator ei;
+        for (ei = entries.begin(); ei != entries.end(); ++ei) {
+          qpCollisionEntry *entry = (*ei);
+          nassertr(entry != (qpCollisionEntry *)NULL, false);
+          nassertr(from_node == entry->get_from_node(), false);
+          
+          if (entry->has_from_intersection_point()) {
+            LPoint3f point = entry->get_from_intersection_point();
+            if (collide_cat.is_debug()) {
+              collide_cat.debug()
+                << "Intersection point detected at " << point << "\n";
+            }
+            
+            float height = point[2];
+            if (!got_max || height > max_height) {
+              got_max = true;
+              max_height = height;
+            }
+          }
+        }
+        
+        // Now set our height accordingly.
+        float adjust = max_height + _offset;
+        if (!IS_THRESHOLD_ZERO(adjust, 0.001)) {
+          if (collide_cat.is_debug()) {
+            collide_cat.debug()
+              << "Adjusting height by " << adjust << "\n";
+          }
+          
+          if (adjust < 0.0f && _max_velocity != 0.0f) {
+            float max_adjust =
+              _max_velocity * ClockObject::get_global_clock()->get_dt();
+            adjust = max(adjust, -max_adjust);
+          }
+          
+          LMatrix4f mat;
+          def.get_mat(mat);
+          mat(3, 2) += adjust;
+          def.set_mat(mat);
+        } else {
+          if (collide_cat.is_spam()) {
+            collide_cat.spam()
+              << "Leaving height unchanged.\n";
+          }
+        }
+      }
+    }
+  }
+
+  return okflag;
+}

+ 77 - 0
panda/src/collide/qpcollisionHandlerFloor.h

@@ -0,0 +1,77 @@
+// Filename: qpcollisionHandlerFloor.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLERFLOOR_H
+#define qpCOLLISIONHANDLERFLOOR_H
+
+#include "pandabase.h"
+
+#include "qpcollisionHandlerPhysical.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandlerFloor
+// Description : A specialized kind of qpCollisionHandler that sets the
+//               Z height of the collider to a fixed linear offset
+//               from the highest detected collision point each frame.
+//               It's intended to implement walking around on a floor
+//               of varying height by casting a ray down from the
+//               avatar's head.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandlerFloor : public qpCollisionHandlerPhysical {
+PUBLISHED:
+  qpCollisionHandlerFloor();
+  virtual ~qpCollisionHandlerFloor();
+
+  INLINE void set_offset(float offset);
+  INLINE float get_offset() const;
+
+  INLINE void set_max_velocity(float max_vel);
+  INLINE float get_max_velocity() const;
+
+protected:
+  virtual bool handle_entries();
+
+private:
+  float _offset;
+  float _max_velocity;
+
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCollisionHandlerPhysical::init_type();
+    register_type(_type_handle, "qpCollisionHandlerFloor",
+                  qpCollisionHandlerPhysical::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 "qpcollisionHandlerFloor.I"
+
+#endif
+
+
+

+ 52 - 0
panda/src/collide/qpcollisionHandlerPhysical.I

@@ -0,0 +1,52 @@
+// Filename: qpcollisionHandlerPhysical.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionHandlerPhysical::ColliderDef::set_drive_interface
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerPhysical::ColliderDef::
+set_drive_interface(qpDriveInterface *drive_interface) {
+  _drive_interface = drive_interface;
+  _node.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::ColliderDef::set_node
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerPhysical::ColliderDef::
+set_node(PandaNode *node) {
+  _node = node;
+  _drive_interface.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::ColliderDef::is_valid
+//       Access: Public
+//  Description: Returns true if this ColliderDef is still valid;
+//               e.g. it refers to a valid node or drive interface.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionHandlerPhysical::ColliderDef::
+is_valid() const {
+  return (_node != (PandaNode *)NULL) ||
+    (_drive_interface != (qpDriveInterface *)NULL);
+}

+ 197 - 0
panda/src/collide/qpcollisionHandlerPhysical.cxx

@@ -0,0 +1,197 @@
+// Filename: qpcollisionHandlerPhysical.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandlerPhysical.h"
+#include "config_collide.h"
+
+#include "transformState.h"
+
+TypeHandle qpCollisionHandlerPhysical::_type_handle;
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::ColliderDef::get_mat
+//       Access: Public
+//  Description: Fills mat with the matrix representing the current
+//               position and orientation of this collider.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::ColliderDef::
+get_mat(LMatrix4f &mat) const {
+  if (_node != (PandaNode *)NULL) {
+    mat = _node->get_transform()->get_mat();
+
+  } else if (_drive_interface != (qpDriveInterface *)NULL) {
+    mat = _drive_interface->get_mat();
+
+  } else {
+    collide_cat.error()
+      << "Invalid qpCollisionHandlerPhysical::ColliderDef\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::ColliderDef::set_mat
+//       Access: Public
+//  Description: Moves this collider to the position and orientation
+//               indicated by the given transform.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::ColliderDef::
+set_mat(const LMatrix4f &mat) {
+  if (_node != (PandaNode *)NULL) {
+    _node->set_transform(TransformState::make_mat(mat));
+
+  } else if (_drive_interface != (qpDriveInterface *)NULL) {
+    _drive_interface->set_mat(mat);
+    _drive_interface->force_dgraph();
+
+  } else {
+    collide_cat.error()
+      << "Invalid qpCollisionHandlerPhysical::ColliderDef\n";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerPhysical::
+qpCollisionHandlerPhysical() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerPhysical::
+~qpCollisionHandlerPhysical() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::begin_group
+//       Access: Public, Virtual
+//  Description: Will be called by the CollisionTraverser before a new
+//               traversal is begun.  It instructs the handler to
+//               reset itself in preparation for a number of
+//               CollisionEntries to be sent.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::
+begin_group() {
+  qpCollisionHandlerEvent::begin_group();
+  _from_entries.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::add_entry
+//       Access: Public, Virtual
+//  Description: Called between a begin_group() .. end_group()
+//               sequence for each collision that is detected.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::
+add_entry(qpCollisionEntry *entry) {
+  nassertv(entry != (qpCollisionEntry *)NULL);
+  qpCollisionHandlerEvent::add_entry(entry);
+
+  if (entry->get_from()->is_tangible() &&
+      (!entry->has_into() || entry->get_into()->is_tangible())) {
+    _from_entries[entry->get_from_node()].push_back(entry);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::end_group
+//       Access: Public, Virtual
+//  Description: Called by the CollisionTraverser at the completion of
+//               all collision detections for this traversal.  It
+//               should do whatever finalization is required for the
+//               handler.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerPhysical::
+end_group() {
+  qpCollisionHandlerEvent::end_group();
+
+  return handle_entries();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::add_collider
+//       Access: Public
+//  Description: Adds a new collider to the list with a qpDriveInterface
+//               pointer that needs to be told about the collider's
+//               new position, or updates the existing collider with a
+//               new qpDriveInterface pointer.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::
+add_collider(qpCollisionNode *node, qpDriveInterface *drive_interface) {
+  _colliders[node].set_drive_interface(drive_interface);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::add_collider
+//       Access: Public
+//  Description: Adds a new collider to the list with a PandaNode
+//               pointer that will be updated with the collider's
+//               new position, or updates the existing collider with a
+//               new PandaNode pointer.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::
+add_collider(qpCollisionNode *node, PandaNode *target) {
+  _colliders[node].set_node(target);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::remove_collider
+//       Access: Public
+//  Description: Removes the collider from the list of colliders that
+//               this handler knows about.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerPhysical::
+remove_collider(qpCollisionNode *node) {
+  Colliders::iterator ci = _colliders.find(node);
+  if (ci == _colliders.end()) {
+    return false;
+  }
+  _colliders.erase(ci);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::has_collider
+//       Access: Public
+//  Description: Returns true if the handler knows about the indicated
+//               collider, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerPhysical::
+has_collider(qpCollisionNode *node) const {
+  Colliders::const_iterator ci = _colliders.find(node);
+  return (ci != _colliders.end());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPhysical::clear_colliders
+//       Access: Public
+//  Description: Completely empties the list of colliders this handler
+//               knows about.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerPhysical::
+clear_colliders() {
+  _colliders.clear();
+}
+
+

+ 102 - 0
panda/src/collide/qpcollisionHandlerPhysical.h

@@ -0,0 +1,102 @@
+// Filename: qpcollisionHandlerPhysical.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLERPHYSICAL_H
+#define qpCOLLISIONHANDLERPHYSICAL_H
+
+#include "pandabase.h"
+
+#include "qpcollisionHandlerEvent.h"
+#include "qpcollisionNode.h"
+
+#include "qpdriveInterface.h"
+#include "pointerTo.h"
+#include "pandaNode.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandlerPhysical
+// Description : The abstract base class for a number of
+//               qpCollisionHandlers that have some physical effect on
+//               their moving bodies: they need to update the nodes'
+//               positions based on the effects of the collision.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandlerPhysical : public qpCollisionHandlerEvent {
+public:
+  qpCollisionHandlerPhysical();
+  virtual ~qpCollisionHandlerPhysical();
+
+  virtual void begin_group();
+  virtual void add_entry(qpCollisionEntry *entry);
+  virtual bool end_group();
+
+PUBLISHED:
+  void add_collider(qpCollisionNode *node, qpDriveInterface *drive_interface);
+  void add_collider(qpCollisionNode *node, PandaNode *target);
+  bool remove_collider(qpCollisionNode *node);
+  bool has_collider(qpCollisionNode *node) const;
+  void clear_colliders();
+
+protected:
+  virtual bool handle_entries()=0;
+
+protected:
+  typedef pvector< PT(qpCollisionEntry) > Entries;
+  typedef pmap<PT(qpCollisionNode), Entries> FromEntries;
+  FromEntries _from_entries;
+
+  class ColliderDef {
+  public:
+    INLINE void set_drive_interface(qpDriveInterface *drive_interface);
+    INLINE void set_node(PandaNode *node);
+    INLINE bool is_valid() const;
+
+    void get_mat(LMatrix4f &mat) const;
+    void set_mat(const LMatrix4f &mat);
+
+    PT(qpDriveInterface) _drive_interface;
+    PT(PandaNode) _node;
+  };
+
+  typedef pmap<PT(qpCollisionNode), ColliderDef> Colliders;
+  Colliders _colliders;
+
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCollisionHandlerEvent::init_type();
+    register_type(_type_handle, "qpCollisionHandlerPhysical",
+                  qpCollisionHandlerEvent::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 "qpcollisionHandlerPhysical.I"
+
+#endif
+
+
+

+ 38 - 0
panda/src/collide/qpcollisionHandlerPusher.I

@@ -0,0 +1,38 @@
+// Filename: qpcollisionHandlerPusher.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionHandlerPusher::set_horizontal
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionHandlerPusher::
+set_horizontal(bool flag) {
+  _horizontal = flag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPusher::get_horizontal
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionHandlerPusher::
+get_horizontal() const {
+  return _horizontal;
+}

+ 208 - 0
panda/src/collide/qpcollisionHandlerPusher.cxx

@@ -0,0 +1,208 @@
+// Filename: qpcollisionHandlerPusher.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandlerPusher.h"
+#include "qpcollisionNode.h"
+#include "qpcollisionEntry.h"
+#include "config_collide.h"
+
+TypeHandle qpCollisionHandlerPusher::_type_handle;
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpShoveData
+// Description : The qpShoveData class is used within
+//               qpCollisionHandlerPusher::handle_entries(), to track
+//               multiple shoves onto a given collider.  It's not
+//               exported outside this file.
+////////////////////////////////////////////////////////////////////
+class qpShoveData {
+public:
+  LVector3f _shove;
+  float _length;
+  LVector3f _normalized_shove;
+  bool _valid;
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPusher::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerPusher::
+qpCollisionHandlerPusher() {
+  _horizontal = true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPusher::Destructor
+//       Access: Public, Virtual
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerPusher::
+~qpCollisionHandlerPusher() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerPusher::handle_entries
+//       Access: Protected, Virtual
+//  Description: Called by the parent class after all collisions have
+//               been detected, this manages the various collisions
+//               and moves around the nodes as necessary.
+//
+//               The return value is normally true, but it may be
+//               false to indicate the CollisionTraverser should
+//               disable this handler from being called in the future.
+////////////////////////////////////////////////////////////////////
+bool qpCollisionHandlerPusher::
+handle_entries() {
+  bool okflag = true;
+
+  FromEntries::const_iterator fi;
+  for (fi = _from_entries.begin(); fi != _from_entries.end(); ++fi) {
+    qpCollisionNode *from_node = (*fi).first;
+    nassertr(from_node != (qpCollisionNode *)NULL, false);
+    const Entries &entries = (*fi).second;
+
+    Colliders::iterator ci;
+    ci = _colliders.find(from_node);
+    if (ci == _colliders.end()) {
+      // Hmm, someone added a qpCollisionNode to a traverser and gave
+      // it this qpCollisionHandler pointer--but they didn't tell us
+      // about the node.
+      collide_cat.error()
+        << "qpCollisionHandlerPusher doesn't know about "
+        << *from_node << ", disabling.\n";
+      okflag = false;
+
+    } else {
+      ColliderDef &def = (*ci).second;
+      if (!def.is_valid()) {
+        collide_cat.error()
+          << "Removing invalid collider " << *from_node << " from "
+          << get_type() << "\n";
+        _colliders.erase(ci);
+
+      } else {
+        // How to apply multiple shoves from different solids onto the
+        // same collider?  One's first intuition is to vector sum all
+        // the shoves.  However, this causes problems when two parallel
+        // walls shove on the collider, because we end up with a double
+        // shove.  We hack around this by testing if two shove vectors
+        // share nearly the same direction, and if so, we keep only the
+        // longer of the two.
+        
+        typedef pvector<qpShoveData> Shoves;
+        Shoves shoves;
+        
+        Entries::const_iterator ei;
+        for (ei = entries.begin(); ei != entries.end(); ++ei) {
+          qpCollisionEntry *entry = (*ei);
+          nassertr(entry != (qpCollisionEntry *)NULL, false);
+          nassertr(from_node == entry->get_from_node(), false);
+          
+          if (!entry->has_into_surface_normal() ||
+              !entry->has_into_depth()) {
+            if (collide_cat.is_debug()) {
+              collide_cat.debug()
+                << "Cannot shove on " << *from_node << " for collision into "
+                << *entry->get_into_node() << "; no normal/depth information.\n";
+            }
+            
+          } else {
+            // Shove it just enough to clear the volume.
+            if (entry->get_into_depth() != 0.0f) {
+              qpShoveData sd;
+              sd._shove =
+                entry->get_into_surface_normal() *
+                entry->get_into_depth();
+              
+              if (collide_cat.is_debug()) {
+                collide_cat.debug()
+                  << "Shove on " << *from_node << " from "
+                  << *entry->get_into_node() << ": " << sd._shove << "\n";
+              }
+              
+              sd._length = sd._shove.length();
+              sd._normalized_shove = sd._shove / sd._length;
+              sd._valid = true;
+              
+              shoves.push_back(sd);
+            }
+          }
+        }
+        
+        if (!shoves.empty()) {
+          // Now we combine any two shoves that shove in largely the
+          // same direction.  Hacky.
+          
+          Shoves::iterator si;
+          for (si = shoves.begin(); si != shoves.end(); ++si) {
+            qpShoveData &sd = (*si);
+            Shoves::iterator sj;
+            for (sj = shoves.begin(); sj != si; ++sj) {
+              qpShoveData &sd2 = (*sj);
+              if (sd2._valid) {
+                
+                float d = sd._normalized_shove.dot(sd2._normalized_shove);
+                if (collide_cat.is_debug()) {
+                  collide_cat.debug()
+                    << "Considering dot product " << d << "\n";
+                }
+                
+                if (d > 0.9) {
+                  // These two shoves are largely in the same direction;
+                  // save the larger of the two.
+                  if (sd2._length < sd._length) {
+                    sd2._valid = false;
+                  } else {
+                    sd._valid = false;
+                  }
+                }
+              }
+            }
+          }
+          
+          // Now we can determine the net shove.
+          LVector3f net_shove(0.0f, 0.0f, 0.0f);
+          for (si = shoves.begin(); si != shoves.end(); ++si) {
+            const qpShoveData &sd = (*si);
+            if (sd._valid) {
+              net_shove += sd._shove;
+            }
+          }
+          
+          if (_horizontal) {
+            net_shove[2] = 0.0f;
+          }
+          
+          if (collide_cat.is_debug()) {
+            collide_cat.debug()
+              << "Net shove on " << *from_node << " is: "
+              << net_shove << "\n";
+          }
+          
+          LMatrix4f mat;
+          def.get_mat(mat);
+          def.set_mat(LMatrix4f::translate_mat(net_shove) * mat);
+        }
+      }
+    }
+  }
+
+  return okflag;
+}

+ 71 - 0
panda/src/collide/qpcollisionHandlerPusher.h

@@ -0,0 +1,71 @@
+// Filename: qpcollisionHandlerPusher.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLERPUSHER_H
+#define qpCOLLISIONHANDLERPUSHER_H
+
+#include "pandabase.h"
+
+#include "qpcollisionHandlerPhysical.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandlerPusher
+// Description : A specialized kind of qpCollisionHandler that simply
+//               pushes back on things that attempt to move into solid
+//               walls.  This is the simplest kind of "real-world"
+//               collisions you can have.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandlerPusher : public qpCollisionHandlerPhysical {
+PUBLISHED:
+  qpCollisionHandlerPusher();
+  virtual ~qpCollisionHandlerPusher();
+
+  INLINE void set_horizontal(bool flag);
+  INLINE bool get_horizontal() const;
+
+protected:
+  virtual bool handle_entries();
+
+private:
+  bool _horizontal;
+
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCollisionHandlerPhysical::init_type();
+    register_type(_type_handle, "qpCollisionHandlerPusher",
+                  qpCollisionHandlerPhysical::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 "qpcollisionHandlerPusher.I"
+
+#endif
+
+
+

+ 144 - 0
panda/src/collide/qpcollisionHandlerQueue.cxx

@@ -0,0 +1,144 @@
+// Filename: qpcollisionHandlerQueue.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionHandlerQueue.h"
+#include "config_collide.h"
+
+TypeHandle qpCollisionHandlerQueue::_type_handle;
+
+// This class is used in sort_entries(), below.
+class qpCollisionEntrySorter {
+public:
+  qpCollisionEntrySorter(qpCollisionEntry *entry) {
+    _entry = entry;
+    LVector3f vec =
+      entry->get_from_intersection_point() -
+      entry->get_from()->get_collision_origin();
+    _dist2 = vec.length_squared();
+  }
+  bool operator < (const qpCollisionEntrySorter &other) const {
+    return _dist2 < other._dist2;
+  }
+
+  qpCollisionEntry *_entry;
+  float _dist2;
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+qpCollisionHandlerQueue::
+qpCollisionHandlerQueue() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::begin_group
+//       Access: Public, Virtual
+//  Description: Will be called by the CollisionTraverser before a new
+//               traversal is begun.  It instructs the handler to
+//               reset itself in preparation for a number of
+//               CollisionEntries to be sent.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerQueue::
+begin_group() {
+  _entries.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::add_entry
+//       Access: Public, Virtual
+//  Description: Called between a begin_group() .. end_group()
+//               sequence for each collision that is detected.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerQueue::
+add_entry(qpCollisionEntry *entry) {
+  nassertv(entry != (qpCollisionEntry *)NULL);
+  _entries.push_back(entry);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::sort_entries
+//       Access: Public
+//  Description: Sorts all the detected collisions front-to-back by
+//               from_intersection_point() so that those intersection
+//               points closest to the collider's origin (e.g., the
+//               center of the CollisionSphere, or the point_a of a
+//               CollisionSegment) appear first.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerQueue::
+sort_entries() {
+  // Build up a temporary vector of entries so we can sort the
+  // pointers.  This uses the class defined above.
+  typedef pvector<qpCollisionEntrySorter> Sorter;
+  Sorter sorter;
+  sorter.reserve(_entries.size());
+
+  Entries::const_iterator ei;
+  for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
+    sorter.push_back(qpCollisionEntrySorter(*ei));
+  }
+
+  sort(sorter.begin(), sorter.end());
+  nassertv(sorter.size() == _entries.size());
+
+  // Now that they're sorted, get them back.  We do this in two steps,
+  // building up a temporary vector first, so we don't accidentally
+  // delete all the entries when the pointers go away.
+  Entries sorted_entries;
+  sorted_entries.reserve(sorter.size());
+  Sorter::const_iterator si;
+  for (si = sorter.begin(); si != sorter.end(); ++si) {
+    sorted_entries.push_back((*si)._entry);
+  }
+
+  _entries.swap(sorted_entries);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::clear_entries
+//       Access: Public
+//  Description: Removes all the entries from the queue.
+////////////////////////////////////////////////////////////////////
+void qpCollisionHandlerQueue::
+clear_entries() {
+  _entries.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::get_num_entries
+//       Access: Public
+//  Description: Returns the number of CollisionEntries detected last
+//               pass.
+////////////////////////////////////////////////////////////////////
+int qpCollisionHandlerQueue::
+get_num_entries() const {
+  return _entries.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionHandlerQueue::get_entry
+//       Access: Public
+//  Description: Returns the nth qpCollisionEntry detected last pass.
+////////////////////////////////////////////////////////////////////
+qpCollisionEntry *qpCollisionHandlerQueue::
+get_entry(int n) const {
+  nassertr(n >= 0 && n < (int)_entries.size(), NULL);
+  return _entries[n];
+}

+ 76 - 0
panda/src/collide/qpcollisionHandlerQueue.h

@@ -0,0 +1,76 @@
+// Filename: qpcollisionHandlerQueue.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONHANDLERQUEUE_H
+#define qpCOLLISIONHANDLERQUEUE_H
+
+#include <pandabase.h>
+
+#include "qpcollisionHandler.h"
+#include "qpcollisionEntry.h"
+
+///////////////////////////////////////////////////////////////////
+//       Class : qpCollisionHandlerQueue
+// Description : A special kind of qpCollisionHandler that does nothing
+//               except remember the CollisionEntries detected the
+//               last pass.  This set of CollisionEntries may then be
+//               queried by the calling function.  It's primarily
+//               useful when a simple intersection test is being made,
+//               e.g. for picking from the window.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpCollisionHandlerQueue : public qpCollisionHandler {
+PUBLISHED:
+  qpCollisionHandlerQueue();
+
+public:
+  virtual void begin_group();
+  virtual void add_entry(qpCollisionEntry *entry);
+
+PUBLISHED:
+  void sort_entries();
+  void clear_entries();
+
+  int get_num_entries() const;
+  qpCollisionEntry *get_entry(int n) const;
+
+private:
+  typedef pvector< PT(qpCollisionEntry) > Entries;
+  Entries _entries;
+
+public:
+  static TypeHandle get_class_type() {
+    return _type_handle;
+  }
+  static void init_type() {
+    qpCollisionHandler::init_type();
+    register_type(_type_handle, "qpCollisionHandlerQueue",
+                  qpCollisionHandler::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;
+};
+
+#endif
+
+
+

+ 220 - 0
panda/src/collide/qpcollisionLevelState.I

@@ -0,0 +1,220 @@
+// Filename: qpcollisionLevelState.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: qpCollisionLevelState::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionLevelState::
+qpCollisionLevelState(const qpNodePath &node_path) :
+  _node_path(node_path)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::Constructor
+//       Access: Public
+//  Description: This constructor goes to the next child node in the
+//               traversal.
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionLevelState::
+qpCollisionLevelState(const qpCollisionLevelState &parent, PandaNode *child) :
+  _node_path(parent._node_path, child),
+  _colliders(parent._colliders),
+  _current(parent._current),
+  _colliders_with_geom(parent._colliders_with_geom),
+  _local_bounds(parent._local_bounds)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_node_path
+//       Access: Public
+//  Description: Returns the NodePath representing the node instance
+//               we have traversed to.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath qpCollisionLevelState::
+get_node_path() const {
+  return _node_path.get_node_path();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_num_colliders
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE int qpCollisionLevelState::
+get_num_colliders() const {
+  return _colliders.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::has_collider
+//       Access: Public
+//  Description: Returns true if the nth collider in the LevelState is
+//               still part of the level.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionLevelState::
+has_collider(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), false);
+  return (_current & get_mask(n)) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::has_collider_with_geom
+//       Access: Public
+//  Description: Returns true if the nth collider in the LevelState is
+//               still part of the level, and it has the
+//               "collide_geom" flag set.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionLevelState::
+has_collider_with_geom(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), false);
+  return (_current & _colliders_with_geom & get_mask(n)) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::has_any_collider
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionLevelState::
+has_any_collider() const {
+  return _current != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::has_any_collide_geom
+//       Access: Public
+//  Description: Returns true if any Collider in the level state has
+//               the "collide_geom" flag set, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpCollisionLevelState::
+has_any_collide_geom() const {
+  return (_current & _colliders_with_geom) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::reached_collision_node
+//       Access: Public
+//  Description: Called by the traverser when we reach a CollisionNode
+//               in the traversal.  At this point, we zero out our set
+//               of colliders with the "collide_geom" flag set,
+//               because no colliders will test against geometry
+//               parented beneath a CollisionNode.
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionLevelState::
+reached_collision_node() {
+  _colliders_with_geom = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_collider
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE CollisionSolid *qpCollisionLevelState::
+get_collider(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), NULL);
+  nassertr(has_collider(n), NULL);
+
+  return _colliders[n]._collider;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_node
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionNode *qpCollisionLevelState::
+get_node(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), NULL);
+  nassertr(has_collider(n), NULL);
+
+  return _colliders[n]._node;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_space
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionLevelState::
+get_space(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), LMatrix4f::ident_mat());
+  nassertr(has_collider(n), LMatrix4f::ident_mat());
+
+  return _colliders[n]._space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_inv_space
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const LMatrix4f &qpCollisionLevelState::
+get_inv_space(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), LMatrix4f::ident_mat());
+  nassertr(has_collider(n), LMatrix4f::ident_mat());
+
+  return _colliders[n]._inv_space;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_local_bound
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE const GeometricBoundingVolume *qpCollisionLevelState::
+get_local_bound(int n) const {
+  nassertr(n >= 0 && n < (int)_colliders.size(), NULL);
+  nassertr(has_collider(n), NULL);
+  nassertr(n >= 0 && n < (int)_local_bounds.size(), NULL);
+
+  // For whatever reason, the Intel compiler can't figure this line
+  // out.
+  //return _local_bounds[n];
+
+  // But it can figure out this equivalent line.
+  return *(_local_bounds + n);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::omit_collider
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void qpCollisionLevelState::
+omit_collider(int n) {
+  nassertv(n >= 0 && n < (int)_colliders.size());
+  nassertv(has_collider(n));
+
+  _current &= ~get_mask(n);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::get_mask
+//       Access: Private
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE qpCollisionLevelState::ColliderMask qpCollisionLevelState::
+get_mask(int n) const {
+  return ((ColliderMask)1) << n;
+}

+ 100 - 0
panda/src/collide/qpcollisionLevelState.cxx

@@ -0,0 +1,100 @@
+// Filename: qpcollisionLevelState.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "qpcollisionLevelState.h"
+#include "collisionSolid.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::clear
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpCollisionLevelState::
+clear() {
+  _colliders.clear();
+  _local_bounds.clear();
+  _current = 0;
+  _colliders_with_geom = 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::reserve
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpCollisionLevelState::
+reserve(int max_colliders) {
+  _colliders.reserve(max_colliders);
+  _local_bounds.reserve(max_colliders);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::prepare_collider
+//       Access: Public
+//  Description: Adds the indicated Collider to the set of Colliders
+//               in the current level state.
+////////////////////////////////////////////////////////////////////
+void qpCollisionLevelState::
+prepare_collider(const ColliderDef &def) {
+  int index = (int)_colliders.size();
+  _colliders.push_back(def);
+
+  CollisionSolid *collider = def._collider;
+  const BoundingVolume &bv = collider->get_bound();
+  if (!bv.is_of_type(GeometricBoundingVolume::get_class_type())) {
+    _local_bounds.push_back((GeometricBoundingVolume *)NULL);
+  } else {
+    GeometricBoundingVolume *gbv;
+    DCAST_INTO_V(gbv, bv.make_copy());
+    gbv->xform(def._space);
+    _local_bounds.push_back(gbv);
+  }
+
+  _current |= get_mask(index);
+
+  if (def._node->get_collide_geom()) {
+    _colliders_with_geom |= get_mask(index);
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpCollisionLevelState::xform
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+void qpCollisionLevelState::
+xform(const LMatrix4f &mat) {
+  BoundingVolumes new_bounds;
+
+  int num_colliders = get_num_colliders();
+  new_bounds.reserve(num_colliders);
+  for (int c = 0; c < num_colliders; c++) {
+    if (!has_collider(c) ||
+        get_local_bound(c) == (GeometricBoundingVolume *)NULL) {
+      new_bounds.push_back((GeometricBoundingVolume *)NULL);
+    } else {
+      const GeometricBoundingVolume *old_bound = get_local_bound(c);
+      GeometricBoundingVolume *new_bound = 
+        DCAST(GeometricBoundingVolume, old_bound->make_copy());
+      new_bound->xform(mat);
+      new_bounds.push_back(new_bound);
+    }
+  }
+
+  _local_bounds = new_bounds;
+}

+ 98 - 0
panda/src/collide/qpcollisionLevelState.h

@@ -0,0 +1,98 @@
+// Filename: qpcollisionLevelState.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 qpCOLLISIONLEVELSTATE_H
+#define qpCOLLISIONLEVELSTATE_H
+
+#include "pandabase.h"
+
+#include "luse.h"
+#include "pointerToArray.h"
+#include "geometricBoundingVolume.h"
+#include "qpnodePath.h"
+#include "workingNodePath.h"
+
+#include "plist.h"
+
+class CollisionSolid;
+class qpCollisionNode;
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpCollisionLevelState
+// Description : This is the state information the
+//               CollisionTraverser retains for each level during
+//               traversal.
+////////////////////////////////////////////////////////////////////
+class qpCollisionLevelState {
+public:
+  class ColliderDef {
+  public:
+    CollisionSolid *_collider;
+    qpCollisionNode *_node;
+    LMatrix4f _space;
+    LMatrix4f _inv_space;
+  };
+
+  INLINE qpCollisionLevelState(const qpNodePath &node_path);
+  INLINE qpCollisionLevelState(const qpCollisionLevelState &parent, 
+                               PandaNode *child);
+
+  void clear();
+  void reserve(int max_colliders);
+  void prepare_collider(const ColliderDef &def);
+  void xform(const LMatrix4f &mat);
+
+  INLINE qpNodePath get_node_path() const;
+
+  INLINE int get_num_colliders() const;
+  INLINE bool has_collider(int n) const;
+  INLINE bool has_collider_with_geom(int n) const;
+  INLINE bool has_any_collider() const;
+  INLINE bool has_any_collide_geom() const;
+
+  INLINE void reached_collision_node();
+
+  INLINE CollisionSolid *get_collider(int n) const;
+  INLINE qpCollisionNode *get_node(int n) const;
+  INLINE const LMatrix4f &get_space(int n) const;
+  INLINE const LMatrix4f &get_inv_space(int n) const;
+  INLINE const GeometricBoundingVolume *get_local_bound(int n) const;
+
+  INLINE void omit_collider(int n);
+
+private:
+  typedef int ColliderMask;
+
+  INLINE ColliderMask get_mask(int n) const;
+
+  WorkingNodePath _node_path;
+
+  typedef PTA(ColliderDef) Colliders;
+  Colliders _colliders;
+  ColliderMask _current;
+  ColliderMask _colliders_with_geom;
+
+  typedef PTA(CPT(GeometricBoundingVolume)) BoundingVolumes;
+  BoundingVolumes _local_bounds;
+};
+
+#include "qpcollisionLevelState.I"
+
+#endif
+
+

+ 47 - 54
panda/src/collide/qpcollisionTraverser.cxx

@@ -18,7 +18,7 @@
 
 
 #include "qpcollisionTraverser.h"
 #include "qpcollisionTraverser.h"
 #include "qpcollisionNode.h"
 #include "qpcollisionNode.h"
-#include "collisionEntry.h"
+#include "qpcollisionEntry.h"
 #include "collisionPolygon.h"
 #include "collisionPolygon.h"
 #include "config_collide.h"
 #include "config_collide.h"
 
 
@@ -65,17 +65,17 @@ qpCollisionTraverser::
 //               again on the same node.
 //               again on the same node.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-add_collider(qpCollisionNode *node, CollisionHandler *handler) {
+add_collider(qpCollisionNode *node, qpCollisionHandler *handler) {
   nassertv(_ordered_colliders.size() == _colliders.size());
   nassertv(_ordered_colliders.size() == _colliders.size());
   nassertv(node != (qpCollisionNode *)NULL);
   nassertv(node != (qpCollisionNode *)NULL);
-  nassertv(handler != (CollisionHandler *)NULL);
+  nassertv(handler != (qpCollisionHandler *)NULL);
 
 
   Colliders::iterator ci = _colliders.find(node);
   Colliders::iterator ci = _colliders.find(node);
   if (ci != _colliders.end()) {
   if (ci != _colliders.end()) {
     // We already knew about this collider.
     // We already knew about this collider.
     if ((*ci).second != handler) {
     if ((*ci).second != handler) {
       // Change the handler.
       // Change the handler.
-      PT(CollisionHandler) old_handler = (*ci).second;
+      PT(qpCollisionHandler) old_handler = (*ci).second;
       (*ci).second = handler;
       (*ci).second = handler;
 
 
       // Now update our own reference counts within our handler set.
       // Now update our own reference counts within our handler set.
@@ -130,7 +130,7 @@ remove_collider(qpCollisionNode *node) {
     return false;
     return false;
   }
   }
 
 
-  CollisionHandler *handler = (*ci).second;
+  qpCollisionHandler *handler = (*ci).second;
 
 
   // Update the set of handlers.
   // Update the set of handlers.
   Handlers::iterator hi = _handlers.find(handler);
   Handlers::iterator hi = _handlers.find(handler);
@@ -197,7 +197,7 @@ get_collider(int n) const {
 //               serve the indicated collision node, or NULL if the
 //               serve the indicated collision node, or NULL if the
 //               node is not on the traverser's set of active nodes.
 //               node is not on the traverser's set of active nodes.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-CollisionHandler *qpCollisionTraverser::
+qpCollisionHandler *qpCollisionTraverser::
 get_handler(qpCollisionNode *node) const {
 get_handler(qpCollisionNode *node) const {
   Colliders::const_iterator ci = _colliders.find(node);
   Colliders::const_iterator ci = _colliders.find(node);
   if (ci != _colliders.end()) {
   if (ci != _colliders.end()) {
@@ -226,10 +226,9 @@ clear_colliders() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
 traverse(const qpNodePath &root) {
 traverse(const qpNodePath &root) {
-  /*
   PStatTimer timer(_collisions_pcollector);
   PStatTimer timer(_collisions_pcollector);
 
 
-  CollisionLevelState level_state(root);
+  qpCollisionLevelState level_state(root);
   prepare_colliders(level_state);
   prepare_colliders(level_state);
 
 
   Handlers::iterator hi;
   Handlers::iterator hi;
@@ -237,8 +236,7 @@ traverse(const qpNodePath &root) {
     (*hi).first->begin_group();
     (*hi).first->begin_group();
   }
   }
 
 
-  df_traverse(root.node(), *this, NullTransitionWrapper(),
-              level_state, _graph_type);
+  //  r_traverse(root.node(), level_state);
 
 
   hi = _handlers.begin();
   hi = _handlers.begin();
   while (hi != _handlers.end()) {
   while (hi != _handlers.end()) {
@@ -252,7 +250,6 @@ traverse(const qpNodePath &root) {
       ++hi;
       ++hi;
     }
     }
   }
   }
-  */
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -279,9 +276,9 @@ write(ostream &out, int indent_level) const {
   Colliders::const_iterator ci;
   Colliders::const_iterator ci;
   for (ci = _colliders.begin(); ci != _colliders.end(); ++ci) {
   for (ci = _colliders.begin(); ci != _colliders.end(); ++ci) {
     qpCollisionNode *cnode = (*ci).first;
     qpCollisionNode *cnode = (*ci).first;
-    CollisionHandler *handler = (*ci).second;
+    qpCollisionHandler *handler = (*ci).second;
     nassertv(cnode != (qpCollisionNode *)NULL);
     nassertv(cnode != (qpCollisionNode *)NULL);
-    nassertv(handler != (CollisionHandler *)NULL);
+    nassertv(handler != (qpCollisionHandler *)NULL);
 
 
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)
       << *cnode << " handled by " << handler->get_type() << "\n";
       << *cnode << " handled by " << handler->get_type() << "\n";
@@ -299,8 +296,7 @@ write(ostream &out, int indent_level) const {
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-prepare_colliders(CollisionLevelState &level_state) {
-  /*
+prepare_colliders(qpCollisionLevelState &level_state) {
   level_state.clear();
   level_state.clear();
   level_state.reserve(_colliders.size());
   level_state.reserve(_colliders.size());
 
 
@@ -308,10 +304,12 @@ prepare_colliders(CollisionLevelState &level_state) {
   while (i < (int)_ordered_colliders.size()) {
   while (i < (int)_ordered_colliders.size()) {
     qpCollisionNode *cnode = _ordered_colliders[i];
     qpCollisionNode *cnode = _ordered_colliders[i];
 
 
-    CollisionLevelState::ColliderDef def;
+    qpCollisionLevelState::ColliderDef def;
     def._node = cnode;
     def._node = cnode;
-    get_rel_mat(cnode, NULL, def._space, _graph_type);
-    def._inv_space.invert_from(def._space);
+    qpNodePath root;
+    qpNodePath cnode_path(cnode);
+    def._space = cnode_path.get_mat(root);
+    def._inv_space = root.get_mat(cnode_path);
 
 
 #ifndef NDEBUG
 #ifndef NDEBUG
     if (def._space.is_nan()) {
     if (def._space.is_nan()) {
@@ -333,32 +331,29 @@ prepare_colliders(CollisionLevelState &level_state) {
         i++;
         i++;
       }
       }
   }
   }
-  */
 }
 }
 
 
-/*
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: qpCollisionTraverser::reached_node
-//       Access: Public
+//     Function: qpCollisionTraverser::r_traverse
+//       Access: Private
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-bool qpCollisionTraverser::
-reached_node(Node *node, NullTransitionWrapper &,
-             CollisionLevelState &level_state) {
-  if (node->is_of_type(qpCollisionNode::get_class_type())) {
+void qpCollisionTraverser::
+r_traverse(PandaNode *node, qpCollisionLevelState &level_state) {
+  if (node->is_exact_type(qpCollisionNode::get_class_type())) {
     level_state.reached_collision_node();
     level_state.reached_collision_node();
 
 
     qpCollisionNode *cnode;
     qpCollisionNode *cnode;
-    DCAST_INTO_R(cnode, node, false);
+    DCAST_INTO_V(cnode, node);
     const BoundingVolume &node_bv = cnode->get_bound();
     const BoundingVolume &node_bv = cnode->get_bound();
     const GeometricBoundingVolume *node_gbv = NULL;
     const GeometricBoundingVolume *node_gbv = NULL;
     if (node_bv.is_of_type(GeometricBoundingVolume::get_class_type())) {
     if (node_bv.is_of_type(GeometricBoundingVolume::get_class_type())) {
-      DCAST_INTO_R(node_gbv, &node_bv, false);
+      DCAST_INTO_V(node_gbv, &node_bv);
     }
     }
 
 
-    CollisionEntry entry;
+    qpCollisionEntry entry;
     entry._into_node = cnode;
     entry._into_node = cnode;
-    entry._into_node_path = qpNodePath(level_state.get_arc_chain(), _graph_type);
+    entry._into_node_path = level_state.get_node_path();
     entry._into_space = entry._into_node_path.get_mat(qpNodePath());
     entry._into_space = entry._into_node_path.get_mat(qpNodePath());
 
 
     int num_colliders = level_state.get_num_colliders();
     int num_colliders = level_state.get_num_colliders();
@@ -372,7 +367,8 @@ reached_node(Node *node, NullTransitionWrapper &,
           entry._from_space = level_state.get_space(c);
           entry._from_space = level_state.get_space(c);
 
 
           qpNodePath root;
           qpNodePath root;
-          LMatrix4f into_space_inv = root.get_mat(entry._into_node_path);
+          const LMatrix4f &into_space_inv = 
+            root.get_mat(entry._into_node_path);
           entry._wrt_space = entry._from_space * into_space_inv;
           entry._wrt_space = entry._from_space * into_space_inv;
           entry._inv_wrt_space =
           entry._inv_wrt_space =
             entry._into_space * level_state.get_inv_space(c);
             entry._into_space * level_state.get_inv_space(c);
@@ -385,19 +381,19 @@ reached_node(Node *node, NullTransitionWrapper &,
       }
       }
     }
     }
 
 
-  } else if (node->is_of_type(qpGeomNode::get_class_type()) &&
-             level_state.has_any_collide_geom()) {
+  } else if (node->is_geom_node() && level_state.has_any_collide_geom()) {
     qpGeomNode *gnode;
     qpGeomNode *gnode;
-    DCAST_INTO_R(gnode, node, false);
+    DCAST_INTO_V(gnode, node);
     const BoundingVolume &node_bv = gnode->get_bound();
     const BoundingVolume &node_bv = gnode->get_bound();
     const GeometricBoundingVolume *node_gbv = NULL;
     const GeometricBoundingVolume *node_gbv = NULL;
     if (node_bv.is_of_type(GeometricBoundingVolume::get_class_type())) {
     if (node_bv.is_of_type(GeometricBoundingVolume::get_class_type())) {
-      DCAST_INTO_R(node_gbv, &node_bv, false);
+      DCAST_INTO_V(node_gbv, &node_bv);
     }
     }
 
 
-    CollisionEntry entry;
+    qpCollisionEntry entry;
     entry._into_node = gnode;
     entry._into_node = gnode;
-    get_rel_mat(node, NULL, entry._into_space, _graph_type);
+    entry._into_node_path = level_state.get_node_path();
+    entry._into_space = entry._into_node_path.get_mat(qpNodePath());
 
 
     int num_colliders = level_state.get_num_colliders();
     int num_colliders = level_state.get_num_colliders();
     for (int c = 0; c < num_colliders; c++) {
     for (int c = 0; c < num_colliders; c++) {
@@ -407,8 +403,9 @@ reached_node(Node *node, NullTransitionWrapper &,
         entry._from = level_state.get_collider(c);
         entry._from = level_state.get_collider(c);
         entry._from_space = level_state.get_space(c);
         entry._from_space = level_state.get_space(c);
 
 
-        LMatrix4f into_space_inv;
-        get_rel_mat(NULL, node, into_space_inv, _graph_type);
+        qpNodePath root;
+        const LMatrix4f &into_space_inv = 
+          root.get_mat(entry._into_node_path);
         entry._wrt_space = entry._from_space * into_space_inv;
         entry._wrt_space = entry._from_space * into_space_inv;
         entry._inv_wrt_space =
         entry._inv_wrt_space =
           entry._into_space * level_state.get_inv_space(c);
           entry._into_space * level_state.get_inv_space(c);
@@ -421,9 +418,13 @@ reached_node(Node *node, NullTransitionWrapper &,
     }
     }
   }
   }
 
 
-  return true;
+  int num_children = node->get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    PandaNode *child_node = node->get_child(i);
+    qpCollisionLevelState next_state(level_state, child_node);
+    r_traverse(child_node, next_state);
+  }
 }
 }
-*/
 
 
 /*
 /*
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -434,7 +435,7 @@ reached_node(Node *node, NullTransitionWrapper &,
 bool qpCollisionTraverser::
 bool qpCollisionTraverser::
 forward_arc(NodeRelation *arc, NullTransitionWrapper &,
 forward_arc(NodeRelation *arc, NullTransitionWrapper &,
             NullTransitionWrapper &, NullTransitionWrapper &,
             NullTransitionWrapper &, NullTransitionWrapper &,
-            CollisionLevelState &level_state) {
+            qpCollisionLevelState &level_state) {
   // Check the bounding volume on the arc against each of our
   // Check the bounding volume on the arc against each of our
   // colliders.
   // colliders.
   const BoundingVolume &arc_bv = arc->get_bound();
   const BoundingVolume &arc_bv = arc->get_bound();
@@ -485,10 +486,9 @@ forward_arc(NodeRelation *arc, NullTransitionWrapper &,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-compare_collider_to_node(CollisionEntry &entry,
+compare_collider_to_node(qpCollisionEntry &entry,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *into_node_gbv) {
                          const GeometricBoundingVolume *into_node_gbv) {
-  /*
   bool within_node_bounds = true;
   bool within_node_bounds = true;
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
       into_node_gbv != (GeometricBoundingVolume *)NULL) {
       into_node_gbv != (GeometricBoundingVolume *)NULL) {
@@ -518,7 +518,6 @@ compare_collider_to_node(CollisionEntry &entry,
       }
       }
     }
     }
   }
   }
-  */
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -527,10 +526,9 @@ compare_collider_to_node(CollisionEntry &entry,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-compare_collider_to_geom_node(CollisionEntry &entry,
+compare_collider_to_geom_node(qpCollisionEntry &entry,
                               const GeometricBoundingVolume *from_node_gbv,
                               const GeometricBoundingVolume *from_node_gbv,
                               const GeometricBoundingVolume *into_node_gbv) {
                               const GeometricBoundingVolume *into_node_gbv) {
-  /*
   bool within_node_bounds = true;
   bool within_node_bounds = true;
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
       into_node_gbv != (GeometricBoundingVolume *)NULL) {
       into_node_gbv != (GeometricBoundingVolume *)NULL) {
@@ -561,7 +559,6 @@ compare_collider_to_geom_node(CollisionEntry &entry,
       }
       }
     }
     }
   }
   }
-  */
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -570,10 +567,9 @@ compare_collider_to_geom_node(CollisionEntry &entry,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-compare_collider_to_solid(CollisionEntry &entry,
+compare_collider_to_solid(qpCollisionEntry &entry,
                           const GeometricBoundingVolume *from_node_gbv,
                           const GeometricBoundingVolume *from_node_gbv,
                           const GeometricBoundingVolume *solid_gbv) {
                           const GeometricBoundingVolume *solid_gbv) {
-  /*
   bool within_solid_bounds = true;
   bool within_solid_bounds = true;
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
       solid_gbv != (GeometricBoundingVolume *)NULL) {
       solid_gbv != (GeometricBoundingVolume *)NULL) {
@@ -585,7 +581,6 @@ compare_collider_to_solid(CollisionEntry &entry,
     nassertv(ci != _colliders.end());
     nassertv(ci != _colliders.end());
     entry.get_from()->test_intersection((*ci).second, entry, entry.get_into());
     entry.get_from()->test_intersection((*ci).second, entry, entry.get_into());
   }
   }
-  */
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -594,10 +589,9 @@ compare_collider_to_solid(CollisionEntry &entry,
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpCollisionTraverser::
 void qpCollisionTraverser::
-compare_collider_to_geom(CollisionEntry &entry, Geom *geom,
+compare_collider_to_geom(qpCollisionEntry &entry, Geom *geom,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *from_node_gbv,
                          const GeometricBoundingVolume *geom_gbv) {
                          const GeometricBoundingVolume *geom_gbv) {
-  /*
   bool within_geom_bounds = true;
   bool within_geom_bounds = true;
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
   if (from_node_gbv != (GeometricBoundingVolume *)NULL &&
       geom_gbv != (GeometricBoundingVolume *)NULL) {
       geom_gbv != (GeometricBoundingVolume *)NULL) {
@@ -627,5 +621,4 @@ compare_collider_to_geom(CollisionEntry &entry, Geom *geom,
       }
       }
     }
     }
   }
   }
-  */
 }
 }

+ 15 - 12
panda/src/collide/qpcollisionTraverser.h

@@ -21,8 +21,8 @@
 
 
 #include "pandabase.h"
 #include "pandabase.h"
 
 
-#include "collisionHandler.h"
-#include "collisionLevelState.h"
+#include "qpcollisionHandler.h"
+#include "qpcollisionLevelState.h"
 
 
 #include "traverserVisitor.h"
 #include "traverserVisitor.h"
 #include "nullTransitionWrapper.h"
 #include "nullTransitionWrapper.h"
@@ -36,6 +36,7 @@
 class qpCollisionNode;
 class qpCollisionNode;
 class Geom;
 class Geom;
 class qpNodePath;
 class qpNodePath;
+class qpCollisionEntry;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : qpCollisionTraverser
 //       Class : qpCollisionTraverser
@@ -46,12 +47,12 @@ PUBLISHED:
   qpCollisionTraverser();
   qpCollisionTraverser();
   ~qpCollisionTraverser();
   ~qpCollisionTraverser();
 
 
-  void add_collider(qpCollisionNode *node, CollisionHandler *handler);
+  void add_collider(qpCollisionNode *node, qpCollisionHandler *handler);
   bool remove_collider(qpCollisionNode *node);
   bool remove_collider(qpCollisionNode *node);
   bool has_collider(qpCollisionNode *node) const;
   bool has_collider(qpCollisionNode *node) const;
   int get_num_colliders() const;
   int get_num_colliders() const;
   qpCollisionNode *get_collider(int n) const;
   qpCollisionNode *get_collider(int n) const;
-  CollisionHandler *get_handler(qpCollisionNode *node) const;
+  qpCollisionHandler *get_handler(qpCollisionNode *node) const;
   void clear_colliders();
   void clear_colliders();
 
 
   void traverse(const qpNodePath &root);
   void traverse(const qpNodePath &root);
@@ -60,31 +61,33 @@ PUBLISHED:
   void write(ostream &out, int indent_level) const;
   void write(ostream &out, int indent_level) const;
 
 
 private:
 private:
-  void prepare_colliders(CollisionLevelState &state);
+  void prepare_colliders(qpCollisionLevelState &state);
 
 
-  void compare_collider_to_node(CollisionEntry &entry,
+  void r_traverse(PandaNode *node, qpCollisionLevelState &level_state);
+
+  void compare_collider_to_node(qpCollisionEntry &entry,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *into_node_gbv);
                                 const GeometricBoundingVolume *into_node_gbv);
-  void compare_collider_to_geom_node(CollisionEntry &entry,
+  void compare_collider_to_geom_node(qpCollisionEntry &entry,
                                      const GeometricBoundingVolume *from_node_gbv,
                                      const GeometricBoundingVolume *from_node_gbv,
                                      const GeometricBoundingVolume *into_node_gbv);
                                      const GeometricBoundingVolume *into_node_gbv);
-  void compare_collider_to_solid(CollisionEntry &entry,
+  void compare_collider_to_solid(qpCollisionEntry &entry,
                                  const GeometricBoundingVolume *from_node_gbv,
                                  const GeometricBoundingVolume *from_node_gbv,
                                  const GeometricBoundingVolume *solid_gbv);
                                  const GeometricBoundingVolume *solid_gbv);
-  void compare_collider_to_geom(CollisionEntry &entry, Geom *geom,
+  void compare_collider_to_geom(qpCollisionEntry &entry, Geom *geom,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *from_node_gbv,
                                 const GeometricBoundingVolume *solid_gbv);
                                 const GeometricBoundingVolume *solid_gbv);
 
 
 private:
 private:
-  PT(CollisionHandler) _default_handler;
+  PT(qpCollisionHandler) _default_handler;
   TypeHandle _graph_type;
   TypeHandle _graph_type;
 
 
-  typedef pmap<PT(qpCollisionNode),  PT(CollisionHandler) > Colliders;
+  typedef pmap<PT(qpCollisionNode),  PT(qpCollisionHandler) > Colliders;
   Colliders _colliders;
   Colliders _colliders;
   typedef pvector<qpCollisionNode *> OrderedColliders;
   typedef pvector<qpCollisionNode *> OrderedColliders;
   OrderedColliders _ordered_colliders;
   OrderedColliders _ordered_colliders;
 
 
-  typedef pmap<PT(CollisionHandler), int> Handlers;
+  typedef pmap<PT(qpCollisionHandler), int> Handlers;
   Handlers _handlers;
   Handlers _handlers;
 
 
   // Statistics
   // Statistics

+ 3 - 5
panda/src/egg2pg/qpeggLoader.cxx

@@ -467,11 +467,11 @@ load_texture(TextureDef &def, const EggTexture *egg_tex) {
   }
   }
 
 
   if (egg_keep_texture_pathnames) {
   if (egg_keep_texture_pathnames) {
-    tex->set_name(egg_tex->get_filename());
+    tex->set_filename(egg_tex->get_filename());
     if (egg_tex->has_alpha_file()) {
     if (egg_tex->has_alpha_file()) {
-      tex->set_alpha_name(egg_tex->get_alpha_file());
+      tex->set_alpha_filename(egg_tex->get_alpha_file());
     } else {
     } else {
-      tex->clear_alpha_name();
+      tex->clear_alpha_filename();
     }
     }
   }
   }
 
 
@@ -492,8 +492,6 @@ load_texture(TextureDef &def, const EggTexture *egg_tex) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpEggLoader::
 void qpEggLoader::
 apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
 apply_texture_attributes(Texture *tex, const EggTexture *egg_tex) {
-  tex->set_name(egg_tex->get_filename().get_fullpath());
-
   switch (egg_tex->determine_wrap_u()) {
   switch (egg_tex->determine_wrap_u()) {
   case EggTexture::WM_repeat:
   case EggTexture::WM_repeat:
     tex->set_wrapu(Texture::WM_repeat);
     tex->set_wrapu(Texture::WM_repeat);

+ 3 - 3
panda/src/egg2sg/eggLoader.cxx

@@ -533,11 +533,11 @@ load_texture(TextureDef &def, const EggTexture *egg_tex) {
   }
   }
 
 
   if (egg_keep_texture_pathnames) {
   if (egg_keep_texture_pathnames) {
-    tex->set_name(egg_tex->get_filename());
+    tex->set_filename(egg_tex->get_filename());
     if (egg_tex->has_alpha_file()) {
     if (egg_tex->has_alpha_file()) {
-      tex->set_alpha_name(egg_tex->get_alpha_file());
+      tex->set_alpha_filename(egg_tex->get_alpha_file());
     } else {
     } else {
-      tex->clear_alpha_name();
+      tex->clear_alpha_filename();
     }
     }
   }
   }
 
 

+ 87 - 32
panda/src/gobj/imageBuffer.I

@@ -16,59 +16,114 @@
 //
 //
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageBuffer::has_filename
+//       Access: Published
+//  Description: Returns true if the filename has been set and
+//               is available.  See set_filename().
+////////////////////////////////////////////////////////////////////
+INLINE bool ImageBuffer::
+has_filename() const {
+  return !_filename.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageBuffer::get_filename
+//       Access: Published
+//  Description: Returns the filename that has been set.  Use
+//               this in conjunction with get_name() to get the names
+//               of the file(s) that were loaded into the buffer.  See
+//               set_filename().
+////////////////////////////////////////////////////////////////////
+INLINE const Filename &ImageBuffer::
+get_filename() const {
+  return _filename;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageBuffer::has_alpha_filename
+//       Access: Published
+//  Description: Returns true if the alpha_filename has been set and
+//               is available.  See set_alpha_filename().
+////////////////////////////////////////////////////////////////////
+INLINE bool ImageBuffer::
+has_alpha_filename() const {
+  return !_alpha_filename.empty();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageBuffer::get_alpha_filename
+//       Access: Published
+//  Description: Returns the alpha_filename that has been set.  Use
+//               this in conjunction with get_name() to get the names
+//               of the file(s) that were loaded into the buffer.  See
+//               set_alpha_filename().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: ImageBuffer::set_alpha_name
+INLINE const Filename &ImageBuffer::
+get_alpha_filename() const {
+  return _alpha_filename;
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: ImageBuffer::set_filename
 //       Access: Public
 //       Access: Public
 //  Description: Sets the name of the file that contains the image's
 //  Description: Sets the name of the file that contains the image's
-//               alpha channel contents.  Normally, this is set
-//               automatically when the image is loaded, for instance
-//               via Texture::read().
+//               contents.  Normally, this is set automatically when
+//               the image is loaded, for instance via
+//               Texture::read().
 //
 //
-//               The ImageBuffer's get_name() function, by convention,
-//               returns the name of the image file that was loaded
-//               into the buffer.  In the case where a texture
-//               specified two separate files to load, a 1- or
-//               3-channel color image and a 1-channel alpha image,
-//               this string is update to contain the name of the
-//               image file that was loaded into the buffer's alpha
-//               channel.
+//               The ImageBuffer's get_name() function used to return
+//               the filename, but now returns just the basename
+//               (without the extension), which is a more useful name
+//               for identifying an image in show code.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void ImageBuffer::
 INLINE void ImageBuffer::
-set_alpha_name(const string &alpha_name) {
-  _alpha_name = alpha_name;
+set_filename(const Filename &filename) {
+  _filename = filename;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: ImageBuffer::clear_alpha_name
+//     Function: ImageBuffer::clear_filename
 //       Access: Public
 //       Access: Public
 //  Description: Removes the alpha filename, if it was previously set.
 //  Description: Removes the alpha filename, if it was previously set.
-//               See set_alpha_name().
+//               See set_filename().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void ImageBuffer::
 INLINE void ImageBuffer::
-clear_alpha_name() {
-  _alpha_name = string();
+clear_filename() {
+  _filename = Filename();
 }
 }
 
 
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: ImageBuffer::has_alpha_name
+//     Function: ImageBuffer::set_alpha_filename
 //       Access: Public
 //       Access: Public
-//  Description: Returns true if the alpha_name has been set and
-//               is available.  See set_alpha_name().
+//  Description: Sets the name of the file that contains the image's
+//               alpha channel contents.  Normally, this is set
+//               automatically when the image is loaded, for instance
+//               via Texture::read().
+//
+//               The ImageBuffer's get_filename() function returns the
+//               name of the image file that was loaded into the
+//               buffer.  In the case where a texture specified two
+//               separate files to load, a 1- or 3-channel color image
+//               and a 1-channel alpha image, this Filename is update
+//               to contain the name of the image file that was loaded
+//               into the buffer's alpha channel.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool ImageBuffer::
-has_alpha_name() const {
-  return !_alpha_name.empty();
+INLINE void ImageBuffer::
+set_alpha_filename(const Filename &alpha_filename) {
+  _alpha_filename = alpha_filename;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: ImageBuffer::get_alpha_name
+//     Function: ImageBuffer::clear_alpha_filename
 //       Access: Public
 //       Access: Public
-//  Description: Returns the alpha_name that has been set.  Use
-//               this in conjunction with get_name() to get the names
-//               of the file(s) that were loaded into the buffer.  See
-//               set_alpha_name().
+//  Description: Removes the alpha filename, if it was previously set.
+//               See set_alpha_filename().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE const string &ImageBuffer::
-get_alpha_name() const {
-  return _alpha_name;
+INLINE void ImageBuffer::
+clear_alpha_filename() {
+  _alpha_filename = Filename();
 }
 }

+ 14 - 6
panda/src/gobj/imageBuffer.cxx

@@ -43,8 +43,8 @@ TypeHandle ImageBuffer::_type_handle;
 void ImageBuffer::
 void ImageBuffer::
 write_datagram(BamWriter *, Datagram &me)
 write_datagram(BamWriter *, Datagram &me)
 {
 {
-  Filename filename = get_name();
-  Filename alpha_filename = get_alpha_name();
+  Filename filename = get_filename();
+  Filename alpha_filename = get_alpha_filename();
 
 
   switch (bam_texture_mode) {
   switch (bam_texture_mode) {
   case BTM_fullpath:
   case BTM_fullpath:
@@ -55,13 +55,13 @@ write_datagram(BamWriter *, Datagram &me)
     filename.find_on_searchpath(get_model_path());
     filename.find_on_searchpath(get_model_path());
     if (gobj_cat.is_debug()) {
     if (gobj_cat.is_debug()) {
       gobj_cat.debug()
       gobj_cat.debug()
-        << "Texture file " << get_name() << " found as " << filename << "\n";
+        << "Texture file " << get_filename() << " found as " << filename << "\n";
     }
     }
     alpha_filename.find_on_searchpath(get_texture_path());
     alpha_filename.find_on_searchpath(get_texture_path());
     alpha_filename.find_on_searchpath(get_model_path());
     alpha_filename.find_on_searchpath(get_model_path());
     if (gobj_cat.is_debug()) {
     if (gobj_cat.is_debug()) {
       gobj_cat.debug()
       gobj_cat.debug()
-        << "Alpha image " << get_alpha_name() << " found as " << alpha_filename << "\n";
+        << "Alpha image " << get_alpha_filename() << " found as " << alpha_filename << "\n";
     }
     }
     break;
     break;
 
 
@@ -75,6 +75,7 @@ write_datagram(BamWriter *, Datagram &me)
       << "Unsupported bam-texture-mode: " << (int)bam_texture_mode << "\n";
       << "Unsupported bam-texture-mode: " << (int)bam_texture_mode << "\n";
   }
   }
 
 
+  me.add_string(get_name());
   me.add_string(filename);
   me.add_string(filename);
   me.add_string(alpha_filename);
   me.add_string(alpha_filename);
 }
 }
@@ -89,6 +90,13 @@ write_datagram(BamWriter *, Datagram &me)
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void ImageBuffer::
 void ImageBuffer::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
-  set_name(scan.get_string());
-  set_alpha_name(scan.get_string());
+  if (manager->get_file_minor_ver() < 6) {
+    // No _filename before bams 3.6.
+    set_filename(scan.get_string());
+    set_name(_filename.get_basename_wo_extension());
+  } else {
+    set_name(scan.get_string());
+    set_filename(scan.get_string());
+  }
+  set_alpha_filename(scan.get_string());
 }
 }

+ 22 - 21
panda/src/gobj/imageBuffer.h

@@ -18,17 +18,14 @@
 
 
 #ifndef IMAGEBUFFER_H
 #ifndef IMAGEBUFFER_H
 #define IMAGEBUFFER_H
 #define IMAGEBUFFER_H
-//
-////////////////////////////////////////////////////////////////////
-// Includes
-////////////////////////////////////////////////////////////////////
-#include <pandabase.h>
+
+#include "pandabase.h"
 
 
 #include "drawable.h"
 #include "drawable.h"
-#include <pointerToArray.h>
-#include <typedef.h>
-#include <filename.h>
-#include <namable.h>
+#include "pointerToArray.h"
+#include "typedef.h"
+#include "filename.h"
+#include "namable.h"
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 // Defines
 // Defines
@@ -41,16 +38,12 @@ class DisplayRegion;
 //       Class : ImageBuffer
 //       Class : ImageBuffer
 // Description :
 // Description :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA ImageBuffer : public dDrawable, public Namable
-{
+class EXPCL_PANDA ImageBuffer : public dDrawable, public Namable {
 PUBLISHED:
 PUBLISHED:
-  ImageBuffer( void ) : dDrawable() { }
-  virtual ~ImageBuffer( void ) { }
+  ImageBuffer() : dDrawable() { }
+  virtual ~ImageBuffer() { }
 
 
 public:
 public:
-  virtual bool read( const string& name ) = 0;
-  virtual bool write( const string& name = "" ) const = 0;
-
   virtual void config( void ) { WritableConfigurable::config(); }
   virtual void config( void ) { WritableConfigurable::config(); }
 
 
   virtual void copy(GraphicsStateGuardianBase *, const DisplayRegion *)=0;
   virtual void copy(GraphicsStateGuardianBase *, const DisplayRegion *)=0;
@@ -61,13 +54,21 @@ public:
   virtual void draw(GraphicsStateGuardianBase *, const DisplayRegion *,
   virtual void draw(GraphicsStateGuardianBase *, const DisplayRegion *,
                     const RenderBuffer &rb)=0;
                     const RenderBuffer &rb)=0;
 
 
-  INLINE void set_alpha_name(const string &alpha_name);
-  INLINE void clear_alpha_name();
-  INLINE bool has_alpha_name() const;
-  INLINE const string &get_alpha_name() const;
+PUBLISHED:
+  INLINE bool has_filename() const;
+  INLINE const Filename &get_filename() const;
+  INLINE bool has_alpha_filename() const;
+  INLINE const Filename &get_alpha_filename() const;
+
+public:
+  INLINE void set_filename(const Filename &filename);
+  INLINE void clear_filename();
+  INLINE void set_alpha_filename(const Filename &alpha_filename);
+  INLINE void clear_alpha_filename();
 
 
 private:
 private:
-  string _alpha_name;
+  Filename _filename;
+  Filename _alpha_filename;
 
 
 public:
 public:
   //Abstract class, so no factory methods for Reading and Writing
   //Abstract class, so no factory methods for Reading and Writing

+ 9 - 8
panda/src/gobj/pixelBuffer.cxx

@@ -125,8 +125,8 @@ void PixelBuffer::config(void)
 //       Access:
 //       Access:
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-bool PixelBuffer::read(const string& name)
-{
+bool PixelBuffer::
+read(const Filename &name) {
   PNMImage pnmimage;
   PNMImage pnmimage;
 
 
   if (!pnmimage.read(name)) {
   if (!pnmimage.read(name)) {
@@ -135,8 +135,9 @@ bool PixelBuffer::read(const string& name)
     return false;
     return false;
   }
   }
 
 
-  set_name(name);
-  clear_alpha_name();
+  set_name(name.get_basename_wo_extension());
+  set_filename(name);
+  clear_alpha_filename();
   return load(pnmimage);
   return load(pnmimage);
 }
 }
 
 
@@ -145,16 +146,16 @@ bool PixelBuffer::read(const string& name)
 //       Access:
 //       Access:
 //  Description:
 //  Description:
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-bool PixelBuffer::write( const string& name ) const
-{
+bool PixelBuffer::
+write(const Filename &name) const {
   PNMImage pnmimage;
   PNMImage pnmimage;
   if (!store(pnmimage)) {
   if (!store(pnmimage)) {
     return false;
     return false;
   }
   }
 
 
-  string tname;
+  Filename tname;
   if (name.empty()) {
   if (name.empty()) {
-    tname = get_name();
+    tname = get_filename();
   } else {
   } else {
     tname = name;
     tname = name;
   }
   }

+ 3 - 2
panda/src/gobj/pixelBuffer.h

@@ -34,6 +34,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 class RenderBuffer;
 class RenderBuffer;
+class Filename;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : PixelBuffer
 //       Class : PixelBuffer
@@ -100,8 +101,8 @@ public:
 
 
   virtual void config( void );
   virtual void config( void );
 
 
-  virtual bool read( const string& name );
-  virtual bool write( const string& name = "" ) const;
+  bool read(const Filename &name);
+  bool write(const Filename &name) const;
 
 
   bool load( const PNMImage& pnmimage );
   bool load( const PNMImage& pnmimage );
   bool store( PNMImage& pnmimage ) const;
   bool store( PNMImage& pnmimage ) const;

+ 48 - 18
panda/src/gobj/texture.cxx

@@ -145,7 +145,7 @@ Texture::
 //  Description: Reads the texture from the indicated filename.
 //  Description: Reads the texture from the indicated filename.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
-read(const string &name) {
+read(const Filename &name) {
   PNMImage pnmimage;
   PNMImage pnmimage;
 
 
   if (!pnmimage.read(name)) {
   if (!pnmimage.read(name)) {
@@ -157,8 +157,9 @@ read(const string &name) {
   // Check to see if we need to scale it.
   // Check to see if we need to scale it.
   consider_rescale(pnmimage, name);
   consider_rescale(pnmimage, name);
 
 
-  set_name(name);
-  clear_alpha_name();
+  set_name(name.get_basename_wo_extension());
+  set_filename(name);
+  clear_alpha_filename();
   return load(pnmimage);
   return load(pnmimage);
 }
 }
 
 
@@ -169,7 +170,7 @@ read(const string &name) {
 //               to get a 4-component image
 //               to get a 4-component image
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
-read(const string &name, const string &gray) {
+read(const Filename &name, const Filename &gray) {
   PNMImage pnmimage;
   PNMImage pnmimage;
   if (!pnmimage.read(name)) {
   if (!pnmimage.read(name)) {
     gobj_cat.error()
     gobj_cat.error()
@@ -210,8 +211,9 @@ read(const string &name, const string &gray) {
     }
     }
   }
   }
 
 
-  set_name(name);
-  set_alpha_name(gray);
+  set_name(name.get_basename_wo_extension());
+  set_filename(name);
+  set_alpha_filename(gray);
   return load(pnmimage);
   return load(pnmimage);
 }
 }
 
 
@@ -221,9 +223,19 @@ read(const string &name, const string &gray) {
 //  Description: Writes the texture to the indicated filename.
 //  Description: Writes the texture to the indicated filename.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool Texture::
 bool Texture::
-write(const string &name) const {
+write(const Filename &name) const {
   nassertr(has_ram_image(), false);
   nassertr(has_ram_image(), false);
-  return _pbuffer->write(name);
+  PNMImage pnmimage;
+  if (!_pbuffer->store(pnmimage)) {
+    return false;
+  }
+
+  if (!pnmimage.write(name)) {
+    gobj_cat.error()
+      << "Texture::write() - couldn't write: " << name << endl;
+    return false;
+  }
+  return true;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -476,15 +488,21 @@ clear_gsg(GraphicsStateGuardianBase *gsg) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PixelBuffer *Texture::
 PixelBuffer *Texture::
 get_ram_image() {
 get_ram_image() {
-  if (!has_ram_image() && has_name()) {
+  if (!has_ram_image() && has_filename()) {
     // Now we have to reload the texture image.
     // Now we have to reload the texture image.
+    string name = get_name();
     gobj_cat.info()
     gobj_cat.info()
-      << "Reloading texture " << get_name() << "\n";
-    if (has_alpha_name()) {
-      read(get_name(), get_alpha_name());
+      << "Reloading texture " << name << "\n";
+    
+    if (has_alpha_filename()) {
+      read(get_filename(), get_alpha_filename());
     } else {
     } else {
-      read(get_name());
+      read(get_filename());
     }
     }
+
+    // Just in case the read operation changed our name, we should
+    // change it back.
+    set_name(name);
   }
   }
 
 
   if (has_ram_image()) {
   if (has_ram_image()) {
@@ -631,15 +649,26 @@ make_Texture(const FactoryParams &params) {
 
 
   parse_params(params, scan, manager);
   parse_params(params, scan, manager);
 
 
-  string name = scan.get_string();
-  string alpha_name = scan.get_string();
+  string name;
+  Filename filename, alpha_filename;
+
+  if (manager->get_file_minor_ver() < 6) {
+    // No _filename before bams 3.6.
+    filename = scan.get_string();
+    name = filename.get_basename_wo_extension();
+  } else {
+    name = scan.get_string();
+    filename = scan.get_string();
+  }
+
+  alpha_filename = scan.get_string();
 
 
   PT(Texture) me;
   PT(Texture) me;
 
 
-  if (alpha_name.empty()) {
-    me = TexturePool::load_texture(name);
+  if (alpha_filename.empty()) {
+    me = TexturePool::load_texture(filename);
   } else {
   } else {
-    me = TexturePool::load_texture(name, alpha_name);
+    me = TexturePool::load_texture(filename, alpha_filename);
   }
   }
 
 
   if (me == (Texture *)NULL) {
   if (me == (Texture *)NULL) {
@@ -650,6 +679,7 @@ make_Texture(const FactoryParams &params) {
     dummy->fillin(scan, manager);
     dummy->fillin(scan, manager);
 
 
   } else {
   } else {
+    me->set_name(name);
     if (gobj_cat.is_debug()) {
     if (gobj_cat.is_debug()) {
       gobj_cat->debug() << "Created texture " << me->get_name() << endl;
       gobj_cat->debug() << "Created texture " << me->get_name() << endl;
     }
     }

+ 3 - 3
panda/src/gobj/texture.h

@@ -83,9 +83,9 @@ PUBLISHED:
   Texture();
   Texture();
   ~Texture();
   ~Texture();
 
 
-  virtual bool read(const string &name);
-  virtual bool read(const string &name, const string &gray);
-  virtual bool write(const string &name = "") const;
+  bool read(const Filename &name);
+  bool read(const Filename &name, const Filename &gray);
+  bool write(const Filename &name = "") const;
 
 
   void set_wrapu(WrapMode wrap);
   void set_wrapu(WrapMode wrap);
   void set_wrapv(WrapMode wrap);
   void set_wrapv(WrapMode wrap);

+ 2 - 2
panda/src/gobj/texturePool.cxx

@@ -127,7 +127,7 @@ ns_load_texture(Filename filename, Filename grayfilename) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 void TexturePool::
 ns_add_texture(Texture *tex) {
 ns_add_texture(Texture *tex) {
-  string filename = tex->get_name();
+  string filename = tex->get_filename();
   if (filename.empty()) {
   if (filename.empty()) {
     gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
     gobj_cat.error() << "Attempt to call add_texture() on an unnamed texture.\n";
   }
   }
@@ -143,7 +143,7 @@ ns_add_texture(Texture *tex) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TexturePool::
 void TexturePool::
 ns_release_texture(Texture *tex) {
 ns_release_texture(Texture *tex) {
-  string filename = tex->get_name();
+  string filename = tex->get_filename();
   Textures::iterator ti;
   Textures::iterator ti;
   ti = _textures.find(filename);
   ti = _textures.find(filename);
   if (ti != _textures.end() && (*ti).second == tex) {
   if (ti != _textures.end() && (*ti).second == tex) {

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

@@ -56,8 +56,10 @@
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureAttrib.I textureAttrib.h \
     textureAttrib.I textureAttrib.h \
+    textureCollection.I textureCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
-    transparencyAttrib.I transparencyAttrib.h
+    transparencyAttrib.I transparencyAttrib.h \
+    workingNodePath.I workingNodePath.h
 
 
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx    
   #define INCLUDED_SOURCES \
   #define INCLUDED_SOURCES \
@@ -111,8 +113,10 @@
     texMatrixAttrib.cxx \
     texMatrixAttrib.cxx \
     textureApplyAttrib.cxx \
     textureApplyAttrib.cxx \
     textureAttrib.cxx \
     textureAttrib.cxx \
+    textureCollection.cxx \
     transformState.cxx \
     transformState.cxx \
-    transparencyAttrib.cxx
+    transparencyAttrib.cxx \
+    workingNodePath.cxx
 
 
   #if $[DONT_COMBINE_PGRAPH]    
   #if $[DONT_COMBINE_PGRAPH]    
     #define SOURCES $[SOURCES] $[INCLUDED_SOURCES]
     #define SOURCES $[SOURCES] $[INCLUDED_SOURCES]
@@ -168,8 +172,10 @@
     texMatrixAttrib.I texMatrixAttrib.h \
     texMatrixAttrib.I texMatrixAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureApplyAttrib.I textureApplyAttrib.h \
     textureAttrib.I textureAttrib.h \
     textureAttrib.I textureAttrib.h \
+    textureCollection.I textureCollection.h \
     transformState.I transformState.h \
     transformState.I transformState.h \
-    transparencyAttrib.I transparencyAttrib.h
+    transparencyAttrib.I transparencyAttrib.h \
+    workingNodePath.I workingNodePath.h
 
 
 // No need to install these.
 // No need to install these.
 //    qpfindApproxLevel.I qpfindApproxLevel.h \
 //    qpfindApproxLevel.I qpfindApproxLevel.h \

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

@@ -347,6 +347,7 @@ private:
   friend class PandaNode::Children;
   friend class PandaNode::Children;
   friend class qpNodePath;
   friend class qpNodePath;
   friend class qpNodePathComponent;
   friend class qpNodePathComponent;
+  friend class WorkingNodePath;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const PandaNode &node) {
 INLINE ostream &operator << (ostream &out, const PandaNode &node) {

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

@@ -26,5 +26,7 @@
 #include "texMatrixAttrib.cxx"
 #include "texMatrixAttrib.cxx"
 #include "textureApplyAttrib.cxx"
 #include "textureApplyAttrib.cxx"
 #include "textureAttrib.cxx"
 #include "textureAttrib.cxx"
+#include "textureCollection.cxx"
 #include "transformState.cxx"
 #include "transformState.cxx"
 #include "transparencyAttrib.cxx"
 #include "transparencyAttrib.cxx"
+#include "workingNodePath.cxx"

+ 78 - 30
panda/src/pgraph/qpnodePath.I

@@ -152,6 +152,45 @@ fail() {
   return result;
   return result;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::set_max_search_depth
+//       Access: Published, Static
+//  Description: Certain operations, such as extend_down_to() or
+//               find_all_matches(), require a traversal of the scene
+//               graph to search for the target node or nodes.  This
+//               traversal does not attempt to detect cycles, so an
+//               arbitrary cap is set on the depth of the traversal as
+//               a poor man's cycle detection, in the event that a
+//               cycle has inadvertently been introduced into the
+//               scene graph.
+//
+//               There may be other reasons you'd want to truncate a
+//               search before the bottom of the scene graph has been
+//               reached.  In any event, this function sets the limit
+//               on the number of levels that a traversal will
+//               continue, and hence the maximum length of a path that
+//               may be returned by a traversal.
+//
+//               This is a static method, and so changing this
+//               parameter affects all of the NodePaths in the
+//               universe.
+////////////////////////////////////////////////////////////////////
+INLINE void qpNodePath::
+set_max_search_depth(int max_search_depth) {
+  _max_search_depth = max_search_depth;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_max_search_depth
+//       Access: Published, Static
+//  Description: Returns the current setting of the search depth
+//               limit.  See set_max_search_depth.
+////////////////////////////////////////////////////////////////////
+INLINE int qpNodePath::
+get_max_search_depth() {
+  return _max_search_depth;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::is_empty
 //     Function: qpNodePath::is_empty
 //       Access: Published
 //       Access: Published
@@ -200,6 +239,31 @@ node() const {
   return _head->get_node();
   return _head->get_node();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::get_key
+//       Access: Published
+//  Description: Returns an integer that is guaranteed to be the same
+//               for all NodePaths that represent the same node
+//               instance, and different for all NodePaths that
+//               represent a different node instance.  
+//
+//               The same key will be returned for a particular
+//               instance as long as at least one NodePath exists that
+//               represents that instance; if all NodePaths for a
+//               particular instance destruct and a new one is later
+//               created, it may have a different index.  However, a
+//               given key will never be reused for a different
+//               instance (unless the app has been running long enough
+//               that we overflow the integer key value).
+////////////////////////////////////////////////////////////////////
+INLINE int qpNodePath::
+get_key() const {
+  if (is_empty()) {
+    return 0;
+  }
+  return _head->get_key();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::get_num_children
 //     Function: qpNodePath::get_num_children
 //       Access: Published
 //       Access: Published
@@ -207,7 +271,7 @@ node() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE int qpNodePath::
 INLINE int qpNodePath::
 get_num_children() const {
 get_num_children() const {
-  nassertr(!is_empty(), 0);
+  nassertr_always(!is_empty(), 0);
   return _head->get_node()->get_num_children();
   return _head->get_node()->get_num_children();
 }
 }
 
 
@@ -260,27 +324,11 @@ get_parent() const {
 INLINE qpNodePath qpNodePath::
 INLINE qpNodePath qpNodePath::
 attach_new_node(const string &name, int sort) const {
 attach_new_node(const string &name, int sort) const {
   nassertr(verify_complete(), qpNodePath::fail());
   nassertr(verify_complete(), qpNodePath::fail());
-  nassertr(!is_empty(), *this);
+  nassertr_always(!is_empty(), *this);
 
 
   return attach_new_node(new PandaNode(name), sort);
   return attach_new_node(new PandaNode(name), sort);
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: qpNodePath::output
-//       Access: Published
-//  Description: Writes a sensible description of the qpNodePath to the
-//               indicated output stream.
-////////////////////////////////////////////////////////////////////
-INLINE void qpNodePath::
-output(ostream &out) const {
-  uncollapse_head();
-  if (_head == (qpNodePathComponent *)NULL) {
-    out << "(empty)";
-  } else {
-    r_output(out, _head);
-  }
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::ls
 //     Function: qpNodePath::ls
 //       Access: Published
 //       Access: Published
@@ -334,7 +382,7 @@ ls_transforms() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(RenderState) qpNodePath::
 INLINE CPT(RenderState) qpNodePath::
 get_state() const {
 get_state() const {
-  nassertr(!is_empty(), RenderState::make_empty());
+  nassertr_always(!is_empty(), RenderState::make_empty());
   return node()->get_state();
   return node()->get_state();
 }
 }
 
 
@@ -345,7 +393,7 @@ get_state() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 set_state(const RenderState *state) const {
 set_state(const RenderState *state) const {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_state(state);
   node()->set_state(state);
 }
 }
 
 
@@ -367,7 +415,7 @@ get_net_state() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(TransformState) qpNodePath::
 INLINE CPT(TransformState) qpNodePath::
 get_transform() const {
 get_transform() const {
-  nassertr(!is_empty(), TransformState::make_identity());
+  nassertr_always(!is_empty(), TransformState::make_identity());
   return node()->get_transform();
   return node()->get_transform();
 }
 }
 
 
@@ -378,7 +426,7 @@ get_transform() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 set_transform(const TransformState *transform) const {
 set_transform(const TransformState *transform) const {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_transform(transform);
   node()->set_transform(transform);
 }
 }
 
 
@@ -519,7 +567,7 @@ set_pos_hpr_scale(float x, float y, float z, float h, float p, float r,
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 clear_mat() {
 clear_mat() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_transform();
   node()->clear_transform();
 }
 }
 
 
@@ -531,7 +579,7 @@ clear_mat() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE bool qpNodePath::
 INLINE bool qpNodePath::
 has_mat() const {
 has_mat() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return !node()->get_transform()->is_identity();
   return !node()->get_transform()->is_identity();
 }
 }
 
 
@@ -544,7 +592,7 @@ has_mat() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const LMatrix4f &qpNodePath::
 INLINE const LMatrix4f &qpNodePath::
 get_mat() const {
 get_mat() const {
-  nassertr(!is_empty(), LMatrix4f::ident_mat());
+  nassertr_always(!is_empty(), LMatrix4f::ident_mat());
 
 
   return node()->get_transform()->get_mat();
   return node()->get_transform()->get_mat();
 }
 }
@@ -882,7 +930,7 @@ get_distance(const qpNodePath &other) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 adjust_all_priorities(int adjustment) {
 adjust_all_priorities(int adjustment) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   r_adjust_all_priorities(node(), adjustment);
   r_adjust_all_priorities(node(), adjustment);
 }
 }
 
 
@@ -895,7 +943,7 @@ adjust_all_priorities(int adjustment) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 show() {
 show() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_draw_mask(DrawMask::all_on());
   node()->set_draw_mask(DrawMask::all_on());
 }
 }
 
 
@@ -909,7 +957,7 @@ show() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 show(DrawMask camera_mask) {
 show(DrawMask camera_mask) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_draw_mask(node()->get_draw_mask() | camera_mask);
   node()->set_draw_mask(node()->get_draw_mask() | camera_mask);
 }
 }
 
 
@@ -924,7 +972,7 @@ show(DrawMask camera_mask) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 hide() {
 hide() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_draw_mask(DrawMask::all_off());
   node()->set_draw_mask(DrawMask::all_off());
 }
 }
 
 
@@ -938,7 +986,7 @@ hide() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE void qpNodePath::
 INLINE void qpNodePath::
 hide(DrawMask camera_mask) {
 hide(DrawMask camera_mask) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->set_draw_mask(node()->get_draw_mask() & ~camera_mask);
   node()->set_draw_mask(node()->get_draw_mask() & ~camera_mask);
 }
 }
 
 

+ 261 - 81
panda/src/pgraph/qpnodePath.cxx

@@ -39,6 +39,8 @@
 #include "boundingSphere.h"
 #include "boundingSphere.h"
 #include "qpgeomNode.h"
 #include "qpgeomNode.h"
 #include "qpsceneGraphReducer.h"
 #include "qpsceneGraphReducer.h"
+#include "textureCollection.h"
+#include "globPattern.h"
 
 
 // stack seems to overflow on Intel C++ at 7000.  If we need more than 
 // stack seems to overflow on Intel C++ at 7000.  If we need more than 
 // 7000, need to increase stack size.
 // 7000, need to increase stack size.
@@ -119,7 +121,7 @@ get_top_node() const {
 qpNodePathCollection qpNodePath::
 qpNodePathCollection qpNodePath::
 get_children() const {
 get_children() const {
   qpNodePathCollection result;
   qpNodePathCollection result;
-  nassertr(!is_empty(), result);
+  nassertr_always(!is_empty(), result);
 
 
   PandaNode *bottom_node = node();
   PandaNode *bottom_node = node();
 
 
@@ -144,7 +146,7 @@ get_children() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 qpNodePath qpNodePath::
 qpNodePath qpNodePath::
 find(const string &path) const {
 find(const string &path) const {
-  nassertr(!is_empty(), fail());
+  nassertr_always(!is_empty(), fail());
 
 
   qpNodePathCollection col;
   qpNodePathCollection col;
   find_matches(col, path, 1);
   find_matches(col, path, 1);
@@ -167,7 +169,7 @@ find(const string &path) const {
 qpNodePathCollection qpNodePath::
 qpNodePathCollection qpNodePath::
 find_all_matches(const string &path) const {
 find_all_matches(const string &path) const {
   qpNodePathCollection col;
   qpNodePathCollection col;
-  nassertr(!is_empty(), col);
+  nassertr_always(!is_empty(), col);
   nassertr(verify_complete(), col);
   nassertr(verify_complete(), col);
   find_matches(col, path, -1);
   find_matches(col, path, -1);
   return col;
   return col;
@@ -183,7 +185,7 @@ find_all_matches(const string &path) const {
 qpNodePathCollection qpNodePath::
 qpNodePathCollection qpNodePath::
 find_all_paths_to(PandaNode *node) const {
 find_all_paths_to(PandaNode *node) const {
   qpNodePathCollection col;
   qpNodePathCollection col;
-  nassertr(!is_empty(), col);
+  nassertr_always(!is_empty(), col);
   nassertr(verify_complete(), col);
   nassertr(verify_complete(), col);
   nassertr(node != (PandaNode *)NULL, col);
   nassertr(node != (PandaNode *)NULL, col);
   qpFindApproxPath approx_path;
   qpFindApproxPath approx_path;
@@ -250,7 +252,7 @@ wrt_reparent_to(const qpNodePath &other, int sort) {
 qpNodePath qpNodePath::
 qpNodePath qpNodePath::
 instance_to(const qpNodePath &other, int sort) const {
 instance_to(const qpNodePath &other, int sort) const {
   nassertr(verify_complete(), qpNodePath::fail());
   nassertr(verify_complete(), qpNodePath::fail());
-  nassertr(!is_empty(), qpNodePath::fail());
+  nassertr_always(!is_empty(), qpNodePath::fail());
   nassertr(!other.is_empty(), qpNodePath::fail());
   nassertr(!other.is_empty(), qpNodePath::fail());
 
 
   uncollapse_head();
   uncollapse_head();
@@ -279,7 +281,7 @@ instance_to(const qpNodePath &other, int sort) const {
 qpNodePath qpNodePath::
 qpNodePath qpNodePath::
 copy_to(const qpNodePath &other, int sort) const {
 copy_to(const qpNodePath &other, int sort) const {
   nassertr(verify_complete(), fail());
   nassertr(verify_complete(), fail());
-  nassertr(!is_empty(), fail());
+  nassertr_always(!is_empty(), fail());
   nassertr(!other.is_empty(), fail());
   nassertr(!other.is_empty(), fail());
 
 
   PandaNode *source_node = node();
   PandaNode *source_node = node();
@@ -305,7 +307,7 @@ copy_to(const qpNodePath &other, int sort) const {
 qpNodePath qpNodePath::
 qpNodePath qpNodePath::
 attach_new_node(PandaNode *node, int sort) const {
 attach_new_node(PandaNode *node, int sort) const {
   nassertr(verify_complete(), qpNodePath::fail());
   nassertr(verify_complete(), qpNodePath::fail());
-  nassertr(!is_empty(), qpNodePath());
+  nassertr_always(!is_empty(), qpNodePath());
   nassertr(node != (PandaNode *)NULL, qpNodePath());
   nassertr(node != (PandaNode *)NULL, qpNodePath());
 
 
   uncollapse_head();
   uncollapse_head();
@@ -348,6 +350,37 @@ remove_node() {
   (*this) = qpNodePath::removed();
   (*this) = qpNodePath::removed();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::output
+//       Access: Published
+//  Description: Writes a sensible description of the qpNodePath to the
+//               indicated output stream.
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+output(ostream &out) const {
+  uncollapse_head();
+
+  switch (_error_type) {
+  case ET_not_found:
+    out << "**not found**";
+    return;
+  case ET_removed:
+    out << "**removed**";
+    return;
+  case ET_fail:
+    out << "**error**";
+    return;
+  default:
+    break;
+  }
+
+  if (_head == (qpNodePathComponent *)NULL) {
+    out << "(empty)";
+  } else {
+    r_output(out, _head);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::get_state
 //     Function: qpNodePath::get_state
 //       Access: Published
 //       Access: Published
@@ -452,13 +485,13 @@ set_transform(const qpNodePath &other, const TransformState *transform) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_pos(const LVecBase3f &pos) {
 set_pos(const LVecBase3f &pos) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   set_transform(get_transform()->set_pos(pos));
   set_transform(get_transform()->set_pos(pos));
 }
 }
 
 
 void qpNodePath::
 void qpNodePath::
 set_x(float x) {
 set_x(float x) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos();
   LPoint3f pos = get_pos();
   pos[0] = x;
   pos[0] = x;
   set_pos(pos);
   set_pos(pos);
@@ -466,7 +499,7 @@ set_x(float x) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_y(float y) {
 set_y(float y) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos();
   LPoint3f pos = get_pos();
   pos[1] = y;
   pos[1] = y;
   set_pos(pos);
   set_pos(pos);
@@ -474,7 +507,7 @@ set_y(float y) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_z(float z) {
 set_z(float z) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos();
   LPoint3f pos = get_pos();
   pos[2] = z;
   pos[2] = z;
   set_pos(pos);
   set_pos(pos);
@@ -487,7 +520,7 @@ set_z(float z) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LPoint3f qpNodePath::
 LPoint3f qpNodePath::
 get_pos() const {
 get_pos() const {
-  nassertr(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
   return get_transform()->get_pos();
   return get_transform()->get_pos();
 }
 }
 
 
@@ -499,7 +532,7 @@ get_pos() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_hpr(const LVecBase3f &hpr) {
 set_hpr(const LVecBase3f &hpr) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   set_transform(transform->set_hpr(hpr));
   set_transform(transform->set_hpr(hpr));
@@ -507,7 +540,7 @@ set_hpr(const LVecBase3f &hpr) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_h(float h) {
 set_h(float h) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -517,7 +550,7 @@ set_h(float h) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_p(float p) {
 set_p(float p) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -527,7 +560,7 @@ set_p(float p) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_r(float r) {
 set_r(float r) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -542,7 +575,7 @@ set_r(float r) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LVecBase3f qpNodePath::
 LVecBase3f qpNodePath::
 get_hpr() const {
 get_hpr() const {
-  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   return transform->get_hpr();
   return transform->get_hpr();
@@ -569,7 +602,7 @@ get_hpr(float roll) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_scale(const LVecBase3f &scale) {
 set_scale(const LVecBase3f &scale) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   set_transform(transform->set_scale(scale));
   set_transform(transform->set_scale(scale));
@@ -577,7 +610,7 @@ set_scale(const LVecBase3f &scale) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sx(float sx) {
 set_sx(float sx) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -587,7 +620,7 @@ set_sx(float sx) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sy(float sy) {
 set_sy(float sy) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -597,7 +630,7 @@ set_sy(float sy) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sz(float sz) {
 set_sz(float sz) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -612,7 +645,7 @@ set_sz(float sz) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LVecBase3f qpNodePath::
 LVecBase3f qpNodePath::
 get_scale() const {
 get_scale() const {
-  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
   CPT(TransformState) transform = get_transform();
   CPT(TransformState) transform = get_transform();
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   return transform->get_scale();
   return transform->get_scale();
@@ -696,7 +729,7 @@ set_color_scale(const LVecBase4f &scale) {
 const LVecBase4f &qpNodePath::
 const LVecBase4f &qpNodePath::
 get_color_scale() const {
 get_color_scale() const {
   static const LVecBase4f ident_scale(1.0f, 1.0f, 1.0f, 1.0f);
   static const LVecBase4f ident_scale(1.0f, 1.0f, 1.0f, 1.0f);
-  nassertr(!is_empty(), ident_scale);
+  nassertr_always(!is_empty(), ident_scale);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(ColorScaleAttrib::get_class_type());
     node()->get_attrib(ColorScaleAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -715,7 +748,7 @@ get_color_scale() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 look_at(const LPoint3f &point, const LVector3f &up) {
 look_at(const LPoint3f &point, const LVector3f &up) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   LPoint3f pos = get_pos();
   LPoint3f pos = get_pos();
 
 
@@ -736,7 +769,7 @@ look_at(const LPoint3f &point, const LVector3f &up) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 heads_up(const LPoint3f &point, const LVector3f &up) {
 heads_up(const LPoint3f &point, const LVector3f &up) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   LPoint3f pos = get_pos();
   LPoint3f pos = get_pos();
 
 
@@ -756,13 +789,13 @@ heads_up(const LPoint3f &point, const LVector3f &up) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_pos(const qpNodePath &other, const LVecBase3f &pos) {
 set_pos(const qpNodePath &other, const LVecBase3f &pos) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   set_transform(other, get_transform(other)->set_pos(pos));
   set_transform(other, get_transform(other)->set_pos(pos));
 }
 }
 
 
 void qpNodePath::
 void qpNodePath::
 set_x(const qpNodePath &other, float x) {
 set_x(const qpNodePath &other, float x) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos(other);
   LPoint3f pos = get_pos(other);
   pos[0] = x;
   pos[0] = x;
   set_pos(other, pos);
   set_pos(other, pos);
@@ -770,7 +803,7 @@ set_x(const qpNodePath &other, float x) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_y(const qpNodePath &other, float y) {
 set_y(const qpNodePath &other, float y) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos(other);
   LPoint3f pos = get_pos(other);
   pos[1] = y;
   pos[1] = y;
   set_pos(other, pos);
   set_pos(other, pos);
@@ -778,7 +811,7 @@ set_y(const qpNodePath &other, float y) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_z(const qpNodePath &other, float z) {
 set_z(const qpNodePath &other, float z) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   LPoint3f pos = get_pos(other);
   LPoint3f pos = get_pos(other);
   pos[2] = z;
   pos[2] = z;
   set_pos(other, pos);
   set_pos(other, pos);
@@ -792,7 +825,7 @@ set_z(const qpNodePath &other, float z) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LPoint3f qpNodePath::
 LPoint3f qpNodePath::
 get_pos(const qpNodePath &other) const {
 get_pos(const qpNodePath &other) const {
-  nassertr(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LPoint3f(0.0f, 0.0f, 0.0f));
   return get_transform(other)->get_pos();
   return get_transform(other)->get_pos();
 }
 }
 
 
@@ -804,7 +837,7 @@ get_pos(const qpNodePath &other) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_hpr(const qpNodePath &other, const LVecBase3f &hpr) {
 set_hpr(const qpNodePath &other, const LVecBase3f &hpr) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   set_transform(other, transform->set_hpr(hpr));
   set_transform(other, transform->set_hpr(hpr));
@@ -812,7 +845,7 @@ set_hpr(const qpNodePath &other, const LVecBase3f &hpr) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_h(const qpNodePath &other, float h) {
 set_h(const qpNodePath &other, float h) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -822,7 +855,7 @@ set_h(const qpNodePath &other, float h) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_p(const qpNodePath &other, float p) {
 set_p(const qpNodePath &other, float p) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -832,7 +865,7 @@ set_p(const qpNodePath &other, float p) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_r(const qpNodePath &other, float r) {
 set_r(const qpNodePath &other, float r) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f hpr = transform->get_hpr();
   LVecBase3f hpr = transform->get_hpr();
@@ -848,7 +881,7 @@ set_r(const qpNodePath &other, float r) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LVecBase3f qpNodePath::
 LVecBase3f qpNodePath::
 get_hpr(const qpNodePath &other) const {
 get_hpr(const qpNodePath &other) const {
-  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   return transform->get_hpr();
   return transform->get_hpr();
@@ -878,7 +911,7 @@ get_hpr(const qpNodePath &other, float roll) const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_scale(const qpNodePath &other, const LVecBase3f &scale) {
 set_scale(const qpNodePath &other, const LVecBase3f &scale) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   set_transform(other, transform->set_scale(scale));
   set_transform(other, transform->set_scale(scale));
@@ -886,7 +919,7 @@ set_scale(const qpNodePath &other, const LVecBase3f &scale) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sx(const qpNodePath &other, float sx) {
 set_sx(const qpNodePath &other, float sx) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -896,7 +929,7 @@ set_sx(const qpNodePath &other, float sx) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sy(const qpNodePath &other, float sy) {
 set_sy(const qpNodePath &other, float sy) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -906,7 +939,7 @@ set_sy(const qpNodePath &other, float sy) {
 
 
 void qpNodePath::
 void qpNodePath::
 set_sz(const qpNodePath &other, float sz) {
 set_sz(const qpNodePath &other, float sz) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertv(transform->has_components());
   nassertv(transform->has_components());
   LVecBase3f scale = transform->get_scale();
   LVecBase3f scale = transform->get_scale();
@@ -922,7 +955,7 @@ set_sz(const qpNodePath &other, float sz) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 LVecBase3f qpNodePath::
 LVecBase3f qpNodePath::
 get_scale(const qpNodePath &other) const {
 get_scale(const qpNodePath &other) const {
-  nassertr(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
+  nassertr_always(!is_empty(), LVecBase3f(0.0f, 0.0f, 0.0f));
   CPT(TransformState) transform = get_transform(other);
   CPT(TransformState) transform = get_transform(other);
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   nassertr(transform->has_components(), LVecBase3f(0.0f, 0.0f, 0.0f));
   return transform->get_scale();
   return transform->get_scale();
@@ -1033,7 +1066,7 @@ get_relative_point(const qpNodePath &other, const LVecBase3f &point) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 look_at(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
 look_at(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   qpNodePath parent = get_parent();
   qpNodePath parent = get_parent();
   LPoint3f rel_point = point * other.get_mat(parent);
   LPoint3f rel_point = point * other.get_mat(parent);
@@ -1057,7 +1090,7 @@ look_at(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 heads_up(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
 heads_up(const qpNodePath &other, const LPoint3f &point, const LVector3f &up) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   qpNodePath parent = get_parent();
   qpNodePath parent = get_parent();
   LPoint3f rel_point = point * other.get_mat(parent);
   LPoint3f rel_point = point * other.get_mat(parent);
@@ -1126,7 +1159,7 @@ set_color_off(int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_color() {
 clear_color() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(ColorAttrib::get_class_type());
   node()->clear_attrib(ColorAttrib::get_class_type());
 }
 }
 
 
@@ -1138,7 +1171,7 @@ clear_color() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_color() const {
 has_color() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_attrib(ColorAttrib::get_class_type());
   return node()->has_attrib(ColorAttrib::get_class_type());
 }
 }
 
 
@@ -1150,7 +1183,7 @@ has_color() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Colorf qpNodePath::
 Colorf qpNodePath::
 get_color() const {
 get_color() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(ColorAttrib::get_class_type());
     node()->get_attrib(ColorAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1204,7 +1237,7 @@ set_bin(const string &bin_name, int draw_order, int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_bin() {
 clear_bin() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(CullBinAttrib::get_class_type());
   node()->clear_attrib(CullBinAttrib::get_class_type());
 }
 }
 
 
@@ -1217,7 +1250,7 @@ clear_bin() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_bin() const {
 has_bin() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_attrib(CullBinAttrib::get_class_type());
   return node()->has_attrib(CullBinAttrib::get_class_type());
 }
 }
 
 
@@ -1230,7 +1263,7 @@ has_bin() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 string qpNodePath::
 string qpNodePath::
 get_bin_name() const {
 get_bin_name() const {
-  nassertr(!is_empty(), string());
+  nassertr_always(!is_empty(), string());
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(ColorAttrib::get_class_type());
     node()->get_attrib(ColorAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1251,7 +1284,7 @@ get_bin_name() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
 get_bin_draw_order() const {
 get_bin_draw_order() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(ColorAttrib::get_class_type());
     node()->get_attrib(ColorAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1301,7 +1334,7 @@ set_texture_off(int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_texture() {
 clear_texture() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(TextureAttrib::get_class_type());
   node()->clear_attrib(TextureAttrib::get_class_type());
 }
 }
 
 
@@ -1317,7 +1350,7 @@ clear_texture() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_texture() const {
 has_texture() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(TextureAttrib::get_class_type());
     node()->get_attrib(TextureAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1340,7 +1373,7 @@ has_texture() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_texture_off() const {
 has_texture_off() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(ColorAttrib::get_class_type());
     node()->get_attrib(ColorAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1360,10 +1393,12 @@ has_texture_off() const {
 //               applied to the geometry at or below this level, as
 //               applied to the geometry at or below this level, as
 //               another texture at a higher or lower level may
 //               another texture at a higher or lower level may
 //               override.
 //               override.
+//
+//               See also find_texture().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Texture *qpNodePath::
 Texture *qpNodePath::
 get_texture() const {
 get_texture() const {
-  nassertr(!is_empty(), NULL);
+  nassertr_always(!is_empty(), NULL);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(TextureAttrib::get_class_type());
     node()->get_attrib(TextureAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1374,6 +1409,64 @@ get_texture() const {
   return NULL;
   return NULL;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::find_texture
+//       Access: Published
+//  Description: Returns the first texture found applied to geometry
+//               at this node or below that matches the indicated name
+//               (which may contain wildcards).  Returns the texture
+//               if it is found, or NULL if it is not.
+////////////////////////////////////////////////////////////////////
+Texture *qpNodePath::
+find_texture(const string &name) const {
+  GlobPattern glob(name);
+  return r_find_texture(node(), RenderState::make_empty(), glob);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::find_all_textures
+//       Access: Published
+//  Description: Returns a list of a textures applied to geometry at
+//               this node and below.
+////////////////////////////////////////////////////////////////////
+TextureCollection qpNodePath::
+find_all_textures() const {
+  Textures textures;
+  r_find_all_textures(node(), RenderState::make_empty(), textures);
+
+  TextureCollection tc;
+  Textures::iterator ti;
+  for (ti = textures.begin(); ti != textures.end(); ++ti) {
+    tc.add_texture(*ti);
+  }
+  return tc;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::find_all_textures
+//       Access: Published
+//  Description: Returns a list of a textures applied to geometry at
+//               this node and below that match the indicated name
+//               (which may contain wildcard characters).
+////////////////////////////////////////////////////////////////////
+TextureCollection qpNodePath::
+find_all_textures(const string &name) const {
+  Textures textures;
+  r_find_all_textures(node(), RenderState::make_empty(), textures);
+
+  GlobPattern glob(name);
+
+  TextureCollection tc;
+  Textures::iterator ti;
+  for (ti = textures.begin(); ti != textures.end(); ++ti) {
+    Texture *texture = (*ti);
+    if (glob.matches(texture->get_name())) {
+      tc.add_texture(texture);
+    }
+  }
+  return tc;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePath::set_material
 //     Function: qpNodePath::set_material
 //       Access: Published
 //       Access: Published
@@ -1425,7 +1518,7 @@ set_material_off(int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_material() {
 clear_material() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(MaterialAttrib::get_class_type());
   node()->clear_attrib(MaterialAttrib::get_class_type());
 }
 }
 
 
@@ -1437,7 +1530,7 @@ clear_material() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_material() const {
 has_material() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(MaterialAttrib::get_class_type());
     node()->get_attrib(MaterialAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1464,7 +1557,7 @@ has_material() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PT(Material) qpNodePath::
 PT(Material) qpNodePath::
 get_material() const {
 get_material() const {
-  nassertr(!is_empty(), NULL);
+  nassertr_always(!is_empty(), NULL);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(MaterialAttrib::get_class_type());
     node()->get_attrib(MaterialAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1514,7 +1607,7 @@ set_fog_off(int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_fog() {
 clear_fog() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(FogAttrib::get_class_type());
   node()->clear_attrib(FogAttrib::get_class_type());
 }
 }
 
 
@@ -1530,7 +1623,7 @@ clear_fog() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_fog() const {
 has_fog() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(FogAttrib::get_class_type());
     node()->get_attrib(FogAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1553,7 +1646,7 @@ has_fog() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_fog_off() const {
 has_fog_off() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(FogAttrib::get_class_type());
     node()->get_attrib(FogAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1576,7 +1669,7 @@ has_fog_off() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 qpFog *qpNodePath::
 qpFog *qpNodePath::
 get_fog() const {
 get_fog() const {
-  nassertr(!is_empty(), NULL);
+  nassertr_always(!is_empty(), NULL);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(FogAttrib::get_class_type());
     node()->get_attrib(FogAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1622,7 +1715,7 @@ set_render_mode_filled(int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_render_mode() {
 clear_render_mode() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(RenderModeAttrib::get_class_type());
   node()->clear_attrib(RenderModeAttrib::get_class_type());
 }
 }
 
 
@@ -1636,7 +1729,7 @@ clear_render_mode() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_render_mode() const {
 has_render_mode() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_attrib(RenderModeAttrib::get_class_type());
   return node()->has_attrib(RenderModeAttrib::get_class_type());
 }
 }
 
 
@@ -1674,7 +1767,7 @@ set_two_sided(bool two_sided, int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_two_sided() {
 clear_two_sided() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(CullFaceAttrib::get_class_type());
   node()->clear_attrib(CullFaceAttrib::get_class_type());
 }
 }
 
 
@@ -1689,7 +1782,7 @@ clear_two_sided() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_two_sided() const {
 has_two_sided() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_attrib(CullFaceAttrib::get_class_type());
   return node()->has_attrib(CullFaceAttrib::get_class_type());
 }
 }
 
 
@@ -1706,7 +1799,7 @@ has_two_sided() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 get_two_sided() const {
 get_two_sided() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(CullFaceAttrib::get_class_type());
     node()->get_attrib(CullFaceAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -1728,7 +1821,7 @@ get_two_sided() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 do_billboard_axis(const qpNodePath &camera, float offset) {
 do_billboard_axis(const qpNodePath &camera, float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   qpNodePath parent = get_parent();
   qpNodePath parent = get_parent();
   LMatrix4f rel_mat = camera.get_mat(parent);
   LMatrix4f rel_mat = camera.get_mat(parent);
@@ -1762,7 +1855,7 @@ do_billboard_axis(const qpNodePath &camera, float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 do_billboard_point_eye(const qpNodePath &camera, float offset) {
 do_billboard_point_eye(const qpNodePath &camera, float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   qpNodePath parent = get_parent();
   qpNodePath parent = get_parent();
   LMatrix4f rel_mat = camera.get_mat(parent);
   LMatrix4f rel_mat = camera.get_mat(parent);
@@ -1794,7 +1887,7 @@ do_billboard_point_eye(const qpNodePath &camera, float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 do_billboard_point_world(const qpNodePath &camera, float offset) {
 do_billboard_point_world(const qpNodePath &camera, float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
 
 
   qpNodePath parent = get_parent();
   qpNodePath parent = get_parent();
   LMatrix4f rel_mat = camera.get_mat(parent);
   LMatrix4f rel_mat = camera.get_mat(parent);
@@ -1825,7 +1918,7 @@ do_billboard_point_world(const qpNodePath &camera, float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_billboard_axis(float offset) {
 set_billboard_axis(float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(RenderEffect) billboard = BillboardEffect::make
   CPT(RenderEffect) billboard = BillboardEffect::make
     (LVector3f::up(), false, true, 
     (LVector3f::up(), false, true, 
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
@@ -1842,7 +1935,7 @@ set_billboard_axis(float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_billboard_point_eye(float offset) {
 set_billboard_point_eye(float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(RenderEffect) billboard = BillboardEffect::make
   CPT(RenderEffect) billboard = BillboardEffect::make
     (LVector3f::up(), true, false,
     (LVector3f::up(), true, false,
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
@@ -1858,7 +1951,7 @@ set_billboard_point_eye(float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 set_billboard_point_world(float offset) {
 set_billboard_point_world(float offset) {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   CPT(RenderEffect) billboard = BillboardEffect::make
   CPT(RenderEffect) billboard = BillboardEffect::make
     (LVector3f::up(), false, false,
     (LVector3f::up(), false, false,
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
      offset, qpNodePath(), LPoint3f(0.0f, 0.0f, 0.0f));
@@ -1872,7 +1965,7 @@ set_billboard_point_world(float offset) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_billboard() {
 clear_billboard() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_effect(BillboardEffect::get_class_type());
   node()->clear_effect(BillboardEffect::get_class_type());
 }
 }
 
 
@@ -1884,7 +1977,7 @@ clear_billboard() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_billboard() const {
 has_billboard() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_effect(BillboardEffect::get_class_type());
   return node()->has_effect(BillboardEffect::get_class_type());
 }
 }
 
 
@@ -1920,7 +2013,7 @@ set_transparency(bool transparency, int priority) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void qpNodePath::
 void qpNodePath::
 clear_transparency() {
 clear_transparency() {
-  nassertv(!is_empty());
+  nassertv_always(!is_empty());
   node()->clear_attrib(TransparencyAttrib::get_class_type());
   node()->clear_attrib(TransparencyAttrib::get_class_type());
 }
 }
 
 
@@ -1936,7 +2029,7 @@ clear_transparency() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 has_transparency() const {
 has_transparency() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   return node()->has_attrib(TransparencyAttrib::get_class_type());
   return node()->has_attrib(TransparencyAttrib::get_class_type());
 }
 }
 
 
@@ -1953,7 +2046,7 @@ has_transparency() const {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 get_transparency() const {
 get_transparency() const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
   const RenderAttrib *attrib =
   const RenderAttrib *attrib =
     node()->get_attrib(TransparencyAttrib::get_class_type());
     node()->get_attrib(TransparencyAttrib::get_class_type());
   if (attrib != (const RenderAttrib *)NULL) {
   if (attrib != (const RenderAttrib *)NULL) {
@@ -2127,7 +2220,7 @@ hide_bounds() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PT(BoundingVolume) qpNodePath::
 PT(BoundingVolume) qpNodePath::
 get_bounds() const {
 get_bounds() const {
-  nassertr(!is_empty(), new BoundingSphere);
+  nassertr_always(!is_empty(), new BoundingSphere);
 
 
   PandaNode *this_node = node();
   PandaNode *this_node = node();
   PT(BoundingVolume) bv = this_node->get_bound().make_copy();
   PT(BoundingVolume) bv = this_node->get_bound().make_copy();
@@ -2208,7 +2301,7 @@ calc_tight_bounds(LPoint3f &min_point, LPoint3f &max_point) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
 flatten_light() {
 flatten_light() {
-  nassertr(!is_empty(), 0);
+  nassertr_always(!is_empty(), 0);
   qpSceneGraphReducer gr;
   qpSceneGraphReducer gr;
   gr.apply_attribs(node());
   gr.apply_attribs(node());
 
 
@@ -2241,7 +2334,7 @@ flatten_light() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
 flatten_medium() {
 flatten_medium() {
-  nassertr(!is_empty(), 0);
+  nassertr_always(!is_empty(), 0);
   qpSceneGraphReducer gr;
   qpSceneGraphReducer gr;
   gr.apply_attribs(node());
   gr.apply_attribs(node());
   int num_removed = gr.flatten(node(), false);
   int num_removed = gr.flatten(node(), false);
@@ -2268,7 +2361,7 @@ flatten_medium() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 int qpNodePath::
 int qpNodePath::
 flatten_strong() {
 flatten_strong() {
-  nassertr(!is_empty(), 0);
+  nassertr_always(!is_empty(), 0);
   qpSceneGraphReducer gr;
   qpSceneGraphReducer gr;
   gr.apply_attribs(node());
   gr.apply_attribs(node());
   int num_removed = gr.flatten(node(), true);
   int num_removed = gr.flatten(node(), true);
@@ -2287,7 +2380,7 @@ flatten_strong() {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 bool qpNodePath::
 bool qpNodePath::
 write_bam_file(const string &filename) const {
 write_bam_file(const string &filename) const {
-  nassertr(!is_empty(), false);
+  nassertr_always(!is_empty(), false);
 
 
   /*
   /*
   BamFile bam_file;
   BamFile bam_file;
@@ -2635,3 +2728,90 @@ r_calc_tight_bounds(PandaNode *node, LPoint3f &min_point, LPoint3f &max_point,
                         found_any, next_transform);
                         found_any, next_transform);
   }
   }
 }
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_find_texture
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+Texture * qpNodePath::
+r_find_texture(PandaNode *node, const RenderState *state,
+               const GlobPattern &glob) const {
+  CPT(RenderState) next_state = state->compose(node->get_state());
+
+  if (node->is_geom_node()) {
+    qpGeomNode *gnode;
+    DCAST_INTO_R(gnode, node, NULL);
+
+    int num_geoms = gnode->get_num_geoms();
+    for (int i = 0; i < num_geoms; i++) {
+      CPT(RenderState) geom_state = 
+        next_state->compose(gnode->get_geom_state(i));
+
+      // Look for a TextureAttrib on the state.
+      const RenderAttrib *attrib =
+        geom_state->get_attrib(TextureAttrib::get_class_type());
+      if (attrib != (const RenderAttrib *)NULL) {
+        const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
+        Texture *texture = ta->get_texture();
+        if (texture != (Texture *)NULL) {
+          if (glob.matches(texture->get_name())) {
+            return texture;
+          }
+        }
+      }
+    }
+  }
+
+  // Now consider children.
+  PandaNode::Children cr = node->get_children();
+  int num_children = cr.get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    Texture *result = r_find_texture(cr.get_child(i), next_state, glob);
+    if (result != (Texture *)NULL) {
+      return result;
+    }
+  }
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePath::r_find_all_textures
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpNodePath::
+r_find_all_textures(PandaNode *node, const RenderState *state,
+                    qpNodePath::Textures &textures) const {
+  CPT(RenderState) next_state = state->compose(node->get_state());
+
+  if (node->is_geom_node()) {
+    qpGeomNode *gnode;
+    DCAST_INTO_V(gnode, node);
+
+    int num_geoms = gnode->get_num_geoms();
+    for (int i = 0; i < num_geoms; i++) {
+      CPT(RenderState) geom_state = 
+        next_state->compose(gnode->get_geom_state(i));
+
+      // Look for a TextureAttrib on the state.
+      const RenderAttrib *attrib =
+        geom_state->get_attrib(TextureAttrib::get_class_type());
+      if (attrib != (const RenderAttrib *)NULL) {
+        const TextureAttrib *ta = DCAST(TextureAttrib, attrib);
+        Texture *texture = ta->get_texture();
+        if (texture != (Texture *)NULL) {
+          textures.insert(texture);
+        }
+      }
+    }
+  }
+
+  // Now consider children.
+  PandaNode::Children cr = node->get_children();
+  int num_children = cr.get_num_children();
+  for (int i = 0; i < num_children; i++) {
+    r_find_all_textures(cr.get_child(i), next_state, textures);
+  }
+}

+ 24 - 1
panda/src/pgraph/qpnodePath.h

@@ -32,11 +32,13 @@
 #include "typedObject.h"
 #include "typedObject.h"
 
 
 class qpNodePathCollection;
 class qpNodePathCollection;
+class TextureCollection;
 class qpFindApproxLevel;
 class qpFindApproxLevel;
 class qpFindApproxPath;
 class qpFindApproxPath;
 class Texture;
 class Texture;
 class Material;
 class Material;
 class qpFog;
 class qpFog;
+class GlobPattern;
 
 
 //
 //
 // A NodePath is the fundamental unit of high-level interaction with
 // A NodePath is the fundamental unit of high-level interaction with
@@ -140,6 +142,9 @@ PUBLISHED:
     ET_not_found,  // returned from a failed find() or similar function.
     ET_not_found,  // returned from a failed find() or similar function.
     ET_removed,    // remove_node() was previously called on this qpNodePath.
     ET_removed,    // remove_node() was previously called on this qpNodePath.
     ET_fail,       // general failure return from some function.
     ET_fail,       // general failure return from some function.
+
+    // Also see qpNodePathComponent::_next_key, which initializes
+    // itself to the last enumerated type here plus one.
   };
   };
 
 
   INLINE qpNodePath();
   INLINE qpNodePath();
@@ -153,6 +158,9 @@ PUBLISHED:
   INLINE static qpNodePath removed();
   INLINE static qpNodePath removed();
   INLINE static qpNodePath fail();
   INLINE static qpNodePath fail();
 
 
+  INLINE static void set_max_search_depth(int max_search_depth);
+  INLINE static int get_max_search_depth();
+
   // Methods to query a qpNodePath's contents.
   // Methods to query a qpNodePath's contents.
   INLINE bool is_empty() const;
   INLINE bool is_empty() const;
   INLINE bool is_singleton() const;
   INLINE bool is_singleton() const;
@@ -164,6 +172,8 @@ PUBLISHED:
   PandaNode *get_top_node() const;
   PandaNode *get_top_node() const;
   INLINE PandaNode *node() const;
   INLINE PandaNode *node() const;
 
 
+  INLINE int get_key() const;
+
   // Methods that return collections of NodePaths derived from or
   // Methods that return collections of NodePaths derived from or
   // related to this one.
   // related to this one.
 
 
@@ -194,7 +204,7 @@ PUBLISHED:
   // Handy ways to look at what's there, and other miscellaneous
   // Handy ways to look at what's there, and other miscellaneous
   // operations.
   // operations.
 
 
-  INLINE void output(ostream &out) const;
+  void output(ostream &out) const;
 
 
   INLINE void ls() const;
   INLINE void ls() const;
   INLINE void ls(ostream &out, int indent_level = 0) const;
   INLINE void ls(ostream &out, int indent_level = 0) const;
@@ -391,6 +401,10 @@ PUBLISHED:
   bool has_texture_off() const;
   bool has_texture_off() const;
   Texture *get_texture() const;
   Texture *get_texture() const;
 
 
+  Texture *find_texture(const string &name) const;
+  TextureCollection find_all_textures() const;
+  TextureCollection find_all_textures(const string &name) const;
+
   void set_material(Material *tex, int priority = 0);
   void set_material(Material *tex, int priority = 0);
   void set_material_off(int priority = 0);
   void set_material_off(int priority = 0);
   void clear_material();
   void clear_material();
@@ -493,6 +507,12 @@ private:
                            LPoint3f &min_point, LPoint3f &max_point,
                            LPoint3f &min_point, LPoint3f &max_point,
                            bool &found_any, const TransformState *transform);
                            bool &found_any, const TransformState *transform);
 
 
+  typedef pset<Texture *> Textures;
+  Texture *r_find_texture(PandaNode *node, const RenderState *state,
+                          const GlobPattern &glob) const;
+  void r_find_all_textures(PandaNode *node, const RenderState *state,
+                           Textures &textures) const;
+
   PT(qpNodePathComponent) _head;
   PT(qpNodePathComponent) _head;
   ErrorType _error_type;
   ErrorType _error_type;
   static int _max_search_depth;
   static int _max_search_depth;
@@ -507,6 +527,9 @@ public:
 
 
 private:
 private:
   static TypeHandle _type_handle;
   static TypeHandle _type_handle;
+
+  friend class qpNodePathCollection;
+  friend WorkingNodePath;
 };
 };
 
 
 INLINE ostream &operator << (ostream &out, const qpNodePath &node_path);
 INLINE ostream &operator << (ostream &out, const qpNodePath &node_path);

+ 9 - 11
panda/src/pgraph/qpnodePathCollection.cxx

@@ -17,8 +17,8 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 #include "qpnodePathCollection.h"
 #include "qpnodePathCollection.h"
-//#include "findApproxPath.h"
-//#include "findApproxLevel.h"
+#include "qpfindApproxPath.h"
+#include "qpfindApproxLevel.h"
 
 
 #include "indent.h"
 #include "indent.h"
 
 
@@ -65,8 +65,8 @@ add_path(const qpNodePath &node_path) {
   // objects.
   // objects.
 
 
   if (_node_paths.get_ref_count() > 1) {
   if (_node_paths.get_ref_count() > 1) {
-    PTA(qpNodePath) old_node_paths = _node_paths;
-    _node_paths = PTA(qpNodePath)::empty_array(0);
+    NodePaths old_node_paths = _node_paths;
+    _node_paths = NodePaths::empty_array(0);
     _node_paths.v() = old_node_paths.v();
     _node_paths.v() = old_node_paths.v();
   }
   }
 
 
@@ -100,8 +100,8 @@ remove_path(const qpNodePath &node_path) {
   // objects.
   // objects.
 
 
   if (_node_paths.get_ref_count() > 1) {
   if (_node_paths.get_ref_count() > 1) {
-    PTA(qpNodePath) old_node_paths = _node_paths;
-    _node_paths = PTA(qpNodePath)::empty_array(0);
+    NodePaths old_node_paths = _node_paths;
+    _node_paths = NodePaths::empty_array(0);
     _node_paths.v() = old_node_paths.v();
     _node_paths.v() = old_node_paths.v();
   }
   }
 
 
@@ -263,7 +263,6 @@ ls(ostream &out, int indent_level) const {
   }
   }
 }
 }
 
 
-/*
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathCollection::find_all_matches
 //     Function: qpNodePathCollection::find_all_matches
 //       Access: Published
 //       Access: Published
@@ -276,12 +275,12 @@ qpNodePathCollection qpNodePathCollection::
 find_all_matches(const string &path) const {
 find_all_matches(const string &path) const {
   qpNodePathCollection result;
   qpNodePathCollection result;
 
 
-  FindApproxPath approx_path;
+  qpFindApproxPath approx_path;
   if (approx_path.add_string(path)) {
   if (approx_path.add_string(path)) {
     if (!is_empty()) {
     if (!is_empty()) {
-      FindApproxLevel level;
+      qpFindApproxLevel level;
       for (int i = 0; i < get_num_paths(); i++) {
       for (int i = 0; i < get_num_paths(); i++) {
-        FindApproxLevelEntry start(get_path(i), approx_path);
+        qpFindApproxLevelEntry start(get_path(i), approx_path);
         level.add_entry(start);
         level.add_entry(start);
       }
       }
       get_path(0).r_find_matches(result, level, -1,
       get_path(0).r_find_matches(result, level, -1,
@@ -291,7 +290,6 @@ find_all_matches(const string &path) const {
 
 
   return result;
   return result;
 }
 }
-*/
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathCollection::reparent_to
 //     Function: qpNodePathCollection::reparent_to

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

@@ -54,7 +54,7 @@ PUBLISHED:
   INLINE void ls() const;
   INLINE void ls() const;
   void ls(ostream &out, int indent_level = 0) const;
   void ls(ostream &out, int indent_level = 0) const;
 
 
-  //  qpNodePathCollection find_all_matches(const string &path) const;
+  qpNodePathCollection find_all_matches(const string &path) const;
   void reparent_to(const qpNodePath &other);
   void reparent_to(const qpNodePath &other);
   void wrt_reparent_to(const qpNodePath &other);
   void wrt_reparent_to(const qpNodePath &other);
 
 

+ 23 - 1
panda/src/pgraph/qpnodePathComponent.I

@@ -48,7 +48,8 @@ CData(const qpNodePathComponent::CData &copy) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE qpNodePathComponent::
 INLINE qpNodePathComponent::
 qpNodePathComponent(PandaNode *node, qpNodePathComponent *next) :
 qpNodePathComponent(PandaNode *node, qpNodePathComponent *next) :
-  _node(node)
+  _node(node),
+  _key(0)
 {
 {
 #ifdef DO_MEMORY_USAGE
 #ifdef DO_MEMORY_USAGE
   MemoryUsage::update_type(this, get_class_type());
   MemoryUsage::update_type(this, get_class_type());
@@ -103,6 +104,27 @@ get_node() const {
   return _node;
   return _node;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpNodePathComponent::get_key
+//       Access: Public
+//  Description: Returns an index number that is guaranteed to be
+//               unique for this particular NodePathComponent, and not
+//               to be reused for the lifetime of the application
+//               (barring integer overflow).
+////////////////////////////////////////////////////////////////////
+INLINE int qpNodePathComponent::
+get_key() const {
+  if (_key == 0) {
+    // The first time someone asks for a particular component's key,
+    // we make it up on the spot.  This helps keep us from wasting
+    // index numbers generating a unique number for *every* component
+    // in the world (we only have 4.2 billion 32-bit integers, after
+    // all)
+    ((qpNodePathComponent *)this)->_key = _next_key++;
+  }
+  return _key;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: qpNodePathComponent::is_top_node
 //     Function: qpNodePathComponent::is_top_node
 //       Access: Public
 //       Access: Public

+ 4 - 0
panda/src/pgraph/qpnodePathComponent.cxx

@@ -18,6 +18,10 @@
 
 
 #include "qpnodePathComponent.h"
 #include "qpnodePathComponent.h"
 
 
+
+// We start the key counters off at 1, since 0 is reserved for an
+// empty NodePath (and also for an unassigned key).
+int qpNodePathComponent::_next_key = 1;
 TypeHandle qpNodePathComponent::_type_handle;
 TypeHandle qpNodePathComponent::_type_handle;
 
 
 
 

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

@@ -54,6 +54,7 @@ public:
   INLINE ~qpNodePathComponent();
   INLINE ~qpNodePathComponent();
   
   
   INLINE PandaNode *get_node() const;
   INLINE PandaNode *get_node() const;
+  INLINE int get_key() const;
   INLINE bool is_top_node() const;
   INLINE bool is_top_node() const;
   INLINE bool is_collapsed() const;
   INLINE bool is_collapsed() const;
   
   
@@ -70,6 +71,7 @@ private:
   INLINE void collapse_with(qpNodePathComponent *next);
   INLINE void collapse_with(qpNodePathComponent *next);
 
 
   PT(PandaNode) _node;
   PT(PandaNode) _node;
+  int _key;
 
 
   // 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 {
@@ -86,6 +88,8 @@ private:
   typedef CycleDataReader<CData> CDReader;
   typedef CycleDataReader<CData> CDReader;
   typedef CycleDataWriter<CData> CDWriter;
   typedef CycleDataWriter<CData> CDWriter;
 
 
+  static int _next_key;
+
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {
     return _type_handle;
     return _type_handle;

+ 27 - 0
panda/src/pgraph/textureCollection.I

@@ -0,0 +1,27 @@
+// Filename: textureCollection.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: TextureCollection::Destructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE TextureCollection::
+~TextureCollection() {
+}

+ 282 - 0
panda/src/pgraph/textureCollection.cxx

@@ -0,0 +1,282 @@
+// Filename: textureCollection.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "textureCollection.h"
+
+#include "indent.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextureCollection::
+TextureCollection() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::Copy Constructor
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+TextureCollection::
+TextureCollection(const TextureCollection &copy) :
+  _textures(copy._textures)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::Copy Assignment Operator
+//       Access: Published
+//  Description:
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+operator = (const TextureCollection &copy) {
+  _textures = copy._textures;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::add_texture
+//       Access: Published
+//  Description: Adds a new Texture to the collection.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+add_texture(Texture *node_texture) {
+  // If the pointer to our internal array is shared by any other
+  // TextureCollections, we have to copy the array now so we won't
+  // inadvertently modify any of our brethren TextureCollection
+  // objects.
+
+  if (_textures.get_ref_count() > 1) {
+    Textures old_textures = _textures;
+    _textures = Textures::empty_array(0);
+    _textures.v() = old_textures.v();
+  }
+
+  _textures.push_back(node_texture);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::remove_texture
+//       Access: Published
+//  Description: Removes the indicated Texture from the collection.
+//               Returns true if the texture was removed, false if it was
+//               not a member of the collection.
+////////////////////////////////////////////////////////////////////
+bool TextureCollection::
+remove_texture(Texture *node_texture) {
+  int texture_index = -1;
+  for (int i = 0; texture_index == -1 && i < (int)_textures.size(); i++) {
+    if (_textures[i] == node_texture) {
+      texture_index = i;
+    }
+  }
+
+  if (texture_index == -1) {
+    // The indicated texture was not a member of the collection.
+    return false;
+  }
+
+  // If the pointer to our internal array is shared by any other
+  // TextureCollections, we have to copy the array now so we won't
+  // inadvertently modify any of our brethren TextureCollection
+  // objects.
+
+  if (_textures.get_ref_count() > 1) {
+    Textures old_textures = _textures;
+    _textures = Textures::empty_array(0);
+    _textures.v() = old_textures.v();
+  }
+
+  _textures.erase(_textures.begin() + texture_index);
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::add_textures_from
+//       Access: Published
+//  Description: Adds all the qpTextures indicated in the other
+//               collection to this texture.  The other textures are simply
+//               appended to the end of the textures in this list;
+//               duplicates are not automatically removed.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+add_textures_from(const TextureCollection &other) {
+  int other_num_textures = other.get_num_textures();
+  for (int i = 0; i < other_num_textures; i++) {
+    add_texture(other.get_texture(i));
+  }
+}
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::remove_textures_from
+//       Access: Published
+//  Description: Removes from this collection all of the qpTextures
+//               listed in the other collection.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+remove_textures_from(const TextureCollection &other) {
+  Textures new_textures;
+  int num_textures = get_num_textures();
+  for (int i = 0; i < num_textures; i++) {
+    PT(Texture) texture = get_texture(i);
+    if (!other.has_texture(texture)) {
+      new_textures.push_back(texture);
+    }
+  }
+  _textures = new_textures;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::remove_duplicate_textures
+//       Access: Published
+//  Description: Removes any duplicate entries of the same Textures
+//               on this collection.  If a Texture appears multiple
+//               times, the first appearance is retained; subsequent
+//               appearances are removed.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+remove_duplicate_textures() {
+  Textures new_textures;
+
+  int num_textures = get_num_textures();
+  for (int i = 0; i < num_textures; i++) {
+    PT(Texture) texture = get_texture(i);
+    bool duplicated = false;
+
+    for (int j = 0; j < i && !duplicated; j++) {
+      duplicated = (texture == get_texture(j));
+    }
+
+    if (!duplicated) {
+      new_textures.push_back(texture);
+    }
+  }
+
+  _textures = new_textures;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::has_texture
+//       Access: Published
+//  Description: Returns true if the indicated Texture appears in
+//               this collection, false otherwise.
+////////////////////////////////////////////////////////////////////
+bool TextureCollection::
+has_texture(Texture *texture) const {
+  for (int i = 0; i < get_num_textures(); i++) {
+    if (texture == get_texture(i)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::clear
+//       Access: Published
+//  Description: Removes all Textures from the collection.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+clear() {
+  _textures.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::find_texture
+//       Access: Published
+//  Description: Returns the texture in the collection with the
+//               indicated name, if any, or NULL if no texture has
+//               that name.
+////////////////////////////////////////////////////////////////////
+Texture *TextureCollection::
+find_texture(const string &name) const {
+  int num_textures = get_num_textures();
+  for (int i = 0; i < num_textures; i++) {
+    Texture *texture = get_texture(i);
+    if (texture->get_name() == name) {
+      return texture;
+    }
+  }
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::get_num_textures
+//       Access: Published
+//  Description: Returns the number of Textures in the collection.
+////////////////////////////////////////////////////////////////////
+int TextureCollection::
+get_num_textures() const {
+  return _textures.size();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::get_texture
+//       Access: Published
+//  Description: Returns the nth Texture in the collection.
+////////////////////////////////////////////////////////////////////
+Texture *TextureCollection::
+get_texture(int index) const {
+  nassertr(index >= 0 && index < (int)_textures.size(), NULL);
+
+  return _textures[index];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::operator []
+//       Access: Published
+//  Description: Returns the nth Texture in the collection.  This is
+//               the same as get_texture(), but it may be a more
+//               convenient way to access it.
+////////////////////////////////////////////////////////////////////
+Texture *TextureCollection::
+operator [] (int index) const {
+  nassertr(index >= 0 && index < (int)_textures.size(), NULL);
+
+  return _textures[index];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::output
+//       Access: Published
+//  Description: Writes a brief one-line description of the
+//               TextureCollection to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+output(ostream &out) const {
+  if (get_num_textures() == 1) {
+    out << "1 Texture";
+  } else {
+    out << get_num_textures() << " Textures";
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TextureCollection::write
+//       Access: Published
+//  Description: Writes a complete multi-line description of the
+//               TextureCollection to the indicated output stream.
+////////////////////////////////////////////////////////////////////
+void TextureCollection::
+write(ostream &out, int indent_level) const {
+  for (int i = 0; i < get_num_textures(); i++) {
+    indent(out, indent_level) << *get_texture(i) << "\n";
+  }
+}

+ 67 - 0
panda/src/pgraph/textureCollection.h

@@ -0,0 +1,67 @@
+// Filename: textureCollection.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 TEXTURECOLLECTION_H
+#define TEXTURECOLLECTION_H
+
+#include "pandabase.h"
+#include "pointerToArray.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : TextureCollection
+// Description : 
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA TextureCollection {
+PUBLISHED:
+  TextureCollection();
+  TextureCollection(const TextureCollection &copy);
+  void operator = (const TextureCollection &copy);
+  INLINE ~TextureCollection();
+
+  void add_texture(Texture *node_texture);
+  bool remove_texture(Texture *node_texture);
+  void add_textures_from(const TextureCollection &other);
+  void remove_textures_from(const TextureCollection &other);
+  void remove_duplicate_textures();
+  bool has_texture(Texture *texture) const;
+  void clear();
+
+  Texture *find_texture(const string &name) const;
+
+  int get_num_textures() const;
+  Texture *get_texture(int index) const;
+  Texture *operator [] (int index) const;
+
+  void output(ostream &out) const;
+  void write(ostream &out, int indent_level = 0) const;
+
+private:
+  typedef PTA(PT(Texture)) Textures;
+  Textures _textures;
+};
+
+INLINE ostream &operator << (ostream &out, const TextureCollection &col) {
+  col.output(out);
+  return out;
+}
+
+#include "textureCollection.I"
+
+#endif
+
+

+ 70 - 0
panda/src/pgraph/workingNodePath.I

@@ -0,0 +1,70 @@
+// Filename: workingNodePath.I
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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: WorkingNodePath::Constructor
+//       Access: Public
+//  Description: Creates a WorkingNodePath that is the same as the
+//               indicated NodePath.  This is generally used to begin
+//               the traversal of a scene graph with the root
+//               NodePath.
+////////////////////////////////////////////////////////////////////
+INLINE WorkingNodePath::
+WorkingNodePath(const qpNodePath &start) {
+  nassertv(!start.is_empty());
+  _start = start._head;
+  _node = start.node();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: WorkingNodePath::Constructor
+//       Access: Public
+//  Description: Creates a WorkingNodePath that is the same as the
+//               indicated WorkingNodePath, plus one node.  This is
+//               generally used to continue the traversal to the next
+//               node.
+////////////////////////////////////////////////////////////////////
+INLINE WorkingNodePath::
+WorkingNodePath(const WorkingNodePath &parent, PandaNode *child) {
+  _next = &parent;
+  _node = child;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: WorkingNodePath::Destructor
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE WorkingNodePath::
+~WorkingNodePath() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: WorkingNodePath::get_node_path
+//       Access: Public
+//  Description: Constructs and returns an actual NodePath that
+//               represents the same path we have just traversed.
+////////////////////////////////////////////////////////////////////
+INLINE qpNodePath WorkingNodePath::
+get_node_path() const {
+  qpNodePath result;
+  result._head = r_get_node_path();
+  nassertr(result._head != (qpNodePathComponent *)NULL, qpNodePath::fail());
+  return result;
+}

+ 41 - 0
panda/src/pgraph/workingNodePath.cxx

@@ -0,0 +1,41 @@
+// Filename: workingNodePath.cxx
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 "workingNodePath.h"
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: WorkingNodePath::r_get_node_path
+//       Access: Private
+//  Description: The private, recursive implementation of
+//               get_node_path(), this returns the NodePathComponent
+//               representing the NodePath.
+////////////////////////////////////////////////////////////////////
+PT(qpNodePathComponent) WorkingNodePath::
+r_get_node_path() const {
+  if (_next == (WorkingNodePath *)NULL) {
+    return _start;
+  }
+
+  nassertr(_start == (qpNodePathComponent *)NULL, NULL);
+  nassertr(_node != (PandaNode *)NULL, NULL);
+
+  PT(qpNodePathComponent) comp = _next->r_get_node_path();
+  nassertr(comp != (qpNodePathComponent *)NULL, NULL);
+  return PandaNode::get_component(comp, _node);
+}

+ 71 - 0
panda/src/pgraph/workingNodePath.h

@@ -0,0 +1,71 @@
+// Filename: workingNodePath.h
+// Created by:  drose (16Mar02)
+//
+////////////////////////////////////////////////////////////////////
+//
+// 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 WORKINGNODEPATH_H
+#define WORKINGNODEPATH_H
+
+#include "pandabase.h"
+
+#include "qpnodePath.h"
+#include "qpnodePathComponent.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : WorkingNodePath
+// Description : This is a class designed to support low-overhead
+//               traversals of the complete scene graph, with a memory
+//               of the complete path through the graph at any given
+//               point.
+//
+//               You could just use a regular NodePath to do this, but
+//               since the NodePath requires storing
+//               NodePathComponents on each node as it is constructed,
+//               and then removing them when it destructs, there is
+//               considerable overhead in that approach.
+//
+//               The WorkingNodePath eliminates this overhead (but
+//               does not guarantee consistency if the scene graph
+//               changes while the path is held).
+//
+//               At any given point, you may ask the WorkingNodePath
+//               for its actual NodePath, and it will construct and
+//               return a new NodePath representing the complete
+//               generated chain.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA WorkingNodePath {
+public:
+  INLINE WorkingNodePath(const qpNodePath &start);
+  INLINE WorkingNodePath(const WorkingNodePath &parent, PandaNode *child);
+  INLINE ~WorkingNodePath();
+
+  INLINE qpNodePath get_node_path() const;
+
+private:
+  PT(qpNodePathComponent) r_get_node_path() const;
+
+  // Either one or the other of these pointers will be filled in, but
+  // never both.  We maintain a linked list of WorkingNodePath
+  // objects, with a NodePathComponent at the head of the list.
+  const WorkingNodePath *_next;
+  PT(qpNodePathComponent) _start;
+
+  PandaNode *_node;
+};
+
+#include "workingNodePath.I"
+
+#endif

+ 2 - 1
panda/src/putil/bam.h

@@ -32,13 +32,14 @@ static const unsigned short _bam_major_ver = 3;
 // Bumped to major version 2 on 7/6/00 due to major changes in Character.
 // Bumped to major version 2 on 7/6/00 due to major changes in Character.
 // Bumped to major version 3 on 12/8/00 to change float64's to float32's.
 // Bumped to major version 3 on 12/8/00 to change float64's to float32's.
 
 
-static const unsigned short _bam_minor_ver = 5;
+static const unsigned short _bam_minor_ver = 6;
 // Bumped to minor version 1 on 12/15/00 to add FFT-style channel
 // Bumped to minor version 1 on 12/15/00 to add FFT-style channel
 // compression.
 // compression.
 // Bumped to minor version 2 on 2/15/01 to add ModelNode::_preserve_transform.
 // Bumped to minor version 2 on 2/15/01 to add ModelNode::_preserve_transform.
 // Bumped to minor version 3 on 4/11/01 to support correctly ordered children.
 // Bumped to minor version 3 on 4/11/01 to support correctly ordered children.
 // Bumped to minor version 4 on 12/11/01 to transpose quaternions.
 // Bumped to minor version 4 on 12/11/01 to transpose quaternions.
 // Bumped to minor version 5 on 12/13/01 to remove obsolete fields from Texture.
 // Bumped to minor version 5 on 12/13/01 to remove obsolete fields from Texture.
+// Bumped to minor version 6 on 5/16/02 to add ImageBuffer::_filename.
 
 
 
 
 #endif
 #endif