Browse Source

hardware animation

David Rose 21 years ago
parent
commit
f77ea5156a
55 changed files with 1846 additions and 300 deletions
  1. 15 0
      panda/src/char/jointVertexTransform.I
  2. 50 7
      panda/src/char/jointVertexTransform.cxx
  3. 4 0
      panda/src/char/jointVertexTransform.h
  4. 3 3
      panda/src/display/Sources.pp
  5. 2 2
      panda/src/display/config_display.cxx
  6. 1 1
      panda/src/display/display_composite1.cxx
  7. 38 0
      panda/src/display/graphicsStateGuardian.I
  8. 4 0
      panda/src/display/graphicsStateGuardian.cxx
  9. 6 0
      panda/src/display/graphicsStateGuardian.h
  10. 1 1
      panda/src/display/standardMunger.I
  11. 70 22
      panda/src/display/standardMunger.cxx
  12. 18 14
      panda/src/display/standardMunger.h
  13. 2 2
      panda/src/dxgsg8/dxGeomMunger8.I
  14. 29 25
      panda/src/dxgsg8/dxGeomMunger8.cxx
  15. 6 7
      panda/src/dxgsg8/dxGeomMunger8.h
  16. 104 35
      panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx
  17. 1 2
      panda/src/dxgsg8/dxGraphicsStateGuardian8.h
  18. 11 0
      panda/src/dxgsg8/dxIndexBufferContext8.cxx
  19. 55 1
      panda/src/dxgsg8/dxVertexBufferContext8.cxx
  20. 7 0
      panda/src/egg2pg/eggLoader.cxx
  21. 2 2
      panda/src/glstuff/glGeomMunger_src.I
  22. 11 12
      panda/src/glstuff/glGeomMunger_src.cxx
  23. 6 5
      panda/src/glstuff/glGeomMunger_src.h
  24. 3 0
      panda/src/gobj/Sources.pp
  25. 15 3
      panda/src/gobj/config_gobj.cxx
  26. 1 0
      panda/src/gobj/config_gobj.h
  27. 1 0
      panda/src/gobj/gobj_composite1.cxx
  28. 26 0
      panda/src/gobj/internalName.I
  29. 2 0
      panda/src/gobj/internalName.cxx
  30. 4 0
      panda/src/gobj/internalName.h
  31. 48 10
      panda/src/gobj/qpgeomMunger.I
  32. 15 72
      panda/src/gobj/qpgeomMunger.cxx
  33. 12 7
      panda/src/gobj/qpgeomMunger.h
  34. 198 0
      panda/src/gobj/qpgeomVertexAnimationSpec.I
  35. 42 0
      panda/src/gobj/qpgeomVertexAnimationSpec.cxx
  36. 83 0
      panda/src/gobj/qpgeomVertexAnimationSpec.h
  37. 52 0
      panda/src/gobj/qpgeomVertexData.I
  38. 105 0
      panda/src/gobj/qpgeomVertexData.cxx
  39. 12 0
      panda/src/gobj/qpgeomVertexData.h
  40. 28 0
      panda/src/gobj/qpgeomVertexFormat.I
  41. 11 10
      panda/src/gobj/qpgeomVertexFormat.cxx
  42. 6 6
      panda/src/gobj/qpgeomVertexFormat.h
  43. 39 0
      panda/src/gobj/qpgeomVertexReader.I
  44. 209 0
      panda/src/gobj/qpgeomVertexReader.cxx
  45. 7 0
      panda/src/gobj/qpgeomVertexReader.h
  46. 174 0
      panda/src/gobj/qpgeomVertexWriter.I
  47. 195 38
      panda/src/gobj/qpgeomVertexWriter.cxx
  48. 16 1
      panda/src/gobj/qpgeomVertexWriter.h
  49. 43 0
      panda/src/gobj/transformBlendPalette.I
  50. 19 5
      panda/src/gobj/transformBlendPalette.cxx
  51. 6 0
      panda/src/gobj/transformBlendPalette.h
  52. 6 4
      panda/src/gobj/transformPalette.cxx
  53. 3 3
      panda/src/gobj/transformPalette.h
  54. 18 0
      panda/src/gobj/vertexTransform.cxx
  55. 1 0
      panda/src/gobj/vertexTransform.h

+ 15 - 0
panda/src/char/jointVertexTransform.I

@@ -27,3 +27,18 @@ INLINE const CharacterJoint *JointVertexTransform::
 get_joint() const {
   return _joint;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: JointVertexTransform::check_matrix
+//       Access: Private
+//  Description: Recomputes _matrix if it needs it.
+////////////////////////////////////////////////////////////////////
+INLINE void JointVertexTransform::
+check_matrix() const {
+  if (_matrix_stale) {
+    ((JointVertexTransform *)this)->_matrix = 
+      _joint->_initial_net_transform_inverse *
+      _joint->_net_transform;
+    ((JointVertexTransform *)this)->_matrix_stale = false;
+  }
+}

+ 50 - 7
panda/src/char/jointVertexTransform.cxx

@@ -70,16 +70,59 @@ JointVertexTransform::
 ////////////////////////////////////////////////////////////////////
 void JointVertexTransform::
 get_matrix(LMatrix4f &matrix) const {
-  if (_matrix_stale) {
-    ((JointVertexTransform *)this)->_matrix = 
-      _joint->_initial_net_transform_inverse *
-      _joint->_net_transform;
-    ((JointVertexTransform *)this)->_matrix_stale = false;
-  }
-  
+  check_matrix();
   matrix = _matrix;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: JointVertexTransform::mult_matrix
+//       Access: Published, Virtual
+//  Description: Premultiplies this transform's matrix with the
+//               indicated previous matrix, so that the result is the
+//               net composition of the given transform with this
+//               transform.  The result is stored in the parameter
+//               "result", which should not be the same matrix as
+//               previous.
+////////////////////////////////////////////////////////////////////
+void JointVertexTransform::
+mult_matrix(LMatrix4f &result, const LMatrix4f &previous) const {
+  check_matrix();
+  result.multiply(_matrix, previous);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: JointVertexTransform::accumulate_matrix
+//       Access: Published, Virtual
+//  Description: Adds the value of this transform's matrix, modified
+//               by the indicated weight, into the indicated
+//               accumulation matrix.  This is used to compute the
+//               result of several blended transforms.
+////////////////////////////////////////////////////////////////////
+void JointVertexTransform::
+accumulate_matrix(LMatrix4f &accum, float weight) const {
+  check_matrix();
+
+  accum._m.m._00 += _matrix._m.m._00 * weight;
+  accum._m.m._01 += _matrix._m.m._01 * weight;
+  accum._m.m._02 += _matrix._m.m._02 * weight;
+  accum._m.m._03 += _matrix._m.m._03 * weight;
+  
+  accum._m.m._10 += _matrix._m.m._10 * weight;
+  accum._m.m._11 += _matrix._m.m._11 * weight;
+  accum._m.m._12 += _matrix._m.m._12 * weight;
+  accum._m.m._13 += _matrix._m.m._13 * weight;
+  
+  accum._m.m._20 += _matrix._m.m._20 * weight;
+  accum._m.m._21 += _matrix._m.m._21 * weight;
+  accum._m.m._22 += _matrix._m.m._22 * weight;
+  accum._m.m._23 += _matrix._m.m._23 * weight;
+  
+  accum._m.m._30 += _matrix._m.m._30 * weight;
+  accum._m.m._31 += _matrix._m.m._31 * weight;
+  accum._m.m._32 += _matrix._m.m._32 * weight;
+  accum._m.m._33 += _matrix._m.m._33 * weight;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: JointVertexTransform::output
 //       Access: Published, Virtual

+ 4 - 0
panda/src/char/jointVertexTransform.h

@@ -51,10 +51,14 @@ PUBLISHED:
   INLINE const CharacterJoint *get_joint() const;
 
   virtual void get_matrix(LMatrix4f &matrix) const;
+  virtual void mult_matrix(LMatrix4f &result, const LMatrix4f &previous) const;
+  virtual void accumulate_matrix(LMatrix4f &accum, float weight) const;
 
   virtual void output(ostream &out) const;
 
 private:
+  INLINE void check_matrix() const;
+
   PT(CharacterJoint) _joint;
 
   LMatrix4f _matrix;

+ 3 - 3
panda/src/display/Sources.pp

@@ -11,7 +11,7 @@
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx $[TARGET]_composite2.cxx 
  
   #define SOURCES  \
-    colorMunger.I colorMunger.h \
+    standardMunger.I standardMunger.h \
     config_display.h \
     drawableRegion.I drawableRegion.h \
     displayRegion.I displayRegion.h  \
@@ -35,7 +35,7 @@
     lensStack.I lensStack.h
     
  #define INCLUDED_SOURCES  \
-    colorMunger.cxx \
+    standardMunger.cxx \
     config_display.cxx \
     drawableRegion.cxx \
     displayRegion.cxx \
@@ -53,7 +53,7 @@
     windowProperties.cxx
 
   #define INSTALL_HEADERS \
-    colorMunger.I colorMunger.h \
+    standardMunger.I standardMunger.h \
     config_display.h \
     drawableRegion.I drawableRegion.h \
     displayRegion.I displayRegion.h displayRegionStack.I \

+ 2 - 2
panda/src/display/config_display.cxx

@@ -18,7 +18,7 @@
 
 
 #include "config_display.h"
-#include "colorMunger.h"
+#include "standardMunger.h"
 #include "graphicsStateGuardian.h"
 #include "graphicsPipe.h"
 #include "graphicsOutput.h"
@@ -248,7 +248,7 @@ init_libdisplay() {
   }
   initialized = true;
   
-  ColorMunger::init_type();
+  StandardMunger::init_type();
   GraphicsStateGuardian::init_type();
   GraphicsPipe::init_type();
   GraphicsOutput::init_type();

+ 1 - 1
panda/src/display/display_composite1.cxx

@@ -1,4 +1,4 @@
-#include "colorMunger.cxx"
+#include "standardMunger.cxx"
 #include "drawableRegion.cxx"
 #include "displayRegion.cxx"
 #include "graphicsEngine.cxx"

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

@@ -201,6 +201,44 @@ get_max_cube_map_dimension() const {
   return _max_cube_map_dimension;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_vertex_transforms
+//       Access: Published
+//  Description: Returns the maximum number of transform matrices that
+//               may be simultaneously used to transform any one
+//               vertex by the graphics hardware.  If this number is
+//               0, then the hardware (or the graphics backend)
+//               doesn't support soft-skinned vertices (in which case
+//               Panda will animate the vertices in software).
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_vertex_transforms() const {
+  return _max_vertex_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GraphicsStateGuardian::get_max_vertex_transform_indices
+//       Access: Published
+//  Description: Returns the maximum number of transforms there may be
+//               in a single TransformPalette for this graphics
+//               hardware.  If this number is 0 (but
+//               get_max_transforms() is nonzero), then the graphics
+//               hardware (or API) doesn't support indexed transforms,
+//               but can support direct transform references.
+//
+//               The value returned may not be meaningful until after
+//               the graphics context has been fully created (e.g. the
+//               window has been opened).
+////////////////////////////////////////////////////////////////////
+INLINE int GraphicsStateGuardian::
+get_max_vertex_transform_indices() const {
+  return _max_vertex_transform_indices;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: GraphicsStateGuardian::get_copy_texture_inverted
 //       Access: Published

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

@@ -104,6 +104,10 @@ GraphicsStateGuardian(const FrameBufferProperties &properties,
   _max_3d_texture_dimension = 0;
   _max_cube_map_dimension = 0;
 
+  // Assume no vertex blending capability.
+  _max_vertex_transforms = 0;
+  _max_vertex_transform_indices = 0;
+
   // Initially, we set this to false; a GSG that knows it has this
   // property should set it to true.
   _copy_texture_inverted = false;

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

@@ -88,6 +88,9 @@ PUBLISHED:
   INLINE int get_max_3d_texture_dimension() const;
   INLINE int get_max_cube_map_dimension() const;
 
+  INLINE int get_max_vertex_transforms() const;
+  INLINE int get_max_vertex_transform_indices() const;
+
   INLINE bool get_copy_texture_inverted() const;
   virtual bool get_supports_multisample() const;
   INLINE bool get_supports_generate_mipmap() const;
@@ -314,6 +317,9 @@ protected:
   int _max_3d_texture_dimension;
   int _max_cube_map_dimension;
 
+  int _max_vertex_transforms;
+  int _max_vertex_transform_indices;
+
   bool _copy_texture_inverted;
   bool _supports_multisample;
   bool _supports_generate_mipmap;

+ 1 - 1
panda/src/display/colorMunger.I → panda/src/display/standardMunger.I

@@ -1,4 +1,4 @@
-// Filename: colorMunger.I
+// Filename: standardMunger.I
 // Created by:  drose (21Mar05)
 //
 ////////////////////////////////////////////////////////////////////

+ 70 - 22
panda/src/display/colorMunger.cxx → panda/src/display/standardMunger.cxx

@@ -1,4 +1,4 @@
-// Filename: colorMunger.cxx
+// Filename: standardMunger.cxx
 // Created by:  drose (21Mar05)
 //
 ////////////////////////////////////////////////////////////////////
@@ -16,47 +16,53 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#include "colorMunger.h"
+#include "standardMunger.h"
 #include "renderState.h"
+#include "graphicsStateGuardian.h"
 #include "dcast.h"
+#include "config_gobj.h"
 
-TypeHandle ColorMunger::_type_handle;
+TypeHandle StandardMunger::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ColorMunger::Constructor
+//     Function: StandardMunger::Constructor
 //       Access: Public
-//  Description: 
+//  Description: The StandardMunger constructor accepts additional
+//               parameters that specify the GSG's preferred color
+//               format (since we might be munging the color anyway,
+//               we might as well convert it as we munge).
 ////////////////////////////////////////////////////////////////////
-ColorMunger::
-ColorMunger(const GraphicsStateGuardianBase *gsg, const RenderState *state,
-            int num_components,
-            qpGeomVertexDataType::NumericType numeric_type,
-            qpGeomVertexDataType::Contents contents) :
+StandardMunger::
+StandardMunger(const GraphicsStateGuardianBase *gsg, const RenderState *state,
+               int num_components,
+               qpGeomVertexDataType::NumericType numeric_type,
+               qpGeomVertexDataType::Contents contents) :
   qpGeomMunger(gsg, state),
   _num_components(num_components),
   _numeric_type(numeric_type),
   _contents(contents)
 {
+  _gsg = DCAST(GraphicsStateGuardian, gsg);
   _color = state->get_color();
   _color_scale = state->get_color_scale();
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ColorMunger::Destructor
+//     Function: StandardMunger::Destructor
 //       Access: Public, Virtual
 //  Description: 
 ////////////////////////////////////////////////////////////////////
-ColorMunger::
-~ColorMunger() {
+StandardMunger::
+~StandardMunger() {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ColorMunger::munge_data_impl
+//     Function: StandardMunger::munge_data_impl
 //       Access: Protected, Virtual
 //  Description: Given a source GeomVertexData, converts it as
 //               necessary for rendering.
 ////////////////////////////////////////////////////////////////////
-CPT(qpGeomVertexData) ColorMunger::
+CPT(qpGeomVertexData) StandardMunger::
 munge_data_impl(const qpGeomVertexData *data) {
   CPT(qpGeomVertexData) new_data = data;
 
@@ -81,20 +87,62 @@ munge_data_impl(const qpGeomVertexData *data) {
                                      _contents);
   }
 
-  return qpGeomMunger::munge_data_impl(new_data);
+  qpGeomVertexAnimationSpec animation = data->get_format()->get_animation();
+  if (hardware_animated_vertices &&
+      animation.get_animation_type() == qpGeomVertexAnimationSpec::AT_panda &&
+      data->get_slider_table() == (SliderTable *)NULL) {
+    // Maybe we can animate the vertices with hardware.
+    const TransformBlendPalette *palette = data->get_transform_blend_palette();
+    if (palette != (TransformBlendPalette *)NULL &&
+        palette->get_max_simultaneous_transforms() <= 
+        _gsg->get_max_vertex_transforms()) {
+      if (palette->get_num_transforms() <= 
+          _gsg->get_max_vertex_transform_indices()) {
+
+        if (palette->get_num_transforms() == palette->get_max_simultaneous_transforms()) {
+          // We can support an indexed palette, but since that won't
+          // save us any per-vertex blends, go ahead and do a plain
+          // old nonindexed palette instead.
+          animation.set_hardware(palette->get_num_transforms(), false);
+
+        } else {
+          // We can support an indexed palette, and that means we can
+          // reduce the number of blends we have to specify for each
+          // vertex.
+          animation.set_hardware(palette->get_max_simultaneous_transforms(), true);
+        }
+
+      } else if (palette->get_num_transforms() <=
+                 _gsg->get_max_vertex_transforms()) {
+        // We can't support an indexed palette, but we have few enough
+        // transforms that we can do a nonindexed palette.
+        animation.set_hardware(palette->get_num_transforms(), false);
+      }
+    }
+  }
+  
+  CPT(qpGeomVertexFormat) orig_format = data->get_format();
+  CPT(qpGeomVertexFormat) new_format = munge_format(orig_format, animation);
+
+  if (new_format == orig_format) {
+    // Trivial case.
+    return data;
+  }
+
+  return data->convert_to(new_format);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ColorMunger::compare_to_impl
+//     Function: StandardMunger::compare_to_impl
 //       Access: Protected, Virtual
 //  Description: Called to compare two GeomMungers who are known to be
 //               of the same type, for an apples-to-apples comparison.
 //               This will never be called on two pointers of a
 //               different type.
 ////////////////////////////////////////////////////////////////////
-int ColorMunger::
+int StandardMunger::
 compare_to_impl(const qpGeomMunger *other) const {
-  const ColorMunger *om = DCAST(ColorMunger, other);
+  const StandardMunger *om = DCAST(StandardMunger, other);
   if (_color != om->_color) {
     return _color < om->_color ? -1 : 1;
   }
@@ -106,16 +154,16 @@ compare_to_impl(const qpGeomMunger *other) const {
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: ColorMunger::geom_compare_to_impl
+//     Function: StandardMunger::geom_compare_to_impl
 //       Access: Protected, Virtual
 //  Description: Called to compare two GeomMungers who are known to be
 //               of the same type, for an apples-to-apples comparison.
 //               This will never be called on two pointers of a
 //               different type.
 ////////////////////////////////////////////////////////////////////
-int ColorMunger::
+int StandardMunger::
 geom_compare_to_impl(const qpGeomMunger *other) const {
-  const ColorMunger *om = DCAST(ColorMunger, other);
+  const StandardMunger *om = DCAST(StandardMunger, other);
   if (_color != om->_color) {
     return _color < om->_color ? -1 : 1;
   }

+ 18 - 14
panda/src/display/colorMunger.h → panda/src/display/standardMunger.h

@@ -1,4 +1,4 @@
-// Filename: colorMunger.h
+// Filename: standardMunger.h
 // Created by:  drose (21Mar05)
 //
 ////////////////////////////////////////////////////////////////////
@@ -16,29 +16,32 @@
 //
 ////////////////////////////////////////////////////////////////////
 
-#ifndef COLORMUNGER_H
-#define COLORMUNGER_H
+#ifndef STANDARDMUNGER_H
+#define STANDARDMUNGER_H
 
 #include "pandabase.h"
 #include "qpgeomMunger.h"
+#include "graphicsStateGuardian.h"
 #include "colorAttrib.h"
 #include "colorScaleAttrib.h"
 #include "pointerTo.h"
 
 ////////////////////////////////////////////////////////////////////
-//       Class : ColorMunger
-// Description : Applies ColorAttrib and ColorScaleAttrib by munging
-//               the vertex data.
+//       Class : StandardMunger
+// Description : Performs some generic munging that is appropriate for
+//               all GSG types; for instance, applies ColorAttrib and
+//               ColorScaleAttrib to the vertices, and checks for
+//               hardware-accelerated animation capabilities.
 //
 //               This is part of the experimental Geom rewrite.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDA ColorMunger : public qpGeomMunger {
+class EXPCL_PANDA StandardMunger : public qpGeomMunger {
 public:
-  ColorMunger(const GraphicsStateGuardianBase *gsg, const RenderState *state,
-              int num_components,
-              qpGeomVertexDataType::NumericType numeric_type,
-              qpGeomVertexDataType::Contents contents);
-  virtual ~ColorMunger();
+  StandardMunger(const GraphicsStateGuardianBase *gsg, const RenderState *state,
+                 int num_components,
+                 qpGeomVertexDataType::NumericType numeric_type,
+                 qpGeomVertexDataType::Contents contents);
+  virtual ~StandardMunger();
 
 protected:
   virtual CPT(qpGeomVertexData) munge_data_impl(const qpGeomVertexData *data);
@@ -49,6 +52,7 @@ private:
   int _num_components;
   qpGeomVertexDataType::NumericType _numeric_type;
   qpGeomVertexDataType::Contents _contents;
+  CPT(GraphicsStateGuardian) _gsg;
   CPT(ColorAttrib) _color;
   CPT(ColorScaleAttrib) _color_scale;
 
@@ -58,7 +62,7 @@ public:
   }
   static void init_type() {
     qpGeomMunger::init_type();
-    register_type(_type_handle, "ColorMunger",
+    register_type(_type_handle, "StandardMunger",
                   qpGeomMunger::get_class_type());
   }
   virtual TypeHandle get_type() const {
@@ -70,7 +74,7 @@ private:
   static TypeHandle _type_handle;
 };
 
-#include "colorMunger.I"
+#include "standardMunger.I"
 
 #endif
 

+ 2 - 2
panda/src/dxgsg8/dxGeomMunger8.I

@@ -24,7 +24,7 @@
 ////////////////////////////////////////////////////////////////////
 INLINE DXGeomMunger8::
 DXGeomMunger8(GraphicsStateGuardian *gsg, const RenderState *state) :
-  ColorMunger(gsg, state, 1, qpGeomVertexDataType::NT_packed_8888,
+  StandardMunger(gsg, state, 1, qpGeomVertexDataType::NT_packed_8888,
               qpGeomVertexDataType::C_argb)
 {
 }
@@ -38,5 +38,5 @@ DXGeomMunger8(GraphicsStateGuardian *gsg, const RenderState *state) :
 ////////////////////////////////////////////////////////////////////
 INLINE void *DXGeomMunger8::
 operator new(size_t size) {
-  return do_operator_new(size, _deleted_chain);
+  return do_operator_new(size, &_deleted_chain);
 }

+ 29 - 25
panda/src/dxgsg8/dxGeomMunger8.cxx

@@ -17,6 +17,9 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "dxGeomMunger8.h"
+#include "qpgeomVertexReader.h"
+#include "qpgeomVertexWriter.h"
+#include "config_dxgsg8.h"
 
 qpGeomMunger *DXGeomMunger8::_deleted_chain = NULL;
 TypeHandle DXGeomMunger8::_type_handle;
@@ -28,11 +31,19 @@ TypeHandle DXGeomMunger8::_type_handle;
 //               necessary to the appropriate format for rendering.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomVertexFormat) DXGeomMunger8::
-munge_format_impl(const qpGeomVertexFormat *orig) {
+munge_format_impl(const qpGeomVertexFormat *orig,
+                  const qpGeomVertexAnimationSpec &animation) {
+  if (dxgsg8_cat.is_debug()) {
+    if (animation.get_animation_type() != qpGeomVertexAnimationSpec::AT_none) {
+      dxgsg8_cat.debug()
+        << "preparing animation type " << animation << "\n";
+    }
+  }
   // We have to build a completely new format that includes only the
   // appropriate components, in the appropriate order, in just one
   // array.
   PT(qpGeomVertexFormat) new_format = new qpGeomVertexFormat(*orig);
+  new_format->set_animation(animation);
   PT(qpGeomVertexArrayFormat) new_array_format = new qpGeomVertexArrayFormat;
 
   const qpGeomVertexDataType *vertex_type = 
@@ -54,6 +65,23 @@ munge_format_impl(const qpGeomVertexFormat *orig) {
     return orig;
   }
 
+  if (animation.get_animation_type() == qpGeomVertexAnimationSpec::AT_hardware &&
+      animation.get_num_transforms() > 0) {
+    // If we want hardware animation, we need to reserve space for the
+    // blend weights.
+    new_array_format->add_data_type
+      (InternalName::get_transform_weight(), animation.get_num_transforms() - 1,
+       qpGeomVertexDataType::NT_float32, qpGeomVertexDataType::C_other);
+
+    if (animation.get_indexed_transforms()) {
+      // Also, if we'll be indexing into the transfom palette, reserve
+      // space for the index.
+      new_array_format->add_data_type
+        (InternalName::get_transform_index(), 1,
+         qpGeomVertexDataType::NT_packed_8888, qpGeomVertexDataType::C_index);
+    }                                    
+  }
+
   if (normal_type != (const qpGeomVertexDataType *)NULL) {
     new_array_format->add_data_type
       (InternalName::get_normal(), 3, qpGeomVertexDataType::NT_float32,
@@ -89,27 +117,3 @@ munge_format_impl(const qpGeomVertexFormat *orig) {
   new_format->insert_array(0, new_array_format);
   return qpGeomVertexFormat::register_format(new_format);
 }
-
-////////////////////////////////////////////////////////////////////
-//     Function: DXGeomMunger8::munge_geom_impl
-//       Access: Protected, Virtual
-//  Description: Converts a Geom and/or its data as necessary.
-////////////////////////////////////////////////////////////////////
-void DXGeomMunger8::
-munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data) {
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: DXGeomMunger8::compare_to_impl
-//       Access: Protected, Virtual
-//  Description: Called to compare two GeomMungers who are known to be
-//               of the same type, for an apples-to-apples comparison.
-//               This will never be called on two pointers of a
-//               different type.
-////////////////////////////////////////////////////////////////////
-int DXGeomMunger8::
-compare_to_impl(const qpGeomMunger *other) const {
-  //  const DXGeomMunger8 *om = DCAST(DXGeomMunger8, other);
-
-  return ColorMunger::compare_to_impl(other);
-}

+ 6 - 7
panda/src/dxgsg8/dxGeomMunger8.h

@@ -20,7 +20,7 @@
 #define DXGEOMMUNGER8_H
 
 #include "pandabase.h"
-#include "colorMunger.h"
+#include "standardMunger.h"
 #include "graphicsStateGuardian.h"
 
 ////////////////////////////////////////////////////////////////////
@@ -31,14 +31,13 @@
 //               and that all relevant components are packed into a
 //               single array, in the correct order.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_PANDADX DXGeomMunger8 : public ColorMunger {
+class EXPCL_PANDADX DXGeomMunger8 : public StandardMunger {
 public:
   INLINE DXGeomMunger8(GraphicsStateGuardian *gsg, const RenderState *state);
 
 protected:
-  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);
-  virtual void munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data);
-  virtual int compare_to_impl(const qpGeomMunger *other) const;
+  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig,
+                                                    const qpGeomVertexAnimationSpec &animation);
 
 public:
   INLINE void *operator new(size_t size);
@@ -51,9 +50,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    ColorMunger::init_type();
+    StandardMunger::init_type();
     register_type(_type_handle, "DXGeomMunger8",
-                  ColorMunger::get_class_type());
+                  StandardMunger::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 104 - 35
panda/src/dxgsg8/dxGraphicsStateGuardian8.cxx

@@ -196,6 +196,7 @@ DXGraphicsStateGuardian8(const FrameBufferProperties &properties) :
     _pD3DDevice = NULL;
     
     _bDXisReady = false;
+    _transform_stale = false;
     _overlay_windows_supported = false;
 
     _pFvfBufBasePtr = NULL;
@@ -287,10 +288,16 @@ free_d3d_device(void) {
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
 free_nondx_resources() {
-    // this must not release any objects associated with D3D/DX!
-    // those should be released in free_dxgsg_objects instead
+  // this must not release any objects associated with D3D/DX!
+  // those should be released in free_dxgsg_objects instead
+  if (_index_buf != NULL) {
     SAFE_DELETE_ARRAY(_index_buf);
+    _index_buf = NULL;
+  }
+  if (_pFvfBufBasePtr != NULL) {
     SAFE_DELETE_ARRAY(_pFvfBufBasePtr);
+    _pFvfBufBasePtr = NULL;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -310,6 +317,34 @@ dx_init(void) {
     assert(_pScrn->pD3D8!=NULL);
     assert(_pD3DDevice!=NULL);
 
+    D3DCAPS8 d3dCaps;
+    _pD3DDevice->GetDeviceCaps(&d3dCaps);
+
+    if (dxgsg8_cat.is_debug()) {
+      dxgsg8_cat.debug()
+        << "\nHwTransformAndLight = " << ((d3dCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) != 0)
+        << "\nMaxTextureWidth = " << d3dCaps.MaxTextureWidth
+        << "\nMaxTextureHeight = " << d3dCaps.MaxTextureHeight
+        << "\nMaxVolumeExtent = " << d3dCaps.MaxVolumeExtent
+        << "\nMaxTextureAspectRatio = " << d3dCaps.MaxTextureAspectRatio
+        << "\nTexCoordCount = " << (d3dCaps.FVFCaps & D3DFVFCAPS_TEXCOORDCOUNTMASK)
+        << "\nMaxTextureBlendStages = " << d3dCaps.MaxTextureBlendStages
+        << "\nMaxSimultaneousTextures = " << d3dCaps.MaxSimultaneousTextures
+        << "\nMaxActiveLights = " << d3dCaps.MaxActiveLights
+        << "\nMaxUserClipPlanes = " << d3dCaps.MaxUserClipPlanes
+        << "\nMaxVertexBlendMatrices = " << d3dCaps.MaxVertexBlendMatrices
+        << "\nMaxVertexBlendMatrixIndex = " << d3dCaps.MaxVertexBlendMatrixIndex
+        << "\nMaxPointSize = " << d3dCaps.MaxPointSize
+        << "\nMaxPrimitiveCount = " << d3dCaps.MaxPrimitiveCount
+        << "\nMaxVertexIndex = " << d3dCaps.MaxVertexIndex
+        << "\nMaxStreams = " << d3dCaps.MaxStreams
+        << "\nMaxStreamStride = " << d3dCaps.MaxStreamStride
+        << "\n";
+    }
+
+    _max_vertex_transforms = d3dCaps.MaxVertexBlendMatrices;
+    _max_vertex_transform_indices = d3dCaps.MaxVertexBlendMatrixIndex;
+
     ZeroMemory(&_lmodel_ambient,sizeof(Colorf));
     _pD3DDevice->SetRenderState(D3DRS_AMBIENT, 0x0);
 
@@ -2616,14 +2651,64 @@ begin_draw_primitives(const qpGeom *geom, const qpGeomMunger *munger,
 
   const qpGeomVertexFormat *format = _vertex_data->get_format();
 
-  // The munger should have put the "vertex" data at the beginning of
-  // the first array.
+  // The munger should have put the FVF data in the first array.
   const qpGeomVertexArrayData *data = _vertex_data->get_array(0);
 
   VertexBufferContext *vbc = ((qpGeomVertexArrayData *)data)->prepare_now(get_prepared_objects(), this);
   nassertr(vbc != (VertexBufferContext *)NULL, false);
   apply_vertex_buffer(vbc);
 
+  const qpGeomVertexAnimationSpec &animation = 
+    vertex_data->get_format()->get_animation();
+  if (animation.get_animation_type() == qpGeomVertexAnimationSpec::AT_hardware) {
+    // Set up vertex blending.
+    switch (animation.get_num_transforms()) {
+    case 1:
+      _pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_0WEIGHTS);
+      break;
+    case 2:
+      _pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_1WEIGHTS);
+      break;
+    case 3:
+      _pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_2WEIGHTS);
+      break;
+    case 4:
+      _pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_3WEIGHTS);
+      break;
+    }
+
+    if (animation.get_indexed_transforms()) {
+      // Set up indexed vertex blending.
+      _pD3DDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);
+    } else {
+      _pD3DDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, FALSE);
+    }
+    
+    const TransformPalette *palette = vertex_data->get_transform_palette();
+    if (palette != (TransformPalette *)NULL) {
+      for (int i = 0; i < palette->get_num_transforms(); i++) {
+        LMatrix4f mat;
+        palette->get_transform(i)->mult_matrix(mat, _transform->get_mat());
+        const D3DMATRIX *d3d_mat = (const D3DMATRIX *)mat.get_data();
+        _pD3DDevice->SetTransform(D3DTS_WORLDMATRIX(i), d3d_mat);
+      }
+
+      // Setting the palette transforms steps on the world matrix, so
+      // we have to set a flag to reload the world matrix later.
+      _transform_stale = true;
+    }
+    
+  } else {
+    // We're not using vertex blending.
+    _pD3DDevice->SetRenderState(D3DRS_VERTEXBLEND, D3DVBF_DISABLE);
+
+    if (_transform_stale) {
+      const D3DMATRIX *d3d_mat = (const D3DMATRIX *)_transform->get_mat().get_data();
+      _pD3DDevice->SetTransform(D3DTS_WORLD, d3d_mat);
+    }
+  }
+
+
   return true;
 }
 
@@ -2938,16 +3023,18 @@ release_texture(TextureContext *tc) {
 ////////////////////////////////////////////////////////////////////
 VertexBufferContext *DXGraphicsStateGuardian8::
 prepare_vertex_buffer(qpGeomVertexArrayData *data) {
-  if (dxgsg8_cat.is_debug()) {
-    dxgsg8_cat.debug()
-      << "prepare_vertex_buffer(" << (void *)data << ")\n";
-  }
-
   DXVertexBufferContext8 *dvbc = new DXVertexBufferContext8(data);
 
   if (vertex_buffers) {
     dvbc->create_vbuffer(*_pScrn);
     dvbc->mark_loaded();
+
+    if (dxgsg8_cat.is_debug()) {
+      dxgsg8_cat.debug()
+        << "creating vertex buffer " << dvbc->_vbuffer << ": "
+        << data->get_num_vertices() << " vertices " 
+        << *data->get_array_format() << "\n";
+    }
   }
 
   return dvbc;
@@ -2967,10 +3054,6 @@ apply_vertex_buffer(VertexBufferContext *vbc) {
     add_to_vertex_buffer_record(dvbc);
   
     if (dvbc->was_modified()) {
-      if (dxgsg8_cat.is_debug()) {
-        dxgsg8_cat.debug()
-          << "apply_vertex_buffer(" << (void *)vbc->get_data() << ")\n";
-      }
       if (dvbc->changed_size()) {
         // Here we have to destroy the old vertex buffer and create a
         // new one.
@@ -3005,11 +3088,6 @@ apply_vertex_buffer(VertexBufferContext *vbc) {
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
 release_vertex_buffer(VertexBufferContext *vbc) {
-  if (dxgsg8_cat.is_debug()) {
-    dxgsg8_cat.debug()
-      << "release_vertex_buffer(" << (void *)vbc->get_data() << ")\n";
-  }
-
   DXVertexBufferContext8 *dvbc = DCAST(DXVertexBufferContext8, vbc);
   delete dvbc;
 }
@@ -3029,16 +3107,16 @@ release_vertex_buffer(VertexBufferContext *vbc) {
 ////////////////////////////////////////////////////////////////////
 IndexBufferContext *DXGraphicsStateGuardian8::
 prepare_index_buffer(qpGeomPrimitive *data) {
-  if (dxgsg8_cat.is_debug()) {
-    dxgsg8_cat.debug()
-      << "prepare_index_buffer(" << (void *)data << ")\n";
-  }
-
   DXIndexBufferContext8 *dibc = new DXIndexBufferContext8(data);
 
   dibc->create_ibuffer(*_pScrn);
   dibc->mark_loaded();
 
+  if (dxgsg8_cat.is_debug()) {
+    dxgsg8_cat.debug()
+      << "creating index buffer " << dibc->_ibuffer << "\n";
+  }
+
   return dibc;
 }
 
@@ -3056,10 +3134,6 @@ apply_index_buffer(IndexBufferContext *ibc) {
     add_to_index_buffer_record(dibc);
   
     if (dibc->was_modified()) {
-      if (dxgsg8_cat.is_debug()) {
-        dxgsg8_cat.debug()
-          << "apply_index_buffer(" << (void *)ibc->get_data() << ")\n";
-      }
       if (dibc->changed_size()) {
         // Here we have to destroy the old index buffer and create a
         // new one.
@@ -3092,11 +3166,6 @@ apply_index_buffer(IndexBufferContext *ibc) {
 ////////////////////////////////////////////////////////////////////
 void DXGraphicsStateGuardian8::
 release_index_buffer(IndexBufferContext *ibc) {
-  if (dxgsg8_cat.is_debug()) {
-    dxgsg8_cat.debug()
-      << "release_index_buffer(" << (void *)ibc->get_data() << ")\n";
-  }
-
   DXIndexBufferContext8 *dibc = DCAST(DXIndexBufferContext8, ibc);
   delete dibc;
 }
@@ -3419,9 +3488,9 @@ void DXGraphicsStateGuardian8::
 issue_transform(const TransformState *transform) {
   DO_PSTATS_STUFF(_transform_state_pcollector.add_level(1));
 
-  // if we're using ONLY vertex shaders, could get avoid calling SetTrans
-  D3DMATRIX *pMat = (D3DMATRIX*)transform->get_mat().get_data();
-  _pD3DDevice->SetTransform(D3DTS_WORLD,pMat);
+  const D3DMATRIX *d3d_mat = (const D3DMATRIX *)transform->get_mat().get_data();
+  _pD3DDevice->SetTransform(D3DTS_WORLD, d3d_mat);
+  _transform_stale = false;
 
   _transform = transform;
   if (_auto_rescale_normal) {

+ 1 - 2
panda/src/dxgsg8/dxGraphicsStateGuardian8.h

@@ -185,8 +185,7 @@ protected:
   HRESULT               _last_testcooplevel_result;
   DXTextureContext8  *_pCurTexContext;
 
-  bool              _bTransformIssued;  // decaling needs to tell when a transform has been issued
-  D3DMATRIX         _SavedTransform;
+  bool _transform_stale;
 
   RenderBuffer::Type _cur_read_pixel_buffer;  // source for copy_pixel_buffer operation
   bool _auto_rescale_normal;

+ 11 - 0
panda/src/dxgsg8/dxIndexBufferContext8.cxx

@@ -43,6 +43,11 @@ DXIndexBufferContext8(qpGeomPrimitive *data) :
 DXIndexBufferContext8::
 ~DXIndexBufferContext8() {
   if (_ibuffer != NULL) {
+    if (dxgsg8_cat.is_debug()) {
+      dxgsg8_cat.debug()
+        << "deleting index buffer " << _ibuffer << "\n";
+    }
+
     RELEASE(_ibuffer, dxgsg8, "index buffer", RELEASE_ONCE);
     _ibuffer = NULL;
   }
@@ -84,6 +89,12 @@ upload_data() {
   nassertv(_ibuffer != NULL);
 
   int data_size = get_data()->get_data_size_bytes();
+  
+  if (dxgsg8_cat.is_debug()) {
+    dxgsg8_cat.debug()
+      << "copying " << data_size
+      << " bytes into index buffer " << _ibuffer << "\n";
+  }
 
   BYTE *local_pointer;
   HRESULT hr = _ibuffer->Lock(0, data_size, &local_pointer, 0);

+ 55 - 1
panda/src/dxgsg8/dxVertexBufferContext8.cxx

@@ -47,9 +47,52 @@ DXVertexBufferContext8(qpGeomVertexArrayData *data) :
   
   if (n < num_data_types && 
       array_format->get_data_type(n)->get_name() == InternalName::get_vertex()) {
-    _fvf |= D3DFVF_XYZ;
     ++n;
+
+    int num_blend_values = 0;
+
+    if (n < num_data_types &&
+        array_format->get_data_type(n)->get_name() == InternalName::get_transform_weight()) {
+      // We have hardware vertex animation.
+      num_blend_values = array_format->get_data_type(n)->get_num_values();
+      ++n;
+      
+      if (n < num_data_types &&
+          array_format->get_data_type(n)->get_name() == InternalName::get_transform_index()) {
+        // Furthermore, it's indexed vertex animation.
+        _fvf |= D3DFVF_LASTBETA_UBYTE4;
+        ++num_blend_values;
+        ++n;
+      }
+    }
+
+    switch (num_blend_values) {
+    case 0:
+      _fvf |= D3DFVF_XYZ;
+      break;
+
+    case 1:
+      _fvf |= D3DFVF_XYZB1;
+      break;
+
+    case 2:
+      _fvf |= D3DFVF_XYZB2;
+      break;
+
+    case 3:
+      _fvf |= D3DFVF_XYZB3;
+      break;
+
+    case 4:
+      _fvf |= D3DFVF_XYZB4;
+      break;
+
+    case 5:
+      _fvf |= D3DFVF_XYZB5;
+      break;
+    }
   }
+
   if (n < num_data_types && 
       array_format->get_data_type(n)->get_name() == InternalName::get_normal()) {
     _fvf |= D3DFVF_NORMAL;
@@ -95,6 +138,11 @@ DXVertexBufferContext8(qpGeomVertexArrayData *data) :
 DXVertexBufferContext8::
 ~DXVertexBufferContext8() {
   if (_vbuffer != NULL) {
+    if (dxgsg8_cat.is_debug()) {
+      dxgsg8_cat.debug()
+        << "deleting vertex buffer " << _vbuffer << "\n";
+    }
+
     RELEASE(_vbuffer, dxgsg8, "vertex buffer", RELEASE_ONCE);
     _vbuffer = NULL;
   }
@@ -136,6 +184,12 @@ upload_data() {
   nassertv(_vbuffer != NULL);
 
   int data_size = get_data()->get_data_size_bytes();
+  
+  if (dxgsg8_cat.is_debug()) {
+    dxgsg8_cat.debug()
+      << "copying " << data_size
+      << " bytes into vertex buffer " << _vbuffer << "\n";
+  }
 
   BYTE *local_pointer;
   HRESULT hr = _vbuffer->Lock(0, data_size, &local_pointer, 0);

+ 7 - 0
panda/src/egg2pg/eggLoader.cxx

@@ -1960,6 +1960,13 @@ make_vertex_data(const EggRenderState *render_state,
     // maybe a SliderTable, and additional columns in the vertex data:
     // one that indexes into the blend palette per vertex, and also
     // one for each different type of morph delta.
+
+    // Tell the format that we're setting it up for Panda-based
+    // animation.
+    qpGeomVertexAnimationSpec animation;
+    animation.set_panda();
+    temp_format->set_animation(animation);
+
     blend_palette = new TransformBlendPalette;
 
     PT(qpGeomVertexArrayFormat) anim_array_format = new qpGeomVertexArrayFormat;

+ 2 - 2
panda/src/glstuff/glGeomMunger_src.I

@@ -24,7 +24,7 @@
 ////////////////////////////////////////////////////////////////////
 INLINE CLP(GeomMunger)::
 CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state) :
-  ColorMunger(gsg, state, 4, qpGeomVertexDataType::NT_uint8,
+  StandardMunger(gsg, state, 4, qpGeomVertexDataType::NT_uint8,
               qpGeomVertexDataType::C_rgba),
   _gsg(gsg),
   _texture(state->get_texture()),
@@ -52,5 +52,5 @@ get_gsg() const {
 ////////////////////////////////////////////////////////////////////
 INLINE void *CLP(GeomMunger)::
 operator new(size_t size) {
-  return do_operator_new(size, _deleted_chain);
+  return do_operator_new(size, &_deleted_chain);
 }

+ 11 - 12
panda/src/glstuff/glGeomMunger_src.cxx

@@ -44,33 +44,33 @@ CLP(GeomMunger)::
 //               necessary to the appropriate format for rendering.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomVertexFormat) CLP(GeomMunger)::
-munge_format_impl(const qpGeomVertexFormat *orig) {
-  CPT(qpGeomVertexFormat) format = orig;
+munge_format_impl(const qpGeomVertexFormat *orig,
+                  const qpGeomVertexAnimationSpec &animation) {
+  PT(qpGeomVertexFormat) new_format = new qpGeomVertexFormat(*orig);
+  new_format->set_animation(animation);
 
   const qpGeomVertexDataType *color_type = 
-    format->get_data_type(InternalName::get_color());
+    orig->get_data_type(InternalName::get_color());
   if (color_type != (qpGeomVertexDataType *)NULL &&
       (color_type->get_numeric_type() == qpGeomVertexDataType::NT_packed_8888 ||
        color_type->get_contents() != qpGeomVertexDataType::C_rgba)) {
     // We need to convert the color format; OpenGL doesn't support the
     // byte order of DirectX's packed ARGB format.
-    int color_array = format->get_array_with(InternalName::get_color());
+    int color_array = orig->get_array_with(InternalName::get_color());
 
-    PT(qpGeomVertexFormat) new_format = new qpGeomVertexFormat(*format);
     PT(qpGeomVertexArrayFormat) new_array_format = new_format->modify_array(color_array);
 
     // Replace the existing color format with the new format.
     new_array_format->add_data_type
       (InternalName::get_color(), 4, qpGeomVertexDataType::NT_uint8,
        qpGeomVertexDataType::C_rgba, color_type->get_start());
-
-    format = qpGeomVertexFormat::register_format(new_format);
   }
 
   /*
   if (true) {
     // Split out the interleaved array into n parallel arrays.
-    PT(qpGeomVertexFormat) new_format = new qpGeomVertexFormat;
+    CPT(qpGeomVertexFormat) format = new_format;
+    new_format = new qpGeomVertexFormat;
     for (int i = 0; i < format->get_num_data_types(); i++) {
       const qpGeomVertexDataType *data_type = format->get_data_type(i);
       PT(qpGeomVertexArrayFormat) new_array_format = new qpGeomVertexArrayFormat;
@@ -78,11 +78,10 @@ munge_format_impl(const qpGeomVertexFormat *orig) {
                                       data_type->get_numeric_type());
       new_format->add_array(new_array_format);
     }
-    format = qpGeomVertexFormat::register_format(new_format);
   }
   */
 
-  return format;
+  return qpGeomVertexFormat::register_format(new_format);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -103,7 +102,7 @@ compare_to_impl(const qpGeomMunger *other) const {
     return _tex_gen < om->_tex_gen ? -1 : 1;
   }
 
-  return ColorMunger::compare_to_impl(other);
+  return StandardMunger::compare_to_impl(other);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -119,5 +118,5 @@ geom_compare_to_impl(const qpGeomMunger *other) const {
   // We don't consider _texture and _tex_gen for this purpose; they
   // affect only whether the GL display list should be regenerated or
   // not, and don't require reconverting the vertices.
-  return ColorMunger::geom_compare_to_impl(other);
+  return StandardMunger::geom_compare_to_impl(other);
 }

+ 6 - 5
panda/src/glstuff/glGeomMunger_src.h

@@ -17,7 +17,7 @@
 ////////////////////////////////////////////////////////////////////
 
 #include "pandabase.h"
-#include "colorMunger.h"
+#include "standardMunger.h"
 #include "graphicsStateGuardian.h"
 #include "textureAttrib.h"
 #include "texGenAttrib.h"
@@ -31,7 +31,7 @@ class CLP(GeomContext);
 //               for OpenGL rendering.  In particular, it makes sure
 //               colors aren't stored in DirectX's packed_argb format.
 ////////////////////////////////////////////////////////////////////
-class EXPCL_GL CLP(GeomMunger) : public ColorMunger {
+class EXPCL_GL CLP(GeomMunger) : public StandardMunger {
 public:
   INLINE CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state);
   virtual ~CLP(GeomMunger)();
@@ -39,7 +39,8 @@ public:
   INLINE GraphicsStateGuardian *get_gsg() const;
 
 protected:
-  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);
+  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig,
+                                                    const qpGeomVertexAnimationSpec &animation);
   virtual int compare_to_impl(const qpGeomMunger *other) const;
   virtual int geom_compare_to_impl(const qpGeomMunger *other) const;
 
@@ -61,9 +62,9 @@ public:
     return _type_handle;
   }
   static void init_type() {
-    ColorMunger::init_type();
+    StandardMunger::init_type();
     register_type(_type_handle, CLASSPREFIX_QUOTED "GeomMunger",
-                  ColorMunger::get_class_type());
+                  StandardMunger::get_class_type());
   }
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 3 - 0
panda/src/gobj/Sources.pp

@@ -32,6 +32,7 @@
     qpgeomVertexArrayFormat.h qpgeomVertexArrayFormat.I \
     qpgeomCacheEntry.h qpgeomCacheEntry.I \
     qpgeomCacheManager.h qpgeomCacheManager.I \
+    qpgeomVertexAnimationSpec.h qpgeomVertexAnimationSpec.I \
     qpgeomVertexData.h qpgeomVertexData.I \
     qpgeomVertexDataType.h qpgeomVertexDataType.I \
     qpgeomVertexFormat.h qpgeomVertexFormat.I \
@@ -82,6 +83,7 @@
     qpgeomVertexArrayFormat.cxx \
     qpgeomCacheEntry.cxx \
     qpgeomCacheManager.cxx \
+    qpgeomVertexAnimationSpec.cxx \
     qpgeomVertexData.cxx \
     qpgeomVertexDataType.cxx \
     qpgeomVertexFormat.cxx \
@@ -131,6 +133,7 @@
     qpgeomVertexArrayFormat.h qpgeomVertexArrayFormat.I \
     qpgeomCacheEntry.h qpgeomCacheEntry.I \
     qpgeomCacheManager.h qpgeomCacheManager.I \
+    qpgeomVertexAnimationSpec.h qpgeomVertexAnimationSpec.I \
     qpgeomVertexData.h qpgeomVertexData.I \
     qpgeomVertexDataType.h qpgeomVertexDataType.I \
     qpgeomVertexFormat.h qpgeomVertexFormat.I \

+ 15 - 3
panda/src/gobj/config_gobj.cxx

@@ -113,9 +113,21 @@ ConfigVariableBool display_lists
           "rendering static geometry.  On some systems, this can result "
           "in a performance improvement over vertex buffers alone; on "
           "other systems (particularly low-end systems) it makes little to "
-          "no difference.  This has no effect on DirectX rendering.  If "
-          "vertex-buffers is also enabled, then OpenGL buffer objects "
-          "will also be created for dynamic geometry."));
+          "no difference.  On some systems, using display lists can actually "
+          "reduce performance.  This has no effect on DirectX rendering or "
+          "on dynamic geometry (e.g. soft-skinned animation)."));
+
+ConfigVariableBool hardware_animated_vertices
+("hardware-animated-vertices", false,
+ PRC_DESC("Set this true to allow the transforming of soft-skinned "
+          "animated vertices via hardware, if supported, or false always "
+          "to perform the vertex animation via software within Panda.  "
+          "If you have a card that supports this, and your scene does "
+          "not contain too many vertices already, this can provide a "
+          "performance boost by offloading some work from your CPU onto "
+          "your graphics card.  It may also help by reducing the bandwidth "
+          "necessary on your computer's bus.  However, in some cases it "
+          "may actually reduce performance."));
 
 ConfigVariableBool use_qpgeom
 ("use-qpgeom", false,

+ 1 - 0
panda/src/gobj/config_gobj.h

@@ -53,6 +53,7 @@ extern EXPCL_PANDA ConfigVariableBool keep_geom_ram;
 extern EXPCL_PANDA ConfigVariableBool retained_mode;
 extern EXPCL_PANDA ConfigVariableBool vertex_buffers;
 extern EXPCL_PANDA ConfigVariableBool display_lists;
+extern EXPCL_PANDA ConfigVariableBool hardware_animated_vertices;
 
 extern EXPCL_PANDA ConfigVariableBool use_qpgeom;
 

+ 1 - 0
panda/src/gobj/gobj_composite1.cxx

@@ -24,6 +24,7 @@
 #include "qpgeomVertexArrayFormat.cxx"
 #include "qpgeomCacheEntry.cxx"
 #include "qpgeomCacheManager.cxx"
+#include "qpgeomVertexAnimationSpec.cxx"
 #include "qpgeomVertexData.cxx"
 #include "qpgeomVertexDataType.cxx"
 #include "qpgeomVertexFormat.cxx"

+ 26 - 0
panda/src/gobj/internalName.I

@@ -191,6 +191,32 @@ get_transform_blend() {
   return _transform_blend;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: InternalName::get_transform_weight
+//       Access: Published, Static
+//  Description: Returns the standard InternalName "transform_weight".
+////////////////////////////////////////////////////////////////////
+INLINE PT(InternalName) InternalName::
+get_transform_weight() {
+  if (_transform_weight == (InternalName *)NULL) {
+    _transform_weight = InternalName::make("transform_weight");
+  }
+  return _transform_weight;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: InternalName::get_transform_index
+//       Access: Published, Static
+//  Description: Returns the standard InternalName "transform_index".
+////////////////////////////////////////////////////////////////////
+INLINE PT(InternalName) InternalName::
+get_transform_index() {
+  if (_transform_index == (InternalName *)NULL) {
+    _transform_index = InternalName::make("transform_index");
+  }
+  return _transform_index;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: InternalName::get_morph
 //       Access: Published, Static

+ 2 - 0
panda/src/gobj/internalName.cxx

@@ -33,6 +33,8 @@ PT(InternalName) InternalName::_binormal;
 PT(InternalName) InternalName::_texcoord;
 PT(InternalName) InternalName::_color;
 PT(InternalName) InternalName::_transform_blend;
+PT(InternalName) InternalName::_transform_weight;
+PT(InternalName) InternalName::_transform_index;
 
 TypeHandle InternalName::_type_handle;
 TypeHandle InternalName::_texcoord_type_handle;

+ 4 - 0
panda/src/gobj/internalName.h

@@ -72,6 +72,8 @@ PUBLISHED:
   INLINE static PT(InternalName) get_texcoord_name(const string &name);
   INLINE static PT(InternalName) get_color();
   INLINE static PT(InternalName) get_transform_blend();
+  INLINE static PT(InternalName) get_transform_weight();
+  INLINE static PT(InternalName) get_transform_index();
   INLINE static PT(InternalName) get_morph(InternalName *data_type, const string &slider);
 
 private:
@@ -91,6 +93,8 @@ private:
   static PT(InternalName) _texcoord;
   static PT(InternalName) _color;
   static PT(InternalName) _transform_blend;
+  static PT(InternalName) _transform_weight;
+  static PT(InternalName) _transform_index;
   
 public:
   // Datagram stuff

+ 48 - 10
panda/src/gobj/qpgeomMunger.I

@@ -19,7 +19,7 @@
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::is_registered
-//       Access: Published
+//       Access: Public
 //  Description: Returns true if this munger has been registered,
 //               false if it has not.  It may not be used for a Geom
 //               until it has been registered, but once registered, it
@@ -32,7 +32,7 @@ is_registered() const {
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::register_munger
-//       Access: Published, Static
+//       Access: Public, Static
 //  Description: Adds the indicated munger to the registry, if there
 //               is not an equivalent munger already there; in either
 //               case, returns the pointer to the equivalent munger
@@ -100,13 +100,19 @@ geom_compare_to(const qpGeomMunger &other) const {
 //       Access: Public
 //  Description: Given a source GeomVertexFormat, converts it if
 //               necessary to the appropriate format for rendering.
+//
+//               If the GeomVertexAnimationSpec so indicates, then the
+//               format will be chosen to convert CPU-based animation
+//               tables to HW-based animation tables, reserving space
+//               for the specified number of transforms per vertex.
 ////////////////////////////////////////////////////////////////////
 INLINE CPT(qpGeomVertexFormat) qpGeomMunger::
-munge_format(const qpGeomVertexFormat *format) const {
+munge_format(const qpGeomVertexFormat *format,
+             const qpGeomVertexAnimationSpec &animation) const {
   // We cast away the const pointer, because do_munge_format() needs
   // to update caches and stuff, but we trust it not to change any
   // user-definable parameters.
-  return ((qpGeomMunger *)this)->do_munge_format(format);
+  return ((qpGeomMunger *)this)->do_munge_format(format, animation);
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -123,6 +129,22 @@ munge_data(const qpGeomVertexData *data) const {
   return ((qpGeomMunger *)this)->munge_data_impl(data);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomMunger::operator new
+//       Access: Public
+//  Description: This implementation of operator new isn't really
+//               supposed to be called, but it's provided in case
+//               someone screws up and doesn't define a more
+//               appropriate operator new (that passes up a valid
+//               _deleted_chain) for a derived class.  Having this
+//               operator new that passes NULL for _deleted_chain will
+//               at least allow us to detect the error.
+////////////////////////////////////////////////////////////////////
+INLINE void *qpGeomMunger::
+operator new(size_t size) {
+  return do_operator_new(size, NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomMunger::operator delete
 //       Access: Public
@@ -133,7 +155,14 @@ munge_data(const qpGeomVertexData *data) const {
 INLINE void qpGeomMunger::
 operator delete(void *ptr) {
   qpGeomMunger *obj = (qpGeomMunger *)ptr;
-  nassertv(obj->_deleted_chain != (qpGeomMunger **)NULL);
+
+#ifndef NDEBUG
+  nassertd(obj->_deleted_chain != (qpGeomMunger **)NULL) {
+    delete obj;
+    return;
+  }
+#endif
+
   nassertv(obj->_next == (qpGeomMunger *)NULL);
   obj->_next = (*obj->_deleted_chain);
   (*obj->_deleted_chain) = obj;
@@ -153,15 +182,24 @@ operator delete(void *ptr) {
 //               there when it is deleted.
 ////////////////////////////////////////////////////////////////////
 INLINE void *qpGeomMunger::
-do_operator_new(size_t size, qpGeomMunger *&deleted_chain) {
+do_operator_new(size_t size, qpGeomMunger **deleted_chain) {
   qpGeomMunger *obj;
-  if (deleted_chain != (qpGeomMunger *)NULL) {
-    obj = deleted_chain;
-    deleted_chain = deleted_chain->_next;
+
+#ifndef NDEBUG
+  nassertd(deleted_chain != (qpGeomMunger **)NULL) {
+    obj = (qpGeomMunger *)::operator new(size);
+    obj->_deleted_chain = NULL;
+    return obj;
+  }
+#endif
+    
+  if ((*deleted_chain) != (qpGeomMunger *)NULL) {
+    obj = (*deleted_chain);
+    (*deleted_chain) = (*deleted_chain)->_next;
   } else {
     obj = (qpGeomMunger *)::operator new(size);
   }
-  obj->_deleted_chain = &deleted_chain;
+  obj->_deleted_chain = deleted_chain;
 
   return obj;
 }

+ 15 - 72
panda/src/gobj/qpgeomMunger.cxx

@@ -76,36 +76,7 @@ qpGeomMunger::
   if (is_registered()) {
     get_registry()->unregister_munger(this);
   }
-  nassertv(_formats.empty());
-}
-
-////////////////////////////////////////////////////////////////////
-//     Function: qpGeomMunger::remove_format
-//       Access: Public
-//  Description: Removes a prepared GeomVertexFormat from the cache.
-////////////////////////////////////////////////////////////////////
-void qpGeomMunger::
-remove_format(const qpGeomVertexFormat *format) {
-  // If this assertion is triggered, maybe we accidentally deleted a
-  // GeomVertexFormat while we were in the process of unregistering,
-  // causing a recursive re-entry.
-  nassertv(_is_registered);
-
-  Formats::iterator fi;
-  fi = _formats.find(format);
-  nassertv(fi != _formats.end());
-
-  // We can't save this in a CPT, because it might be the same pointer
-  // as format, which might be in the middle of its destructor.
-  // Putting it in a CPT would bump up its reference count again, and
-  // end up calling the destructor twice.
-  const qpGeomVertexFormat *derived_format = (*fi).second;
-  _formats.erase(fi);
-
-  // We need to unref the derived format, if we reffed it earlier.
-  if (derived_format != format) {
-    unref_delete(derived_format);
-  }
+  nassertv(_formats_by_animation.empty());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -128,43 +99,29 @@ remove_data(const qpGeomVertexData *data) {
 //               exists just to cast away the const pointer.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomVertexFormat) qpGeomMunger::
-do_munge_format(const qpGeomVertexFormat *format) {
+do_munge_format(const qpGeomVertexFormat *format, 
+                const qpGeomVertexAnimationSpec &animation) {
   nassertr(_is_registered, NULL);
   nassertr(format->is_registered(), NULL);
 
+  Formats &formats = _formats_by_animation[animation];
+
   Formats::iterator fi;
-  fi = _formats.find(format);
-  if (fi != _formats.end()) {
+  fi = formats.find(format);
+  if (fi != formats.end()) {
     // This format was previously munged, so the answer will be the
     // same.
     return (*fi).second;
   }
 
   // We have to munge this format for the first time.
-  CPT(qpGeomVertexFormat) derived_format = munge_format_impl(format);
+  CPT(qpGeomVertexFormat) derived_format = munge_format_impl(format, animation);
   nassertr(derived_format->is_registered(), NULL);
 
   // Store the answer in the map, so we can quickly get it next time.
-  bool inserted = _formats.insert(Formats::value_type(format, derived_format)).second;
+  bool inserted = formats.insert(Formats::value_type(format, derived_format)).second;
   nassertr(inserted, NULL);
 
-  // Tell the source format that we have its pointer.
-  {
-    qpGeomVertexFormat *f = (qpGeomVertexFormat *)format;
-    MutexHolder holder(f->_cache_lock);
-    nassertr(f->is_registered(), NULL);
-    inserted = f->_mungers.insert(this).second;
-    nassertr(inserted, NULL);
-  }
-
-  // We also want to keep a reference count on the derived format, but
-  // only if it is actually a different pointer from the original
-  // format--otherwise it would cause a self-referential reference
-  // count.
-  if (derived_format != format) {
-    derived_format->ref();
-  }
-
   return derived_format;
 }
 
@@ -175,7 +132,7 @@ do_munge_format(const qpGeomVertexFormat *format) {
 //               necessary to the appropriate format for rendering.
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomVertexFormat) qpGeomMunger::
-munge_format_impl(const qpGeomVertexFormat *orig) {
+munge_format_impl(const qpGeomVertexFormat *orig, const qpGeomVertexAnimationSpec &) {
   return orig;
 }
 
@@ -190,7 +147,8 @@ munge_data_impl(const qpGeomVertexData *data) {
   nassertr(_is_registered, NULL);
 
   CPT(qpGeomVertexFormat) orig_format = data->get_format();
-  CPT(qpGeomVertexFormat) new_format = munge_format(orig_format);
+  CPT(qpGeomVertexFormat) new_format = 
+    munge_format(orig_format, orig_format->get_animation());
 
   if (new_format == orig_format) {
     // Trivial case.
@@ -208,7 +166,7 @@ munge_data_impl(const qpGeomVertexData *data) {
 void qpGeomMunger::
 munge_geom_impl(CPT(qpGeom) &, CPT(qpGeomVertexData) &) {
   // The default implementation does nothing (the work has already
-  // been done in munge_format_impl).
+  // been done in munge_format_impl() and munge_data_impl()).
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -261,7 +219,7 @@ do_register() {
       << "GeomMunger::do_register(): " << (void *)this << "\n";
   }
   nassertv(!_is_registered);
-  nassertv(_formats.empty());
+  nassertv(_formats_by_animation.empty());
 
   // Tell the cache manager to hang on to this new GeomMunger, so we
   // don't waste our time re-registering the same GeomMunger over and
@@ -288,22 +246,7 @@ do_unregister() {
   _is_registered = false;
 
   // Unregistering means we should blow away the cache.
-  Formats::iterator fi;
-  for (fi = _formats.begin(); fi != _formats.end(); ++fi) {
-    qpGeomVertexFormat *format = (qpGeomVertexFormat *)(*fi).first;
-    CPT(qpGeomVertexFormat) derived_format = (*fi).second;
-
-    {
-      MutexHolder holder(format->_cache_lock);
-      size_t num_erased = format->_mungers.erase(this);
-      nassertv(num_erased == 1);
-    }
-
-    if (derived_format != format) {
-      derived_format->unref();
-    }
-  }
-  _formats.clear();
+  _formats_by_animation.clear();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 12 - 7
panda/src/gobj/qpgeomMunger.h

@@ -21,6 +21,7 @@
 
 #include "pandabase.h"
 #include "typedReferenceCount.h"
+#include "qpgeomVertexAnimationSpec.h"
 #include "qpgeomVertexFormat.h"
 #include "qpgeomVertexData.h"
 #include "qpgeomCacheEntry.h"
@@ -67,8 +68,8 @@ public:
   INLINE bool is_registered() const;
   INLINE static CPT(qpGeomMunger) register_munger(qpGeomMunger *munger);
 
-  INLINE CPT(qpGeomVertexFormat) munge_format(const qpGeomVertexFormat *format) const;
-  void remove_format(const qpGeomVertexFormat *format);
+  INLINE CPT(qpGeomVertexFormat) munge_format(const qpGeomVertexFormat *format,
+                                              const qpGeomVertexAnimationSpec &animation) const;
 
   INLINE CPT(qpGeomVertexData) munge_data(const qpGeomVertexData *data) const;
   void remove_data(const qpGeomVertexData *data);
@@ -80,9 +81,11 @@ public:
   INLINE int geom_compare_to(const qpGeomMunger &other) const;
 
 protected:
-  CPT(qpGeomVertexFormat) do_munge_format(const qpGeomVertexFormat *format);
+  CPT(qpGeomVertexFormat) do_munge_format(const qpGeomVertexFormat *format,
+                                          const qpGeomVertexAnimationSpec &animation);
 
-  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig);
+  virtual CPT(qpGeomVertexFormat) munge_format_impl(const qpGeomVertexFormat *orig,
+                                                    const qpGeomVertexAnimationSpec &animation);
   virtual CPT(qpGeomVertexData) munge_data_impl(const qpGeomVertexData *data);
   virtual void munge_geom_impl(CPT(qpGeom) &geom, CPT(qpGeomVertexData) &data);
   virtual int compare_to_impl(const qpGeomMunger *other) const;
@@ -93,10 +96,11 @@ public:
   // and delete using their own deleted_chain.  This is the base class
   // implementation, which requires a pointer to deleted_chain be
   // stored on each instance.
+  INLINE void *operator new(size_t size);
   INLINE void operator delete(void *ptr);
 
 protected:
-  INLINE static void *do_operator_new(size_t size, qpGeomMunger *&_deleted_chain);
+  INLINE static void *do_operator_new(size_t size, qpGeomMunger **_deleted_chain);
 
 private:
   qpGeomMunger **_deleted_chain;
@@ -120,8 +124,9 @@ private:
     PT(qpGeomMunger) _munger;
   };
 
-  typedef pmap<const qpGeomVertexFormat *, const qpGeomVertexFormat *> Formats;
-  Formats _formats;
+  typedef pmap<CPT(qpGeomVertexFormat), CPT(qpGeomVertexFormat) > Formats;
+  typedef pmap<qpGeomVertexAnimationSpec, Formats> FormatsByAnimation;
+  FormatsByAnimation _formats_by_animation;
 
   bool _is_registered;
   typedef pset<qpGeomMunger *, IndirectCompareTo<qpGeomMunger> > Mungers;

+ 198 - 0
panda/src/gobj/qpgeomVertexAnimationSpec.I

@@ -0,0 +1,198 @@
+// Filename: qpgeomVertexAnimationSpec.I
+// Created by:  drose (29Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexAnimationSpec::
+qpGeomVertexAnimationSpec() :
+  _animation_type(AT_none),
+  _num_transforms(0),
+  _indexed_transforms(0)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::Copy Constructor
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexAnimationSpec::
+qpGeomVertexAnimationSpec(const qpGeomVertexAnimationSpec &other) :
+  _animation_type(other._animation_type),
+  _num_transforms(other._num_transforms),
+  _indexed_transforms(other._indexed_transforms)
+{
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::Copy Assignment Operator
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexAnimationSpec::
+operator = (const qpGeomVertexAnimationSpec &other) {
+  _animation_type = other._animation_type;
+  _num_transforms = other._num_transforms;
+  _indexed_transforms = other._indexed_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::get_animation_type
+//       Access: Published
+//  Description: Returns the type of animation represented by this
+//               spec.
+////////////////////////////////////////////////////////////////////
+INLINE qpGeomVertexAnimationSpec::AnimationType qpGeomVertexAnimationSpec::
+get_animation_type() const {
+  return _animation_type;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::get_num_transforms
+//       Access: Published
+//  Description: This is only meaningful for animation_type
+//               AT_hardware.  It specifies the maximum number of
+//               transforms that might be simultaneously applied to
+//               any one vertex by the data in this format.
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomVertexAnimationSpec::
+get_num_transforms() const {
+  return _num_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::get_indexed_transforms
+//       Access: Published
+//  Description: This is only meaningful for animation_type
+//               AT_hardware.  If true, it indicates that the format
+//               uses indexed animation palettes.  It is false if each
+//               vertex will reference the first _num_transforms
+//               palette entries only.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomVertexAnimationSpec::
+get_indexed_transforms() const {
+  return _indexed_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::set_none
+//       Access: Published
+//  Description: Specifies that no vertex animation is represented by
+//               this spec.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexAnimationSpec::
+set_none() {
+  _animation_type = AT_none;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::set_panda
+//       Access: Published
+//  Description: Specifies that vertex animation is to be performed by
+//               Panda.  This is the most general setting and can
+//               handle any kind of vertex animation represented.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexAnimationSpec::
+set_panda() {
+  _animation_type = AT_panda;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::set_hardware
+//       Access: Published
+//  Description: Specifies that vertex animation is to be performed by
+//               the graphics hardware (or at least by the graphics
+//               backend API, which is actually still free to animate
+//               the vertices on the CPU).
+//
+//               This is only legal if the graphics hardware can
+//               support the specified limits on number of transforms
+//               and/or indexed transforms.  Also, no current graphics
+//               API's support morphing.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexAnimationSpec::
+set_hardware(int num_transforms, bool indexed_transforms) {
+  _animation_type = AT_hardware;
+  _num_transforms = num_transforms;
+  _indexed_transforms = indexed_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::operator <
+//       Access: Public
+//  Description: Provides an arbitrary ordering between different
+//               animation specs.
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomVertexAnimationSpec::
+operator < (const qpGeomVertexAnimationSpec &other) const {
+  return (compare_to(other) < 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::operator ==
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomVertexAnimationSpec::
+operator == (const qpGeomVertexAnimationSpec &other) const {
+  return (compare_to(other) == 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::operator !=
+//       Access: Public
+//  Description: 
+////////////////////////////////////////////////////////////////////
+INLINE bool qpGeomVertexAnimationSpec::
+operator != (const qpGeomVertexAnimationSpec &other) const {
+  return (compare_to(other) != 0);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::compare_to
+//       Access: Public
+//  Description: Provides an arbitrary ordering between different
+//               animation specs.
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomVertexAnimationSpec::
+compare_to(const qpGeomVertexAnimationSpec &other) const {
+  if (_animation_type != other._animation_type) {
+    return (int)_animation_type - (int)other._animation_type;
+  }
+
+  if (_animation_type == AT_hardware) {
+    if (_num_transforms != other._num_transforms) {
+      return _num_transforms - other._num_transforms;
+    }
+    if (_indexed_transforms != other._indexed_transforms) {
+      return (int)_indexed_transforms - (int)other._indexed_transforms;
+    }
+  }
+  
+  return 0;
+}
+
+INLINE ostream &
+operator << (ostream &out, const qpGeomVertexAnimationSpec &animation) {
+  animation.output(out);
+  return out;
+}

+ 42 - 0
panda/src/gobj/qpgeomVertexAnimationSpec.cxx

@@ -0,0 +1,42 @@
+// Filename: qpgeomVertexAnimationSpec.cxx
+// Created by:  drose (29Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "qpgeomVertexAnimationSpec.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexAnimationSpec::output
+//       Access: Published
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexAnimationSpec::
+output(ostream &out) const {
+  switch (_animation_type) {
+  case AT_none:
+    out << "none";
+    break;
+
+  case AT_panda:
+    out << "panda";
+    break;
+
+  case AT_hardware:
+    out << "hardware(" << _num_transforms << ", " 
+        << _indexed_transforms << ")";
+    break;
+  }
+}

+ 83 - 0
panda/src/gobj/qpgeomVertexAnimationSpec.h

@@ -0,0 +1,83 @@
+// Filename: qpgeomVertexAnimationSpec.h
+// Created by:  drose (29Mar05)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, 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://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef qpGEOMVERTEXANIMATIONSPEC_H
+#define qpGEOMVERTEXANIMATIONSPEC_H
+
+#include "pandabase.h"
+
+////////////////////////////////////////////////////////////////////
+//       Class : qpGeomVertexAnimationSpec
+// Description : This object describes how the vertex animation, if
+//               any, represented in a GeomVertexData is encoded.
+//
+//               Vertex animation includes soft-skinned skeleton
+//               animation and morphs (blend shapes), and might be
+//               performed on the CPU by Panda, or passed down to the
+//               graphics backed to be performed on the hardware
+//               (depending on the hardware's advertised
+//               capabilities).
+//
+//               Changing this setting doesn't by itself change the
+//               way the animation is actually performed; this just
+//               specifies how the vertices are set up to be animated.
+//
+//               This is part of the experimental Geom rewrite.
+////////////////////////////////////////////////////////////////////
+class EXPCL_PANDA qpGeomVertexAnimationSpec {
+PUBLISHED:
+  INLINE qpGeomVertexAnimationSpec();
+  INLINE qpGeomVertexAnimationSpec(const qpGeomVertexAnimationSpec &other);
+  INLINE void operator = (const qpGeomVertexAnimationSpec &other);
+
+  enum AnimationType {
+    AT_none,     // No vertex animation.
+    AT_panda,    // Vertex animation calculated on the CPU by Panda.
+    AT_hardware, // Hardware-accelerated animation on the graphics card.
+  };
+
+  INLINE AnimationType get_animation_type() const;
+
+  INLINE int get_num_transforms() const;
+  INLINE bool get_indexed_transforms() const;
+
+  INLINE void set_none();
+  INLINE void set_panda();
+  INLINE void set_hardware(int num_transforms, bool indexed_transforms);
+
+  void output(ostream &out) const;
+
+public:
+  INLINE bool operator < (const qpGeomVertexAnimationSpec &other) const;
+  INLINE bool operator == (const qpGeomVertexAnimationSpec &other) const;
+  INLINE bool operator != (const qpGeomVertexAnimationSpec &other) const;
+  INLINE int compare_to(const qpGeomVertexAnimationSpec &other) const;
+
+private:  
+  AnimationType _animation_type;
+
+  int _num_transforms;
+  bool _indexed_transforms;
+};
+
+INLINE ostream &
+operator << (ostream &out, const qpGeomVertexAnimationSpec &animation);
+
+#include "qpgeomVertexAnimationSpec.I"
+
+#endif

+ 52 - 0
panda/src/gobj/qpgeomVertexData.I

@@ -118,6 +118,40 @@ get_array(int i) const {
   return cdata->_arrays[i];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::get_transform_palette
+//       Access: Published
+//  Description: Returns a const pointer to the TransformPalette
+//               assigned to this data.  Vertices within the table
+//               will index into this palette to indicate their
+//               dynamic skinning information; this table is used when
+//               the vertex animation is to be performed by the
+//               graphics hardware (but also see
+//               get_transform_blend_palette()).
+//
+//               This will return NULL if the vertex data does not
+//               have a TransformPalette assigned (which implies the
+//               vertices will not be animated by the graphics
+//               hardware).
+////////////////////////////////////////////////////////////////////
+INLINE const TransformPalette *qpGeomVertexData::
+get_transform_palette() const {
+  CDReader cdata(_cycler);
+  return cdata->_transform_palette;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::clear_transform_palette
+//       Access: Published
+//  Description: Sets the TransformPalette pointer to NULL,
+//               removing the palette from the vertex data.  This
+//               disables hardware-driven vertex animation.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexData::
+clear_transform_palette() {
+  set_transform_palette(NULL);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::get_transform_blend_palette
 //       Access: Published
@@ -288,6 +322,24 @@ unpack_8888_d(PN_uint32 data) {
   return data & 0xff;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::add_transform
+//       Access: Private, Static
+//  Description: Adds the indicated transform to the palette, if it is
+//               not already there, and returns its index number.
+////////////////////////////////////////////////////////////////////
+INLINE int qpGeomVertexData::
+add_transform(TransformPalette *palette, const VertexTransform *transform,
+              TransformMap &already_added) {
+  pair<TransformMap::iterator, bool> result = already_added.insert(TransformMap::value_type(transform, palette->get_num_transforms()));
+  
+  if (result.second) {
+    palette->add_transform(transform);
+  }
+
+  return (*(result.first)).second;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::CData::Constructor
 //       Access: Public

+ 105 - 0
panda/src/gobj/qpgeomVertexData.cxx

@@ -201,6 +201,47 @@ set_array(int i, const qpGeomVertexArrayData *array) {
   cdata->_animated_vertices_modified = UpdateSeq();
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::modify_transform_palette
+//       Access: Published
+//  Description: Returns a modifiable pointer to the current
+//               TransformPalette on this vertex data, if any, or
+//               NULL if there is not a TransformPalette.  See
+//               get_transform_palette().
+////////////////////////////////////////////////////////////////////
+TransformPalette *qpGeomVertexData::
+modify_transform_palette() {
+  // Perform copy-on-write: if the reference count on the palette is
+  // greater than 1, assume some other GeomVertexData has the same
+  // pointer, so make a copy of it first.
+  CDWriter cdata(_cycler);
+
+  if (cdata->_transform_palette->get_ref_count() > 1) {
+    cdata->_transform_palette = new TransformPalette(*cdata->_transform_palette);
+  }
+  cdata->_modified = qpGeom::get_next_modified();
+  cdata->_animated_vertices_modified = UpdateSeq();
+
+  return cdata->_transform_palette;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::set_transform_palette
+//       Access: Published
+//  Description: Replaces the TransformPalette on this vertex
+//               data with the indicated palette.  The length of this
+//               palette should be consistent with the maximum palette
+//               index assigned to the vertices under the
+//               "transform_index" name.
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexData::
+set_transform_palette(const TransformPalette *palette) {
+  CDWriter cdata(_cycler);
+  cdata->_transform_palette = (TransformPalette *)palette;
+  cdata->_modified = qpGeom::get_next_modified();
+  cdata->_animated_vertices_modified = UpdateSeq();
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::modify_transform_blend_palette
 //       Access: Published
@@ -442,6 +483,65 @@ copy_from(const qpGeomVertexData &source, bool keep_data_objects) {
       }
     }
   }
+
+    // Also convert the animation tables as necessary.
+  const qpGeomVertexAnimationSpec &source_animation = source_format->get_animation();
+  const qpGeomVertexAnimationSpec &dest_animation = dest_format->get_animation();
+  if (source_animation != dest_animation) {
+    if (dest_animation.get_animation_type() == qpGeomVertexAnimationSpec::AT_hardware) {
+      // Convert Panda-style animation tables to hardware-style
+      // animation tables.
+      CPT(TransformBlendPalette) blend_palette = source.get_transform_blend_palette();
+      if (blend_palette != (TransformBlendPalette *)NULL) {
+        PT(TransformPalette) transform_palette = new TransformPalette;
+        TransformMap already_added;
+
+        if (dest_animation.get_indexed_transforms()) {
+          // Build an indexed transform array.  This is easier; this
+          // means we can put the blends in any order.
+          qpGeomVertexWriter weight(this, InternalName::get_transform_weight());
+          qpGeomVertexWriter index(this, InternalName::get_transform_index());
+          qpGeomVertexReader from(&source, InternalName::get_transform_blend());
+        
+          while (!from.is_at_end()) {
+            const TransformBlend &blend = blend_palette->get_blend(from.get_data1i());
+            LVecBase4f weights = LVecBase4f::zero();
+            int indices[4] = {0, 0, 0, 0};
+            nassertv(blend.get_num_transforms() <= 4);
+            
+            for (int i = 0; i < blend.get_num_transforms(); i++) {
+              weights[i] = blend.get_weight(i);
+              indices[i] = add_transform(transform_palette, blend.get_transform(i),
+                                         already_added);
+            }
+            weight.set_data4f(weights);
+            index.set_data4i(indices[3], indices[2], indices[1], indices[0]);
+          }
+        } else {
+          // Build a nonindexed transform array.  This means we have to
+          // use the same n transforms, in the same order, for each vertex.
+          qpGeomVertexWriter weight(this, InternalName::get_transform_weight());
+          qpGeomVertexReader from(&source, InternalName::get_transform_blend());
+        
+          while (!from.is_at_end()) {
+            const TransformBlend &blend = blend_palette->get_blend(from.get_data1i());
+            LVecBase4f weights = LVecBase4f::zero();
+            
+            for (int i = 0; i < blend.get_num_transforms(); i++) {
+              int index = add_transform(transform_palette, blend.get_transform(i),
+                                        already_added);
+              nassertv(index <= 4);
+              weights[index] = blend.get_weight(i);
+            }
+            weight.set_data4f(weights);
+          }
+        }
+        
+        clear_transform_blend_palette();
+        set_transform_palette(transform_palette);
+      }
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -725,6 +825,11 @@ CPT(qpGeomVertexData) qpGeomVertexData::
 do_animate_vertices(bool from_app) const {
   CDReader cdata(_cycler);
 
+  if (_format->get_animation().get_animation_type() !=
+      qpGeomVertexAnimationSpec::AT_panda) {
+    return this;
+  }
+
   UpdateSeq modified;
   if (cdata->_transform_blend_palette != (TransformBlendPalette *)NULL) {
     if (cdata->_slider_table != (SliderTable *)NULL) {

+ 12 - 0
panda/src/gobj/qpgeomVertexData.h

@@ -25,6 +25,7 @@
 #include "qpgeomVertexDataType.h"
 #include "qpgeomVertexArrayData.h"
 #include "qpgeomUsageHint.h"
+#include "transformPalette.h"
 #include "transformBlendPalette.h"
 #include "sliderTable.h"
 #include "internalName.h"
@@ -87,6 +88,11 @@ PUBLISHED:
   qpGeomVertexArrayData *modify_array(int i);
   void set_array(int i, const qpGeomVertexArrayData *array);
 
+  INLINE const TransformPalette *get_transform_palette() const;
+  TransformPalette *modify_transform_palette();
+  void set_transform_palette(const TransformPalette *palette);
+  INLINE void clear_transform_palette();
+
   INLINE const TransformBlendPalette *get_transform_blend_palette() const;
   TransformBlendPalette *modify_transform_blend_palette();
   void set_transform_blend_palette(const TransformBlendPalette *palette);
@@ -154,6 +160,11 @@ private:
   uint8_rgba_to_packed_argb(unsigned char *to, int to_stride,
                             const unsigned char *from, int from_stride,
                             int num_records);
+
+  typedef pmap<const VertexTransform *, int> TransformMap;
+  INLINE static int 
+  add_transform(TransformPalette *palette, const VertexTransform *transform,
+                TransformMap &already_added);
   
 private:
   string _name;
@@ -173,6 +184,7 @@ private:
     virtual void fillin(DatagramIterator &scan, BamReader *manager);
 
     Arrays _arrays;
+    PT(TransformPalette) _transform_palette;
     PT(TransformBlendPalette) _transform_blend_palette;
     PT(SliderTable) _slider_table;
     PT(qpGeomVertexData) _animated_vertices;

+ 28 - 0
panda/src/gobj/qpgeomVertexFormat.I

@@ -67,6 +67,34 @@ register_format(qpGeomVertexArrayFormat *format) {
   return get_registry()->register_format(format);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexFormat::get_animation
+//       Access: Published
+//  Description: Returns the GeomVertexAnimationSpec that indicates
+//               how this format's vertices are set up for animation.
+////////////////////////////////////////////////////////////////////
+INLINE const qpGeomVertexAnimationSpec &qpGeomVertexFormat::
+get_animation() const {
+  return _animation;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexFormat::set_animation
+//       Access: Published
+//  Description: Resets the GeomVertexAnimationSpec that indicates
+//               how this format's vertices are set up for animation.
+//               You should also, of course, change the columns in the
+//               tables accordingly.
+//
+//               This may not be called once the format has been
+//               registered.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexFormat::
+set_animation(const qpGeomVertexAnimationSpec &animation) {
+  nassertv(!_is_registered);
+  _animation = animation;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexFormat::get_num_arrays
 //       Access: Published

+ 11 - 10
panda/src/gobj/qpgeomVertexFormat.cxx

@@ -58,6 +58,7 @@ qpGeomVertexFormat(qpGeomVertexArrayFormat *array_format) :
 qpGeomVertexFormat::
 qpGeomVertexFormat(const qpGeomVertexFormat &copy) :
   _is_registered(false),
+  _animation(copy._animation),
   _arrays(copy._arrays)
 {
 }
@@ -71,6 +72,7 @@ void qpGeomVertexFormat::
 operator = (const qpGeomVertexFormat &copy) {
   nassertv(!_is_registered);
 
+  _animation = copy._animation;
   _arrays = copy._arrays;
 }
 
@@ -84,8 +86,6 @@ qpGeomVertexFormat::
   if (is_registered()) {
     get_registry()->unregister_format(this);
   }
-
-  nassertv(_mungers.empty());
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -379,6 +379,10 @@ output(ostream &out) const {
       ++ai;
     }
   }
+
+  if (_animation.get_animation_type() != qpGeomVertexAnimationSpec::AT_none) {
+    out << ", anim " << _animation;
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -446,6 +450,11 @@ get_array_info(const InternalName *name, int &array_index,
 ////////////////////////////////////////////////////////////////////
 int qpGeomVertexFormat::
 compare_to(const qpGeomVertexFormat &other) const {
+  int compare = _animation.compare_to(other._animation);
+  if (compare != 0) {
+    return compare;
+  }
+
   if (_arrays.size() != other._arrays.size()) {
     return (int)_arrays.size() - (int)other._arrays.size();
   }
@@ -481,7 +490,6 @@ void qpGeomVertexFormat::
 do_register() {
   nassertv(!_is_registered);
   nassertv(_data_types_by_name.empty());
-  nassertv(_mungers.empty());
 
   for (int array = 0; array < (int)_arrays.size(); ++array) {
     const qpGeomVertexArrayFormat *array_format = _arrays[array];
@@ -556,13 +564,6 @@ do_unregister() {
   _is_registered = false;
 
   _data_types_by_name.clear();
-
-  MutexHolder holder(_cache_lock);
-  Mungers::iterator mi;
-  for (mi = _mungers.begin(); mi != _mungers.end(); ++mi) {
-    (*mi)->remove_format(this);
-  }
-  _mungers.clear();  
 }
 
 ////////////////////////////////////////////////////////////////////

+ 6 - 6
panda/src/gobj/qpgeomVertexFormat.h

@@ -21,6 +21,7 @@
 
 #include "pandabase.h"
 #include "typedWritableReferenceCount.h"
+#include "qpgeomVertexAnimationSpec.h"
 #include "qpgeomVertexArrayFormat.h"
 #include "internalName.h"
 #include "luse.h"
@@ -66,6 +67,9 @@ PUBLISHED:
   INLINE static CPT(qpGeomVertexFormat) register_format(qpGeomVertexFormat *format);
   INLINE static CPT(qpGeomVertexFormat) register_format(qpGeomVertexArrayFormat *format);
 
+  INLINE const qpGeomVertexAnimationSpec &get_animation() const;
+  INLINE void set_animation(const qpGeomVertexAnimationSpec &animation);
+
   INLINE int get_num_arrays() const;
   INLINE const qpGeomVertexArrayFormat *get_array(int array) const;
   qpGeomVertexArrayFormat *modify_array(int array);
@@ -140,6 +144,8 @@ private:
 
   bool _is_registered;
 
+  qpGeomVertexAnimationSpec _animation;
+
   typedef pvector< PT(qpGeomVertexArrayFormat) > Arrays;
   Arrays _arrays;
 
@@ -161,12 +167,6 @@ private:
   typedef pvector<MorphRecord> Morphs;
   Morphs _morphs;
 
-  // This set keeps track of things that need to be told when we
-  // destruct, and it is protected by the mutex.
-  Mutex _cache_lock;
-  typedef pset<qpGeomMunger *> Mungers;
-  Mungers _mungers;
-
   // This is the global registry of all currently-in-use formats.
   typedef pset<qpGeomVertexFormat *, IndirectCompareTo<qpGeomVertexFormat> > Formats;
   class EXPCL_PANDA Registry {

+ 39 - 0
panda/src/gobj/qpgeomVertexReader.I

@@ -320,6 +320,45 @@ get_data1i() {
   return _reader->get_data1i(inc_pointer());
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::get_data2i
+//       Access: Published
+//  Description: Returns the data associated with the read vertex,
+//               expressed as a 2-component value, and advances the
+//               read vertex.
+////////////////////////////////////////////////////////////////////
+INLINE const int *qpGeomVertexReader::
+get_data2i() {
+  nassertr(_data_type != (qpGeomVertexDataType *)NULL, 0);
+  return _reader->get_data2i(inc_pointer());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::get_data3i
+//       Access: Published
+//  Description: Returns the data associated with the read vertex,
+//               expressed as a 3-component value, and advances the
+//               read vertex.
+////////////////////////////////////////////////////////////////////
+INLINE const int *qpGeomVertexReader::
+get_data3i() {
+  nassertr(_data_type != (qpGeomVertexDataType *)NULL, 0);
+  return _reader->get_data3i(inc_pointer());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::get_data4i
+//       Access: Published
+//  Description: Returns the data associated with the read vertex,
+//               expressed as a 4-component value, and advances the
+//               read vertex.
+////////////////////////////////////////////////////////////////////
+INLINE const int *qpGeomVertexReader::
+get_data4i() {
+  nassertr(_data_type != (qpGeomVertexDataType *)NULL, 0);
+  return _reader->get_data4i(inc_pointer());
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexReader::set_pointer
 //       Access: Private

+ 209 - 0
panda/src/gobj/qpgeomVertexReader.cxx

@@ -423,6 +423,215 @@ get_data1i(const unsigned char *pointer) {
   return 0;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::Reader::get_data2i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+const int *qpGeomVertexReader::Reader::
+get_data2i(const unsigned char *pointer) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    _i[0] = get_data1i(pointer);
+    _i[1] = 0;
+    return _i;
+
+  default:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      _i[0] = pointer[0];
+      _i[1] = pointer[1];
+      return _i;
+      
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        const PN_uint16 *pi = (const PN_uint16 *)pointer;
+        _i[0] = pi[0];
+        _i[1] = pi[1];
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      {
+        PN_uint32 dword = *(const PN_uint32 *)pointer;
+        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
+          _i[0] = qpGeomVertexData::unpack_8888_b(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_c(dword);
+        } else {
+          _i[0] = qpGeomVertexData::unpack_8888_a(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_b(dword);
+        }
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        const PN_float32 *pi = (const PN_float32 *)pointer;
+        _i[0] = (int)pi[0];
+        _i[1] = (int)pi[1];
+      }
+      return _i;
+    }
+  }
+
+  return _i;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::Reader::get_data3i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+const int *qpGeomVertexReader::Reader::
+get_data3i(const unsigned char *pointer) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    _i[0] = get_data1i(pointer);
+    _i[1] = 0;
+    _i[2] = 0;
+    return _i;
+
+  case 2:
+    {
+      const int *i = get_data2i(pointer);
+      _i[0] = i[0];
+      _i[1] = i[1];
+      _i[2] = 0;
+    }
+    return _i;
+
+  default:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      _i[0] = pointer[0];
+      _i[1] = pointer[1];
+      _i[2] = pointer[2];
+      return _i;
+      
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        const PN_uint16 *pi = (const PN_uint16 *)pointer;
+        _i[0] = pi[0];
+        _i[1] = pi[1];
+        _i[2] = pi[2];
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      {
+        PN_uint32 dword = *(const PN_uint32 *)pointer;
+        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
+          _i[0] = qpGeomVertexData::unpack_8888_b(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_c(dword);
+          _i[2] = qpGeomVertexData::unpack_8888_d(dword);
+        } else {
+          _i[0] = qpGeomVertexData::unpack_8888_a(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_b(dword);
+          _i[2] = qpGeomVertexData::unpack_8888_c(dword);
+        }
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        const PN_float32 *pi = (const PN_float32 *)pointer;
+        _i[0] = (int)pi[0];
+        _i[1] = (int)pi[1];
+        _i[2] = (int)pi[2];
+      }
+      return _i;
+    }
+  }
+
+  return _i;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexReader::Reader::get_data4i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+const int *qpGeomVertexReader::Reader::
+get_data4i(const unsigned char *pointer) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    _i[0] = get_data1i(pointer);
+    _i[1] = 0;
+    _i[2] = 0;
+    _i[3] = 0;
+    return _i;
+
+  case 2:
+    {
+      const int *i = get_data2i(pointer);
+      _i[0] = i[0];
+      _i[1] = i[1];
+      _i[2] = 0;
+      _i[3] = 0;
+    }
+    return _i;
+
+  case 3:
+    {
+      const int *i = get_data3i(pointer);
+      _i[0] = i[0];
+      _i[1] = i[1];
+      _i[2] = i[2];
+      _i[3] = 0;
+    }
+    return _i;
+
+  default:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      _i[0] = pointer[0];
+      _i[1] = pointer[1];
+      _i[2] = pointer[2];
+      _i[3] = pointer[3];
+      return _i;
+      
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        const PN_uint16 *pi = (const PN_uint16 *)pointer;
+        _i[0] = pi[0];
+        _i[1] = pi[1];
+        _i[2] = pi[2];
+        _i[3] = pi[3];
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      {
+        PN_uint32 dword = *(const PN_uint32 *)pointer;
+        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
+          _i[0] = qpGeomVertexData::unpack_8888_b(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_c(dword);
+          _i[2] = qpGeomVertexData::unpack_8888_d(dword);
+          _i[3] = qpGeomVertexData::unpack_8888_a(dword);
+        } else {
+          _i[0] = qpGeomVertexData::unpack_8888_a(dword);
+          _i[1] = qpGeomVertexData::unpack_8888_b(dword);
+          _i[2] = qpGeomVertexData::unpack_8888_c(dword);
+          _i[3] = qpGeomVertexData::unpack_8888_d(dword);
+        }
+      }
+      return _i;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        const PN_float32 *pi = (const PN_float32 *)pointer;
+        _i[0] = (int)pi[0];
+        _i[1] = (int)pi[1];
+        _i[2] = (int)pi[2];
+        _i[3] = (int)pi[3];
+      }
+      return _i;
+    }
+  }
+
+  return _i;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexReader::Reader_point::get_data1f
 //       Access: Public, Virtual

+ 7 - 0
panda/src/gobj/qpgeomVertexReader.h

@@ -91,6 +91,9 @@ PUBLISHED:
   INLINE const LVecBase4f &get_data4f();
 
   INLINE int get_data1i();
+  INLINE const int *get_data2i();
+  INLINE const int *get_data3i();
+  INLINE const int *get_data4i();
 
 private:
   class Reader;
@@ -124,6 +127,9 @@ private:
     virtual const LVecBase3f &get_data3f(const unsigned char *pointer);
     virtual const LVecBase4f &get_data4f(const unsigned char *pointer);
     virtual int get_data1i(const unsigned char *pointer);
+    virtual const int *get_data2i(const unsigned char *pointer);
+    virtual const int *get_data3i(const unsigned char *pointer);
+    virtual const int *get_data4i(const unsigned char *pointer);
 
     INLINE float maybe_scale_color(unsigned int value);
     INLINE void maybe_scale_color(unsigned int a, unsigned int b);
@@ -136,6 +142,7 @@ private:
     LVecBase2f _v2;
     LVecBase3f _v3;
     LVecBase4f _v4;
+    int _i[4];
   };
 
   // This is a specialization on the generic Reader that handles

+ 174 - 0
panda/src/gobj/qpgeomVertexWriter.I

@@ -372,6 +372,93 @@ set_data1i(int data) {
   _writer->set_data1i(inc_pointer(), data);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data2i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 2-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data2i(int a, int b) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data2i(inc_pointer(), a, b);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data2i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 2-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data2i(const int data[2]) {
+  set_data2i(data[0], data[1]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data3i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 3-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data3i(int a, int b, int c) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data3i(inc_pointer(), a, b, c);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data3i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 3-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data3i(const int data[3]) {
+  set_data3i(data[0], data[1], data[2]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data4i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 4-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data4i(int a, int b, int c, int d) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data4i(inc_pointer(), a, b, c, d);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::set_data4i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 4-component
+//               value, and advances the write vertex.
+//
+//               It is an error for the write vertex to advance past
+//               the end of data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+set_data4i(const int data[4]) {
+  set_data4i(data[0], data[1], data[2], data[3]);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexWriter::add_data1f
 //       Access: Published
@@ -489,6 +576,93 @@ add_data1i(int data) {
   _writer->set_data1i(inc_add_pointer(), data);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data2i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 2-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data2i(int a, int b) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data2i(inc_add_pointer(), a, b);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data2i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 2-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data2i(const int data[2]) {
+  add_data2i(data[0], data[1]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data3i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 3-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data3i(int a, int b, int c) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data3i(inc_add_pointer(), a, b, c);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data3i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 3-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data3i(const int data[3]) {
+  add_data3i(data[0], data[1], data[2]);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data4i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 4-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data4i(int a, int b, int c, int d) {
+  nassertv(_data_type != (qpGeomVertexDataType *)NULL);
+  _writer->set_data4i(inc_add_pointer(), a, b, c, d);
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::add_data4i
+//       Access: Published
+//  Description: Sets the write vertex to a particular 4-component
+//               value, and advances the write vertex.
+//
+//               If the write vertex advances past the end of data,
+//               implicitly adds a new vertex to the data.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexWriter::
+add_data4i(const int data[4]) {
+  add_data4i(data[0], data[1], data[2], data[3]);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexWriter::set_pointer
 //       Access: Private

+ 195 - 38
panda/src/gobj/qpgeomVertexWriter.cxx

@@ -185,14 +185,7 @@ set_data1f(unsigned char *pointer, float data) {
       break;
       
     case qpGeomVertexDataType::NT_packed_8888:
-      {
-        maybe_scale_color(data);
-        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(0, _a, 0, 0);
-        } else {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(_a, 0, 0, 0);
-        }
-      }
+      nassertv(false);
       break;
       
     case qpGeomVertexDataType::NT_float32:
@@ -243,14 +236,7 @@ set_data2f(unsigned char *pointer, const LVecBase2f &data) {
       break;
       
     case qpGeomVertexDataType::NT_packed_8888:
-      {
-        maybe_scale_color(data);
-        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(0, _a, _b, 0);
-        } else {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(_a, _b, 0, 0);
-        }
-      }
+      nassertv(false);
       break;
       
     case qpGeomVertexDataType::NT_float32:
@@ -308,14 +294,7 @@ set_data3f(unsigned char *pointer, const LVecBase3f &data) {
       break;
       
     case qpGeomVertexDataType::NT_packed_8888:
-      {
-        maybe_scale_color(data);
-        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(0, _a, _b, _c);
-        } else {
-          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(_a, _b, _c, 0);
-        }
-      }
+      nassertv(false);
       break;
       
     case qpGeomVertexDataType::NT_float32:
@@ -406,28 +385,206 @@ set_data4f(unsigned char *pointer, const LVecBase4f &data) {
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexWriter::Writer::
-set_data1i(unsigned char *pointer, int data) {
-  switch (_data_type->get_numeric_type()) {
-  case qpGeomVertexDataType::NT_uint8:
-    *pointer = data;
+set_data1i(unsigned char *pointer, int a) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      *pointer = a;
+      break;
+      
+    case qpGeomVertexDataType::NT_uint16:
+      *(PN_uint16 *)pointer = a;
+      break;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      nassertv(false);
+      break;
+      
+    case qpGeomVertexDataType::NT_float32:
+      *(PN_float32 *)pointer = (float)a;
+      break;
+    }
+    break;
+
+  case 2:
+    set_data2i(pointer, a, 0);
     break;
 
-  case qpGeomVertexDataType::NT_uint16:
-    *(PN_uint16 *)pointer = data;
+  case 3:
+    set_data3i(pointer, a, 0, 0);
     break;
 
-  case qpGeomVertexDataType::NT_packed_8888:
-    {
-      if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
-        *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(0, data, 0, 0);
-      } else {
-        *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(_a, data, 0, 0);
+  default:
+    set_data4i(pointer, a, 0, 0, 0);
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::Writer::set_data2i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexWriter::Writer::
+set_data2i(unsigned char *pointer, int a, int b) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    set_data1i(pointer, a);
+    break;
+
+  case 2:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      pointer[0] = a;
+      pointer[1] = b;
+      break;
+
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        PN_uint16 *pi = (PN_uint16 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
       }
+      break;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      nassertv(false);
+      break;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        PN_float32 *pi = (PN_float32 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
+      }
+      break;
     }
     break;
 
-  case qpGeomVertexDataType::NT_float32:
-    *(PN_float32 *)pointer = (float)data;
+  case 3:
+    set_data3i(pointer, a, b, 0);
+    break;
+
+  default:
+    set_data4i(pointer, a, b, 0, 0);
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::Writer::set_data3i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexWriter::Writer::
+set_data3i(unsigned char *pointer, int a, int b, int c) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    set_data1i(pointer, a);
+    break;
+
+  case 2:
+    set_data2i(pointer, a, b);
+    break;
+
+  case 3:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      pointer[0] = a;
+      pointer[1] = b;
+      pointer[2] = c;
+      break;
+
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        PN_uint16 *pi = (PN_uint16 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
+        pi[2] = c;
+      }
+      break;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      nassertv(false);
+      break;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        PN_float32 *pi = (PN_float32 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
+        pi[2] = c;
+      }
+      break;
+    }
+    break;
+
+  default:
+    set_data4i(pointer, a, b, c, 0);
+    break;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexWriter::Writer::set_data4i
+//       Access: Public, Virtual
+//  Description: 
+////////////////////////////////////////////////////////////////////
+void qpGeomVertexWriter::Writer::
+set_data4i(unsigned char *pointer, int a, int b, int c, int d) {
+  switch (_data_type->get_num_values()) {
+  case 1:
+    set_data1i(pointer, a);
+    break;
+
+  case 2:
+    set_data2i(pointer, a, b);
+    break;
+
+  case 3:
+    set_data3i(pointer, a, b, c);
+    break;
+
+  default:
+    switch (_data_type->get_numeric_type()) {
+    case qpGeomVertexDataType::NT_uint8:
+      pointer[0] = a;
+      pointer[1] = b;
+      pointer[2] = c;
+      pointer[3] = d;
+      break;
+
+    case qpGeomVertexDataType::NT_uint16:
+      {
+        PN_uint16 *pi = (PN_uint16 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
+        pi[2] = c;
+        pi[3] = d;
+      }
+      break;
+      
+    case qpGeomVertexDataType::NT_packed_8888:
+      {
+        if (_data_type->get_contents() == qpGeomVertexDataType::C_argb) {
+          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(d, a, b, c);
+        } else {
+          *(PN_uint32 *)pointer = qpGeomVertexData::pack_8888(a, b, c, d);
+        }
+      }
+      break;
+      
+    case qpGeomVertexDataType::NT_float32:
+      {
+        PN_float32 *pi = (PN_float32 *)pointer;
+        pi[0] = a;
+        pi[1] = b;
+        pi[2] = c;
+        pi[3] = d;
+      }
+      break;
+    }
     break;
   }
 }

+ 16 - 1
panda/src/gobj/qpgeomVertexWriter.h

@@ -106,6 +106,12 @@ PUBLISHED:
   INLINE void set_data4f(const LVecBase4f &data);
 
   INLINE void set_data1i(int data);
+  INLINE void set_data2i(int a, int b);
+  INLINE void set_data2i(const int data[2]);
+  INLINE void set_data3i(int a, int b, int c);
+  INLINE void set_data3i(const int data[3]);
+  INLINE void set_data4i(int a, int b, int c, int d);
+  INLINE void set_data4i(const int data[4]);
 
   INLINE void add_data1f(float data);
   INLINE void add_data2f(float x, float y);
@@ -116,6 +122,12 @@ PUBLISHED:
   INLINE void add_data4f(const LVecBase4f &data);
 
   INLINE void add_data1i(int data);
+  INLINE void add_data2i(int a, int b);
+  INLINE void add_data2i(const int data[2]);
+  INLINE void add_data3i(int a, int b, int c);
+  INLINE void add_data3i(const int data[3]);
+  INLINE void add_data4i(int a, int b, int c, int d);
+  INLINE void add_data4i(const int data[4]);
 
 private:
   class Writer;
@@ -150,7 +162,10 @@ private:
     virtual void set_data3f(unsigned char *pointer, const LVecBase3f &data);
     virtual void set_data4f(unsigned char *pointer, const LVecBase4f &data);
     
-    virtual void set_data1i(unsigned char *pointer, int data);
+    virtual void set_data1i(unsigned char *pointer, int a);
+    virtual void set_data2i(unsigned char *pointer, int a, int b);
+    virtual void set_data3i(unsigned char *pointer, int a, int b, int c);
+    virtual void set_data4i(unsigned char *pointer, int a, int b, int c, int d);
 
     INLINE unsigned int maybe_scale_color(float data);
     INLINE void maybe_scale_color(const LVecBase2f &data);

+ 43 - 0
panda/src/gobj/transformBlendPalette.I

@@ -58,6 +58,49 @@ get_modified() const {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlendPalette::get_num_transforms
+//       Access: Published
+//  Description: Returns the number of unique VertexTransform objects
+//               represented in the palette.  This will correspond to
+//               the size of the TransformPalette object that would
+//               represent the same table.  This is also the same
+//               limit reflected by
+//               GraphicsStateGuardian::get_max_vertex_transform_indices().
+////////////////////////////////////////////////////////////////////
+INLINE int TransformBlendPalette::
+get_num_transforms() const {
+  consider_rebuild_index();
+  return _num_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlendPalette::get_max_simultaneous_transforms
+//       Access: Published
+//  Description: Returns the maximum number of unique VertexTransform
+//               objects that are applied to any one vertex
+//               simultaneously.  This is the same limit reflected by
+//               GraphicsStateGuardian::get_max_vertex_transforms().
+////////////////////////////////////////////////////////////////////
+INLINE int TransformBlendPalette::
+get_max_simultaneous_transforms() const {
+  consider_rebuild_index();
+  return _max_simultaneous_transforms;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlendPalette::consider_rebuild_index
+//       Access: Private
+//  Description: Calls rebuild_index() if the index needs to be
+//               rebuilt.
+////////////////////////////////////////////////////////////////////
+INLINE void TransformBlendPalette::
+consider_rebuild_index() const {
+  if (_blend_index.empty()) {
+    ((TransformBlendPalette *)this)->rebuild_index();
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformBlendPalette::CData::Constructor
 //       Access: Public

+ 19 - 5
panda/src/gobj/transformBlendPalette.cxx

@@ -95,9 +95,7 @@ remove_blend(int n) {
 ////////////////////////////////////////////////////////////////////
 int TransformBlendPalette::
 add_blend(const TransformBlend &blend) {
-  if (_blend_index.empty()) {
-    rebuild_index();
-  }
+  consider_rebuild_index();
 
   BlendIndex::iterator bi;
   bi = _blend_index.find(&blend);
@@ -148,9 +146,25 @@ void TransformBlendPalette::
 rebuild_index() {
   _blend_index.clear();
 
-  for (int i = 0; i < (int)_blends.size(); i++) {
-    _blend_index[&_blends[i]] = i;
+  // We'll also count up these two statistics while we rebuild the
+  // index.
+  _num_transforms = 0;
+  _max_simultaneous_transforms = 0;
+
+  pset<const VertexTransform *> transforms;
+
+  for (int i = 0; i < (int)_blends.size(); ++i) {
+    const TransformBlend &blend = _blends[i];
+    _blend_index[&blend] = i;
+
+    for (int ti = 0; ti < blend.get_num_transforms(); ++ti) {
+      transforms.insert(blend.get_transform(ti));
+    }
+    _max_simultaneous_transforms = max(_max_simultaneous_transforms,
+                                       blend.get_num_transforms());
   }
+
+  _num_transforms = transforms.size();
 }
 
 ////////////////////////////////////////////////////////////////////

+ 6 - 0
panda/src/gobj/transformBlendPalette.h

@@ -64,10 +64,14 @@ PUBLISHED:
   void remove_blend(int n);
   int add_blend(const TransformBlend &blend);
 
+  INLINE int get_num_transforms() const;
+  INLINE int get_max_simultaneous_transforms() const;
+
   void write(ostream &out, int indent_level) const;
 
 private:
   void clear_index();
+  INLINE void consider_rebuild_index() const;
   void rebuild_index();
 
 private:
@@ -84,6 +88,8 @@ private:
   // invalidating all the pointers into it).
   typedef pmap<const TransformBlend *, int, IndirectLess<TransformBlend> > BlendIndex;
   BlendIndex _blend_index;
+  int _num_transforms;
+  int _max_simultaneous_transforms;
 
   // Even though we don't store the actual blend palette data in a
   // CycleData structure, we do need to keep a local cache of the

+ 6 - 4
panda/src/gobj/transformPalette.cxx

@@ -75,7 +75,7 @@ TransformPalette::
 //               unregistered palettes.
 ////////////////////////////////////////////////////////////////////
 void TransformPalette::
-set_transform(int n, VertexTransform *transform) {
+set_transform(int n, const VertexTransform *transform) {
   nassertv(!_is_registered);
   nassertv(n >= 0 && n < (int)_transforms.size());
   _transforms[n] = transform;
@@ -102,7 +102,7 @@ remove_transform(int n) {
 //               unregistered palettes.
 ////////////////////////////////////////////////////////////////////
 int TransformPalette::
-add_transform(VertexTransform *transform) {
+add_transform(const VertexTransform *transform) {
   nassertr(!_is_registered, -1);
   int new_index = (int)_transforms.size();
   _transforms.push_back(transform);
@@ -132,7 +132,8 @@ do_register() {
 
   Transforms::iterator ti;
   for (ti = _transforms.begin(); ti != _transforms.end(); ++ti) {
-    bool inserted = (*ti)->_palettes.insert(this).second;
+    VertexTransform *transform = (VertexTransform *)(*ti).p();
+    bool inserted = transform->_palettes.insert(this).second;
     nassertv(inserted);
   }
   _is_registered = true;
@@ -150,7 +151,8 @@ do_unregister() {
 
   Transforms::iterator ti;
   for (ti = _transforms.begin(); ti != _transforms.end(); ++ti) {
-    (*ti)->_palettes.erase(this);
+    VertexTransform *transform = (VertexTransform *)(*ti).p();
+    transform->_palettes.erase(this);
   }
   _is_registered = false;
 }

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

@@ -58,9 +58,9 @@ PUBLISHED:
   INLINE const VertexTransform *get_transform(int n) const;
   INLINE UpdateSeq get_modified() const;
 
-  void set_transform(int n, VertexTransform *transform);
+  void set_transform(int n, const VertexTransform *transform);
   void remove_transform(int n);
-  int add_transform(VertexTransform *transform);
+  int add_transform(const VertexTransform *transform);
 
   void write(ostream &out) const;
 
@@ -72,7 +72,7 @@ private:
 private:
   bool _is_registered;
 
-  typedef pvector< PT(VertexTransform) > Transforms;
+  typedef pvector< CPT(VertexTransform) > Transforms;
   Transforms _transforms;
 
   // This is the data that must be cycled between pipeline stages.

+ 18 - 0
panda/src/gobj/vertexTransform.cxx

@@ -47,6 +47,24 @@ VertexTransform::
   nassertv(_palettes.empty());
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: VertexTransform::mult_matrix
+//       Access: Published, Virtual
+//  Description: Premultiplies this transform's matrix with the
+//               indicated previous matrix, so that the result is the
+//               net composition of the given transform with this
+//               transform.  The result is stored in the parameter
+//               "result", which should not be the same matrix as
+//               previous.
+////////////////////////////////////////////////////////////////////
+void VertexTransform::
+mult_matrix(LMatrix4f &result, const LMatrix4f &previous) const {
+  nassertv(&result != &previous);
+  LMatrix4f me;
+  get_matrix(me);
+  result.multiply(me, previous);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: VertexTransform::accumulate_matrix
 //       Access: Published, Virtual

+ 1 - 0
panda/src/gobj/vertexTransform.h

@@ -48,6 +48,7 @@ PUBLISHED:
   virtual ~VertexTransform();
 
   virtual void get_matrix(LMatrix4f &matrix) const=0;
+  virtual void mult_matrix(LMatrix4f &result, const LMatrix4f &previous) const;
   virtual void accumulate_matrix(LMatrix4f &accum, float weight) const;
 
   INLINE UpdateSeq get_modified() const;