2
0
David Rose 21 жил өмнө
parent
commit
3716a4b470

+ 3 - 0
panda/src/egg/eggCompositePrimitive.cxx

@@ -197,6 +197,7 @@ unify_attributes(EggPrimitive::Shading shading) {
         EggVertexPool *vertex_pool = orig_vertex->get_pool();
         nassertv(vertex_pool != (EggVertexPool *)NULL);
         vertex = vertex_pool->create_unique_vertex(*vertex);
+        vertex->copy_grefs_from(*orig_vertex);
         replace(pi, vertex);
       }
       clear_normal();
@@ -225,6 +226,7 @@ unify_attributes(EggPrimitive::Shading shading) {
           EggVertexPool *vertex_pool = orig_vertex->get_pool();
           nassertv(vertex_pool != (EggVertexPool *)NULL);
           vertex = vertex_pool->create_unique_vertex(*vertex);
+          vertex->copy_grefs_from(*orig_vertex);
           replace(pi, vertex);
         }
       }
@@ -272,6 +274,7 @@ unify_attributes(EggPrimitive::Shading shading) {
         EggVertexPool *vertex_pool = orig_vertex->get_pool();
         nassertv(vertex_pool != (EggVertexPool *)NULL);
         vertex = vertex_pool->create_unique_vertex(*vertex);
+        vertex->copy_grefs_from(*orig_vertex);
         replace(pi, vertex);
       }
       Components::iterator ci;

+ 6 - 2
panda/src/egg/eggGroupNode.cxx

@@ -944,7 +944,9 @@ rebuild_vertex_pool(EggVertexPool *vertex_pool, bool recurse) {
       Vertices::const_iterator vi;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vertex = (*vi);
-        prim->add_vertex(vertex_pool->create_unique_vertex(*vertex));
+        EggVertex *new_vertex = vertex_pool->create_unique_vertex(*vertex);
+        new_vertex->copy_grefs_from(*vertex);
+        prim->add_vertex(new_vertex);
       }
       for (i = 0; i < num_components; i++) {
         prim->set_component(i, &attributes[i]);
@@ -965,7 +967,9 @@ rebuild_vertex_pool(EggVertexPool *vertex_pool, bool recurse) {
       Vertices::const_iterator vi;
       for (vi = vertices.begin(); vi != vertices.end(); ++vi) {
         EggVertex *vertex = (*vi);
-        prim->add_vertex(vertex_pool->create_unique_vertex(*vertex));
+        EggVertex *new_vertex = vertex_pool->create_unique_vertex(*vertex);
+        new_vertex->copy_grefs_from(*vertex);
+        prim->add_vertex(new_vertex);
       }
 
     } else if (child->is_of_type(EggGroupNode::get_class_type())) {

+ 3 - 0
panda/src/egg/eggPrimitive.cxx

@@ -375,6 +375,7 @@ unify_attributes(EggPrimitive::Shading shading) {
         EggVertexPool *vertex_pool = orig_vertex->get_pool();
         nassertv(vertex_pool != (EggVertexPool *)NULL);
         vertex = vertex_pool->create_unique_vertex(*vertex);
+        vertex->copy_grefs_from(*orig_vertex);
         replace(pi, vertex);
       }
       clear_normal();
@@ -406,6 +407,7 @@ unify_attributes(EggPrimitive::Shading shading) {
         EggVertexPool *vertex_pool = orig_vertex->get_pool();
         nassertv(vertex_pool != (EggVertexPool *)NULL);
         vertex = vertex_pool->create_unique_vertex(*vertex);
+        vertex->copy_grefs_from(*orig_vertex);
         replace(pi, vertex);
       }
     }
@@ -1123,6 +1125,7 @@ do_apply_flat_attribute(int vertex_index, EggAttributes *attrib) {
 
   if (significant_change) {
     new_vertex = get_pool()->create_unique_vertex(*new_vertex);
+    new_vertex->copy_grefs_from(*orig_vertex);
     set_vertex(vertex_index, new_vertex);
   } else {
     // Just copy the new attributes back into the pool.

+ 247 - 6
panda/src/egg2pg/characterMaker.cxx

@@ -19,10 +19,11 @@
 #include "characterMaker.h"
 #include "eggLoader.h"
 #include "config_egg2pg.h"
-
+#include "eggBinner.h"
 #include "computedVertices.h"
 #include "eggGroup.h"
 #include "eggPrimitive.h"
+#include "eggBin.h"
 #include "partGroup.h"
 #include "characterJoint.h"
 #include "characterJointBundle.h"
@@ -32,6 +33,8 @@
 #include "eggSurface.h"
 #include "eggCurve.h"
 #include "modelNode.h"
+#include "jointVertexTransform.h"
+#include "userVertexTransform.h"
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CharacterMaker::Construtor
@@ -61,6 +64,15 @@ make_node() {
   return _character_node;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterMaker::get_name
+//       Access: Public
+//  Description: Returns the name of the character.
+////////////////////////////////////////////////////////////////////
+string CharacterMaker::
+get_name() const {
+  return _egg_root->get_name();
+}
 
 ////////////////////////////////////////////////////////////////////
 //     Function: CharacterMaker::egg_to_part
@@ -82,6 +94,38 @@ egg_to_part(EggNode *egg_node) const {
   return _parts[index];
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterMaker::egg_to_transform
+//       Access: Public
+//  Description: Returns a JointVertexTransform suitable for
+//               applying the animation associated with the given
+//               egg node (which should be a joint).  Returns an
+//               identity transform if the egg node is not a joint in
+//               the character's hierarchy.
+////////////////////////////////////////////////////////////////////
+VertexTransform *CharacterMaker::
+egg_to_transform(EggNode *egg_node) {
+  int index = egg_to_index(egg_node);
+  if (index < 0) {
+    // Not a joint in the hierarchy.
+    return get_identity_transform();
+  }
+
+  VertexTransforms::iterator vi = _vertex_transforms.find(index);
+  if (vi != _vertex_transforms.end()) {
+    return (*vi).second;
+  }
+
+  PartGroup *part = _parts[index];
+  CharacterJoint *joint;
+  DCAST_INTO_R(joint, part, get_identity_transform());
+
+  PT(VertexTransform) vt = new JointVertexTransform(joint);
+  _vertex_transforms[index] = vt;
+
+  return vt;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CharacterMaker::egg_to_index
 //       Access: Public
@@ -148,12 +192,19 @@ make_bundle() {
   build_joint_hierarchy(_egg_root, _skeleton_root);
   _bundle->sort_descendants();
 
-  parent_joint_nodes(_skeleton_root);
-  make_geometry(_egg_root);
-
-  _character_node->_computed_vertices =
-    _comp_verts_maker.make_computed_vertices(_character_node, *this);
+  if (use_qpgeom) {
+    // The new, experimental Geom system.
+    make_qpgeometry(_egg_root);
 
+  } else {
+    // The old Geom system.
+    make_geometry(_egg_root);
+    
+    _character_node->_computed_vertices =
+      _comp_verts_maker.make_computed_vertices(_character_node, *this);
+  }
+  parent_joint_nodes(_skeleton_root);
+    
   return _bundle;
 }
 
@@ -276,6 +327,57 @@ make_geometry(EggNode *egg_node) {
   }
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterMaker::make_qpgeometry
+//       Access: Private
+//  Description: Walks the hierarchy, looking for bins that represent
+//               polysets, which are to be animated with the
+//               character.  Invokes the egg loader to create the
+//               animated geometry.
+//
+//               This is part of the experimental Geom rewrite.
+////////////////////////////////////////////////////////////////////
+void CharacterMaker::
+make_qpgeometry(EggNode *egg_node) {
+  if (egg_node->is_of_type(EggBin::get_class_type())) {
+    EggBin *egg_bin = DCAST(EggBin, egg_node);
+
+    if (!egg_bin->empty() && 
+        egg_bin->get_bin_number() == EggBinner::BN_polyset) {
+      EggGroupNode *bin_home = determine_bin_home(egg_bin);
+
+      bool is_dynamic;
+      if (bin_home == (EggGroupNode *)NULL) {
+        // This is a dynamic polyset that lives under the character's
+        // root node.
+        bin_home = _egg_root;
+        is_dynamic = true;
+      } else {
+        // This is a totally static polyset that is parented under
+        // some animated joint node.
+        is_dynamic = false;
+      }
+
+      PandaNode *parent = part_to_node(egg_to_part(bin_home));
+      LMatrix4d transform =
+        egg_bin->get_vertex_frame() *
+        bin_home->get_node_frame_inv();
+      
+      _loader.make_polyset(egg_bin, parent, &transform, is_dynamic,
+                           this);
+    }
+  }
+
+  if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
+    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);
+
+    EggGroupNode::const_iterator ci;
+    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
+      make_qpgeometry(*ci);
+    }
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: CharacterMaker::make_static_primitive
 //       Access: Private
@@ -419,3 +521,142 @@ determine_primitive_home(EggPrimitive *egg_primitive) {
   // explicit joint assignment.
   return home;
 }
+
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterMaker::determine_bin_home
+//       Access: Private
+//  Description: Examines the joint assignment of the vertices of all
+//               of the primitives within this bin to determine which
+//               parent node the bin's polyset should be created
+//               under.
+////////////////////////////////////////////////////////////////////
+EggGroupNode *CharacterMaker::
+determine_bin_home(EggBin *egg_bin) {
+  // A primitive's vertices may be referenced by any joint in the
+  // character.  Or, the primitive itself may be explicitly placed
+  // under a joint.
+
+  // If any of the vertices, in any primitive, are referenced by
+  // multiple joints, or if any two vertices are referenced by
+  // different joints, then the entire bin must be considered dynamic.
+  // (We'll indicate a dynamic bin by returning NULL.)
+
+  // We need to keep track of the one joint we've encountered so far,
+  // to see if all the vertices are referenced by the same joint.
+  EggGroupNode *home = NULL;
+
+  EggGroupNode::const_iterator ci;
+  for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
+    CPT(EggPrimitive) egg_primitive = DCAST(EggPrimitive, (*ci));
+
+    EggPrimitive::const_iterator vi;
+    for (vi = egg_primitive->begin();
+         vi != egg_primitive->end();
+         ++vi) {
+      EggVertex *vertex = (*vi);
+      if (vertex->gref_size() > 1) {
+        // This vertex is referenced by multiple joints; the primitive
+        // is dynamic.
+        return NULL;
+      }
+      
+      if (!vertex->_dxyzs.empty() ||
+          !vertex->_dnormals.empty() ||
+          !vertex->_drgbas.empty()) {
+        // This vertex has some morph slider definitions; therefore, the
+        // primitive is dynamic.
+        return NULL;
+      }
+      EggVertex::const_uv_iterator uvi;
+      for (uvi = vertex->uv_begin(); uvi != vertex->uv_end(); ++uvi) {
+        if (!(*uvi)->_duvs.empty()) {
+          // Ditto: the vertex has some UV morphs; therefore the
+          // primitive is dynamic.
+          return NULL;
+        }
+      }
+
+      EggGroupNode *vertex_home;
+      
+      if (vertex->gref_size() == 0) {
+        // This vertex is not referenced at all, which means it belongs
+        // right where it is.
+        vertex_home = egg_primitive->get_parent();
+      } else {
+        nassertr(vertex->gref_size() == 1, NULL);
+        // This vertex is referenced exactly once.
+        vertex_home = *vertex->gref_begin();
+      }
+      
+      if (home != NULL && home != vertex_home) {
+        // Oops, two vertices are referenced by different joints!  The
+        // primitive is dynamic.
+        return NULL;
+      }
+
+      home = vertex_home;
+    }
+  }
+
+  // This shouldn't be possible, unless there are no vertices--but we
+  // eliminate invalid primitives before we begin, so all primitives
+  // should have vertices, and all bins should have primitives.
+  nassertr(home != NULL, NULL);
+
+  // So, all the vertices are assigned to the same group.  This means
+  // all the primitives in the bin belong entirely to one joint.
+
+  // If the group is not, in fact, a joint then we return the first
+  // joint above the group.
+  EggGroup *egg_group = (EggGroup *)NULL;
+  if (home->is_of_type(EggGroup::get_class_type())) {
+    egg_group = DCAST(EggGroup, home);
+  }
+  while (egg_group != (EggGroup *)NULL &&
+         egg_group->get_group_type() != EggGroup::GT_joint &&
+         egg_group->get_dart_type() == EggGroup::DT_none) {
+    nassertr(egg_group->get_parent() != (EggGroupNode *)NULL, NULL);
+    home = egg_group->get_parent();
+    egg_group = (EggGroup *)NULL;
+    if (home->is_of_type(EggGroup::get_class_type())) {
+      egg_group = DCAST(EggGroup, home);
+    }
+  }
+
+  if (egg_group != (EggGroup *)NULL &&
+      egg_group->get_group_type() == EggGroup::GT_joint &&
+      egg_group->get_dcs_type() == EggGroup::DC_none) {
+    // If we have rigid geometry that is assigned to a joint without a
+    // <DCS> flag, which means the joint didn't get created as its own
+    // node, go ahead and make an implicit <DCS> flag for the joint.
+
+    // The alternative is to return NULL to treat the geometry as
+    // dynamic (and animate it by animating its vertices), but display
+    // lists and vertex buffers will perform better if as much
+    // geometry as possible is rigid.
+
+    egg_group->set_dcs_type(EggGroup::DC_default);
+    PT(ModelNode) geom_node = new ModelNode(egg_group->get_name());
+    geom_node->set_preserve_transform(ModelNode::PT_local);
+
+    CharacterJoint *joint;
+    DCAST_INTO_R(joint, egg_to_part(egg_group), home);
+    joint->_geom_node = geom_node.p();
+  }
+
+  return home;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: CharacterMaker::get_identity_transform
+//       Access: Private
+//  Description: Returns a VertexTransform that represents the root of
+//               the character--it never animates.
+////////////////////////////////////////////////////////////////////
+VertexTransform *CharacterMaker::
+get_identity_transform() {
+  if (_identity_transform == (VertexTransform *)NULL) {
+    _identity_transform = new UserVertexTransform("root");
+  }
+  return _identity_transform;
+}

+ 11 - 1
panda/src/egg2pg/characterMaker.h

@@ -22,7 +22,7 @@
 #include "pandabase.h"
 
 #include "computedVerticesMaker.h"
-
+#include "vertexTransform.h"
 #include "vector_PartGroupStar.h"
 #include "typedef.h"
 #include "pmap.h"
@@ -31,6 +31,7 @@ class EggNode;
 class EggGroup;
 class EggGroupNode;
 class EggPrimitive;
+class EggBin;
 class PartGroup;
 class CharacterJointBundle;
 class Character;
@@ -51,7 +52,9 @@ public:
 
   Character *make_node();
 
+  string get_name() const;
   PartGroup *egg_to_part(EggNode *egg_node) const;
+  VertexTransform *egg_to_transform(EggNode *egg_node);
   int egg_to_index(EggNode *egg_node) const;
   PandaNode *part_to_node(PartGroup *part) const;
 
@@ -63,12 +66,15 @@ private:
   void parent_joint_nodes(PartGroup *part);
 
   void make_geometry(EggNode *egg_node);
+  void make_qpgeometry(EggNode *egg_node);
 
   void make_static_primitive(EggPrimitive *egg_primitive,
                              EggGroupNode *prim_home);
   void make_dynamic_primitive(EggPrimitive *egg_primitive,
                               EggGroupNode *prim_home);
   EggGroupNode *determine_primitive_home(EggPrimitive *egg_primitive);
+  EggGroupNode *determine_bin_home(EggBin *egg_bin);
+  VertexTransform *get_identity_transform();
 
   typedef pmap<EggNode *, int> NodeMap;
   NodeMap _node_map;
@@ -76,6 +82,10 @@ private:
   typedef vector_PartGroupStar Parts;
   Parts _parts;
 
+  typedef pmap<int, PT(VertexTransform) > VertexTransforms;
+  VertexTransforms _vertex_transforms;
+  PT(VertexTransform) _identity_transform;
+
   EggLoader &_loader;
   EggGroup *_egg_root;
   Character *_character_node;

+ 186 - 123
panda/src/egg2pg/eggLoader.cxx

@@ -89,6 +89,8 @@
 #include "sheetNode.h"
 #include "look_at.h"
 #include "configVariableString.h"
+#include "transformBlendPalette.h"
+#include "transformBlend.h"
 
 #include <ctype.h>
 #include <algorithm>
@@ -527,6 +529,135 @@ make_indexed_primitive(EggPrimitive *egg_prim, PandaNode *parent,
   _builder.add_prim(bucket, bprim);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggLoader::make_polyset
+//       Access: Public
+//  Description: Creates a polyset--that is, a Geom--from the
+//               primitives that have already been grouped into a bin.
+//               If transform is non-NULL, it represents the transform
+//               to apply to the vertices (instead of the default
+//               transform based on the bin's position within the
+//               hierarchy).
+////////////////////////////////////////////////////////////////////
+void EggLoader::
+make_polyset(EggBin *egg_bin, PandaNode *parent, const LMatrix4d *transform,
+             bool is_dynamic, CharacterMaker *character_maker) {
+  if (egg_bin->empty()) {
+    // If there are no children--no primitives--never mind.
+    return;
+  }
+
+  // We know that all of the primitives in the bin have the same
+  // render state, so we can get that information from the first
+  // primitive.
+  EggGroupNode::const_iterator ci = egg_bin->begin();
+  nassertv(ci != egg_bin->end());
+  CPT(EggPrimitive) first_prim = DCAST(EggPrimitive, (*ci));
+  nassertv(first_prim != (EggPrimitive *)NULL);
+  const EggRenderState *render_state;
+  DCAST_INTO_V(render_state, first_prim->get_user_data(EggRenderState::get_class_type()));
+
+  if (render_state->_hidden && egg_suppress_hidden) {
+    // Eat this polyset.
+    return;
+  }
+
+  if (!use_qpgeom) {
+    // In the old Geom system, just send each primitive to the
+    // Builder.
+    for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
+      EggPrimitive *egg_prim;
+      DCAST_INTO_V(egg_prim, (*ci));
+      make_nonindexed_primitive(egg_prim, parent, transform, _comp_verts_maker);
+    }
+
+    return;
+  }
+
+  // Generate an optimal vertex pool for the polygons within just the
+  // bin (which translates directly to an optimal GeomVertexData
+  // structure).
+  PT(EggVertexPool) vertex_pool = new EggVertexPool("bin");
+  egg_bin->rebuild_vertex_pool(vertex_pool, false);
+
+  if (egg_mesh) {
+    // If we're using the mesher, mesh now.
+    egg_bin->mesh_triangles(0);
+
+  } else {
+    // If we're not using the mesher, at least triangulate any
+    // higher-order polygons we might have.
+    egg_bin->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex);
+  }
+
+  // Now that we've meshed, apply the per-prim attributes onto the
+  // vertices, so we can copy them to the GeomVertexData.
+  egg_bin->apply_last_attribute(false);
+  egg_bin->post_apply_flat_attribute(false);
+  vertex_pool->remove_unused_vertices();
+
+  //  vertex_pool->write(cerr, 0);
+  //  egg_bin->write(cerr, 0);
+
+  // Now create a handful of GeomPrimitives corresponding to the
+  // various types of primitives we have.
+  Primitives primitives;
+  for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
+    EggPrimitive *egg_prim;
+    DCAST_INTO_V(egg_prim, (*ci));
+    make_primitive(render_state, egg_prim, primitives);
+  }
+
+  if (!primitives.empty()) {
+    LMatrix4d mat;
+    if (transform != NULL) {
+      mat = (*transform);
+    } else {
+      mat = egg_bin->get_vertex_to_node();
+    }
+
+    // Now convert the vertex pool to a GeomVertexData.
+    nassertv(vertex_pool != (EggVertexPool *)NULL);
+    PT(qpGeomVertexData) vertex_data = 
+      make_vertex_data(render_state, vertex_pool, mat,
+                       is_dynamic, character_maker);
+    nassertv(vertex_data != (qpGeomVertexData *)NULL);
+
+    // And create a Geom to hold the primitives.
+    PT(qpGeom) geom = new qpGeom;
+    geom->set_vertex_data(vertex_data);
+
+    // Add each new primitive to the Geom.
+    Primitives::const_iterator pi;
+    for (pi = primitives.begin(); pi != primitives.end(); ++pi) {
+      qpGeomPrimitive *primitive = (*pi).second;
+      geom->add_primitive(primitive);
+    }
+
+    //    vertex_data->write(cerr);
+    //    geom->write(cerr);
+    //    render_state->_state->write(cerr, 0);
+    
+    // Now, is our parent node a GeomNode, or just an ordinary
+    // PandaNode?  If it's a GeomNode, we can add the new Geom directly
+    // to our parent; otherwise, we need to create a new node.
+    if (parent->is_geom_node() && !render_state->_hidden) {
+      DCAST(GeomNode, parent)->add_geom(geom, render_state->_state);
+      
+    } else {
+      PT(GeomNode) geom_node = new GeomNode(egg_bin->get_name());
+      if (render_state->_hidden) {
+        parent->add_stashed(geom_node);
+      } else {
+        parent->add_child(geom_node);
+      }
+      geom_node->add_geom(geom, render_state->_state);
+    }
+  }
+
+  return;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::make_nurbs_curve
 //       Access: Private
@@ -1404,7 +1535,8 @@ make_node(EggBin *egg_bin, PandaNode *parent) {
   // node (a parent of one or more similar EggPrimitives).
   switch (egg_bin->get_bin_number()) {
   case EggBinner::BN_polyset:
-    return make_polyset(egg_bin, parent);
+    make_polyset(egg_bin, parent, NULL, false, NULL);
+    return NULL;
 
   case EggBinner::BN_lod:
     return make_lod(egg_bin, parent);
@@ -1417,122 +1549,6 @@ make_node(EggBin *egg_bin, PandaNode *parent) {
   return (PandaNode *)NULL;
 }
 
-////////////////////////////////////////////////////////////////////
-//     Function: EggLoader::make_polyset
-//       Access: Private
-//  Description:
-////////////////////////////////////////////////////////////////////
-PandaNode *EggLoader::
-make_polyset(EggBin *egg_bin, PandaNode *parent) {
-  if (egg_bin->empty()) {
-    // If there are no children--no primitives--never mind.
-    return NULL;
-  }
-
-  // We know that all of the primitives in the bin have the same
-  // render state, so we can get that information from the first
-  // primitive.
-  EggGroup::const_iterator ci = egg_bin->begin();
-  nassertr(ci != egg_bin->end(), NULL);
-  CPT(EggPrimitive) first_prim = DCAST(EggPrimitive, (*ci));
-  nassertr(first_prim != (EggPrimitive *)NULL, NULL);
-  const EggRenderState *render_state;
-  DCAST_INTO_R(render_state, first_prim->get_user_data(EggRenderState::get_class_type()), NULL);
-
-  if (render_state->_hidden && egg_suppress_hidden) {
-    // Eat this polyset.
-    return NULL;
-  }
-
-  if (!use_qpgeom) {
-    // In the old Geom system, just send each primitive to the
-    // Builder.
-    for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
-      EggPrimitive *egg_prim;
-      DCAST_INTO_R(egg_prim, (*ci), NULL);
-      make_nonindexed_primitive(egg_prim, parent, NULL, _comp_verts_maker);
-    }
-
-    return NULL;
-  }
-
-  // Generate an optimal vertex pool for the polygons within just the
-  // bin (which translates directly to an optimal GeomVertexData
-  // structure).
-  PT(EggVertexPool) vertex_pool = new EggVertexPool("bin");
-  egg_bin->rebuild_vertex_pool(vertex_pool, false);
-
-  if (egg_mesh) {
-    // If we're using the mesher, mesh now.
-    egg_bin->mesh_triangles(0);
-
-  } else {
-    // If we're not using the mesher, at least triangulate any
-    // higher-order polygons we might have.
-    egg_bin->triangulate_polygons(EggGroupNode::T_polygon | EggGroupNode::T_convex);
-  }
-
-  // Now that we've meshed, apply the per-prim attributes onto the
-  // vertices, so we can copy them to the GeomVertexData.
-  egg_bin->apply_last_attribute(false);
-  egg_bin->post_apply_flat_attribute(false);
-  vertex_pool->remove_unused_vertices();
-
-  //  vertex_pool->write(cerr, 0);
-  //  egg_bin->write(cerr, 0);
-
-  // Now create a handful of GeomPrimitives corresponding to the
-  // various types of primitives we have.
-  Primitives primitives;
-  for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
-    EggPrimitive *egg_prim;
-    DCAST_INTO_R(egg_prim, (*ci), NULL);
-    make_primitive(render_state, egg_prim, primitives);
-  }
-
-  if (!primitives.empty()) {
-    // Now convert the vertex pool to a GeomVertexData.
-    nassertr(vertex_pool != (EggVertexPool *)NULL, NULL);
-    PT(qpGeomVertexData) vertex_data = 
-      make_vertex_data(render_state, vertex_pool, 
-                       egg_bin->get_vertex_to_node());
-    nassertr(vertex_data != (qpGeomVertexData *)NULL, NULL);
-
-    // And create a Geom to hold the primitives.
-    PT(qpGeom) geom = new qpGeom;
-    geom->set_vertex_data(vertex_data);
-
-    // Add each new primitive to the Geom.
-    Primitives::const_iterator pi;
-    for (pi = primitives.begin(); pi != primitives.end(); ++pi) {
-      qpGeomPrimitive *primitive = (*pi).second;
-      geom->add_primitive(primitive);
-    }
-
-    //    vertex_data->write(cerr);
-    //    geom->write(cerr);
-    //    render_state->_state->write(cerr, 0);
-    
-    // Now, is our parent node a GeomNode, or just an ordinary
-    // PandaNode?  If it's a GeomNode, we can add the new Geom directly
-    // to our parent; otherwise, we need to create a new node.
-    if (parent->is_geom_node() && !render_state->_hidden) {
-      DCAST(GeomNode, parent)->add_geom(geom, render_state->_state);
-      
-    } else {
-      PT(GeomNode) geom_node = new GeomNode(egg_bin->get_name());
-      if (render_state->_hidden) {
-        parent->add_stashed(geom_node);
-      } else {
-        parent->add_child(geom_node);
-      }
-      geom_node->add_geom(geom, render_state->_state);
-    }
-  }
-
-  return NULL;
-}
-
 ////////////////////////////////////////////////////////////////////
 //     Function: EggLoader::make_lod
 //       Access: Private
@@ -1888,7 +1904,8 @@ check_for_polysets(EggGroup *egg_group, bool &all_polysets, bool &any_hidden) {
 ////////////////////////////////////////////////////////////////////
 PT(qpGeomVertexData) EggLoader::
 make_vertex_data(const EggRenderState *render_state, 
-                 EggVertexPool *vertex_pool, const LMatrix4d &transform) {
+                 EggVertexPool *vertex_pool, const LMatrix4d &transform,
+                 bool is_dynamic, CharacterMaker *character_maker) {
   VertexPoolTransform vpt;
   vpt._vertex_pool = vertex_pool;
   vpt._bake_in_uvs = render_state->_bake_in_uvs;
@@ -1928,12 +1945,39 @@ make_vertex_data(const EggRenderState *render_state,
     array_format->add_data_type(iname, 2, qpGeomVertexDataType::NT_float32);
   }
 
-  CPT(qpGeomVertexFormat) format = 
-    qpGeomVertexFormat::register_format(new qpGeomVertexFormat(array_format));
+  PT(qpGeomVertexFormat) temp_format = new qpGeomVertexFormat(array_format);
+
+  PT(TransformBlendPalette) blend_palette;
+  string name;
 
-  // Now create a new GeomVertexData using the indicated format.
-  PT(qpGeomVertexData) vertex_data = 
-    new qpGeomVertexData(format, qpGeomUsageHint::UH_static);
+  if (is_dynamic) {
+    // If it's a dynamic object, we need a TransformBlendPalette, and
+    // another array that indexes into the palette per vertex.
+    blend_palette = new TransformBlendPalette;
+    PT(qpGeomVertexArrayFormat) blend_array_format = new qpGeomVertexArrayFormat;
+    blend_array_format->add_data_type
+      (InternalName::get_transform_blend(), 1, qpGeomVertexDataType::NT_uint16);
+    temp_format->add_array(blend_array_format);
+
+    // We'll also assign the character name to the vertex data, so it
+    // will show up in PStats.
+    name = character_maker->get_name();
+  }
+
+  CPT(qpGeomVertexFormat) format =
+    qpGeomVertexFormat::register_format(temp_format);
+
+  // Now create a new GeomVertexData using the indicated format.  It
+  // is actually correct to create it with UH_static even it
+  // represents a dynamic object, because the vertex data itself won't
+  // be changing--just the result of applying the animation is
+  // dynamic.
+  PT(qpGeomVertexData) vertex_data =
+    new qpGeomVertexData(name, format, qpGeomUsageHint::UH_static);
+
+  if (is_dynamic) {
+    vertex_data->set_transform_blend_palette(blend_palette);
+  }
 
   // And fill the data from the vertex pool.
   EggVertexPool::const_iterator vi;
@@ -1975,6 +2019,25 @@ make_vertex_data(const EggRenderState *render_state,
 
       gvi.set_data2f(LCAST(float, uv));
     }
+
+    if (is_dynamic) {
+      // Figure out the transforms affecting this particular vertex.
+      TransformBlend blend;
+      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);
+
+        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 palette_index = blend_palette->add_blend(blend);
+      gvi.set_data_type(InternalName::get_transform_blend());
+      gvi.set_data1i(palette_index);
+    }
   }
 
   bool inserted = _vertex_pool_data.insert

+ 9 - 3
panda/src/egg2pg/eggLoader.h

@@ -58,6 +58,7 @@ class CollisionPolygon;
 class PortalNode;
 class PolylightNode;
 class EggRenderState;
+class CharacterMaker;
 
 ///////////////////////////////////////////////////////////////////
 //       Class : EggLoader
@@ -83,6 +84,10 @@ public:
                               const LMatrix4d *transform,
                               ComputedVerticesMaker &comp_verts_maker);
 
+  void make_polyset(EggBin *egg_bin, PandaNode *parent, 
+                    const LMatrix4d *transform, bool is_dynamic,
+                    CharacterMaker *character_maker);
+
 private:
   class TextureDef {
   public:
@@ -137,9 +142,10 @@ private:
 
   void check_for_polysets(EggGroup *egg_group, bool &all_polysets, 
                           bool &any_hidden);
-  PT(qpGeomVertexData) make_vertex_data(const EggRenderState *render_state,
-                                        EggVertexPool *vertex_pool, 
-                                        const LMatrix4d &transform);
+  PT(qpGeomVertexData) make_vertex_data
+  (const EggRenderState *render_state, EggVertexPool *vertex_pool, 
+   const LMatrix4d &transform, bool is_dynamic, 
+   CharacterMaker *character_maker);
   void make_primitive(const EggRenderState *render_state, 
                       EggPrimitive *egg_prim, Primitives &primitives);
 

+ 2 - 2
panda/src/framework/windowFramework.cxx

@@ -633,7 +633,7 @@ load_default_model(const NodePath &parent) {
   if (use_qpgeom) {
     // New, experimental Geom code.
     PT(qpGeomVertexData) vdata = new qpGeomVertexData
-      (qpGeomVertexFormat::get_v3n3cpt2(),
+      (string(), qpGeomVertexFormat::get_v3n3cpt2(),
        qpGeomUsageHint::UH_static);
     qpGeomVertexIterator vertex(vdata, InternalName::get_vertex());
     qpGeomVertexIterator normal(vdata, InternalName::get_normal());
@@ -1060,7 +1060,7 @@ load_image_as_model(const Filename &filename) {
 
   if (use_qpgeom) {
     PT(qpGeomVertexData) vdata = new qpGeomVertexData
-      (qpGeomVertexFormat::get_v3t2(),
+      (string(), qpGeomVertexFormat::get_v3t2(),
        qpGeomUsageHint::UH_static);
     qpGeomVertexIterator vertex(vdata, InternalName::get_vertex());
     qpGeomVertexIterator texcoord(vdata, InternalName::get_texcoord());

+ 26 - 2
panda/src/gobj/qpgeomVertexData.I

@@ -17,6 +17,30 @@
 ////////////////////////////////////////////////////////////////////
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::get_name
+//       Access: Published
+//  Description: Returns the name passed to the constructor, if any.
+//               This name is reported on the PStats graph for vertex
+//               computations.
+////////////////////////////////////////////////////////////////////
+INLINE const string &qpGeomVertexData::
+get_name() const {
+  return _name;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: qpGeomVertexData::set_name
+//       Access: Published
+//  Description: Changes the name of the vertex data.  This name is
+//               reported on the PStats graph for vertex computations.
+////////////////////////////////////////////////////////////////////
+INLINE void qpGeomVertexData::
+set_name(const string &name) {
+  _name = name;
+  _this_animate_vertices_pcollector = PStatCollector(_animate_vertices_pcollector, name);
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::get_format
 //       Access: Published
@@ -154,8 +178,8 @@ INLINE qpGeomVertexData::CData::
 CData(const qpGeomVertexData::CData &copy) :
   _arrays(copy._arrays),
   _transform_blend_palette(copy._transform_blend_palette),
-  _computed_vertices(copy._computed_vertices),
-  _computed_vertices_modified(copy._computed_vertices_modified),
+  _animated_vertices(copy._animated_vertices),
+  _animated_vertices_modified(copy._animated_vertices_modified),
   _modified(copy._modified)
 {
 }

+ 73 - 40
panda/src/gobj/qpgeomVertexData.cxx

@@ -22,13 +22,14 @@
 #include "bamReader.h"
 #include "bamWriter.h"
 #include "pset.h"
+#include "indent.h"
 
 TypeHandle qpGeomVertexData::_type_handle;
 
 PStatCollector qpGeomVertexData::_convert_pcollector("Cull:Munge:Convert");
 PStatCollector qpGeomVertexData::_scale_color_pcollector("Cull:Munge:Scale color");
 PStatCollector qpGeomVertexData::_set_color_pcollector("Cull:Munge:Set color");
-PStatCollector qpGeomVertexData::_compute_vertices_pcollector("Cull:Compute vertices");
+PStatCollector qpGeomVertexData::_animate_vertices_pcollector("Cull:Animate vertices");
 
 ////////////////////////////////////////////////////////////////////
 //     Function: qpGeomVertexData::Default Constructor
@@ -37,7 +38,9 @@ PStatCollector qpGeomVertexData::_compute_vertices_pcollector("Cull:Compute vert
 //               reading from the bam file.
 ////////////////////////////////////////////////////////////////////
 qpGeomVertexData::
-qpGeomVertexData() {
+qpGeomVertexData() :
+  _this_animate_vertices_pcollector(_animate_vertices_pcollector)
+{
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -46,10 +49,13 @@ qpGeomVertexData() {
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 qpGeomVertexData::
-qpGeomVertexData(const qpGeomVertexFormat *format,
+qpGeomVertexData(const string &name,
+                 const qpGeomVertexFormat *format,
                  qpGeomUsageHint::UsageHint usage_hint) :
+  _name(name),
   _format(format),
-  _usage_hint(usage_hint)
+  _usage_hint(usage_hint),
+  _this_animate_vertices_pcollector(_animate_vertices_pcollector, name)
 {
   nassertv(_format->is_registered());
 
@@ -72,8 +78,10 @@ qpGeomVertexData(const qpGeomVertexFormat *format,
 qpGeomVertexData::
 qpGeomVertexData(const qpGeomVertexData &copy) :
   TypedWritableReferenceCount(copy),
+  _name(copy._name),
   _format(copy._format),
-  _cycler(copy._cycler)  
+  _cycler(copy._cycler),
+  _this_animate_vertices_pcollector(copy._this_animate_vertices_pcollector)
 {
 }
   
@@ -85,8 +93,10 @@ qpGeomVertexData(const qpGeomVertexData &copy) :
 void qpGeomVertexData::
 operator = (const qpGeomVertexData &copy) {
   TypedWritableReferenceCount::operator = (copy);
+  _name = copy._name;
   _format = copy._format;
   _cycler = copy._cycler;
+  _this_animate_vertices_pcollector = copy._this_animate_vertices_pcollector;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -138,7 +148,7 @@ clear_vertices() {
     (*ai)->clear_vertices();
   }
   cdata->_modified = qpGeom::get_next_modified();
-  cdata->_computed_vertices.clear();
+  cdata->_animated_vertices.clear();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -162,7 +172,7 @@ modify_array(int i) {
     cdata->_arrays[i] = new qpGeomVertexArrayData(*cdata->_arrays[i]);
   }
   cdata->_modified = qpGeom::get_next_modified();
-  cdata->_computed_vertices_modified = UpdateSeq();
+  cdata->_animated_vertices_modified = UpdateSeq();
 
   return cdata->_arrays[i];
 }
@@ -181,7 +191,7 @@ set_array(int i, const qpGeomVertexArrayData *array) {
   nassertv(i >= 0 && i < (int)cdata->_arrays.size());
   cdata->_arrays[i] = (qpGeomVertexArrayData *)array;
   cdata->_modified = qpGeom::get_next_modified();
-  cdata->_computed_vertices_modified = UpdateSeq();
+  cdata->_animated_vertices_modified = UpdateSeq();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -203,7 +213,7 @@ modify_transform_blend_palette() {
     cdata->_transform_blend_palette = new TransformBlendPalette(*cdata->_transform_blend_palette);
   }
   cdata->_modified = qpGeom::get_next_modified();
-  cdata->_computed_vertices_modified = UpdateSeq();
+  cdata->_animated_vertices_modified = UpdateSeq();
 
   return cdata->_transform_blend_palette;
 }
@@ -222,7 +232,7 @@ set_transform_blend_palette(const TransformBlendPalette *palette) {
   CDWriter cdata(_cycler);
   cdata->_transform_blend_palette = (TransformBlendPalette *)palette;
   cdata->_modified = qpGeom::get_next_modified();
-  cdata->_computed_vertices_modified = UpdateSeq();
+  cdata->_animated_vertices_modified = UpdateSeq();
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -270,7 +280,7 @@ convert_to(const qpGeomVertexFormat *new_format) const {
   PStatTimer timer(_convert_pcollector);
 
   PT(qpGeomVertexData) new_data = 
-    new qpGeomVertexData(new_format, get_usage_hint());
+    new qpGeomVertexData(get_name(), new_format, get_usage_hint());
   new_data->set_transform_blend_palette(get_transform_blend_palette());
 
   pset<int> done_arrays;
@@ -420,7 +430,7 @@ set_color(const Colorf &color, int num_components,
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexData::compute_vertices
+//     Function: qpGeomVertexData::animate_vertices
 //       Access: Published
 //  Description: Returns a GeomVertexData that represents the results
 //               of computing the vertex animation on the CPU for this
@@ -438,27 +448,27 @@ set_color(const Colorf &color, int num_components,
 //               graphics backend to update vertex buffers optimally).
 ////////////////////////////////////////////////////////////////////
 CPT(qpGeomVertexData) qpGeomVertexData::
-compute_vertices() const {
+animate_vertices() const {
   CDReader cdata(_cycler);
   if (cdata->_transform_blend_palette == (TransformBlendPalette *)NULL) {
     // No vertex animation.
     return this;
   }
 
-  if (cdata->_computed_vertices == (qpGeomVertexData *)NULL) {
+  if (cdata->_animated_vertices == (qpGeomVertexData *)NULL) {
     CDWriter cdataw(((qpGeomVertexData *)this)->_cycler, cdata);
-    ((qpGeomVertexData *)this)->make_computed_vertices(cdataw);
-    return cdataw->_computed_vertices;
+    ((qpGeomVertexData *)this)->make_animated_vertices(cdataw);
+    return cdataw->_animated_vertices;
   } else {
     UpdateSeq blend_modified = cdata->_transform_blend_palette->get_modified();
-    if (cdata->_computed_vertices_modified == blend_modified) {
+    if (cdata->_animated_vertices_modified == blend_modified) {
       // No changes.
-      return cdata->_computed_vertices;
+      return cdata->_animated_vertices;
     }
     CDWriter cdataw(((qpGeomVertexData *)this)->_cycler, cdata);
-    cdataw->_computed_vertices_modified = blend_modified;
-    ((qpGeomVertexData *)this)->update_computed_vertices(cdataw);
-    return cdataw->_computed_vertices;
+    cdataw->_animated_vertices_modified = blend_modified;
+    ((qpGeomVertexData *)this)->update_animated_vertices(cdataw);
+    return cdataw->_animated_vertices;
   }
 }
 
@@ -510,7 +520,8 @@ replace_data_type(const InternalName *name, int num_components,
   }
     
   PT(qpGeomVertexData) new_data = 
-    new qpGeomVertexData(qpGeomVertexFormat::register_format(new_format),
+    new qpGeomVertexData(get_name(),
+                         qpGeomVertexFormat::register_format(new_format),
                          usage_hint);
   if (keep_animation) {
     new_data->set_transform_blend_palette(get_transform_blend_palette());
@@ -567,6 +578,11 @@ output(ostream &out) const {
 void qpGeomVertexData::
 write(ostream &out, int indent_level) const {
   _format->write_with_data(out, indent_level, this);
+  if (get_transform_blend_palette() != (TransformBlendPalette *)NULL) {
+    indent(out, indent_level)
+      << "Transform blend palette:\n";
+    get_transform_blend_palette()->write(out, indent_level + 2);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -1155,63 +1171,80 @@ do_set_num_vertices(int n, qpGeomVertexData::CDWriter &cdata) {
 
   if (any_changed) {
     cdata->_modified = qpGeom::get_next_modified();
-    cdata->_computed_vertices.clear();
+    cdata->_animated_vertices.clear();
   }
 
   return any_changed;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexData::make_computed_vertices
+//     Function: qpGeomVertexData::make_animated_vertices
 //       Access: Private
 //  Description: Creates the GeomVertexData that represents the
 //               results of computing the vertex animation on the CPU.
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexData::
-make_computed_vertices(qpGeomVertexData::CDWriter &cdata) {
+make_animated_vertices(qpGeomVertexData::CDWriter &cdata) {
   // First, make a new format that doesn't have the transform_blend
   // array.
-  cdata->_computed_vertices = replace_data_type
+  cdata->_animated_vertices = replace_data_type
     (InternalName::get_transform_blend(), 0, qpGeomVertexDataType::NT_uint16,
      min(get_usage_hint(), qpGeomUsageHint::UH_dynamic), false);
 
   // Now fill it up with the appropriate data.
-  update_computed_vertices(cdata);
+  update_animated_vertices(cdata);
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: qpGeomVertexData::update_computed_vertices
+//     Function: qpGeomVertexData::update_animated_vertices
 //       Access: Private
 //  Description: Recomputes the results of computing the vertex
 //               animation on the CPU, and applies them to the
-//               existing computed_vertices object.
+//               existing animated_vertices object.
 ////////////////////////////////////////////////////////////////////
 void qpGeomVertexData::
-update_computed_vertices(qpGeomVertexData::CDWriter &cdata) {
+update_animated_vertices(qpGeomVertexData::CDWriter &cdata) {
   int num_vertices = get_num_vertices();
 
   if (gobj_cat.is_debug()) {
     gobj_cat.debug()
-      << "Computing " << num_vertices << " vertices.\n";
+      << "Animating " << num_vertices << " vertices for " << get_name()
+      << "\n";
   }
-  PStatTimer timer(_compute_vertices_pcollector);
+
+  PStatTimer timer(_this_animate_vertices_pcollector);
 
   CPT(TransformBlendPalette) palette = cdata->_transform_blend_palette;
   nassertv(palette != (TransformBlendPalette *)NULL);
-  PT(qpGeomVertexData) new_data = cdata->_computed_vertices;
+
+  // Recompute all the blends up front.
+  int num_blends = palette->get_num_blends();
+  int bi;
+  for (bi = 0; bi < num_blends; bi++) {
+    palette->get_blend(bi).update_blend();
+  }
+
+  PT(qpGeomVertexData) new_data = cdata->_animated_vertices;
 
   // Now go through and apply the scale, copying it to the new data.
   qpGeomVertexIterator from(this, InternalName::get_vertex());
   qpGeomVertexIterator blendi(this, InternalName::get_transform_blend());
   qpGeomVertexIterator to(new_data, InternalName::get_vertex());
 
-  for (int i = 0; i < num_vertices; i++) {
-    LPoint4f vertex = from.get_data4f();
-    int bi = blendi.get_data1i();
-    const TransformBlend &blend = palette->get_blend(bi);
-    blend.transform_point(vertex);
-
-    to.set_data4f(vertex);
+  if (from.get_data_type()->get_num_values() == 4) {
+    for (int i = 0; i < num_vertices; i++) {
+      LPoint4f vertex = from.get_data4f();
+      int bi = blendi.get_data1i();
+      palette->get_blend(bi).transform_point(vertex);
+      to.set_data4f(vertex);
+    }
+  } else {
+    for (int i = 0; i < num_vertices; i++) {
+      LPoint3f vertex = from.get_data3f();
+      int bi = blendi.get_data1i();
+      palette->get_blend(bi).transform_point(vertex);
+      to.set_data3f(vertex);
+    }
   }
 }
 

+ 14 - 7
panda/src/gobj/qpgeomVertexData.h

@@ -64,12 +64,16 @@ class EXPCL_PANDA qpGeomVertexData : public TypedWritableReferenceCount {
 private:
   qpGeomVertexData();
 PUBLISHED:
-  qpGeomVertexData(const qpGeomVertexFormat *format, 
+  qpGeomVertexData(const string &name,
+                   const qpGeomVertexFormat *format, 
                    qpGeomUsageHint::UsageHint usage_hint);
   qpGeomVertexData(const qpGeomVertexData &copy);
   void operator = (const qpGeomVertexData &copy);
   virtual ~qpGeomVertexData();
 
+  INLINE const string &get_name() const;
+  INLINE void set_name(const string &name);
+
   INLINE const qpGeomVertexFormat *get_format() const;
   INLINE qpGeomUsageHint::UsageHint get_usage_hint() const;
 
@@ -98,7 +102,7 @@ PUBLISHED:
     set_color(const Colorf &color, int num_components,
               qpGeomVertexDataType::NumericType numeric_type) const;
 
-  CPT(qpGeomVertexData) compute_vertices() const;
+  CPT(qpGeomVertexData) animate_vertices() const;
 
   PT(qpGeomVertexData) 
     replace_data_type(const InternalName *name, int num_components,
@@ -135,6 +139,7 @@ public:
   static void unpack_argb(int data[4], unsigned int packed_argb);
 
 private:
+  string _name;
   CPT(qpGeomVertexFormat) _format;
   qpGeomUsageHint::UsageHint _usage_hint;
 
@@ -152,8 +157,8 @@ private:
 
     Arrays _arrays;
     PT(TransformBlendPalette) _transform_blend_palette;
-    PT(qpGeomVertexData) _computed_vertices;
-    UpdateSeq _computed_vertices_modified;
+    PT(qpGeomVertexData) _animated_vertices;
+    UpdateSeq _animated_vertices_modified;
     UpdateSeq _modified;
   };
 
@@ -163,13 +168,15 @@ private:
 
 private:
   bool do_set_num_vertices(int n, CDWriter &cdata);
-  void make_computed_vertices(CDWriter &cdata);
-  void update_computed_vertices(CDWriter &cdata);
+  void make_animated_vertices(CDWriter &cdata);
+  void update_animated_vertices(CDWriter &cdata);
 
   static PStatCollector _convert_pcollector;
   static PStatCollector _scale_color_pcollector;
   static PStatCollector _set_color_pcollector;
-  static PStatCollector _compute_vertices_pcollector;
+  static PStatCollector _animate_vertices_pcollector;
+
+  PStatCollector _this_animate_vertices_pcollector;
 
 public:
   static void register_with_read_factory();

+ 43 - 14
panda/src/gobj/transformBlend.I

@@ -177,41 +177,70 @@ get_weight(int n) const {
   return _entries[n]._weight;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlend::update_blend
+//       Access: Published
+//  Description: Recomputes the internal representation of the blend
+//               value, if necessary.  You should call this before
+//               calling get_blend() or transform_point().
+////////////////////////////////////////////////////////////////////
+INLINE void TransformBlend::
+update_blend() const {
+  CDReader cdata(_cycler);
+  if (cdata->_global_modified != VertexTransform::get_global_modified()) {
+    CDWriter cdataw(((TransformBlend *)this)->_cycler, cdata);
+    ((TransformBlend *)this)->recompute_result(cdataw);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformBlend::get_blend
 //       Access: Published
 //  Description: Returns the current value of the blend, based on the
 //               current value of all of the nested transform objects
 //               and their associated weights.
+//
+//               You should call update_blend() to ensure that the
+//               cache is up-to-date before calling this.
 ////////////////////////////////////////////////////////////////////
 INLINE void TransformBlend::
 get_blend(LMatrix4f &result) const {
   CDReader cdata(_cycler);
-  if (cdata->_global_modified != VertexTransform::get_global_modified()) {
-    CDWriter cdataw(((TransformBlend *)this)->_cycler, cdata);
-    ((TransformBlend *)this)->recompute_result(cdataw);
-    result = cdataw->_result;
-  } else {
-    result = cdata->_result;
-  }
+  nassertv(cdata->_global_modified == VertexTransform::get_global_modified());
+  result = cdata->_result;
 }
 
 ////////////////////////////////////////////////////////////////////
 //     Function: TransformBlend::transform_point
 //       Access: Published
 //  Description: Transforms the indicated point by the blend matrix.
+//
+//               You should call update_blend() to ensure that the
+//               cache is up-to-date before calling this.
 ////////////////////////////////////////////////////////////////////
 INLINE void TransformBlend::
 transform_point(LPoint4f &point) const {
   if (!_entries.empty()) {
     CDReader cdata(_cycler);
-    if (cdata->_global_modified != VertexTransform::get_global_modified()) {
-      CDWriter cdataw(((TransformBlend *)this)->_cycler, cdata);
-      ((TransformBlend *)this)->recompute_result(cdataw);
-      point = point * cdataw->_result;
-    } else {
-      point = point * cdata->_result;
-    }
+    nassertv(cdata->_global_modified == VertexTransform::get_global_modified());
+    point = point * cdata->_result;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: TransformBlend::transform_point
+//       Access: Published
+//  Description: Transforms the indicated point by the blend matrix.
+//
+//               You should call update_blend() to ensure that the
+//               cache is up-to-date before calling this.
+////////////////////////////////////////////////////////////////////
+INLINE void TransformBlend::
+transform_point(LPoint3f &point) const {
+  if (!_entries.empty()) {
+    CDReader cdata(_cycler);
+    nassertv(cdata->_global_modified == VertexTransform::get_global_modified());
+    point = point * cdata->_result;
   }
 }
 

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

@@ -70,8 +70,11 @@ PUBLISHED:
   INLINE const VertexTransform *get_transform(int n) const;
   INLINE float get_weight(int n) const;
 
+  INLINE void update_blend() const;
+
   INLINE void get_blend(LMatrix4f &result) const;
   INLINE void transform_point(LPoint4f &point) const;
+  INLINE void transform_point(LPoint3f &point) const;
   INLINE UpdateSeq get_modified() const;
 
   void output(ostream &out) const;

+ 3 - 2
panda/src/gobj/transformBlendPalette.cxx

@@ -119,9 +119,10 @@ add_blend(const TransformBlend &blend) {
 //  Description: 
 ////////////////////////////////////////////////////////////////////
 void TransformBlendPalette::
-write(ostream &out) const {
+write(ostream &out, int indent_level) const {
   for (int i = 0; i < (int)_blends.size(); i++) {
-    out << i << ". " << _blends[i] << "\n";
+    indent(out, indent_level)
+      << i << ". " << _blends[i] << "\n";
   }
 }
 

+ 1 - 1
panda/src/gobj/transformBlendPalette.h

@@ -64,7 +64,7 @@ PUBLISHED:
   void remove_blend(int n);
   int add_blend(const TransformBlend &blend);
 
-  void write(ostream &out) const;
+  void write(ostream &out, int indent_level) const;
 
 private:
   void clear_index();

+ 1 - 1
panda/src/pgraph/cullableObject.cxx

@@ -38,7 +38,7 @@ munge_geom(const qpGeomMunger *munger) {
       _munger = munger;
       CPT(qpGeom) qpgeom = DCAST(qpGeom, _geom);
       qpgeom->munge_geom(munger, qpgeom, _munged_data);
-      _munged_data = _munged_data->compute_vertices();
+      _munged_data = _munged_data->animate_vertices();
       _geom = qpgeom;
     }
   }

+ 1 - 0
panda/src/pstatclient/pStatProperties.cxx

@@ -123,6 +123,7 @@ static TimeCollectorProperties time_properties[] = {
   { 0, "App:Show code:Nametags:3d:Contents", { 0.0, 0.5, 0.0 } },
   { 0, "App:Show code:Nametags:3d:Adjust",   { 0.5, 0.0, 0.5 } },
   { 1, "Cull",                             { 0.0, 1.0, 0.0 },  1.0 / 30.0 },
+  { 1, "Cull:Animate vertices",            { 1.0, 0.5, 0.3 },  1.0 / 30.0 },
   { 1, "Cull:Show fps",                    { 0.5, 0.8, 1.0 } },
   { 1, "Cull:Bins",                        { 0.3, 0.6, 0.3 } },
   { 1, "Cull:Munge",                       { 0.3, 0.3, 0.9 } },