Ver código fonte

begin animation support for xfile

David Rose 21 anos atrás
pai
commit
9d564e2470

+ 6 - 2
pandatool/src/xfile/Sources.pp

@@ -20,12 +20,16 @@
   #define COMBINED_SOURCES $[TARGET]_composite1.cxx     
     
   #define SOURCES \
-     config_xfile.h xFileFace.h xFileMaker.h xFileMaterial.h \
+     config_xfile.h \
+     xFileAnimationSet.h \
+     xFileFace.h xFileMaker.h xFileMaterial.h \
      xFileMesh.h xFileNormal.h xFileTemplates.h \
      xFileToEggConverter.h xFileVertex.h 
 
   #define INCLUDED_SOURCES \
-     config_xfile.cxx xFileFace.cxx xFileMaker.cxx xFileMaterial.cxx \
+     config_xfile.cxx \
+     xFileAnimationSet.cxx \
+     xFileFace.cxx xFileMaker.cxx xFileMaterial.cxx \
      xFileMesh.cxx xFileNormal.cxx xFileTemplates.cxx \
      xFileToEggConverter.cxx xFileVertex.cxx 
 

+ 167 - 0
pandatool/src/xfile/xFileAnimationSet.cxx

@@ -0,0 +1,167 @@
+// Filename: xFileAnimationSet.cxx
+// Created by:  drose (02Oct04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#include "xFileAnimationSet.h"
+#include "xFileToEggConverter.h"
+
+#include "eggGroup.h"
+#include "eggTable.h"
+#include "eggData.h"
+#include "eggXfmSAnim.h"
+#include "dcast.h"
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::Constructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+XFileAnimationSet::
+XFileAnimationSet() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::Destructor
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+XFileAnimationSet::
+~XFileAnimationSet() {
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::create_hierarchy
+//       Access: Public
+//  Description: Sets up the hierarchy of EggTables corresponding to
+//               this AnimationSet.
+////////////////////////////////////////////////////////////////////
+bool XFileAnimationSet::
+create_hierarchy(XFileToEggConverter *converter) {
+  // Egg animation tables start off with one Table entry, enclosing a
+  // Bundle entry.
+  EggTable *table = new EggTable(get_name());
+  converter->get_egg_data().add_child(table);
+  EggTable *bundle = new EggTable(converter->_char_name);
+  table->add_child(bundle);
+  bundle->set_table_type(EggTable::TT_bundle);
+
+  // Then the Bundle contains a "<skeleton>" entry, which begins the
+  // animation table hierarchy.
+  EggTable *skeleton = new EggTable("<skeleton>");
+  bundle->add_child(skeleton);
+
+  // Fill in the rest of the hierarchy with empty tables.
+  mirror_table(converter->get_dart_node(), skeleton);
+
+  // Now populate those empty tables with the frame data.
+  JointData::const_iterator ji;
+  for (ji = _joint_data.begin(); ji != _joint_data.end(); ++ji) {
+    const string &joint_name = (*ji).first;
+    const FrameData &table = (*ji).second;
+
+    EggXfmSAnim *anim_table = get_table(joint_name);
+    if (anim_table == (EggXfmSAnim *)NULL) {
+      xfile_cat.warning()
+        << "Frame " << joint_name << ", named by animation data, not defined.\n";
+    } else {
+      // If we have animation data, apply it.
+      FrameData::const_iterator fi;
+      for (fi = table.begin(); fi != table.end(); ++fi) {
+        anim_table->add_data(*fi);
+      }
+      anim_table->optimize();
+    }
+  }
+
+  // Put some data in the empty tables also.
+  Tables::iterator ti;
+  for (ti = _tables.begin(); ti != _tables.end(); ++ti) {
+    const string &joint_name = (*ti).first;
+    EggXfmSAnim *anim_table = (*ti).second._table;
+    EggGroup *joint = (*ti).second._joint;
+    if (anim_table->empty() && joint != (EggGroup *)NULL) {
+      // If there's no animation data, assign the rest transform.
+      anim_table->add_data(joint->get_transform());
+    }
+    anim_table->optimize();
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::get_table
+//       Access: Public
+//  Description: Returns the table associated with the indicated joint
+//               name.
+////////////////////////////////////////////////////////////////////
+EggXfmSAnim *XFileAnimationSet::
+get_table(const string &joint_name) const {
+  Tables::const_iterator ti;
+  ti = _tables.find(joint_name);
+  if (ti != _tables.end()) {
+    return (*ti).second._table;
+  }
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::create_frame_data
+//       Access: Public
+//  Description: Returns a reference to a new FrameData table
+//               corresponding to the indicated joint.
+////////////////////////////////////////////////////////////////////
+XFileAnimationSet::FrameData &XFileAnimationSet::
+create_frame_data(const string &joint_name) {
+  return _joint_data[joint_name];
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileAnimationSet::mirror_table
+//       Access: Private
+//  Description: Builds up a new set of EggTable nodes, as a
+//               mirror of the existing set of EggGroup (joint)
+//               nodes, and saves each new table in the _tables
+//               record.
+////////////////////////////////////////////////////////////////////
+void XFileAnimationSet::
+mirror_table(EggGroup *model_node, EggTable *anim_node) {
+  EggGroupNode::iterator gi;
+  for (gi = model_node->begin(); gi != model_node->end(); ++gi) {
+    EggNode *child = (*gi);
+    if (child->is_of_type(EggGroup::get_class_type())) {
+      EggGroup *group = DCAST(EggGroup, child);
+      if (group->get_group_type() == EggGroup::GT_joint) {
+        // When we come to a <Joint>, create a new Table for it.
+        EggTable *new_table = new EggTable(group->get_name());
+        anim_node->add_child(new_table);
+        EggXfmSAnim *xform = new EggXfmSAnim("xform");
+        new_table->add_child(xform);
+        TablePair &table_pair = _tables[group->get_name()];
+        table_pair._table = xform;
+        table_pair._joint = group;
+
+        // Now recurse.
+        mirror_table(group, new_table);
+
+      } else {
+        // If we come to an ordinary <Group>, skip past it.
+        mirror_table(group, anim_node);
+      }
+    }
+  }
+}

+ 69 - 0
pandatool/src/xfile/xFileAnimationSet.h

@@ -0,0 +1,69 @@
+// Filename: xFileAnimationSet.h
+// Created by:  drose (02Oct04)
+//
+////////////////////////////////////////////////////////////////////
+//
+// PANDA 3D SOFTWARE
+// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
+//
+// All use of this software is subject to the terms of the Panda 3d
+// Software license.  You should have received a copy of this license
+// along with this source code; you will also find a current copy of
+// the license at http://etc.cmu.edu/panda3d/docs/license/ .
+//
+// To contact the maintainers of this program write to
+// [email protected] .
+//
+////////////////////////////////////////////////////////////////////
+
+#ifndef XFILEANIMATIONSET_H
+#define XFILEANIMATIONSET_H
+
+#include "pandatoolbase.h"
+#include "pmap.h"
+#include "pvector.h"
+#include "luse.h"
+#include "namable.h"
+
+class XFileToEggConverter;
+class EggGroup;
+class EggTable;
+class EggXfmSAnim;
+
+////////////////////////////////////////////////////////////////////
+//       Class : XFileAnimationSet
+// Description : This represents a tree of EggTables, corresponding to
+//               Animation entries in the X file.  There is one
+//               EggTable for each joint in the character's joint
+//               set, and the whole tree is structured as a
+//               mirror of the joint set.
+////////////////////////////////////////////////////////////////////
+class XFileAnimationSet : public Namable {
+public:
+  XFileAnimationSet();
+  ~XFileAnimationSet();
+
+  bool create_hierarchy(XFileToEggConverter *converter);
+  EggXfmSAnim *get_table(const string &joint_name) const;
+
+  typedef pvector<LMatrix4d> FrameData;
+  FrameData &create_frame_data(const string &joint_name);
+
+private:
+  void mirror_table(EggGroup *model_node, EggTable *anim_node);
+
+  typedef pmap<string, FrameData> JointData;
+  JointData _joint_data;
+
+  class TablePair {
+  public:
+    EggGroup *_joint;
+    EggXfmSAnim *_table;
+  };
+
+  typedef pmap<string, TablePair> Tables;
+  Tables _tables;
+};
+
+#endif
+

+ 122 - 28
pandatool/src/xfile/xFileMesh.cxx

@@ -21,6 +21,7 @@
 #include "xFileVertex.h"
 #include "xFileNormal.h"
 #include "xFileMaterial.h"
+#include "config_xfile.h"
 
 #include "eggVertexPool.h"
 #include "eggVertex.h"
@@ -38,6 +39,7 @@ XFileMesh(CoordinateSystem cs) : _cs(cs) {
   _has_colors = false;
   _has_uvs = false;
   _has_materials = false;
+  _egg_parent = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -251,30 +253,41 @@ add_material(XFileMaterial *material) {
   return next_index;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileMesh::set_egg_parent
+//       Access: Public
+//  Description: Specifies the egg node that will eventually be the
+//               parent of this mesh, when create_polygons() is later
+//               called.
+////////////////////////////////////////////////////////////////////
+void XFileMesh::
+set_egg_parent(EggGroupNode *egg_parent) {
+  // We actually put the mesh under its own group.
+  EggGroup *egg_group = new EggGroup(get_name());
+  egg_parent->add_child(egg_group);
+
+  _egg_parent = egg_group;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileMesh::create_polygons
 //       Access: Public
 //  Description: Creates a slew of EggPolygons according to the faces
-//               in the mesh, and adds them to the indicated parent
-//               node.
+//               in the mesh, and adds them to the
+//               previously-indicated parent node.
 ////////////////////////////////////////////////////////////////////
 bool XFileMesh::
-create_polygons(EggGroupNode *egg_parent, XFileToEggConverter *converter) {
-  if (has_name()) {
-    // Put a named mesh within its own group.
-    EggGroup *egg_group = new EggGroup(get_name());
-    egg_parent->add_child(egg_group);
-    egg_parent = egg_group;
-  }
+create_polygons(XFileToEggConverter *converter) {
+  nassertr(_egg_parent != (EggGroupNode *)NULL, false);
 
   EggVertexPool *vpool = new EggVertexPool(get_name());
-  egg_parent->add_child(vpool);
+  _egg_parent->add_child(vpool);
   Faces::const_iterator fi;
   for (fi = _faces.begin(); fi != _faces.end(); ++fi) {
     XFileFace *face = (*fi);
 
     EggPolygon *egg_poly = new EggPolygon;
-    egg_parent->add_child(egg_poly);
+    _egg_parent->add_child(egg_poly);
 
     // Set up the vertices for the polygon.
     XFileFace::Vertices::reverse_iterator vi;
@@ -282,7 +295,8 @@ create_polygons(EggGroupNode *egg_parent, XFileToEggConverter *converter) {
       int vertex_index = (*vi)._vertex_index;
       int normal_index = (*vi)._normal_index;
       if (vertex_index < 0 || vertex_index >= (int)_vertices.size()) {
-        nout << "Vertex index out of range in Mesh.\n";
+        xfile_cat.error()
+          << "Vertex index out of range in Mesh.\n";
         return false;
       }
       XFileVertex *vertex = _vertices[vertex_index];
@@ -294,6 +308,7 @@ create_polygons(EggGroupNode *egg_parent, XFileToEggConverter *converter) {
 
       // Create a temporary EggVertex before adding it to the pool.
       EggVertex temp_vtx;
+      temp_vtx.set_external_index(vertex_index);
       temp_vtx.set_pos(LCAST(double, vertex->_point));
       if (vertex->_has_color) {
         temp_vtx.set_color(vertex->_color);
@@ -311,7 +326,7 @@ create_polygons(EggGroupNode *egg_parent, XFileToEggConverter *converter) {
 
       // Transform the vertex into the appropriate (global) coordinate
       // space.
-      temp_vtx.transform(egg_parent->get_node_to_vertex());
+      temp_vtx.transform(_egg_parent->get_node_to_vertex());
 
       // Now get a real EggVertex matching our template.
       EggVertex *egg_vtx = vpool->create_unique_vertex(temp_vtx);
@@ -326,11 +341,32 @@ create_polygons(EggGroupNode *egg_parent, XFileToEggConverter *converter) {
     }
   }
 
+  // Now go through all of the vertices and skin them up.
+  EggVertexPool::iterator vi;
+  for (vi = vpool->begin(); vi != vpool->end(); ++vi) {
+    EggVertex *egg_vtx = (*vi);
+    int vertex_index = egg_vtx->get_external_index();
+
+    SkinWeights::const_iterator swi;
+    for (swi = _skin_weights.begin(); swi != _skin_weights.end(); ++swi) {
+      const SkinWeightsData &data = (*swi);
+      WeightMap::const_iterator wmi = data._weight_map.find(vertex_index);
+      if (wmi != data._weight_map.end()) {
+        EggGroup *joint = converter->find_joint(data._joint_name,
+                                                data._matrix_offset);
+        if (joint != (EggGroup *)NULL) {
+          double weight = (*wmi).second;
+          joint->ref_vertex(egg_vtx, weight);
+        }
+      }
+    }
+  }
+    
   if (!has_normals()) {
     // If we don't have explicit normals, make some up, per the DX
     // spec.  Since the DX spec doesn't mention anything about a
     // crease angle, we should be as generous as possible.
-    egg_parent->recompute_vertex_normals(180.0, _cs);
+    _egg_parent->recompute_vertex_normals(180.0, _cs);
   }
 
   return true;
@@ -574,7 +610,8 @@ read_mesh_data(const Datagram &raw_data) {
   }
 
   if (di.get_remaining_size() != 0) {
-    nout << "Ignoring " << di.get_remaining_size() << " trailing Mesh.\n";
+    xfile_cat.warning()
+      << "Ignoring " << di.get_remaining_size() << " trailing Mesh.\n";
   }
 
   return true;
@@ -604,7 +641,8 @@ read_normal_data(const Datagram &raw_data) {
   int num_faces = di.get_int32();
 
   if (num_faces != _faces.size()) {
-    nout << "Incorrect number of faces in MeshNormals.\n";
+    xfile_cat.error()
+      << "Incorrect number of faces in MeshNormals.\n";
     return false;
   }
 
@@ -612,7 +650,8 @@ read_normal_data(const Datagram &raw_data) {
     XFileFace *face = _faces[i];
     int num_vertices = di.get_int32();
     if (num_vertices != face->_vertices.size()) {
-      nout << "Incorrect number of vertices for face in MeshNormals.\n";
+      xfile_cat.error() 
+        << "Incorrect number of vertices for face in MeshNormals.\n";
       return false;
     }
     for (int j = 0; j < num_vertices; j++) {
@@ -621,8 +660,9 @@ read_normal_data(const Datagram &raw_data) {
   }
 
   if (di.get_remaining_size() != 0) {
-    nout << "Ignoring " << di.get_remaining_size()
-         << " trailing MeshNormals.\n";
+    xfile_cat.warning()
+      << "Ignoring " << di.get_remaining_size()
+      << " trailing MeshNormals.\n";
   }
 
   return true;
@@ -643,7 +683,8 @@ read_color_data(const Datagram &raw_data) {
   for (i = 0; i < num_colors; i++) {
     unsigned int vertex_index = di.get_int32();
     if (vertex_index < 0 || vertex_index >= _vertices.size()) {
-      nout << "Vertex index out of range in MeshVertexColors.\n";
+      xfile_cat.error()
+        << "Vertex index out of range in MeshVertexColors.\n";
       return false;
     }
     XFileVertex *vertex = _vertices[vertex_index];
@@ -655,8 +696,9 @@ read_color_data(const Datagram &raw_data) {
   }
 
   if (di.get_remaining_size() != 0) {
-    nout << "Ignoring " << di.get_remaining_size()
-         << " trailing MeshVertexColors.\n";
+    xfile_cat.warning()
+      << "Ignoring " << di.get_remaining_size()
+      << " trailing MeshVertexColors.\n";
   }
 
   return true;
@@ -674,7 +716,8 @@ read_uv_data(const Datagram &raw_data) {
 
   int num_vertices = di.get_int32();
   if (num_vertices != _vertices.size()) {
-    nout << "Wrong number of vertices in MeshTextureCoords.\n";
+    xfile_cat.error()
+      << "Wrong number of vertices in MeshTextureCoords.\n";
     return false;
   }
 
@@ -687,13 +730,62 @@ read_uv_data(const Datagram &raw_data) {
   }
 
   if (di.get_remaining_size() != 0) {
-    nout << "Ignoring " << di.get_remaining_size()
-         << " trailing MeshTextureCoords.\n";
+    xfile_cat.warning()
+      << "Ignoring " << di.get_remaining_size()
+      << " trailing MeshTextureCoords.\n";
   }
 
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileMesh::read_skin_weights_data
+//       Access: Public
+//  Description: Fills the structure based on the raw data from the
+//               SkinWeights template.
+////////////////////////////////////////////////////////////////////
+bool XFileMesh::
+read_skin_weights_data(const Datagram &raw_data) {
+  DatagramIterator di(raw_data);
+
+  // Create a new SkinWeightsData record for the table.  We'll need
+  // this data later when we create the vertices.
+  _skin_weights.push_back(SkinWeightsData());
+  SkinWeightsData &data = _skin_weights.back();
+
+  // The DX system encodes a pointer to a character string in four
+  // bytes within the stream.  Weird, in a Microsofty sort of way.
+  data._joint_name = (const char *)di.get_uint32();
+
+  int num_weights = di.get_int32();
+
+  vector_int vindices;
+  vindices.reserve(num_weights);
+
+  // Unpack the list of vertices first
+  int i;
+  for (i = 0; i < num_weights; i++) {
+    int vindex = di.get_int32();
+    if (vindex < 0 || vindex > (int)_vertices.size()) {
+      xfile_cat.error()
+        << "Illegal vertex index " << vindex << " in SkinWeights.\n";
+      return false;
+    }
+    vindices.push_back(vindex);
+  }
+
+  // Then unpack the weight for each vertex.
+  for (i = 0; i < num_weights; i++) {
+    float weight = di.get_float32();
+    data._weight_map[vindices[i]] = weight;
+  }
+
+  // Finally, read the matrix offset.
+  data._matrix_offset.read_datagram(di);
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileMesh::read_material_list_data
 //       Access: Public
@@ -708,7 +800,8 @@ read_material_list_data(const Datagram &raw_data) {
   unsigned int num_faces = di.get_int32();
 
   if (num_faces > _faces.size()) {
-    nout << "Too many faces in MaterialList.\n";
+    xfile_cat.error()
+      << "Too many faces in MaterialList.\n";
     return false;
   }
 
@@ -730,8 +823,9 @@ read_material_list_data(const Datagram &raw_data) {
   }
 
   if (di.get_remaining_size() != 0) {
-    nout << "Ignoring " << di.get_remaining_size()
-         << " trailing MeshMaterialList.\n";
+    xfile_cat.warning()
+      << "Ignoring " << di.get_remaining_size()
+      << " trailing MeshMaterialList.\n";
   }
 
   return true;

+ 17 - 2
pandatool/src/xfile/xFileMesh.h

@@ -58,8 +58,9 @@ public:
   int add_normal(XFileNormal *normal);
   int add_material(XFileMaterial *material);
 
-  bool create_polygons(EggGroupNode *egg_parent, 
-                       XFileToEggConverter *converter);
+  void set_egg_parent(EggGroupNode *egg_parent);
+
+  bool create_polygons(XFileToEggConverter *converter);
 
   bool has_normals() const;
   bool has_colors() const;
@@ -79,6 +80,7 @@ public:
   bool read_normal_data(const Datagram &raw_data);
   bool read_color_data(const Datagram &raw_data);
   bool read_uv_data(const Datagram &raw_data);
+  bool read_skin_weights_data(const Datagram &raw_data);
   bool read_material_list_data(const Datagram &raw_data);
 
 private:
@@ -94,6 +96,17 @@ private:
   Materials _materials;
   Faces _faces;
 
+  typedef pmap<int, float> WeightMap;
+
+  class SkinWeightsData {
+  public:
+    string _joint_name;
+    WeightMap _weight_map;
+    LMatrix4f _matrix_offset;
+  };
+  typedef pvector<SkinWeightsData> SkinWeights;
+  SkinWeights _skin_weights;
+
   typedef pmap<XFileVertex *, int, IndirectCompareTo<XFileVertex> > UniqueVertices;
   typedef pmap<XFileNormal *, int, IndirectCompareTo<XFileNormal> > UniqueNormals;
   typedef pmap<XFileMaterial *, int, IndirectCompareTo<XFileMaterial> > UniqueMaterials;
@@ -105,6 +118,8 @@ private:
   bool _has_colors;
   bool _has_uvs;
   bool _has_materials;
+
+  EggGroupNode *_egg_parent;
 };
 
 #endif

+ 706 - 26
pandatool/src/xfile/xFileToEggConverter.cxx

@@ -20,15 +20,38 @@
 #include "xFileMesh.h"
 #include "xFileMaterial.h"
 #include "xFileTemplates.h"
+#include "xFileAnimationSet.h"
 #include "config_xfile.h"
 
 #include "eggData.h"
 #include "eggGroup.h"
+#include "eggXfmSAnim.h"
+#include "eggGroupUniquifier.h"
 #include "datagram.h"
 #include "eggMaterialCollection.h"
 #include "eggTextureCollection.h"
 #include "dcast.h"
 
+#define MY_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
+        EXTERN_C const GUID DECLSPEC_SELECTANY name \
+                = { l, w1, w2, { b1, b2,  b3,  b4,  b5,  b6,  b7,  b8 } }
+
+// These are defined in d3dx9mesh.h, which we may not have available
+// (so far, Panda is only dependent on dx8 API's).
+#ifndef DXFILEOBJ_XSkinMeshHeader
+// {3CF169CE-FF7C-44ab-93C0-F78F62D172E2}
+MY_DEFINE_GUID(DXFILEOBJ_XSkinMeshHeader,
+0x3cf169ce, 0xff7c, 0x44ab, 0x93, 0xc0, 0xf7, 0x8f, 0x62, 0xd1, 0x72, 0xe2);
+#endif
+
+#ifndef DXFILEOBJ_SkinWeights
+// {6F0D123B-BAD2-4167-A0D0-80224F25FABB}
+MY_DEFINE_GUID(DXFILEOBJ_SkinWeights, 
+0x6f0d123b, 0xbad2, 0x4167, 0xa0, 0xd0, 0x80, 0x22, 0x4f, 0x25, 0xfa, 0xbb);
+#endif
+
+
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileToEggConverter::Constructor
 //       Access: Public
@@ -36,8 +59,10 @@
 ////////////////////////////////////////////////////////////////////
 XFileToEggConverter::
 XFileToEggConverter() {
+  _make_char = false;
   _dx_file = NULL;
   _dx_file_enum = NULL;
+  _dart_node = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -47,10 +72,12 @@ XFileToEggConverter() {
 ////////////////////////////////////////////////////////////////////
 XFileToEggConverter::
 XFileToEggConverter(const XFileToEggConverter &copy) :
-  SomethingToEggConverter(copy)
+  SomethingToEggConverter(copy),
+  _make_char(copy._make_char)
 {
   _dx_file = NULL;
   _dx_file_enum = NULL;
+  _dart_node = NULL;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -138,7 +165,25 @@ convert_file(const Filename &filename) {
     _egg_data->set_coordinate_system(CS_yup_left);
   }
 
-  return get_toplevel();
+  if (!get_toplevel()) {
+    return false;
+  }
+
+  if (!create_polygons()) {
+    return false;
+  }
+
+  if (_make_char) {
+    // Now make sure that each joint has a unique name.
+    EggGroupUniquifier uniquifier;
+    uniquifier.uniquify(_dart_node);
+  }
+
+  if (!create_hierarchy()) {
+    return false;
+  }
+
+  return true;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -157,6 +202,37 @@ close() {
     _dx_file->Release();
     _dx_file = NULL;
   }
+
+  // Clean up all the other stuff.
+  Meshes::const_iterator mi;
+  for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) {
+    delete (*mi);
+  }
+  _meshes.clear();
+
+  for (mi = _toplevel_meshes.begin(); mi != _toplevel_meshes.end(); ++mi) {
+    delete (*mi);
+  }
+  _toplevel_meshes.clear();
+  
+  AnimationSets::const_iterator asi;
+  for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) {
+    delete (*asi);
+  }
+  _animation_sets.clear();
+
+  _joints.clear();
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::get_dart_node
+//       Access: Public
+//  Description: Returns the root of the joint hierarchy, if
+//               _make_char is true, or NULL otherwise.
+////////////////////////////////////////////////////////////////////
+EggGroup *XFileToEggConverter::
+get_dart_node() const {
+  return _dart_node;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -183,6 +259,98 @@ create_unique_material(const EggMaterial &copy) {
   return _materials.create_unique_material(copy, ~EggMaterial::E_mref_name);
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::find_joint (one parameter)
+//       Access: Public
+//  Description: This is called by set_animation_frame, for
+//               the purposes of building the frame data for the
+//               animation--it needs to know the original rest frame
+//               transform.
+////////////////////////////////////////////////////////////////////
+EggGroup *XFileToEggConverter::
+find_joint(const string &joint_name) {
+  Joints::iterator ji;
+  ji = _joints.find(joint_name);
+  if (ji != _joints.end()) {
+    JointDef &joint_def = (*ji).second;
+    if (joint_def._node == (EggGroup *)NULL) {
+      // An invalid joint detected earlier.
+      return NULL;
+    }
+
+    return joint_def._node;
+  }
+
+  // Joint name is unknown.  Issue a warning, then insert NULL into
+  // the table so we don't get the same warning again with the next
+  // polygon.
+  if (_make_char) {
+    xfile_cat.warning()
+      << "Joint name " << joint_name << " in animation data is undefined.\n";
+  }
+  _joints[joint_name]._node = NULL;
+
+  return NULL;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::find_joint (two parameters)
+//       Access: Public
+//  Description: This is called by XFileMesh::create_polygons(), for
+//               the purposes of applying skinning to vertices.  It
+//               searches for the joint matching the indicated name,
+//               and returns it, possibly creating a new joint if the
+//               requested matrix_offset demands it.  Returns NULL if
+//               the joint name is unknown.
+////////////////////////////////////////////////////////////////////
+EggGroup *XFileToEggConverter::
+find_joint(const string &joint_name, const LMatrix4f &matrix_offset) {
+  return find_joint(joint_name);
+  /*
+  Joints::iterator ji;
+  ji = _joints.find(joint_name);
+  if (ji != _joints.end()) {
+    JointDef &joint_def = (*ji).second;
+    if (joint_def._node == (EggGroup *)NULL) {
+      // An invalid joint detected earlier.
+      return NULL;
+    }
+
+    OffsetJoints::iterator oji = joint_def._offsets.find(matrix_offset);
+    if (oji != joint_def._offsets.end()) {
+      // We've previously created a joint for this matrix, so just
+      // reuse it.
+      return (*oji).second;
+    }
+
+    if (!joint_def._offsets.empty()) {
+      const LMatrix4f &mat = (*joint_def._offsets.begin()).first;
+    }
+
+    // We need to create a new joint for this matrix.
+    EggGroup *new_joint = new EggGroup("synth");
+    joint_def._node->add_child(new_joint);
+
+    new_joint->set_group_type(EggGroup::GT_joint);
+    new_joint->set_transform(LCAST(double, matrix_offset));
+    joint_def._offsets[matrix_offset] = new_joint;
+
+    return new_joint;
+  }
+
+  // Joint name is unknown.  Issue a warning, then insert NULL into
+  // the table so we don't get the same warning again with the next
+  // polygon.
+  if (_make_char) {
+    xfile_cat.warning()
+      << "Joint name " << joint_name << " in animation data is undefined.\n";
+  }
+  _joints[joint_name]._node = NULL;
+
+  return NULL;
+  */
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileToEggConverter::get_toplevel
 //       Access: Private
@@ -195,13 +363,22 @@ get_toplevel() {
   HRESULT hr;
   LPDIRECTXFILEDATA obj;
 
-  PT(EggGroup) egg_toplevel = new EggGroup;
-  bool any_frames = false;
+  EggGroupNode *egg_parent = _egg_data;
+  
+  // If we are converting an animatable model, make an extra node to
+  // represent the root of the hierarchy.
+  if (_make_char) {
+    _dart_node = new EggGroup(_char_name);
+    egg_parent->add_child(_dart_node);
+    _dart_node->set_dart_type(EggGroup::DT_default);
+    egg_parent = _dart_node;
+  }
+
+  _any_frames = false;
 
   hr = _dx_file_enum->GetNextDataObject(&obj);
   while (hr == DXFILE_OK) {
-    if (!convert_toplevel_object(obj, _egg_data,
-                                 egg_toplevel, any_frames)) {
+    if (!convert_toplevel_object(obj, egg_parent)) {
       return false;
     }
     hr = _dx_file_enum->GetNextDataObject(&obj);
@@ -213,13 +390,6 @@ get_toplevel() {
     return false;
   }
 
-  if (!any_frames) {
-    // If the file contained no frames at all, then all of the meshes
-    // that appeared at the toplevel were meant to be directly
-    // included.
-    _egg_data->steal_children(*egg_toplevel);
-  }
-
   return true;
 }
 
@@ -230,8 +400,7 @@ get_toplevel() {
 //               any Frames, to the appropriate egg structures.
 ////////////////////////////////////////////////////////////////////
 bool XFileToEggConverter::
-convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent,
-                        EggGroupNode *egg_toplevel, bool &any_frames) {
+convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
   HRESULT hr;
 
   // Determine what type of data object we have.
@@ -251,7 +420,7 @@ convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent,
     // referenced below.
 
   } else if (*type == TID_D3DRMFrame) {
-    any_frames = true;
+    _any_frames = true;
     if (!convert_frame(obj, egg_parent)) {
       return false;
     }
@@ -261,12 +430,17 @@ convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent,
       return false;
     }
 
+  } else if (*type == TID_D3DRMAnimationSet) {
+    if (!convert_animation_set(obj)) {
+      return false;
+    }
+
   } else if (*type == TID_D3DRMMesh) {
     // Assume a Mesh at the toplevel is just present to define a
     // reference that will be included below.  Convert it into the
-    // egg_toplevel group, where it will be ignored unless there are
+    // _toplevel_meshes set, where it will be ignored unless there are
     // no frames at all in the file.
-    if (!convert_mesh(obj, egg_toplevel)) {
+    if (!convert_mesh(obj, egg_parent, true)) {
       return false;
     }
 
@@ -352,7 +526,7 @@ convert_data_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
     }
 
   } else if (*type == TID_D3DRMMesh) {
-    if (!convert_mesh(obj, egg_parent)) {
+    if (!convert_mesh(obj, egg_parent, false)) {
       return false;
     }
 
@@ -381,6 +555,24 @@ convert_frame(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
   EggGroup *group = new EggGroup(name);
   egg_parent->add_child(group);
 
+  if (_make_char) {
+    group->set_group_type(EggGroup::GT_joint);
+    if (name.empty()) {
+      // Make up a name for this unnamed joint.
+      group->set_name("unnamed");
+
+    } else {
+      JointDef joint_def;
+      joint_def._node = group;
+      bool inserted = _joints.insert(Joints::value_type(name, joint_def)).second;
+      if (!inserted) {
+        xfile_cat.warning()
+          << "Nonunique Frame name " << name
+          << " encountered; animation will be ambiguous.\n";
+      }
+    }
+  }
+
   // Now walk through the children of the frame.
   LPDIRECTXFILEOBJECT child_obj;
 
@@ -434,6 +626,398 @@ convert_transform(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_set
+//       Access: Private
+//  Description: Begins an AnimationSet.  This is the root of one
+//               particular animation (table of frames per joint) to
+//               be applied to the model within this file.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_set(LPDIRECTXFILEDATA obj) {
+  HRESULT hr;
+
+  XFileAnimationSet *animation_set = new XFileAnimationSet();
+  animation_set->set_name(get_object_name(obj));
+
+  // Now walk through the children of the set; each one animates a
+  // different joint.
+  LPDIRECTXFILEOBJECT child_obj;
+
+  hr = obj->GetNextObject(&child_obj);
+  while (hr == DXFILE_OK) {
+    if (!convert_animation_set_object(child_obj, *animation_set)) {
+      return false;
+    }
+    hr = obj->GetNextObject(&child_obj);
+  }
+
+  if (hr != DXFILEERR_NOMOREOBJECTS) {
+    xfile_cat.error()
+      << "Error extracting children of AnimationSet " 
+      << get_object_name(obj) << ".\n";
+    delete animation_set;
+    return false;
+  }
+
+  _animation_sets.push_back(animation_set);
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_set_object
+//       Access: Private
+//  Description: Converts the indicated object, a child of a
+//               AnimationSet.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_set_object(LPDIRECTXFILEOBJECT obj, 
+                             XFileAnimationSet &animation_set) {
+  HRESULT hr;
+  LPDIRECTXFILEDATA data_obj;
+  LPDIRECTXFILEDATAREFERENCE ref_obj;
+
+  // See if the object is a data object.
+  hr = obj->QueryInterface(IID_IDirectXFileData, (void **)&data_obj);
+  if (hr == DD_OK) {
+    // It is.
+    return convert_animation_set_data_object(data_obj, animation_set);
+  }
+
+  // Or maybe it's a reference to a previous object.
+  hr = obj->QueryInterface(IID_IDirectXFileDataReference, (void **)&ref_obj);
+  if (hr == DD_OK) {
+    // It is.
+    if (ref_obj->Resolve(&data_obj) == DXFILE_OK) {
+      return convert_animation_set_data_object(data_obj, animation_set);
+    }
+  }
+
+  // It isn't.
+  if (xfile_cat.is_debug()) {
+    xfile_cat.debug()
+      << "Ignoring animation set object of unknown type: "
+      << get_object_name(obj) << "\n";
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_set_data_object
+//       Access: Private
+//  Description: Converts the indicated data object, a child of a
+//               AnimationSet.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_set_data_object(LPDIRECTXFILEDATA obj, XFileAnimationSet &animation_set) {
+  HRESULT hr;
+
+  // Determine what type of data object we have.
+  const GUID *type;
+  hr = obj->GetType(&type);
+  if (hr != DXFILE_OK) {
+    xfile_cat.error()
+      << "Unable to get type of template\n";
+    return false;
+  }
+
+  if (*type == TID_D3DRMAnimation) {
+    if (!convert_animation(obj, animation_set)) {
+      return false;
+    }
+  } else {
+    if (xfile_cat.is_debug()) {
+      xfile_cat.debug()
+        << "Ignoring animation set data object of unknown type: "
+        << get_object_name(obj) << "\n";
+    }
+  }
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation
+//       Access: Private
+//  Description: Converts the indicated Animation template object.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation(LPDIRECTXFILEDATA obj, XFileAnimationSet &animation_set) {
+  HRESULT hr;
+
+  // Within an Animation template, we expect to find a reference to a
+  // frame, possibly an AnimationOptions object, and one or more
+  // AnimationKey objects.
+  LPDIRECTXFILEOBJECT child_obj;
+
+  // First, walk through the list of children, to find the one that is
+  // the frame reference.  We need to know this up front so we know
+  // which table we should be building up.
+  string frame_name;
+  bool got_frame_name = false;
+
+  pvector<LPDIRECTXFILEOBJECT> children;
+
+  hr = obj->GetNextObject(&child_obj);
+  while (hr == DXFILE_OK) {
+    LPDIRECTXFILEDATAREFERENCE ref_obj;
+    if (child_obj->QueryInterface(IID_IDirectXFileDataReference, (void **)&ref_obj) == DD_OK) {
+      // Here's a reference!
+      LPDIRECTXFILEDATA data_obj;
+      if (ref_obj->Resolve(&data_obj) == DXFILE_OK) {
+        const GUID *type;
+        if (data_obj->GetType(&type) == DXFILE_OK) {
+          if (*type == TID_D3DRMFrame) {
+            // Ok, this one is a reference to a frame.  Save the name.
+            frame_name = get_object_name(data_obj);
+            got_frame_name = true;
+          }
+        }
+      }
+    } else {
+      children.push_back(child_obj);
+    }
+
+    hr = obj->GetNextObject(&child_obj);
+  }
+
+  if (hr != DXFILEERR_NOMOREOBJECTS) {
+    xfile_cat.error()
+      << "Error extracting children of Animation " 
+      << get_object_name(obj) << ".\n";
+    return false;
+  }
+
+  if (!got_frame_name) {
+    xfile_cat.error()
+      << "Animation " << get_object_name(obj)
+      << " includes no reference to a frame.\n";
+    return false;
+  }
+
+  FrameData &table = animation_set.create_frame_data(frame_name);
+
+  // Now go back again and get the actual data.
+  pvector<LPDIRECTXFILEOBJECT>::iterator ci;
+  for (ci = children.begin(); ci != children.end(); ++ci) {
+    if (!convert_animation_object((*ci), frame_name, table)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_object
+//       Access: Private
+//  Description: Converts the indicated object, a child of a
+//               Animation.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_object(LPDIRECTXFILEOBJECT obj, const string &joint_name,
+                         XFileToEggConverter::FrameData &table) {
+  HRESULT hr;
+  LPDIRECTXFILEDATA data_obj;
+  LPDIRECTXFILEDATAREFERENCE ref_obj;
+
+  // See if the object is a data object.
+  hr = obj->QueryInterface(IID_IDirectXFileData, (void **)&data_obj);
+  if (hr == DD_OK) {
+    // It is.
+    return convert_animation_data_object(data_obj, joint_name, table);
+  }
+
+  // Or maybe it's a reference to a previous object.
+  hr = obj->QueryInterface(IID_IDirectXFileDataReference, (void **)&ref_obj);
+  if (hr == DD_OK) {
+    // It is.
+    if (ref_obj->Resolve(&data_obj) == DXFILE_OK) {
+      return convert_animation_data_object(data_obj, joint_name, table);
+    }
+  }
+
+  // It isn't.
+  if (xfile_cat.is_debug()) {
+    xfile_cat.debug()
+      << "Ignoring animation set object of unknown type: "
+      << get_object_name(obj) << "\n";
+  }
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_data_object
+//       Access: Private
+//  Description: Converts the indicated data object, a child of a
+//               Animation.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_data_object(LPDIRECTXFILEDATA obj, const string &joint_name,
+                              XFileToEggConverter::FrameData &table) {
+  HRESULT hr;
+
+  // Determine what type of data object we have.
+  const GUID *type;
+  hr = obj->GetType(&type);
+  if (hr != DXFILE_OK) {
+    xfile_cat.error()
+      << "Unable to get type of template\n";
+    return false;
+  }
+
+  if (*type == TID_D3DRMAnimationOptions) {
+    // Quietly ignore AnimationOptions.
+
+  } else  if (*type == TID_D3DRMAnimationKey) {
+    if (!convert_animation_key(obj, joint_name, table)) {
+      return false;
+    }
+
+  } else {
+    if (xfile_cat.is_debug()) {
+      xfile_cat.debug()
+        << "Ignoring animation set data object of unknown type: "
+        << get_object_name(obj) << "\n";
+    }
+  }
+  
+  return true;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_animation_key
+//       Access: Private
+//  Description: Converts the indicated AnimationKey template object.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_animation_key(LPDIRECTXFILEDATA obj, const string &joint_name, 
+                      XFileToEggConverter::FrameData &table) {
+  Datagram raw_data;
+  if (!get_data(obj, raw_data)) {
+    return false;
+  }
+
+  DatagramIterator di(raw_data);
+  int key_type = di.get_uint32();
+  int nkeys = di.get_uint32();
+
+  int last_time = 0;
+
+  for (int i = 0; i < nkeys; i++) {
+    int time = di.get_uint32();
+
+    int nvalues = di.get_uint32();
+    pvector<float> values;
+    values.reserve(nvalues);
+    for (int j = 0; j < nvalues; j++) {
+      float value = di.get_float32();
+      values.push_back(value);
+    }
+
+    while (last_time <= time) {
+      if (!set_animation_frame(joint_name, table, last_time, key_type, 
+                               &values[0], nvalues)) {
+        return false;
+      }
+      last_time++;
+    }
+  }
+
+  return true;
+}
+    
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::set_animation_frame
+//       Access: Private
+//  Description: Sets a single frame of the animation data.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+set_animation_frame(const string &joint_name, 
+                    XFileToEggConverter::FrameData &table, int frame,
+                    int key_type, const float *values, int nvalues) {
+  LMatrix4d mat;
+
+  // Pad out the table by duplicating the last row as necessary.
+  if ((int)table.size() <= frame) {
+    if (table.empty()) {
+      // Get the initial transform from the joint's rest transform.
+      EggGroup *joint = find_joint(joint_name);
+      if (joint != (EggGroup *)NULL) {
+        mat = joint->get_transform();
+      } else {
+        mat = LMatrix4d::ident_mat();
+      }
+    } else {
+      // Get the initial transform from the last frame of animation.
+      mat = table.back();
+    }
+    table.push_back(mat);
+    while ((int)table.size() <= frame) {
+      table.push_back(mat);
+    }
+
+  } else {
+    mat = table.back();
+  }
+
+  // Now modify the last row in the table.
+  switch (key_type) {
+    /*
+  case 0:
+    // Key type 0: rotation
+    break;
+    */
+    
+    /*
+  case 1:
+    // Key type 1: scale
+    break;
+    */
+    
+  case 2:
+    // Key type 2: position
+    if (nvalues != 3) {
+      xfile_cat.error()
+        << "Incorrect number of values in animation table: "
+        << nvalues << " for position data.\n";
+      return false;
+    }
+    mat.set_row(3, LVecBase3d(values[0], values[1], values[2]));
+    break;
+
+    /*
+  case 3:
+    // Key type 3: ????
+    break;
+    */
+
+  case 4:
+    // Key type 4: full matrix
+    if (nvalues != 16) {
+      xfile_cat.error()
+        << "Incorrect number of values in animation table: "
+        << nvalues << " for matrix data.\n";
+      return false;
+    }
+    mat.set(values[0], values[1], values[2], values[3],
+            values[4], values[5], values[6], values[7],
+            values[8], values[9], values[10], values[11],
+            values[12], values[13], values[14], values[15]);
+    break;
+
+  default:
+    xfile_cat.error()
+      << "Unsupported key type " << key_type << " in animation table.\n";
+    return false;
+  }
+
+  table.back() = mat;
+  
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileToEggConverter::convert_mesh
 //       Access: Private
@@ -441,7 +1025,8 @@ convert_transform(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
 //               structures.
 ////////////////////////////////////////////////////////////////////
 bool XFileToEggConverter::
-convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
+convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent, 
+             bool is_toplevel) {
   HRESULT hr;
 
   Datagram raw_data;
@@ -449,9 +1034,12 @@ convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
     return false;
   }
 
-  XFileMesh mesh(_egg_data->get_coordinate_system());
-  mesh.set_name(get_object_name(obj));
-  if (!mesh.read_mesh_data(raw_data)) {
+  XFileMesh *mesh = new XFileMesh(_egg_data->get_coordinate_system());
+  mesh->set_name(get_object_name(obj));
+  mesh->set_egg_parent(egg_parent);
+
+  if (!mesh->read_mesh_data(raw_data)) {
+    delete mesh;
     return false;
   }
 
@@ -460,7 +1048,7 @@ convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
 
   hr = obj->GetNextObject(&child_obj);
   while (hr == DXFILE_OK) {
-    if (!convert_mesh_object(child_obj, mesh)) {
+    if (!convert_mesh_object(child_obj, *mesh)) {
       return false;
     }
     hr = obj->GetNextObject(&child_obj);
@@ -470,11 +1058,14 @@ convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent) {
     xfile_cat.error()
       << "Error extracting children of mesh " << get_object_name(obj)
       << ".\n";
+    delete mesh;
     return false;
   }
 
-  if (!mesh.create_polygons(egg_parent, this)) {
-    return false;
+  if (is_toplevel) {
+    _toplevel_meshes.push_back(mesh);
+  } else {
+    _meshes.push_back(mesh);
   }
 
   return true;
@@ -556,6 +1147,14 @@ convert_mesh_data_object(LPDIRECTXFILEDATA obj, XFileMesh &mesh) {
       return false;
     }
 
+  } else if (*type == DXFILEOBJ_XSkinMeshHeader) {
+    // Quietly ignore a skin mesh header.
+    
+  } else if (*type == DXFILEOBJ_SkinWeights) {
+    if (!convert_skin_weights(obj, mesh)) {
+      return false;
+    }
+
   } else {
     if (xfile_cat.is_debug()) {
       xfile_cat.debug()
@@ -627,6 +1226,26 @@ convert_mesh_uvs(LPDIRECTXFILEDATA obj, XFileMesh &mesh) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::convert_skin_weights
+//       Access: Private
+//  Description: Converts the indicated SkinWeights template
+//               object.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+convert_skin_weights(LPDIRECTXFILEDATA obj, XFileMesh &mesh) {
+  Datagram raw_data;
+  if (!get_data(obj, raw_data)) {
+    return false;
+  }
+
+  if (!mesh.read_skin_weights_data(raw_data)) {
+    return false;
+  }
+
+  return true;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileToEggConverter::convert_mesh_material_list
 //       Access: Private
@@ -874,6 +1493,67 @@ convert_texture(LPDIRECTXFILEDATA obj, XFileMaterial &material) {
   return true;
 }
 
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::create_polygons
+//       Access: Private
+//  Description: Creates all the polygons associated with
+//               previously-saved meshes.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+create_polygons() {
+  bool okflag = true;
+
+  Meshes::const_iterator mi;
+  for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) {
+    if (!(*mi)->create_polygons(this)) {
+      okflag = false;
+    }
+    delete (*mi);
+  }
+  _meshes.clear();
+
+  for (mi = _toplevel_meshes.begin(); mi != _toplevel_meshes.end(); ++mi) {
+    if (!_any_frames) {
+      if (!(*mi)->create_polygons(this)) {
+        okflag = false;
+      }
+    }
+    delete (*mi);
+  }
+  _toplevel_meshes.clear();
+
+  return okflag;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: XFileToEggConverter::create_hierarchy
+//       Access: Private
+//  Description: Creates the animation table hierarchies for the
+//               previously-saved animation sets.
+////////////////////////////////////////////////////////////////////
+bool XFileToEggConverter::
+create_hierarchy() {
+  bool okflag = true;
+
+  if (!_make_char && !_animation_sets.empty()) {
+    xfile_cat.warning()
+      << "Ignoring animation data without -a.\n";
+  }
+
+  AnimationSets::const_iterator asi;
+  for (asi = _animation_sets.begin(); asi != _animation_sets.end(); ++asi) {
+    if (_make_char) {
+      if (!(*asi)->create_hierarchy(this)) {
+        okflag = false;
+      }
+    }
+    delete (*asi);
+  }
+  _animation_sets.clear();
+
+  return okflag;
+}
+
 ////////////////////////////////////////////////////////////////////
 //     Function: XFileToEggConverter::get_object_name
 //       Access: Private

+ 68 - 4
pandatool/src/xfile/xFileToEggConverter.h

@@ -20,9 +20,13 @@
 #define XFILETOEGGCONVERTER_H
 
 #include "pandatoolbase.h"
+#include "xFileAnimationSet.h"
 #include "somethingToEggConverter.h"
 #include "eggTextureCollection.h"
 #include "eggMaterialCollection.h"
+#include "pvector.h"
+#include "pmap.h"
+#include "luse.h"
 
 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
@@ -31,10 +35,11 @@
 #include <rmxfguid.h>
 #undef WIN32_LEAN_AND_MEAN
 
-class EggGroupNode;
 class Datagram;
 class XFileMesh;
 class XFileMaterial;
+class EggGroup;
+class EggGroupNode;
 class EggTexture;
 class EggMaterial;
 
@@ -56,24 +61,53 @@ public:
   virtual bool convert_file(const Filename &filename);
   void close();
 
+  EggGroup *get_dart_node() const;
+
   EggTexture *create_unique_texture(const EggTexture &copy);
   EggMaterial *create_unique_material(const EggMaterial &copy);
+  EggGroup *find_joint(const string &joint_name);
+  EggGroup *find_joint(const string &joint_name,
+                       const LMatrix4f &matrix_offset);
+
+public:
+  bool _make_char;
+  string _char_name;
 
 private:
+  typedef XFileAnimationSet::FrameData FrameData;
+  
   bool get_toplevel();
-  bool convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent,
-                               EggGroupNode *egg_toplevel, bool &any_frames);
+  bool convert_toplevel_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent);
   bool convert_object(LPDIRECTXFILEOBJECT obj, EggGroupNode *egg_parent);
   bool convert_data_object(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent);
   bool convert_frame(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent);
   bool convert_transform(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent);
-  bool convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent);
+  bool convert_animation_set(LPDIRECTXFILEDATA obj);
+  bool convert_animation_set_object(LPDIRECTXFILEOBJECT obj, 
+                                    XFileAnimationSet &animation_set);
+  bool convert_animation_set_data_object(LPDIRECTXFILEDATA obj, 
+                                         XFileAnimationSet &animation_set);
+  bool convert_animation(LPDIRECTXFILEDATA obj, 
+                         XFileAnimationSet &animation_set);
+  bool convert_animation_object(LPDIRECTXFILEOBJECT obj, 
+                                const string &joint_name, FrameData &table);
+  bool convert_animation_data_object(LPDIRECTXFILEDATA obj, 
+                                     const string &joint_name, 
+                                     FrameData &table);
+  bool convert_animation_key(LPDIRECTXFILEDATA obj, const string &joint_name,
+                             FrameData &table);
+  bool set_animation_frame(const string &joint_name, FrameData &table, 
+                           int frame, int key_type,
+                           const float *values, int nvalues);
+  bool convert_mesh(LPDIRECTXFILEDATA obj, EggGroupNode *egg_parent,
+                    bool is_toplevel);
 
   bool convert_mesh_object(LPDIRECTXFILEOBJECT obj, XFileMesh &mesh);
   bool convert_mesh_data_object(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
   bool convert_mesh_normals(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
   bool convert_mesh_colors(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
   bool convert_mesh_uvs(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
+  bool convert_skin_weights(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
   bool convert_mesh_material_list(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
   bool convert_material_list_object(LPDIRECTXFILEOBJECT obj, XFileMesh &mesh);
   bool convert_material_list_data_object(LPDIRECTXFILEDATA obj, XFileMesh &mesh);
@@ -82,12 +116,42 @@ private:
   bool convert_material_data_object(LPDIRECTXFILEDATA obj, XFileMaterial &material);
   bool convert_texture(LPDIRECTXFILEDATA obj, XFileMaterial &material);
 
+  bool create_polygons();
+  bool create_hierarchy();
+
   string get_object_name(LPDIRECTXFILEOBJECT obj);
   bool get_data(LPDIRECTXFILEDATA obj, Datagram &raw_data);
 
   LPDIRECTXFILE _dx_file;
   LPDIRECTXFILEENUMOBJECT _dx_file_enum;
 
+  bool _any_frames;
+
+  typedef pvector<XFileMesh *> Meshes;
+  Meshes _meshes;
+  Meshes _toplevel_meshes;
+
+  typedef pvector<XFileAnimationSet *> AnimationSets;
+  AnimationSets _animation_sets;
+
+  typedef pmap<LMatrix4f, EggGroup *> OffsetJoints;
+
+  // A joint definition consists of the pointer to the EggGroup that
+  // represents the actual joint, plus a table of synthetic joints
+  // that were created for each animation set's offset matrix (we need
+  // to create a different joint to apply each unique offset matrix in
+  // an animation set).
+  class JointDef {
+  public:
+    EggGroup *_node;
+    OffsetJoints _offsets;
+  };
+    
+  typedef pmap<string, JointDef> Joints;
+  Joints _joints;
+
+  EggGroup *_dart_node;
+
   EggTextureCollection _textures;
   EggMaterialCollection _materials;
 };

+ 1 - 0
pandatool/src/xfile/xfile_composite1.cxx

@@ -1,5 +1,6 @@
 
 #include "config_xfile.cxx"
+#include "xFileAnimationSet.cxx"
 #include "xFileFace.cxx"
 #include "xFileMaker.cxx"
 #include "xFileMaterial.cxx"

+ 1 - 1
pandatool/src/xfileprogs/eggToX.cxx

@@ -33,7 +33,7 @@ EggToX() : EggToSomething("DirectX", ".x", true, false) {
     ("This program reads an Egg file and outputs an equivalent, "
      "or nearly equivalent, DirectX-style .x file.  Only simple "
      "hierarchy and polygon meshes are supported; advanced features "
-     "like LOD's, decals, and characters cannot be supported.");
+     "like LOD's, decals, and animation or skinning are not supported.");
 
   add_option
     ("m", "", 0,

+ 16 - 3
pandatool/src/xfileprogs/xFileToEgg.cxx

@@ -36,9 +36,19 @@ XFileToEgg() :
   add_transform_options();
 
   set_program_description
-    ("This program converts DirectX retained-mode (.x) files to egg.  This "
-     "is a simple converter that only supports basic polygons, materials, "
-     "and textures, in a hierarchy; animation is not supported at this time.");
+    ("This program converts DirectX retained-mode (.x) files to egg.  "
+     "Polygon meshes, materials, and textures, as well as skeleton "
+     "animation and skinning data, are supported.  All animations "
+     "found in the source .x file are written together into the same "
+     "egg file.");
+
+  add_option
+    ("a", "name", 0,
+     "Convert as an animatable model, converting Frames into Joints.  This "
+     "should be specified for a model which is intended to be animated.  The "
+     "default is to convert the model as a normal static model, which is "
+     "usually more optimal if animation is not required.",
+     &XFileToEgg::dispatch_string, &_make_char, &_char_name);
 
   redescribe_option
     ("cs",
@@ -60,6 +70,9 @@ run() {
   XFileToEggConverter converter;
   converter.set_egg_data(&_data, false);
 
+  converter._make_char = _make_char;
+  converter._char_name = _char_name;
+
   // Copy in the path and animation parameters.
   apply_parameters(converter);
 

+ 4 - 0
pandatool/src/xfileprogs/xFileToEgg.h

@@ -35,6 +35,10 @@ public:
   XFileToEgg();
 
   void run();
+
+public:
+  bool _make_char;
+  string _char_name;
 };
 
 #endif