2
0
Эх сурвалжийг харах

some animation optimizations

David Rose 14 жил өмнө
parent
commit
7e8737dd3d

+ 47 - 0
panda/src/egg/eggVertexPool.cxx

@@ -800,6 +800,53 @@ transform(const LMatrix4d &mat) {
 }
 }
 
 
 
 
+// A function object for sort_by_external_index(), below.
+class SortByExternalIndex {
+public:
+  bool operator () (EggVertex *a, EggVertex *b) const {
+    int ai = a->get_external_index();
+    int bi = b->get_external_index();
+    if (ai != bi) {
+      return ai < bi;
+    }
+    return a->get_index() < b->get_index();
+  }
+};
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggVertexPool::sort_by_external_index
+//       Access: Published
+//  Description: Re-orders (and re-numbers) the vertices in this
+//               vertex pool so that they appear in increasing order
+//               by the optional external_index that has been assigned
+//               to each vertex.
+////////////////////////////////////////////////////////////////////
+void EggVertexPool::
+sort_by_external_index() {
+  // Copy the vertices into a vector for sorting.
+  typedef pvector<EggVertex *> SortedVertices;
+  SortedVertices sorted_vertices;
+  sorted_vertices.reserve(size());
+  iterator i;
+  for (i = begin(); i != end(); ++i) {
+    sorted_vertices.push_back(*i);
+  }
+
+  ::sort(sorted_vertices.begin(), sorted_vertices.end(), SortByExternalIndex());
+
+  // Now reassign the indices, and copy them into a new index map.
+  IndexVertices new_index_vertices;
+  int vi;
+  for (vi = 0; vi < (int)sorted_vertices.size(); ++vi) {
+    EggVertex *vertex = sorted_vertices[vi];
+    vertex->_index = vi;
+    new_index_vertices[vi] = vertex;
+  }
+
+  // Finally, assign the new index map.
+  _index_vertices.swap(new_index_vertices);
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggVertexPool::write
 //     Function: EggVertexPool::write
 //       Access: Public
 //       Access: Public

+ 1 - 0
panda/src/egg/eggVertexPool.h

@@ -133,6 +133,7 @@ PUBLISHED:
   void add_unused_vertices_to_prim(EggPrimitive *prim);
   void add_unused_vertices_to_prim(EggPrimitive *prim);
 
 
   void transform(const LMatrix4d &mat);
   void transform(const LMatrix4d &mat);
+  void sort_by_external_index();
 
 
   void write(ostream &out, int indent_level) const;
   void write(ostream &out, int indent_level) const;
 
 

+ 8 - 1
panda/src/egg2pg/config_egg2pg.cxx

@@ -170,13 +170,20 @@ ConfigVariableBool egg_preload_simple_textures
           "either this or preload-simple-textures is true."));
           "either this or preload-simple-textures is true."));
 
 
 ConfigVariableDouble egg_vertex_membership_quantize
 ConfigVariableDouble egg_vertex_membership_quantize
-("egg-vertex-membership-quantize", 0.01,
+("egg-vertex-membership-quantize", 0.1,
  PRC_DESC("Specifies the nearest amount to round each vertex joint "
  PRC_DESC("Specifies the nearest amount to round each vertex joint "
           "membership value when loading an egg file.  This affects animated "
           "membership value when loading an egg file.  This affects animated "
           "egg files only.  There is a substantial runtime "
           "egg files only.  There is a substantial runtime "
           "performance advantage for reducing trivial differences in joint "
           "performance advantage for reducing trivial differences in joint "
           "membership.  Set this to 0 to leave joint membership as it is."));
           "membership.  Set this to 0 to leave joint membership as it is."));
 
 
+ConfigVariableInt egg_vertex_max_num_joints
+("egg-vertex-max-num-joints", 4,
+ PRC_DESC("Specifies the maximum number of distinct joints that are allowed "
+          "to control any one vertex.  If a vertex requests assignment to "
+          "more than this number of joints, the joints with the lesser membership "
+          "value are ignored.  Set this to -1 to allow any number of joints."));
+
 ConfigureFn(config_egg2pg) {
 ConfigureFn(config_egg2pg) {
   init_libegg2pg();
   init_libegg2pg();
 }
 }

+ 1 - 0
panda/src/egg2pg/config_egg2pg.h

@@ -53,6 +53,7 @@ extern EXPCL_PANDAEGG ConfigVariableInt egg_max_indices;
 extern EXPCL_PANDAEGG ConfigVariableBool egg_emulate_bface;
 extern EXPCL_PANDAEGG ConfigVariableBool egg_emulate_bface;
 extern EXPCL_PANDAEGG ConfigVariableBool egg_preload_simple_textures;
 extern EXPCL_PANDAEGG ConfigVariableBool egg_preload_simple_textures;
 extern EXPCL_PANDAEGG ConfigVariableDouble egg_vertex_membership_quantize;
 extern EXPCL_PANDAEGG ConfigVariableDouble egg_vertex_membership_quantize;
+extern EXPCL_PANDAEGG ConfigVariableInt egg_vertex_max_num_joints;
 
 
 extern EXPCL_PANDAEGG void init_libegg2pg();
 extern EXPCL_PANDAEGG void init_libegg2pg();
 
 

+ 74 - 35
panda/src/egg2pg/eggLoader.cxx

@@ -379,6 +379,20 @@ make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform,
       // pools as if they contain a combination of multiple colors.
       // pools as if they contain a combination of multiple colors.
       has_overall_color = false;
       has_overall_color = false;
     }
     }
+
+    PT(TransformBlendTable) blend_table;
+    if (is_dynamic) {
+      // Dynamic vertex pools will require a TransformBlendTable to
+      // indicate how the vertices are to be animated.
+      blend_table = make_blend_table(vertex_pool, egg_bin, character_maker);
+
+      // Now that we've created the blend table, we can re-order the
+      // vertices in the pool to efficiently group vertices together
+      // that will share the same transform matrix.  (We have to
+      // re-order them before we create primitives, below, because
+      // this will change the vertex index numbers.)
+      vertex_pool->sort_by_external_index();
+    }
     
     
     // Create a handful of GeomPrimitives corresponding to the various
     // Create a handful of GeomPrimitives corresponding to the various
     // types of primitives that reference this vertex pool.
     // types of primitives that reference this vertex pool.
@@ -400,10 +414,10 @@ make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform,
       } else {
       } else {
         mat = egg_bin->get_vertex_to_node();
         mat = egg_bin->get_vertex_to_node();
       }
       }
-      
+
       // Now convert this vertex pool to a GeomVertexData.
       // Now convert this vertex pool to a GeomVertexData.
       PT(GeomVertexData) vertex_data = 
       PT(GeomVertexData) vertex_data = 
-        make_vertex_data(render_state, vertex_pool, egg_bin, mat,
+        make_vertex_data(render_state, vertex_pool, egg_bin, mat, blend_table,
                          is_dynamic, character_maker, has_overall_color);
                          is_dynamic, character_maker, has_overall_color);
       nassertv(vertex_data != (GeomVertexData *)NULL);
       nassertv(vertex_data != (GeomVertexData *)NULL);
 
 
@@ -2185,7 +2199,7 @@ check_for_polysets(EggGroup *egg_group, bool &all_polysets, bool &any_hidden) {
 PT(GeomVertexData) EggLoader::
 PT(GeomVertexData) EggLoader::
 make_vertex_data(const EggRenderState *render_state, 
 make_vertex_data(const EggRenderState *render_state, 
                  EggVertexPool *vertex_pool, EggNode *primitive_home,
                  EggVertexPool *vertex_pool, EggNode *primitive_home,
-                 const LMatrix4d &transform,
+                 const LMatrix4d &transform, TransformBlendTable *blend_table,
                  bool is_dynamic, CharacterMaker *character_maker,
                  bool is_dynamic, CharacterMaker *character_maker,
                  bool ignore_color) {
                  bool ignore_color) {
   VertexPoolTransform vpt;
   VertexPoolTransform vpt;
@@ -2255,7 +2269,6 @@ make_vertex_data(const EggRenderState *render_state,
 
 
   PT(GeomVertexFormat) temp_format = new GeomVertexFormat(array_format);
   PT(GeomVertexFormat) temp_format = new GeomVertexFormat(array_format);
 
 
-  PT(TransformBlendTable) blend_table;
   PT(SliderTable) slider_table;
   PT(SliderTable) slider_table;
   string name = _data->get_egg_filename().get_basename_wo_extension();
   string name = _data->get_egg_filename().get_basename_wo_extension();
 
 
@@ -2271,9 +2284,6 @@ make_vertex_data(const EggRenderState *render_state,
     animation.set_panda();
     animation.set_panda();
     temp_format->set_animation(animation);
     temp_format->set_animation(animation);
 
 
-    blend_table = new TransformBlendTable;
-    blend_table->set_rows(SparseArray::lower_on(vertex_pool->size()));
-
     PT(GeomVertexArrayFormat) anim_array_format = new GeomVertexArrayFormat;
     PT(GeomVertexArrayFormat) anim_array_format = new GeomVertexArrayFormat;
     anim_array_format->add_column
     anim_array_format->add_column
       (InternalName::get_transform_blend(), 1, 
       (InternalName::get_transform_blend(), 1, 
@@ -2477,34 +2487,7 @@ make_vertex_data(const EggRenderState *render_state,
     }
     }
 
 
     if (is_dynamic) {
     if (is_dynamic) {
-      // Figure out the transforms affecting this particular vertex.
-      TransformBlend blend;
-      if (vertex->gref_size() == 0) {
-        // If the vertex has no explicit membership, it belongs right
-        // where it is.
-        PT(VertexTransform) vt = character_maker->egg_to_transform(primitive_home);
-        nassertr(vt != (VertexTransform *)NULL, vertex_data);
-        blend.add_transform(vt, 1.0f);
-      } else {
-        // If the vertex does have an explicit membership, ignore its
-        // parentage and assign it where it wants to be.
-        double quantize = egg_vertex_membership_quantize;
-        EggVertex::GroupRef::const_iterator gri;
-        for (gri = vertex->gref_begin(); gri != vertex->gref_end(); ++gri) {
-          EggGroup *egg_joint = (*gri);
-          double membership = egg_joint->get_vertex_membership(vertex);
-          if (quantize != 0.0) {
-            membership = cfloor(membership / quantize + 0.5) * quantize;
-          }
-          
-          PT(VertexTransform) vt = character_maker->egg_to_transform(egg_joint);
-          nassertr(vt != (VertexTransform *)NULL, vertex_data);
-          blend.add_transform(vt, membership);
-        }
-      }
-      blend.normalize_weights();
-
-      int table_index = blend_table->add_blend(blend);
+      int table_index = vertex->get_external_index();
       gvw.set_column(InternalName::get_transform_blend());
       gvw.set_column(InternalName::get_transform_blend());
       gvw.set_data1i(table_index);
       gvw.set_data1i(table_index);
     }
     }
@@ -2518,6 +2501,62 @@ make_vertex_data(const EggRenderState *render_state,
   return vertex_data;
   return vertex_data;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::make_blend_table
+//       Access: Private
+//  Description: 
+////////////////////////////////////////////////////////////////////
+PT(TransformBlendTable) EggLoader::
+make_blend_table(EggVertexPool *vertex_pool, EggNode *primitive_home,
+                 CharacterMaker *character_maker) {
+  PT(TransformBlendTable) blend_table;
+  blend_table = new TransformBlendTable;
+  blend_table->set_rows(SparseArray::lower_on(vertex_pool->size()));
+
+  EggVertexPool::const_iterator vi;
+  for (vi = vertex_pool->begin(); vi != vertex_pool->end(); ++vi) {
+    EggVertex *vertex = (*vi);
+
+    // Figure out the transforms affecting this particular vertex.
+    TransformBlend blend;
+    if (vertex->gref_size() == 0) {
+      // If the vertex has no explicit membership, it belongs right
+      // where it is.
+      PT(VertexTransform) vt = character_maker->egg_to_transform(primitive_home);
+      nassertr(vt != (VertexTransform *)NULL, NULL);
+      blend.add_transform(vt, 1.0f);
+    } else {
+      // If the vertex does have an explicit membership, ignore its
+      // parentage and assign it where it wants to be.
+      double quantize = egg_vertex_membership_quantize;
+      EggVertex::GroupRef::const_iterator gri;
+      for (gri = vertex->gref_begin(); gri != vertex->gref_end(); ++gri) {
+        EggGroup *egg_joint = (*gri);
+        double membership = egg_joint->get_vertex_membership(vertex);
+        if (quantize != 0.0) {
+          membership = cfloor(membership / quantize + 0.5) * quantize;
+        }
+        
+        PT(VertexTransform) vt = character_maker->egg_to_transform(egg_joint);
+        nassertr(vt != (VertexTransform *)NULL, NULL);
+        blend.add_transform(vt, membership);
+      }
+    }
+    if (egg_vertex_max_num_joints >= 0) {
+      blend.limit_transforms(egg_vertex_max_num_joints);
+    }
+    blend.normalize_weights();
+    
+    int table_index = blend_table->add_blend(blend);
+
+    // We take advantage of the "external index" field of the
+    // EggVertex to temporarily store the transform blend index.
+    vertex->set_external_index(table_index);
+  }  
+
+  return blend_table;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::record_morph
 //     Function: EggLoader::record_morph
 //       Access: Private
 //       Access: Private

+ 5 - 2
panda/src/egg2pg/eggLoader.h

@@ -143,8 +143,11 @@ private:
                           bool &any_hidden);
                           bool &any_hidden);
   PT(GeomVertexData) make_vertex_data
   PT(GeomVertexData) make_vertex_data
   (const EggRenderState *render_state, EggVertexPool *vertex_pool, 
   (const EggRenderState *render_state, EggVertexPool *vertex_pool, 
-   EggNode *primitive_home, const LMatrix4d &transform, bool is_dynamic, 
-   CharacterMaker *character_maker, bool ignore_color);
+   EggNode *primitive_home, const LMatrix4d &transform, TransformBlendTable *blend_table,
+   bool is_dynamic, CharacterMaker *character_maker, bool ignore_color);
+  PT(TransformBlendTable) make_blend_table
+  (EggVertexPool *vertex_bool, EggNode *primitive_home,
+   CharacterMaker *character_maker);
   void record_morph
   void record_morph
   (GeomVertexArrayFormat *array_format,
   (GeomVertexArrayFormat *array_format,
    CharacterMaker *character_maker, const string &morph_name, 
    CharacterMaker *character_maker, const string &morph_name, 

+ 270 - 121
panda/src/gobj/geomVertexData.cxx

@@ -35,6 +35,7 @@ PStatCollector GeomVertexData::_scale_color_pcollector("*:Munge:Scale color");
 PStatCollector GeomVertexData::_set_color_pcollector("*:Munge:Set color");
 PStatCollector GeomVertexData::_set_color_pcollector("*:Munge:Set color");
 PStatCollector GeomVertexData::_animation_pcollector("*:Animation");
 PStatCollector GeomVertexData::_animation_pcollector("*:Animation");
 
 
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::Default Constructor
 //     Function: GeomVertexData::Default Constructor
 //       Access: Private
 //       Access: Private
@@ -1100,6 +1101,49 @@ clear_animated_vertices() {
   cdata->_animated_vertices.clear();
   cdata->_animated_vertices.clear();
 }
 }
 
 
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::transform_vertices
+//       Access: Published
+//  Description: Applies the indicated transform matrix to all of the
+//               vertices in the GeomVertexData.  The transform is
+//               applied to all "point" and "vector" type columns
+//               described in the format.
+////////////////////////////////////////////////////////////////////
+void GeomVertexData::
+transform_vertices(const LMatrix4 &mat) {
+  transform_vertices(mat, 0, get_num_rows());
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::transform_vertices
+//       Access: Published
+//  Description: Applies the indicated transform matrix to all of the
+//               vertices from begin_row up to but not including
+//               end_row.  The transform is applied to all "point" and
+//               "vector" type columns described in the format.
+////////////////////////////////////////////////////////////////////
+void GeomVertexData::
+transform_vertices(const LMatrix4 &mat, int begin_row, int end_row) {
+  if (end_row <= begin_row) {
+    // Trivial no-op.
+    return;
+  }
+
+  const GeomVertexFormat *format = get_format();
+
+  int ci;
+  for (ci = 0; ci < format->get_num_points(); ci++) {
+    GeomVertexRewriter data(this, format->get_point(ci));
+    do_transform_point_column(format, data, mat, begin_row, end_row);
+  }
+  
+  for (ci = 0; ci < format->get_num_vectors(); ci++) {
+    GeomVertexRewriter data(this, format->get_vector(ci));
+    do_transform_vector_column(format, data, mat, begin_row, end_row);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::bytewise_copy
 //     Function: GeomVertexData::bytewise_copy
 //       Access: Private, Static
 //       Access: Private, Static
@@ -1552,17 +1596,18 @@ update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
   // Then apply the transforms.
   // Then apply the transforms.
   CPT(TransformBlendTable) tb_table = cdata->_transform_blend_table.get_read_pointer();
   CPT(TransformBlendTable) tb_table = cdata->_transform_blend_table.get_read_pointer();
   if (tb_table != (TransformBlendTable *)NULL) {
   if (tb_table != (TransformBlendTable *)NULL) {
-    PStatTimer timer3(_skinning_pcollector);
-
     // Recompute all the blends up front, so we don't have to test
     // Recompute all the blends up front, so we don't have to test
     // each one for staleness at each vertex.
     // each one for staleness at each vertex.
-    int num_blends = tb_table->get_num_blends();
-    int bi;
-    for (bi = 0; bi < num_blends; bi++) {
-      tb_table->get_blend(bi).update_blend(current_thread);
+    {
+      PStatTimer timer4(_blends_pcollector);
+      int num_blends = tb_table->get_num_blends();
+      for (int bi = 0; bi < num_blends; bi++) {
+        tb_table->get_blend(bi).update_blend(current_thread);
+      }
     }
     }
 
 
     // Now go through and apply the transforms.
     // Now go through and apply the transforms.
+    PStatTimer timer3(_skinning_pcollector);
 
 
     const SparseArray &rows = tb_table->get_rows();
     const SparseArray &rows = tb_table->get_rows();
     int num_subranges = rows.get_num_subranges();
     int num_subranges = rows.get_num_subranges();
@@ -1577,8 +1622,7 @@ update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
 
 
     CPT(GeomVertexArrayFormat) blend_array_format = orig_format->get_array(blend_array_index);
     CPT(GeomVertexArrayFormat) blend_array_format = orig_format->get_array(blend_array_index);
 
 
-    if (blend_array_format->get_num_columns() == 1 && 
-        blend_array_format->get_stride() == 2 && 
+    if (blend_array_format->get_stride() == 2 && 
         blend_array_format->get_column(0)->get_component_bytes() == 2) {
         blend_array_format->get_column(0)->get_component_bytes() == 2) {
       // The blend indices are a table of ushorts.  Optimize this
       // The blend indices are a table of ushorts.  Optimize this
       // common case.
       // common case.
@@ -1588,98 +1632,80 @@ update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
       int ci;
       int ci;
       for (ci = 0; ci < new_format->get_num_points(); ci++) {
       for (ci = 0; ci < new_format->get_num_points(); ci++) {
         GeomVertexRewriter data(new_data, new_format->get_point(ci));
         GeomVertexRewriter data(new_data, new_format->get_point(ci));
-        const GeomVertexColumn *data_column = data.get_column();
-        if (data_column->get_num_values() == 3 &&
-            data_column->get_numeric_type() == NT_float32) {
-          // Table of points is a table of LPoint3f's.  Optimize this
-          // common case.
-          int data_index = new_format->get_array_with(new_format->get_point(ci));
-          PT(GeomVertexArrayData) data_array = new_data->modify_array(data_index);
-          PT(GeomVertexArrayDataHandle) data_handle = data_array->modify_handle();
-          unsigned char *datat = data_handle->get_write_pointer();
-          datat += data_column->get_start();
-          size_t stride = data_array->get_array_format()->get_stride();
-
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            for (int j = begin; j < end; ++j) {
-              LPoint3f &vertex = *(LPoint3f *)(&datat[j * stride]);
-              int bi = blendt[j];
-              tb_table->get_blend(bi).transform_point(vertex, current_thread);
-            }
-          }
 
 
-        } else if (data_column->get_num_values() == 4) {
-          // Use the GeomVertexRewriter to adjust the 4-component
-          // points.
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            data.set_row_unsafe(begin);
-            for (int j = begin; j < end; ++j) {
-              LPoint4 vertex = data.get_data4();
-              int bi = blendt[j];
-              tb_table->get_blend(bi).transform_point(vertex, current_thread);
-              data.set_data4(vertex);
-            }
-          }
+        for (int i = 0; i < num_subranges; ++i) {
+          int begin = rows.get_subrange_begin(i);
+          int end = rows.get_subrange_end(i);
+          nassertv(begin < end);
+
+          int first_vertex = begin;
+          int first_bi = blendt[first_vertex];
           
           
-        } else {
-          // Use the GeomVertexRewriter to adjust the 3-component
-          // points.
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            data.set_row_unsafe(begin);
-            for (int j = begin; j < end; ++j) {
-              LPoint3 vertex = data.get_data3();
-              int bi = blendt[j];
-              tb_table->get_blend(bi).transform_point(vertex, current_thread);
-              data.set_data3(vertex);
+          while (first_vertex < end) {
+            // At this point, first_vertex is the first of a series of
+            // vertices that shares the blend index first_bi.
+            
+            // Scan for the end of this series of vertices--we're
+            // looking for the next vertex with a different blend index.
+            int next_vertex = first_vertex;
+            int next_bi = first_bi;
+            ++next_vertex;
+            while (next_vertex < end) {
+              next_bi = blendt[next_vertex];
+              if (next_bi != first_bi) {
+                break;
+              }
+              ++next_vertex;
             }
             }
+            
+            // We've just reached the end of the vertices with a matching
+            // blend index.  Transform all those vertices as a block.
+            LMatrix4 mat;
+            tb_table->get_blend(first_bi).get_blend(mat, current_thread);
+            new_data->do_transform_point_column(new_format, data, mat, first_vertex, next_vertex);
+            
+            first_vertex = next_vertex;
+            first_bi = next_bi;
           }
           }
         }
         }
       }
       }
 
 
-      // Also process vectors: normals, etc.
       for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
       for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
         GeomVertexRewriter data(new_data, new_format->get_vector(ci));
         GeomVertexRewriter data(new_data, new_format->get_vector(ci));
-        const GeomVertexColumn *data_column = data.get_column();
-        if (data_column->get_num_values() == 3 &&
-            data_column->get_numeric_type() == NT_float32) {
-          // Table of vectors is a table of LVector3f's.  Optimize this
-          // common case.
-          int data_index = new_format->get_array_with(new_format->get_vector(ci));
-          PT(GeomVertexArrayData) data_array = new_data->modify_array(data_index);
-          PT(GeomVertexArrayDataHandle) data_handle = data_array->modify_handle();
-          unsigned char *datat = data_handle->get_write_pointer();
-          datat += data_column->get_start();
-          size_t stride = data_array->get_array_format()->get_stride();
-
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            for (int j = begin; j < end; ++j) {
-              LVector3f &vertex = *(LVector3f *)(&datat[j * stride]);
-              int bi = blendt[j];
-              tb_table->get_blend(bi).transform_vector(vertex, current_thread);
-            }
-          }
+
+        for (int i = 0; i < num_subranges; ++i) {
+          int begin = rows.get_subrange_begin(i);
+          int end = rows.get_subrange_end(i);
+          nassertv(begin < end);
+
+          int first_vertex = begin;
+          int first_bi = blendt[first_vertex];
           
           
-        } else {
-          // Use the GeomVertexRewriter to adjust the vectors.
-
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            data.set_row_unsafe(begin);
-            for (int j = begin; j < end; ++j) {
-              LVector3 vertex = data.get_data3();
-              int bi = blendt[j];
-              tb_table->get_blend(bi).transform_vector(vertex, current_thread);
-              data.set_data3(vertex);
+          while (first_vertex < end) {
+            // At this point, first_vertex is the first of a series of
+            // vertices that shares the blend index first_bi.
+            
+            // Scan for the end of this series of vertices--we're
+            // looking for the next vertex with a different blend index.
+            int next_vertex = first_vertex;
+            int next_bi = first_bi;
+            ++next_vertex;
+            while (next_vertex < end) {
+              next_bi = blendt[next_vertex];
+              if (next_bi != first_bi) {
+                break;
+              }
+              ++next_vertex;
             }
             }
+            
+            // We've just reached the end of the vertices with a matching
+            // blend index.  Transform all those vertices as a block.
+            LMatrix4 mat;
+            tb_table->get_blend(first_bi).get_blend(mat, current_thread);
+            new_data->do_transform_vector_column(new_format, data, mat, first_vertex, next_vertex);
+            
+            first_vertex = next_vertex;
+            first_bi = next_bi;
           }
           }
         }
         }
       }
       }
@@ -1693,48 +1719,82 @@ update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
       int ci;
       int ci;
       for (ci = 0; ci < new_format->get_num_points(); ci++) {
       for (ci = 0; ci < new_format->get_num_points(); ci++) {
         GeomVertexRewriter data(new_data, new_format->get_point(ci));
         GeomVertexRewriter data(new_data, new_format->get_point(ci));
-        const GeomVertexColumn *data_column = data.get_column();
-        
-        if (data_column->get_num_values() == 4) {
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            data.set_row_unsafe(begin);
-            blendi.set_row_unsafe(begin);
-            for (int j = begin; j < end; ++j) {
-              LPoint4 vertex = data.get_data4();
-              int bi = blendi.get_data1i();
-              tb_table->get_blend(bi).transform_point(vertex, current_thread);
-              data.set_data4(vertex);
-            }
-          }
-        } else {
-          for (int i = 0; i < num_subranges; ++i) {
-            int begin = rows.get_subrange_begin(i);
-            int end = rows.get_subrange_end(i);
-            data.set_row_unsafe(begin);
-            blendi.set_row_unsafe(begin);
-            for (int j = begin; j < end; ++j) {
-              LPoint3 vertex = data.get_data3();
-              int bi = blendi.get_data1i();
-              tb_table->get_blend(bi).transform_point(vertex, current_thread);
-              data.set_data3(vertex);
+
+        for (int i = 0; i < num_subranges; ++i) {
+          int begin = rows.get_subrange_begin(i);
+          int end = rows.get_subrange_end(i);
+          nassertv(begin < end);
+          blendi.set_row_unsafe(begin);
+          
+          int first_vertex = begin;
+          int first_bi = blendi.get_data1i();
+          
+          while (first_vertex < end) {
+            // At this point, first_vertex is the first of a series of
+            // vertices that shares the blend index first_bi.
+            
+            // Scan for the end of this series of vertices--we're
+            // looking for the next vertex with a different blend index.
+            int next_vertex = first_vertex;
+            int next_bi = first_bi;
+            ++next_vertex;
+            while (next_vertex < end) {
+              next_bi = blendi.get_data1i();
+              if (next_bi != first_bi) {
+                break;
+              }
+              ++next_vertex;
             }
             }
+            
+            // We've just reached the end of the vertices with a matching
+            // blend index.  Transform all those vertices as a block.
+            LMatrix4 mat;
+            tb_table->get_blend(first_bi).get_blend(mat, current_thread);
+            new_data->do_transform_point_column(new_format, data, mat, first_vertex, next_vertex);
+            
+            first_vertex = next_vertex;
+            first_bi = next_bi;
           }
           }
         }
         }
       }
       }
+
       for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
       for (ci = 0; ci < new_format->get_num_vectors(); ci++) {
         GeomVertexRewriter data(new_data, new_format->get_vector(ci));
         GeomVertexRewriter data(new_data, new_format->get_vector(ci));
+
         for (int i = 0; i < num_subranges; ++i) {
         for (int i = 0; i < num_subranges; ++i) {
           int begin = rows.get_subrange_begin(i);
           int begin = rows.get_subrange_begin(i);
           int end = rows.get_subrange_end(i);
           int end = rows.get_subrange_end(i);
-          data.set_row_unsafe(begin);
+          nassertv(begin != end);
           blendi.set_row_unsafe(begin);
           blendi.set_row_unsafe(begin);
-          for (int j = begin; j < end; ++j) {
-            LVector3 vertex = data.get_data3();
-            int bi = blendi.get_data1i();
-            tb_table->get_blend(bi).transform_vector(vertex, current_thread);
-            data.set_data3(vertex);
+          
+          int first_vertex = begin;
+          int first_bi = blendi.get_data1i();
+          
+          while (first_vertex < end) {
+            // At this point, first_vertex is the first of a series of
+            // vertices that shares the blend index first_bi.
+            
+            // Scan for the end of this series of vertices--we're
+            // looking for the next vertex with a different blend index.
+            int next_vertex = first_vertex;
+            int next_bi = first_bi;
+            ++next_vertex;
+            while (next_vertex < end) {
+              next_bi = blendi.get_data1i();
+              if (next_bi != first_bi) {
+                break;
+              }
+              ++next_vertex;
+            }
+            
+            // We've just reached the end of the vertices with a matching
+            // blend index.  Transform all those vertices as a block.
+            LMatrix4 mat;
+            tb_table->get_blend(first_bi).get_blend(mat, current_thread);
+            new_data->do_transform_vector_column(new_format, data, mat, first_vertex, next_vertex);
+            
+            first_vertex = next_vertex;
+            first_bi = next_bi;
           }
           }
         }
         }
       }
       }
@@ -1742,6 +1802,95 @@ update_animated_vertices(GeomVertexData::CData *cdata, Thread *current_thread) {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::do_transform_point_column
+//       Access: Private
+//  Description: Transforms a range of vertices for one particular
+//               column, as a point.
+////////////////////////////////////////////////////////////////////
+void GeomVertexData::
+do_transform_point_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
+                          const LMatrix4 &mat, int begin_row, int end_row) {
+  const GeomVertexColumn *data_column = data.get_column();
+
+  if (data_column->get_num_values() == 3 &&
+      data_column->get_numeric_type() == NT_float32) {
+    // The table of points is a table of LPoint3f's.  Optimize this
+    // common case.
+    PT(GeomVertexArrayDataHandle) data_handle = data.get_array_handle();
+    PT(GeomVertexArrayData) data_array = data_handle->get_object();
+
+    unsigned char *datat = data_handle->get_write_pointer();
+    datat += data_column->get_start();
+    size_t stride = data_array->get_array_format()->get_stride();
+
+    LMatrix4f matf = LCAST(float, mat);
+    for (int j = begin_row; j < end_row; ++j) {
+      LPoint3f &vertex = *(LPoint3f *)(&datat[j * stride]);
+      vertex = vertex * matf;
+    }
+    
+  } else if (data_column->get_num_values() == 4) {
+    // Use the GeomVertexRewriter to adjust the 4-component
+    // points.
+  
+    data.set_row_unsafe(begin_row);
+    for (int j = begin_row; j < end_row; ++j) {
+      LPoint4 vertex = data.get_data4();
+      data.set_data4(vertex * mat);
+    }
+
+  } else {
+    // Use the GeomVertexRewriter to adjust the 3-component
+    // points.
+
+    data.set_row_unsafe(begin_row);
+    for (int j = begin_row; j < end_row; ++j) {
+      LPoint3 vertex = data.get_data3();
+      data.set_data3(vertex * mat);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexData::do_transform_vector_column
+//       Access: Private
+//  Description: Transforms a range of vertices for one particular
+//               column, as a vector.
+////////////////////////////////////////////////////////////////////
+void GeomVertexData::
+do_transform_vector_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
+                          const LMatrix4 &mat, int begin_row, int end_row) {
+  const GeomVertexColumn *data_column = data.get_column();
+
+  if (data_column->get_num_values() == 3 &&
+      data_column->get_numeric_type() == NT_float32) {
+    // The table of vectors is a table of LVector3f's.  Optimize this
+    // common case.
+    PT(GeomVertexArrayDataHandle) data_handle = data.get_array_handle();
+    PT(GeomVertexArrayData) data_array = data_handle->get_object();
+
+    unsigned char *datat = data_handle->get_write_pointer();
+    datat += data_column->get_start();
+    size_t stride = data_array->get_array_format()->get_stride();
+
+    LMatrix4f matf = LCAST(float, mat);
+    for (int j = begin_row; j < end_row; ++j) {
+      LVector3f &vector = *(LVector3f *)(&datat[j * stride]);
+      vector = vector * matf;
+    }
+
+  } else {
+    // Use the GeomVertexRewriter to transform the vectors.
+
+    data.set_row_unsafe(begin_row);
+    for (int j = begin_row; j < end_row; ++j) {
+      LVector3 vertex = data.get_data3();
+      data.set_data3(vertex * mat);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexData::register_with_read_factory
 //     Function: GeomVertexData::register_with_read_factory
 //       Access: Public, Static
 //       Access: Public, Static

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

@@ -42,6 +42,7 @@
 
 
 class FactoryParams;
 class FactoryParams;
 class GeomVertexColumn;
 class GeomVertexColumn;
+class GeomVertexRewriter;
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //       Class : GeomVertexData
 //       Class : GeomVertexData
@@ -152,6 +153,8 @@ PUBLISHED:
 
 
   CPT(GeomVertexData) animate_vertices(bool force, Thread *current_thread) const;
   CPT(GeomVertexData) animate_vertices(bool force, Thread *current_thread) const;
   void clear_animated_vertices();
   void clear_animated_vertices();
+  void transform_vertices(const LMatrix4 &mat);
+  void transform_vertices(const LMatrix4 &mat, int begin_row, int end_row);
 
 
   PT(GeomVertexData) 
   PT(GeomVertexData) 
     replace_column(InternalName *name, int num_components,
     replace_column(InternalName *name, int num_components,
@@ -315,6 +318,10 @@ private:
 
 
 private:
 private:
   void update_animated_vertices(CData *cdata, Thread *current_thread);
   void update_animated_vertices(CData *cdata, Thread *current_thread);
+  void do_transform_point_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
+                                 const LMatrix4 &mat, int begin_row, int end_row);
+  void do_transform_vector_column(const GeomVertexFormat *format, GeomVertexRewriter &data,
+                                  const LMatrix4 &mat, int begin_row, int end_row);
 
 
   static PStatCollector _convert_pcollector;
   static PStatCollector _convert_pcollector;
   static PStatCollector _scale_color_pcollector;
   static PStatCollector _scale_color_pcollector;

+ 12 - 0
panda/src/gobj/geomVertexReader.I

@@ -206,6 +206,18 @@ get_array_data() const {
   return _array_data;
   return _array_data;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexReader::get_array_handle
+//       Access: Published
+//  Description: Returns the read handle to the array object that the
+//               read is currently processing.  This low-level call
+//               should be used with caution.
+////////////////////////////////////////////////////////////////////
+INLINE const GeomVertexArrayDataHandle *GeomVertexReader::
+get_array_handle() const {
+  return _handle;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexReader::get_current_thread
 //     Function: GeomVertexReader::get_current_thread
 //       Access: Published
 //       Access: Published

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

@@ -82,6 +82,7 @@ PUBLISHED:
 
 
   INLINE const GeomVertexData *get_vertex_data() const;
   INLINE const GeomVertexData *get_vertex_data() const;
   INLINE const GeomVertexArrayData *get_array_data() const;
   INLINE const GeomVertexArrayData *get_array_data() const;
+  INLINE const GeomVertexArrayDataHandle *get_array_handle() const;
   INLINE Thread *get_current_thread() const;
   INLINE Thread *get_current_thread() const;
 
 
   INLINE void set_force(bool force);
   INLINE void set_force(bool force);

+ 14 - 0
panda/src/gobj/geomVertexRewriter.I

@@ -159,6 +159,20 @@ get_array_data() const {
   return GeomVertexWriter::get_array_data();
   return GeomVertexWriter::get_array_data();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexRewriter::get_array_handle
+//       Access: Published
+//  Description: Returns the write handle to the array object that the
+//               rewriter is currently processing.  This low-level call
+//               should be used with caution; be careful with
+//               modifying the data in the handle out from under the
+//               GeomVertexRewriter.
+////////////////////////////////////////////////////////////////////
+INLINE GeomVertexArrayDataHandle *GeomVertexRewriter::
+get_array_handle() const {
+  return GeomVertexWriter::get_array_handle();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexRewriter::set_column
 //     Function: GeomVertexRewriter::set_column
 //       Access: Published
 //       Access: Published

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

@@ -53,6 +53,7 @@ PUBLISHED:
 
 
   INLINE GeomVertexData *get_vertex_data() const;
   INLINE GeomVertexData *get_vertex_data() const;
   INLINE GeomVertexArrayData *get_array_data() const;
   INLINE GeomVertexArrayData *get_array_data() const;
+  INLINE GeomVertexArrayDataHandle *get_array_handle() const;
 
 
   INLINE bool set_column(int column);
   INLINE bool set_column(int column);
   INLINE bool set_column(const string &name);
   INLINE bool set_column(const string &name);

+ 14 - 0
panda/src/gobj/geomVertexWriter.I

@@ -201,6 +201,20 @@ get_array_data() const {
   return _array_data;
   return _array_data;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: GeomVertexWriter::get_array_handle
+//       Access: Published
+//  Description: Returns the write handle to the array object that the
+//               writer is currently processing.  This low-level call
+//               should be used with caution; be careful with
+//               modifying the data in the handle out from under the
+//               GeomVertexWriter.
+////////////////////////////////////////////////////////////////////
+INLINE GeomVertexArrayDataHandle *GeomVertexWriter::
+get_array_handle() const {
+  return _handle;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: GeomVertexWriter::get_current_thread
 //     Function: GeomVertexWriter::get_current_thread
 //       Access: Published
 //       Access: Published

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

@@ -94,6 +94,7 @@ PUBLISHED:
 
 
   INLINE GeomVertexData *get_vertex_data() const;
   INLINE GeomVertexData *get_vertex_data() const;
   INLINE GeomVertexArrayData *get_array_data() const;
   INLINE GeomVertexArrayData *get_array_data() const;
+  INLINE GeomVertexArrayDataHandle *get_array_handle() const;
   INLINE Thread *get_current_thread() const;
   INLINE Thread *get_current_thread() const;
 
 
   INLINE bool set_column(int column);
   INLINE bool set_column(int column);

+ 33 - 1
panda/src/gobj/transformBlend.cxx

@@ -79,7 +79,7 @@ add_transform(const VertexTransform *transform, PN_stdfloat weight) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformBlend::remove_transform
 //     Function: TransformBlend::remove_transform
 //       Access: Published
 //       Access: Published
-//  Description: Removes the indicated transform to the blend.
+//  Description: Removes the indicated transform from the blend.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void TransformBlend::
 void TransformBlend::
 remove_transform(const VertexTransform *transform) {
 remove_transform(const VertexTransform *transform) {
@@ -94,6 +94,38 @@ remove_transform(const VertexTransform *transform) {
   clear_result(current_thread);
   clear_result(current_thread);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlend::limit_transforms
+//       Access: Published
+//  Description: If the total number of transforms in the blend
+//               exceeds max_transforms, removes the n least-important
+//               transforms as needed to reduce the number of
+//               transforms to max_transforms.
+////////////////////////////////////////////////////////////////////
+void TransformBlend::
+limit_transforms(int max_transforms) {
+  if (max_transforms <= 0) {
+    _entries.clear();
+    return;
+  }
+
+  while (_entries.size() > max_transforms) {
+    // Repeatedly find and remove the least-important transform.
+    nassertv(!_entries.empty());
+    Entries::iterator ei_least = _entries.begin();
+    Entries::iterator ei = ei_least;
+    ++ei;
+    while (ei != _entries.end()) {
+      if ((*ei)._weight < (*ei_least)._weight) {
+        ei_least = ei;
+      }
+      ++ei;
+    }
+
+    _entries.erase(ei_least);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformBlend::normalize_weights
 //     Function: TransformBlend::normalize_weights
 //       Access: Published
 //       Access: Published

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

@@ -57,6 +57,7 @@ PUBLISHED:
 
 
   void add_transform(const VertexTransform *transform, PN_stdfloat weight);
   void add_transform(const VertexTransform *transform, PN_stdfloat weight);
   void remove_transform(const VertexTransform *transform);
   void remove_transform(const VertexTransform *transform);
+  void limit_transforms(int max_transforms);
   void normalize_weights();
   void normalize_weights();
   bool has_transform(const VertexTransform *transform) const;
   bool has_transform(const VertexTransform *transform) const;
   PN_stdfloat get_weight(const VertexTransform *transform) const;
   PN_stdfloat get_weight(const VertexTransform *transform) const;