Browse Source

Add animation support to dae2egg

rdb 10 years ago
parent
commit
9aafde0579

+ 270 - 42
pandatool/src/daeegg/daeCharacter.cxx

@@ -17,88 +17,316 @@
 #include "fcollada_utils.h"
 #include "pt_EggVertex.h"
 #include "eggXfmSAnim.h"
+#include "daeToEggConverter.h"
+#include "daeMaterials.h"
+
+#include "eggExternalReference.h"
 
 #include "FCDocument/FCDocument.h"
 #include "FCDocument/FCDController.h"
+#include "FCDocument/FCDGeometry.h"
 #include "FCDocument/FCDSceneNodeTools.h"
 
+#include "FCDocument/FCDSceneNode.h"
+#include "FCDocument/FCDTransform.h"
+#include "FCDocument/FCDAnimated.h"
+#include "FCDocument/FCDAnimationCurve.h"
+#include "FCDocument/FCDAnimationKey.h"
+
 TypeHandle DaeCharacter::_type_handle;
 
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeCharacter::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeCharacter::
-DaeCharacter(const string name, const FCDControllerInstance* controller_instance) {
-  _controller_instance = (FCDControllerInstance*) controller_instance;
-  _name = name;
-  _frame_rate = 0;
-  _skin_controller = NULL;
+DaeCharacter(EggGroup *node_group, const FCDControllerInstance *instance) :
+  _node_group(node_group),
+  _name(node_group->get_name()),
+  _instance(instance),
+  _skin_controller(NULL),
+  _skin_mesh(NULL) {
+
+  _bind_shape_mat = LMatrix4d::ident_mat();
+
   // If it's a skin controller, add the controller joints.
-  FCDController* controller = (FCDController*) controller_instance->GetEntity();
-  if (controller == NULL) return;
+  const FCDController *controller = (const FCDController *)instance->GetEntity();
+  if (controller == NULL) {
+    return;
+  }
+  _skin_mesh = controller->GetBaseGeometry()->GetMesh();
+
   if (controller->IsSkin()) {
     _skin_controller = controller->GetSkinController();
-    if (_skin_controller == NULL) return;
-    for (size_t j = 0; j < _skin_controller->GetJointCount(); ++j) {
-      _controller_joints[FROM_FSTRING(_skin_controller->GetJoint(j)->GetId())] = (FCDSkinControllerJoint*) _skin_controller->GetJoint(j);
+    _bind_shape_mat = DAEToEggConverter::convert_matrix(_skin_controller->GetBindShapeTransform());
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::bind_joints
+//       Access: Public
+//  Description: Binds the joints to the character.  This means
+//               changing them to the bind pose.  It is necessary
+//               to call this before process_skin_geometry.
+//
+//               Returns the root group.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+bind_joints(JointMap &joint_map) {
+  _joints.clear();
+
+  size_t num_joints = _skin_controller->GetJointCount();
+  _joints.reserve(num_joints);
+
+  // Record the bind pose for each joint.
+  for (size_t j = 0; j < num_joints; ++j) {
+    const FCDSkinControllerJoint *skin_joint = _skin_controller->GetJoint(j);
+    string sid = FROM_FSTRING(skin_joint->GetId());
+    LMatrix4d bind_pose;
+    bind_pose.invert_from(DAEToEggConverter::convert_matrix(
+                          skin_joint->GetBindPoseInverse()));
+
+    // Check that we already encountered this joint during traversal.
+    JointMap::iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      Joint &joint = ji->second;
+
+      if (joint._character != (DaeCharacter *)NULL) {
+        // In some cases, though, multiple controllers share the same joints.
+        // We can't support this without duplicating the joint structure,
+        // so we check if the bind poses are the same.
+        if (!joint._bind_pose.almost_equal(bind_pose, 0.0001)) {
+          // Ugh.  What else could we do?
+          daeegg_cat.error()
+            << "Multiple controllers share joint with sid " << sid
+            << ", with different bind poses.\n";
+        }
+      } else {
+        // Mark the joint as being controlled by this character.
+        joint._bind_pose = bind_pose;
+        joint._character = this;
+      }
+
+      _joints.push_back(joint);
+    } else {
+      daeegg_cat.warning()
+        << "Unknown joint sid being referenced: '" << sid << "'\n";
+
+      // We still have to add a dummy joint or the index will be off.
+      _joints.push_back(Joint(NULL, NULL));
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::adjust_joints
+//       Access: Public
+//  Description: Traverses through the character hierarchy in order
+//               to bind the mesh to the character.  This involves
+//               reorienting the joints to match the bind pose.
+//
+//               It is important that this is called only once.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+              const LMatrix4d &transform) {
+
+  LMatrix4d this_transform = transform;
+
+  if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
+
+    JointMap::const_iterator ji = joint_map.find(sid);
+    if (ji != joint_map.end()) {
+      const Joint &joint = ji->second;
+
+      // Panda needs the joints to be in bind pose.  Not fun!  We copy the joint
+      // transform to the default pose, though, so that Panda will restore the
+      // joint transformation after binding.
+
+      if (joint._character == this) {
+        LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+                        invert(transform);
+        //LMatrix4d bind_pose = joint._bind_pose * _bind_shape_mat *
+        //                joint._group->get_parent()->get_node_frame_inv();
+
+        this_transform = bind_pose * this_transform;
+        joint._group->set_default_pose(*joint._group);
+        joint._group->set_transform3d(bind_pose);
+
+        /*
+        PT(EggGroup) sphere = new EggGroup;
+        sphere->add_uniform_scale(0.1);
+        sphere->set_group_type(EggGroup::GT_instance);
+        sphere->add_child(new EggExternalReference("", "jack.egg"));
+        joint._group->add_child(sphere);
+        */
+      }
+    }
+  } else {
+    //this_transform = DAEToEggConverter::convert_matrix(node->ToMatrix());
+  }
+
+  // Loop through the children joints
+  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+    //if (node->GetChild(ch)->IsJoint()) {
+    adjust_joints(node->GetChild(ch), joint_map, this_transform);
+    //}
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::influence_vertex
+//       Access: Public
+//  Description: Adds the influences for the given vertex.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+influence_vertex(int index, EggVertex *vertex) {
+  const FCDSkinControllerVertex *influence = _skin_controller->GetVertexInfluence(index);
+
+  for (size_t pa = 0; pa < influence->GetPairCount(); ++pa) {
+    const FCDJointWeightPair* jwpair = influence->GetPair(pa);
+
+    if (jwpair->jointIndex >= 0 && jwpair->jointIndex < _joints.size()) {
+      EggGroup *joint = _joints[jwpair->jointIndex]._group.p();
+      if (joint != NULL) {
+        joint->ref_vertex(vertex, jwpair->weight);
+      }
+    } else {
+      daeegg_cat.error()
+        << "Invalid joint index: " << jwpair->jointIndex << "\n";
     }
   }
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::as_egg_bundle
+//     Function: DaeCharacter::collect_keys
 //       Access: Public
-//  Description: Returns the character as a <Bundle> element,
-//               suited for the animation table.
-////////////////////////////////////////////////////////////////////
-PT(EggTable) DaeCharacter::
-as_egg_bundle() {
-  PT(EggTable) bundle = new EggTable(_name);
-  bundle->set_table_type(EggTable::TT_bundle);
-  PT(EggTable) skeleton = new EggTable("<skeleton>");
-  skeleton->set_table_type(EggTable::TT_table);
-  bundle->add_child(skeleton);
-  // Loop through the joint hierarchy
+//  Description: Collects all animation keys of animations applied
+//               to this character.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+collect_keys(pset<float> &keys) {
 #if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = _controller_instance->FindSkeletonNodes();
+  FCDSceneNodeList roots = _instance->FindSkeletonNodes();
 #else
   FCDSceneNodeList roots;
-  _controller_instance->FindSkeletonNodes(roots);
+  _instance->FindSkeletonNodes(roots);
 #endif
+
   for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_joint(skeleton, *it);
+    r_collect_keys(*it, keys);
   }
-  return bundle;
 }
 
 ////////////////////////////////////////////////////////////////////
-//     Function: DaeCharacter::process_joint
+//     Function: DaeCharacter::r_collect_keys
+//       Access: Public
+//  Description: Collects all animation keys found for the given
+//               node tree.
+////////////////////////////////////////////////////////////////////
+void DaeCharacter::
+r_collect_keys(FCDSceneNode* node, pset<float> &keys) {
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+
+    if (animated != NULL) {
+      const FCDAnimationCurveListList &all_curves = animated->GetCurves();
+
+      for (size_t ci = 0; ci < all_curves.size(); ++ci) {
+        const FCDAnimationCurveTrackList &curves = all_curves[ci];
+        if (curves.empty()) {
+          continue;
+        }
+
+        size_t num_keys = curves.front()->GetKeyCount();
+        const FCDAnimationKey **curve_keys = curves.front()->GetKeys();
+
+        for (size_t c = 0; c < num_keys; ++c) {
+          keys.insert(curve_keys[c]->input);
+        }
+      }
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: DaeCharacter::build_table
 //       Access: Public
 //  Description: Processes a joint node and its transforms.
 ////////////////////////////////////////////////////////////////////
 void DaeCharacter::
-process_joint(PT(EggTable) parent, FCDSceneNode* node) {
+build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys) {
   nassertv(node != NULL);
+
+  if (!node->IsJoint()) {
+    for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
+      build_table(parent, node->GetChild(ch), keys);
+    }
+    return;
+  }
+
   string node_id = FROM_FSTRING(node->GetDaeId());
-  PT(EggTable) joint = new EggTable(node_id);
-  joint->set_table_type(EggTable::TT_table);
-  parent->add_child(joint);
+  PT(EggTable) table = new EggTable(node_id);
+  table->set_table_type(EggTable::TT_table);
+  parent->add_child(table);
+
   PT(EggXfmSAnim) xform = new EggXfmSAnim("xform");
-  joint->add_child(xform);
-  xform->set_fps(_frame_rate);
+  table->add_child(xform);
+
   // Generate the sampled animation and loop through the matrices
-  FCDSceneNodeTools::GenerateSampledAnimation(node);
-  FMMatrix44List matrices = FCDSceneNodeTools::GetSampledAnimationMatrices();
-  for (FMMatrix44List::const_iterator it = matrices.begin(); it != matrices.end(); ++it) {
-    LMatrix4d matr = DAEToEggConverter::convert_matrix(*it);
-    assert(xform->add_data(matr));
+  FCDAnimatedList animateds;
+
+  // Collect all the animation curves
+  for (size_t t = 0; t < node->GetTransformCount(); ++t) {
+    FCDTransform *transform = node->GetTransform(t);
+    FCDAnimated *animated = transform->GetAnimated();
+    if (animated != (FCDAnimated *)NULL) {
+      if (animated->HasCurve()) {
+        animateds.push_back(animated);
+      }
+    }
   }
+
+  // Sample the scene node transform
+  float last_key;
+  float timing_total = 0;
+  pset<float>::const_iterator ki;
+  for (ki = keys.begin(); ki != keys.end(); ++ki) {
+    for (FCDAnimatedList::iterator it = animateds.begin(); it != animateds.end(); ++it) {
+      // Sample each animated, which changes the transform values directly
+      (*it)->Evaluate(*ki);
+    }
+
+    if (ki != keys.begin()) {
+      timing_total += (*ki - last_key);
+    }
+    last_key = *ki;
+
+    // Retrieve the new transform matrix for the COLLADA scene node
+    FMMatrix44 fmat = node->ToMatrix();
+
+    // Work around issue in buggy exporters (like ColladaMax)
+    if (IS_NEARLY_ZERO(fmat[3][3])) {
+      fmat[3][3] = 1;
+    }
+
+    xform->add_data(DAEToEggConverter::convert_matrix(fmat));
+  }
+
+  // Quantize the FPS, otherwise Panda complains about FPS mismatches.
+  float fps = round(((keys.size() - 1) / timing_total) * 100) * 0.01f;
+  xform->set_fps(fps);
+
   // Loop through the children joints
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    if (node->GetChild(ch)->IsJoint()) {
-      process_joint(joint, node->GetChild(ch));
-    }
+    //if (node->GetChild(ch)->IsJoint()) {
+      build_table(table, node->GetChild(ch), keys);
+    //}
   }
 }

+ 43 - 12
pandatool/src/daeegg/daeCharacter.h

@@ -12,20 +12,22 @@
 //
 ////////////////////////////////////////////////////////////////////
 
+#ifndef DAECHARACTER_H
+#define DAECHARACTER_H
+
 #include "pandatoolbase.h"
 #include "typedReferenceCount.h"
 #include "typeHandle.h"
 #include "eggTable.h"
-#include "daeToEggConverter.h"
 
 #include "pre_fcollada_include.h"
 #include "FCollada.h"
 #include "FCDocument/FCDSceneNode.h"
 #include "FCDocument/FCDControllerInstance.h"
 #include "FCDocument/FCDSkinController.h"
+#include "FCDocument/FCDGeometryMesh.h"
 
-#ifndef DAECHARACTER_H
-#define DAECHARACTER_H
+class DAEToEggConverter;
 
 ////////////////////////////////////////////////////////////////////
 //       Class : DaeCharacter
@@ -33,17 +35,46 @@
 ////////////////////////////////////////////////////////////////////
 class DaeCharacter : public TypedReferenceCount {
 public:
-  DaeCharacter(const string name, const FCDControllerInstance* controller_instance);
-  PT(EggTable) as_egg_bundle();
-  void process_joint(PT(EggTable) parent, FCDSceneNode* node);
-  
+  DaeCharacter(EggGroup *node_group, const FCDControllerInstance* controller_instance);
+
+  struct Joint {
+    INLINE Joint(EggGroup *group, const FCDSceneNode *scene_node) :
+      _group(group),
+      _scene_node(scene_node),
+      _character(NULL),
+      _bind_pose(LMatrix4d::ident_mat()) {}
+
+    LMatrix4d _bind_pose;
+    const PT(EggGroup) _group;
+    const FCDSceneNode *_scene_node;
+    DaeCharacter *_character;
+  };
+  typedef pvector<Joint> Joints;
+  typedef pmap<string, Joint> JointMap;
+
+  void bind_joints(JointMap &joint_map);
+  void adjust_joints(FCDSceneNode *node, const JointMap &joint_map,
+                     const LMatrix4d &transform = LMatrix4d::ident_mat());
+
+  void influence_vertex(int index, EggVertex *vertex);
+
+  void collect_keys(pset<float> &keys);
+  void r_collect_keys(FCDSceneNode *node, pset<float> &keys);
+
+  void build_table(EggTable *parent, FCDSceneNode* node, const pset<float> &keys);
+
+public:
+  PT(EggGroup) _node_group;
+  const FCDGeometryMesh *_skin_mesh;
+  const FCDControllerInstance *_instance;
+  LMatrix4d _bind_shape_mat;
+
 private:
-  int _frame_rate;
   string _name;
-  FCDControllerInstance* _controller_instance;
-  FCDSkinController* _skin_controller;
-  pmap<string, FCDSkinControllerJoint*> _controller_joints;
-  
+  const FCDSkinController *_skin_controller;
+  Joints _joints;
+  JointMap _bound_joints;
+
 public:
   virtual TypeHandle get_type() const {
     return get_class_type();

+ 11 - 10
pandatool/src/daeegg/daeMaterials.cxx

@@ -35,7 +35,7 @@ TypeHandle DaeMaterials::_type_handle;
 ////////////////////////////////////////////////////////////////////
 //     Function: DaeMaterials::Constructor
 //       Access: Public
-//  Description: 
+//  Description:
 ////////////////////////////////////////////////////////////////////
 DaeMaterials::
 DaeMaterials(const FCDGeometryInstance* geometry_instance) {
@@ -58,7 +58,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
     return;
   }
   _materials[semantic] = new DaeMaterial();
-  
+
   // Load in the uvsets
   for (size_t vib = 0; vib < instance->GetVertexInputBindingCount(); ++vib) {
     const FCDMaterialInstanceBindVertexInput* mivib = instance->GetVertexInputBinding(vib);
@@ -74,7 +74,7 @@ void DaeMaterials::add_material_instance(const FCDMaterialInstance* instance) {
 #endif
     _materials[semantic]->_uvsets.push_back(bvi);
   }
-  
+
   // Handle the material stuff
   daeegg_cat.spam() << "Trying to process material with semantic " << semantic << endl;
   PT_EggMaterial egg_material = new EggMaterial(semantic);
@@ -204,12 +204,13 @@ process_extra(const string semantic, const FCDExtra* extra) {
   for (size_t et = 0; et < etype->GetTechniqueCount(); ++et) {
     const FCDENode* enode = ((const FCDENode*)(etype->GetTechnique(et)))->FindChildNode("double_sided");
     if (enode != NULL) {
-      if (trim(enode->GetContent()) == "1") {
+      string content = trim(enode->GetContent());
+      if (content == "1" || content == "true") {
         _materials[semantic]->_double_sided = true;
-      } else if (trim(enode->GetContent()) == "0") {
+      } else if (content == "0" || content == "false") {
         _materials[semantic]->_double_sided = false;
       } else {
-        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << enode->GetContent() << "' instead" << endl;
+        daeegg_cat.warning() << "Expected <double_sided> tag to be either 1 or 0, found '" << content << "' instead" << endl;
       }
     }
   }
@@ -391,7 +392,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
   blend->_color = LColor::zero();
   blend->_operand_a = EggGroup::BO_unspecified;
   blend->_operand_b = EggGroup::BO_unspecified;
-  
+
   // First fill in the color value.
   if (mode == FCDEffectStandard::A_ONE) {// || mode == FCDEffectStandard::A_ZERO) {
     double value = transparent[3] * transparency;
@@ -404,7 +405,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // Now figure out the operands.
   if (mode == FCDEffectStandard::RGB_ZERO) {// || mode == FCDEffectStandard::A_ZERO) {
     blend->_operand_a = EggGroup::BO_one_minus_constant_color;
@@ -417,7 +418,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
     blend->_enabled = false;
     return blend;
   }
-  
+
   // See if we can optimize out the color.
   if (blend->_operand_a == EggGroup::BO_constant_color) {
     if (blend->_color == LColor::zero()) {
@@ -447,7 +448,7 @@ convert_blend(FCDEffectStandard::TransparencyMode mode, const LColor &transparen
       blend->_operand_b = EggGroup::BO_zero;
     }
   }
-  
+
   // See if we can entirely disable the blend.
   if (blend->_operand_a == EggGroup::BO_one && blend->_operand_b == EggGroup::BO_zero) {
     blend->_enabled = false;

+ 396 - 221
pandatool/src/daeegg/daeToEggConverter.cxx

@@ -56,9 +56,10 @@
 ////////////////////////////////////////////////////////////////////
 DAEToEggConverter::
 DAEToEggConverter() {
+  _unit_name = "meter";
+  _unit_meters = 1.0;
   _document = NULL;
   _table = NULL;
-  _frame_rate = -1;
   _error_handler = NULL;
   _invert_transparency = false;
 }
@@ -131,18 +132,15 @@ convert_file(const Filename &filename) {
   // Reset stuff
   clear_error();
   _joints.clear();
-  _vertex_pools.clear();
-  _skeletons.clear();
-  _frame_rate = -1;
   if (_error_handler == NULL) {
     _error_handler = new FUErrorSimpleHandler;
   }
-  
+
   // The default coordinate system is Y-up
   if (_egg_data->get_coordinate_system() == CS_default) {
     _egg_data->set_coordinate_system(CS_yup_right);
   }
-  
+
   // Read the file
   FCollada::Initialize();
   _document = FCollada::LoadDocument(filename.to_os_specific().c_str());
@@ -155,201 +153,375 @@ convert_file(const Filename &filename) {
   if (_document->GetAsset() != NULL) {
     FCDocumentTools::StandardizeUpAxisAndLength(_document);
   }
-  
-  _table = new EggTable();
-  _table->set_table_type(EggTable::TT_table);
-  // Process the stuff
+
+  // Process the scene
   process_asset();
-  preprocess();
+  PT(EggGroup) scene_group;
+  string model_name = _character_name;
+
   FCDSceneNode* visual_scene = _document->GetVisualSceneInstance();
   if (visual_scene != NULL) {
-    // First check for an <extra> tag
-    const FCDExtra* extra = visual_scene->GetExtra();
-    //FIXME: eek this looks horrid
-    if (extra != NULL) {
-      const FCDEType* etype = extra->GetDefaultType();
-      if (etype != NULL) {
-        const FCDENode* enode = (const FCDENode*) etype->FindTechnique("MAX3D");
-        if (enode != NULL) {
-          enode = enode->FindChildNode("frame_rate");
-          if (enode != NULL && !string_to_int(enode->GetContent(), _frame_rate)) {
-            daeegg_cat.warning() << "Invalid integer in <frame_rate> tag: '" << enode->GetContent() << "'" << endl;
-    } } } }
-    // Now loop through the children
+    if (model_name.empty()) {
+      // By lack of anything better...
+      model_name = FROM_FSTRING(visual_scene->GetName());
+    }
+    scene_group = new EggGroup(model_name);
+    _egg_data->add_child(scene_group);
+
     for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
-      process_node(DCAST(EggGroupNode, _egg_data), visual_scene->GetChild(ch));
+      process_node(scene_group, visual_scene->GetChild(ch));
     }
+  } else {
+    daeegg_cat.warning()
+      << "No visual scene instance found in COLLADA document.\n";
   }
-  SAFE_DELETE(visual_scene);
-  
-  _egg_data->add_child(_table);
-  
+
+  // Now process the characters.  This depends on information from collected
+  // joints, which is why it's done in a second step.
+  if (get_animation_convert() != AC_none) {
+    Characters::iterator it;
+    DaeCharacter *character;
+    for (it = _characters.begin(); it != _characters.end(); ++it) {
+      character = *it;
+      if (get_animation_convert() != AC_chan) {
+        character->bind_joints(_joints);
+
+        const FCDGeometryMesh *mesh = character->_skin_mesh;
+
+        if (mesh != NULL) {
+          PT(DaeMaterials) materials = new DaeMaterials(character->_instance);
+          daeegg_cat.spam() << "Processing mesh for controller\n";
+          process_mesh(character->_node_group, mesh, materials, character);
+        }
+      }
+    }
+
+    // Put the joints in bind pose.
+    for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+      character->adjust_joints(visual_scene->GetChild(ch), _joints, LMatrix4d::ident_mat());
+    }
+
+    if (scene_group != NULL) {
+      // Mark the scene as character.
+      if (get_animation_convert() == AC_chan) {
+        _egg_data->remove_child(scene_group);
+      } else {
+        scene_group->set_dart_type(EggGroup::DT_default);
+      }
+    }
+
+    if (get_animation_convert() != AC_model) {
+      _table = new EggTable();
+      _table->set_table_type(EggTable::TT_table);
+      _egg_data->add_child(_table);
+
+      PT(EggTable) bundle = new EggTable(model_name);
+      bundle->set_table_type(EggTable::TT_bundle);
+      _table->add_child(bundle);
+
+      PT(EggTable) skeleton = new EggTable("<skeleton>");
+      skeleton->set_table_type(EggTable::TT_table);
+      bundle->add_child(skeleton);
+
+      pset<float> keys;
+
+      Characters::iterator it;
+      DaeCharacter *character;
+      for (it = _characters.begin(); it != _characters.end(); ++it) {
+        DaeCharacter *character = *it;
+
+        // Collect key frame timings.
+        if (get_animation_convert() == AC_both ||
+            get_animation_convert() == AC_chan) {
+          character->collect_keys(keys);
+        }
+      }
+
+      if (_frame_inc != 0.0) {
+        // A frame increment was given, this means that we have to sample the
+        // animation.
+        float start, end;
+        if (_end_frame != _start_frame) {
+          start = _start_frame;
+          end = _end_frame;
+        } else {
+          // No range was given.  Infer the frame range from the keys.
+          start = *keys.begin();
+          end = *keys.rbegin();
+        }
+        keys.clear();
+
+        for (float t = start; t <= end; t += _frame_inc) {
+          keys.insert(t);
+        }
+      } else {
+        // No sampling parameters given; not necessarily a failure, since the
+        // animation may already be sampled.  We use the key frames as animation
+        // frames.
+        if (_end_frame != 0.0) {
+          // An end frame was given, chop off all keys after that.
+          float end = _end_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > end && !IS_THRESHOLD_EQUAL(*ki, end, 0.001)) {
+              keys.erase(ki, keys.end());
+              break;
+            }
+          }
+        }
+        if (_start_frame != 0.0) {
+          // A start frame was given, chop off all keys before that.
+          float start = _start_frame;
+          pset<float>::iterator ki;
+          for (ki = keys.begin(); ki != keys.end(); ++ki) {
+            if (*ki > start && !IS_THRESHOLD_EQUAL(*ki, start, 0.001)) {
+              keys.erase(keys.begin(), ki);
+              break;
+            }
+          }
+        }
+
+        // Check that this does indeed look like a sampled animation; if not,
+        // issue an appropriate warning.
+        pset<float>::const_iterator ki = keys.begin();
+        if (ki != keys.end()) {
+          float last = *ki;
+          float diff = 0;
+
+          for (++ki; ki != keys.end(); ++ki) {
+            if (diff != 0 && !IS_THRESHOLD_EQUAL((*ki - last), diff, 0.001)) {
+              daeegg_cat.error()
+                << "This does not appear to be a sampled animation.\n"
+                << "Specify the -sf, -ef and -if options to indicate how the "
+                << "animations should be sampled.\n";
+              break;
+            }
+            diff = (*ki - last);
+            last = *ki;
+          }
+        }
+      }
+
+      // It doesn't really matter which character we grab for this as
+      // it'll iterate over the whole graph right now anyway.
+      for (size_t ch = 0; ch < visual_scene->GetChildrenCount(); ++ch) {
+        character->build_table(skeleton, visual_scene->GetChild(ch), keys);
+      }
+    }
+  }
+
   // Clean up and return
+  SAFE_DELETE(visual_scene);
   SAFE_DELETE(_document);
   FCollada::Release();
   return true;
 }
 
-void DAEToEggConverter::process_asset() {
-  if (_document->GetAsset() == NULL) return;
+////////////////////////////////////////////////////////////////////
+//     Function: DAEToEggConverter::get_input_units
+//       Access: Public, Virtual
+//  Description: This may be called after convert_file() has been
+//               called and returned true, indicating a successful
+//               conversion.  It will return the distance units
+//               represented by the converted egg file, if known, or
+//               DU_invalid if not known.
+////////////////////////////////////////////////////////////////////
+DistanceUnit DAEToEggConverter::
+get_input_units() {
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.001)) {
+    return DU_millimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.01)) {
+    return DU_centimeters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1.0)) {
+    return DU_meters;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1000.0)) {
+    return DU_kilometers;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 3.0 * 12.0 * 0.0254)) {
+    return DU_yards;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 12.0 * 0.0254)) {
+    return DU_feet;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 0.0254)) {
+    return DU_inches;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 1852.0)) {
+    return DU_nautical_miles;
+  }
+  if (IS_NEARLY_EQUAL(_unit_meters, 5280.0 * 12.0 * 0.0254)) {
+    return DU_statute_miles;
+  }
+
+  // Whatever.
+  return DU_invalid;
+}
+
+void DAEToEggConverter::
+process_asset() {
+  const FCDAsset *asset = _document->GetAsset();
+  if (_document->GetAsset() == NULL) {
+    return;
+  }
+
+  _unit_name = FROM_FSTRING(asset->GetUnitName());
+  _unit_meters = asset->GetUnitConversionFactor();
+
   // Read out the coordinate system
-  FMVector3 up_axis (_document->GetAsset()->GetUpAxis());
+  FMVector3 up_axis = asset->GetUpAxis();
+
   if (up_axis == FMVector3(0, 1, 0)) {
     _egg_data->set_coordinate_system(CS_yup_right);
+
   } else if (up_axis == FMVector3(0, 0, 1)) {
     _egg_data->set_coordinate_system(CS_zup_right);
+
   } else {
     _egg_data->set_coordinate_system(CS_invalid);
     daeegg_cat.warning() << "Unrecognized coordinate system!\n";
   }
 }
 
-// This function lists all the joints and referenced skeletons
-void DAEToEggConverter::preprocess(const FCDSceneNode* node) {
-  // If the node is NULL, take the visual scene instance.
-  if (node == NULL) {
-    assert(_document != NULL);
-    _skeletons.clear();
-    _joints.clear();
-    node = _document->GetVisualSceneInstance();
-  }
-  if (node == NULL) return;
-  if (node->IsJoint()) {
-    _joints[FROM_FSTRING(node->GetDaeId())] = NULL;
-  }
-  // Loop through the instances first.
-  for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetType() == FCDEntityInstance::CONTROLLER) {
-      // Loop through the skeleton roots now.
-#if FCOLLADA_VERSION < 0x00030005
-      FCDSceneNodeList roots = ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes();
-#else
-      FCDSceneNodeList roots;
-      ((FCDControllerInstance*) node->GetInstance(in))->FindSkeletonNodes(roots);
-#endif
-      for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-        daeegg_cat.spam() << "Found referenced skeleton root " << FROM_FSTRING((*it)->GetDaeId()) << endl;
-        _skeletons.push_back(FROM_FSTRING((*it)->GetDaeId()));
-      }
-    }
-  }
-  // Now loop through the children and recurse.
-  for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
-    preprocess(node->GetChild(ch));
-  }
-}
-
 // Process the node. If forced is true, it will even process it if its known to be a skeleton root.
-void DAEToEggConverter::process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced) {
+void DAEToEggConverter::
+process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) {
   nassertv(node != NULL);
   string node_id = FROM_FSTRING(node->GetDaeId());
   daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl;
-  // Important! If it's known to be a skeleton root, ignore it for now, unless we're processing forced.
-  if (!forced && count(_skeletons.begin(), _skeletons.end(), node_id) > 0) {
-    daeegg_cat.spam() << "Ignoring skeleton root node with ID '" << node_id << "', we'll process it later" << endl;
-    return;
-  }
+
   // Create an egg group for this node
-  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetName()));
+  PT(EggGroup) node_group = new EggGroup(FROM_FSTRING(node->GetDaeId()));
   process_extra(node_group, node->GetExtra());
   parent->add_child(node_group);
+
   // Check if its a joint
   if (node->IsJoint()) {
+    string sid = FROM_FSTRING(node->GetSubId());
     node_group->set_group_type(EggGroup::GT_joint);
-    _joints[node_id] = node_group;
+
+    if (!_joints.insert(DaeCharacter::JointMap::value_type(sid,
+                        DaeCharacter::Joint(node_group, node))).second) {
+      daeegg_cat.error()
+        << "Joint with sid " << sid << " occurs more than once!\n";
+    }
   }
-  // Loop through the transforms and apply them
-  for (size_t tr = 0; tr < node->GetTransformCount(); ++tr) {
-    apply_transform(node_group, node->GetTransform(tr));
+
+  // Loop through the transforms and apply them (in reverse order)
+  for (size_t tr = node->GetTransformCount(); tr > 0; --tr) {
+    apply_transform(node_group, node->GetTransform(tr - 1));
   }
+  //node_group->set_transform3d(convert_matrix(node->ToMatrix()));
+
   // Loop through the instances and process them
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
     process_instance(node_group, node->GetInstance(in));
   }
+
   // Loop through the children and recursively process them
   for (size_t ch = 0; ch < node->GetChildrenCount(); ++ch) {
     process_node(DCAST(EggGroupNode, node_group), node->GetChild(ch));
   }
+
   // Loop through any possible scene node instances and process those, too.
   for (size_t in = 0; in < node->GetInstanceCount(); ++in) {
-    if (node->GetInstance(in)->GetEntity() && node->GetInstance(in)->GetEntity()->GetType() == FCDEntity::SCENE_NODE) {
-      process_node(DCAST(EggGroupNode, node_group), (const FCDSceneNode*) node->GetInstance(in)->GetEntity());
+    const FCDEntity *entity = node->GetInstance(in)->GetEntity();
+    if (entity && entity->GetType() == FCDEntity::SCENE_NODE) {
+      process_node(node_group, (const FCDSceneNode*) entity);
     }
   }
 }
 
-void DAEToEggConverter::process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance) {
+void DAEToEggConverter::
+process_instance(EggGroup *parent, const FCDEntityInstance* instance) {
   nassertv(instance != NULL);
   nassertv(instance->GetEntity() != NULL);
   // Check what kind of instance this is
   switch (instance->GetType()) {
-    case FCDEntityInstance::GEOMETRY: {
-      const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
-      assert(geometry != NULL);
-      if (geometry->IsMesh()) {
-        // Now, handle the mesh.
-        process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
-      }
-      if (geometry->IsSpline()) {
-        process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+  case FCDEntityInstance::GEOMETRY:
+    {
+      if (get_animation_convert() != AC_chan) {
+        const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
+        assert(geometry != NULL);
+        if (geometry->IsMesh()) {
+          // Now, handle the mesh.
+          process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+        }
+        if (geometry->IsSpline()) {
+          process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
+        }
       }
-      break; }
-    case FCDEntityInstance::CONTROLLER: {
-      // Add the dart tag and process the controller instance
-      parent->set_dart_type(EggGroup::DT_default);
-      process_controller(parent, (const FCDControllerInstance*) instance);
-      break; }
-    case FCDEntityInstance::MATERIAL:
-      // We don't process this directly, handled per-geometry instead.
-      break;
-    case FCDEntityInstance::SIMPLE: {
-      // Grab the entity and check it's type.
+    }
+    break;
+
+  case FCDEntityInstance::CONTROLLER:
+    // Add the dart tag and process the controller instance
+    //parent->set_dart_type(EggGroup::DT_default);
+    process_controller(parent, (const FCDControllerInstance*) instance);
+    break;
+
+  case FCDEntityInstance::MATERIAL:
+    // We don't process this directly, handled per-geometry instead.
+    break;
+
+  case FCDEntityInstance::SIMPLE:
+    {
+      // Grab the entity and check its type.
       const FCDEntity* entity = instance->GetEntity();
       if (entity->GetType() != FCDEntity::SCENE_NODE) {
         daeegg_cat.warning() << "Unsupported entity type found" << endl;
       }
-      break; }
-    default:
-      daeegg_cat.warning() << "Unsupported instance type found" << endl;
+    }
+    break;
+
+  default:
+    daeegg_cat.warning() << "Unsupported instance type found" << endl;
   }
 }
 
 // Processes the given mesh.
-void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials) {
+void DAEToEggConverter::
+process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+             DaeMaterials *materials, DaeCharacter *character) {
+
   nassertv(mesh != NULL);
   daeegg_cat.debug() << "Processing mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << endl;
-  
+
   // Create the egg stuff to hold this mesh
   PT(EggGroup) mesh_group = new EggGroup(FROM_FSTRING(mesh->GetDaeId()));
   parent->add_child(mesh_group);
   PT(EggVertexPool) mesh_pool = new EggVertexPool(FROM_FSTRING(mesh->GetDaeId()));
   mesh_group->add_child(mesh_pool);
-  _vertex_pools[FROM_FSTRING(mesh->GetDaeId())] = mesh_pool;
-  
+
   // First retrieve the vertex source
   if (mesh->GetSourceCount() == 0) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl;
     return;
   }
-  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);  
+  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);
   if (vsource == NULL) {
     daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no source for POSITION data" << endl;
     return;
   }
-  
+
   // Loop through the polygon groups and add them
   daeegg_cat.spam() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has " << mesh->GetPolygonsCount() << " polygon groups" << endl;
   if (mesh->GetPolygonsCount() == 0) return;
-  
+
   // This is an array of pointers, I know. But since they are refcounted, I don't have a better idea.
   PT(EggGroup) *primitive_holders = new PT(EggGroup) [mesh->GetPolygonsCount()];
   for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
     const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
+    string material_semantic = FROM_FSTRING(polygons->GetMaterialSemantic());
+
     // Stores which group holds the primitives.
     PT(EggGroup) primitiveholder;
     // If we have materials, make a group for each material. Then, apply the material's per-group stuff.
     if (materials != NULL && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) {
-      primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + FROM_FSTRING(polygons->GetMaterialSemantic()));
+      //primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." + material_semantic);
+      primitiveholder = new EggGroup;
       mesh_group->add_child(primitiveholder);
     } else {
       primitiveholder = mesh_group;
@@ -357,7 +529,7 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     primitive_holders[gr] = primitiveholder;
     // Apply the per-group data of the materials, if we have it.
     if (materials != NULL) {
-      materials->apply_to_group(FROM_FSTRING(polygons->GetMaterialSemantic()), primitiveholder, _invert_transparency);
+      materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency);
     }
     // Find the position sources
     const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION);
@@ -389,26 +561,47 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
     const uint32* tindices;
     if (tinput != NULL) tindices = tinput->GetIndices();
     // Get a name for potential coordinate sets
-    string tcsetname ("");
+    string tcsetname;
     if (materials != NULL && tcinput != NULL) {
-      daeegg_cat.debug() << "Assigning texcoord set " << tcinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tcsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texcoord set " << tcinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tcsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXCOORD, tcinput->GetSet());
     }
-    string tbsetname ("");
+    string tbsetname;
     if (materials != NULL && binput != NULL) {
-      daeegg_cat.debug() << "Assigning texbinormal set " << binput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      tbsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning texbinormal set " << binput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+      }
+      tbsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXBINORMAL, binput->GetSet());
     }
-    string ttsetname ("");
+    string ttsetname;
     if (materials != NULL && tinput != NULL) {
-      daeegg_cat.debug() << "Assigning textangent set " << tinput->GetSet() << " to semantic '" << FROM_FSTRING(polygons->GetMaterialSemantic()) << "'\n";
-      ttsetname = materials->get_uvset_name(FROM_FSTRING(polygons->GetMaterialSemantic()), FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
+      if (daeegg_cat.is_debug()) {
+        daeegg_cat.debug()
+          << "Assigning textangent set " << tinput->GetSet()
+          << " to semantic '" << material_semantic << "'\n";
+        }
+      ttsetname = materials->get_uvset_name(material_semantic,
+                    FUDaeGeometryInput::TEXTANGENT, tinput->GetSet());
     }
     // Loop through the indices and add the vertices.
     for (size_t ix = 0; ix < pinput->GetIndexCount(); ++ix) {
       PT_EggVertex vertex = mesh_pool->make_new_vertex();
       const float* data = &vsource->GetData()[indices[ix]*3];
       vertex->set_pos(LPoint3d(data[0], data[1], data[2]));
+
+      if (character != NULL) {
+        // If this is skinned geometry, add the vertex influences.
+        character->influence_vertex(indices[ix], vertex);
+      }
+
       // Process the normal
       if (nsource != NULL && ninput != NULL) {
         assert(nsource->GetStride() == 3);
@@ -469,26 +662,26 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
       PT(EggPrimitive) primitive = NULL;
       // Create a primitive that matches the fcollada type
       switch (polygons->GetPrimitiveType()) {
-        case FCDGeometryPolygons::LINES:
-          primitive = new EggLine();
-          break;
-        case FCDGeometryPolygons::POLYGONS:
-          primitive = new EggPolygon();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_FANS:
-          primitive = new EggTriangleFan();
-          break;
-        case FCDGeometryPolygons::TRIANGLE_STRIPS:
-          primitive = new EggTriangleStrip();
-          break;
-        case FCDGeometryPolygons::POINTS:
-          primitive = new EggPoint();
-          break;
-        case FCDGeometryPolygons::LINE_STRIPS:
-          daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
-          break;
-        default:
-          daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
+      case FCDGeometryPolygons::LINES:
+        primitive = new EggLine();
+        break;
+      case FCDGeometryPolygons::POLYGONS:
+        primitive = new EggPolygon();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_FANS:
+        primitive = new EggTriangleFan();
+        break;
+      case FCDGeometryPolygons::TRIANGLE_STRIPS:
+        primitive = new EggTriangleStrip();
+        break;
+      case FCDGeometryPolygons::POINTS:
+        primitive = new EggPoint();
+        break;
+      case FCDGeometryPolygons::LINE_STRIPS:
+        daeegg_cat.warning() << "Linestrips not yet supported!" << endl;
+        break;
+      default:
+        daeegg_cat.warning() << "Unsupported primitive type found!" << endl;
       }
       if (primitive != NULL) {
         primitive_holders[gr]->add_child(primitive);
@@ -506,7 +699,8 @@ void DAEToEggConverter::process_mesh(PT(EggGroup) parent, const FCDGeometryMesh*
   delete[] primitive_holders;
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) {
   assert(geometry_spline != NULL);
   PT(EggGroup) result = new EggGroup(group_name);
   parent->add_child(result);
@@ -521,7 +715,8 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const string group_n
   }
 }
 
-void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spline) {
+void DAEToEggConverter::
+process_spline(EggGroup *parent, const FCDSpline* spline) {
   assert(spline != NULL);
   nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS);
   // Now load in the nurbs curve to the egg library
@@ -542,70 +737,26 @@ void DAEToEggConverter::process_spline(PT(EggGroup) parent, const FCDSpline* spl
   }
 }
 
-void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance) {
+void DAEToEggConverter::
+process_controller(EggGroup *parent, const FCDControllerInstance *instance) {
   assert(instance != NULL);
-  const FCDController* controller = (const FCDController*) instance->GetEntity();
+  const FCDController* controller = (const FCDController *)instance->GetEntity();
   assert(controller != NULL);
-  PT(EggVertexPool) vertex_pool = NULL;
-  // Add the skin geometry
-  const FCDGeometry* geometry = controller->GetBaseGeometry();
-  if (geometry != NULL) {
-    if (geometry->IsMesh()) {
-      process_mesh(parent, geometry->GetMesh(), new DaeMaterials((const FCDGeometryInstance*) instance));
+
+  if (get_animation_convert() == AC_none) {
+    // If we're exporting a static mesh, export the base geometry as-is.
+    const FCDGeometryMesh *mesh = controller->GetBaseGeometry()->GetMesh();
+    if (mesh != NULL) {
+      PT(DaeMaterials) materials = new DaeMaterials(instance);
       daeegg_cat.spam() << "Processing mesh for controller\n";
-      if (_vertex_pools.count(FROM_FSTRING(geometry->GetMesh()->GetDaeId()))) {
-        daeegg_cat.debug() << "Using vertex pool " << FROM_FSTRING(geometry->GetMesh()->GetDaeId()) << "\n";
-        vertex_pool = _vertex_pools[FROM_FSTRING(geometry->GetMesh()->GetDaeId())];
-      }
-    }
-    if (geometry->IsSpline()) {
-      process_spline(parent, FROM_FSTRING(geometry->GetName()), const_cast<FCDGeometrySpline*> (geometry->GetSpline()));
-    }
-  }
-  // Add the joint hierarchy
-#if FCOLLADA_VERSION < 0x00030005
-  FCDSceneNodeList roots = (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes();
-#else
-  FCDSceneNodeList roots;
-  (const_cast<FCDControllerInstance*> (instance))->FindSkeletonNodes(roots);
-#endif
-  for (FCDSceneNodeList::iterator it = roots.begin(); it != roots.end(); ++it) {
-    process_node(DCAST(EggGroupNode, parent), *it, true);
-  }
-  if (controller->IsSkin()) {
-    // Load in the vertex influences first
-    pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > > influences;
-    if (vertex_pool) {
-      for (size_t in = 0; in < controller->GetSkinController()->GetInfluenceCount(); ++in) {
-        assert(vertex_pool->has_vertex(in));
-        for (size_t pa = 0; pa < controller->GetSkinController()->GetVertexInfluence(in)->GetPairCount(); ++pa) {
-          const FCDJointWeightPair* jwpair = controller->GetSkinController()->GetVertexInfluence(in)->GetPair(pa);
-          influences[jwpair->jointIndex].push_back(pair<PT_EggVertex, PN_stdfloat> (vertex_pool->get_vertex(in), jwpair->weight));
-        }
-      }
-    }
-    // Loop through the joints in the vertex influences
-    for (pmap<int32, pvector<pair<PT_EggVertex, PN_stdfloat> > >::iterator it = influences.begin(); it != influences.end(); ++it) {
-      if (it->first == -1) {
-        daeegg_cat.warning() << "Ignoring vertex influence with negative joint index\n";
-        //FIXME: Why are there joints with index -1
-      } else {
-        const string joint_id = FROM_FSTRING(controller->GetSkinController()->GetJoint(it->first)->GetId());
-        //TODO: what if the joints have just not been defined yet?
-        if (_joints.count(joint_id) > 0) {
-          if (_joints[joint_id]) {
-            for (pvector<pair<PT_EggVertex, PN_stdfloat> >::iterator vi = it->second.begin(); vi != it->second.end(); ++vi) {
-              _joints[joint_id]->ref_vertex(vi->first, vi->second);
-            }
-          } else {
-            daeegg_cat.warning() << "Unprocessed joint being referenced: '" << joint_id << "'" << endl;
-          }
-        } else {
-          daeegg_cat.warning() << "Unknown joint being referenced: '" << joint_id << "'" << endl;
-        }
-      }
+      process_mesh(parent, mesh, materials);
     }
+  } else {
+    // Add a character for this to the table, the mesh is processed later
+    PT(DaeCharacter) character = new DaeCharacter(parent, instance);
+    _characters.push_back(character);
   }
+
   if (controller->IsMorph()) {
     assert(controller != NULL);
     const FCDMorphController* morph_controller = controller->GetMorphController();
@@ -628,28 +779,25 @@ void DAEToEggConverter::process_controller(PT(EggGroup) parent, const FCDControl
       morph->add_child(target);
     }
   }
-  
-  // Get a <Bundle> for the character and add it to the table
-  PT(DaeCharacter) character = new DaeCharacter(parent->get_name(), instance);
-  _table->add_child(character->as_egg_bundle());
 }
 
-void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra) {
+void DAEToEggConverter::
+process_extra(EggGroup *group, const FCDExtra* extra) {
   if (extra == NULL) {
     return;
   }
   nassertv(group != NULL);
-  
+
   const FCDEType* etype = extra->GetDefaultType();
   if (etype == NULL) {
     return;
   }
-  
+
   const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D");
   if (enode == NULL) {
     return;
   }
-  
+
   FCDENodeList tags;
   enode->FindChildrenNodes("param", tags);
   for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) {
@@ -660,18 +808,45 @@ void DAEToEggConverter::process_extra(PT(EggGroup) group, const FCDExtra* extra)
   }
 }
 
-LMatrix4d DAEToEggConverter::convert_matrix(const FMMatrix44& matrix) {
-  LMatrix4d result = LMatrix4d::zeros_mat();
-  for (char x = 0; x < 4; ++x) {
-    for (char y = 0; y < 4; ++y) {
-      result(x, y) = matrix[x][y];
-    }
-  }
-  return result;
+LMatrix4d DAEToEggConverter::
+convert_matrix(const FMMatrix44 &matrix) {
+  return LMatrix4d(
+    matrix[0][0], matrix[0][1], matrix[0][2], matrix[0][3],
+    matrix[1][0], matrix[1][1], matrix[1][2], matrix[1][3],
+    matrix[2][0], matrix[2][1], matrix[2][2], matrix[2][3],
+    matrix[3][0], matrix[3][1], matrix[3][2], matrix[3][3]);
 }
 
-void DAEToEggConverter::apply_transform(const PT(EggGroup) to, const FCDTransform* from) {
+void DAEToEggConverter::
+apply_transform(EggGroup *to, const FCDTransform* from) {
   assert(from != NULL);
   assert(to != NULL);
-  to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  //to->set_transform3d(convert_matrix(from->ToMatrix()) * to->get_transform3d());
+  switch (from->GetType()) {
+  case FCDTransform::TRANSLATION:
+    {
+      const FCDTTranslation *trans = (const FCDTTranslation *)from;
+      to->add_translate3d(TO_VEC3(trans->GetTranslation()));
+    }
+    break;
+
+  case FCDTransform::ROTATION:
+    {
+      const FCDTRotation *rot = (const FCDTRotation *)from;
+      to->add_rotate3d(rot->GetAngle(), TO_VEC3(rot->GetAxis()));
+    }
+    break;
+
+  case FCDTransform::SCALE:
+    {
+      const FCDTScale *scale = (const FCDTScale *)from;
+      to->add_scale3d(TO_VEC3(scale->GetScale()));
+    }
+    break;
+
+  default:
+    // Either a matrix, or something we can't handle.
+    to->add_matrix4(convert_matrix(from->ToMatrix()));
+    break;
+  }
 }

+ 23 - 20
pandatool/src/daeegg/daeToEggConverter.h

@@ -35,6 +35,7 @@
 #include "FMath/FMMatrix44.h"
 
 #include "daeMaterials.h"
+#include "daeCharacter.h"
 #include "pvector.h" // Include last
 
 ////////////////////////////////////////////////////////////////////
@@ -47,39 +48,41 @@ public:
   DAEToEggConverter();
   DAEToEggConverter(const DAEToEggConverter &copy);
   ~DAEToEggConverter();
-  
+
   virtual SomethingToEggConverter *make_copy();
-  
+
   virtual string get_name() const;
   virtual string get_extension() const;
-  
+
   virtual bool convert_file(const Filename &filename);
+  virtual DistanceUnit get_input_units();
 
   bool _invert_transparency;
 
 private:
+  string _unit_name;
+  double _unit_meters;
   PT(EggTable) _table;
   FCDocument* _document;
   FUErrorSimpleHandler* _error_handler;
-  pmap<const string, PT(EggGroup)> _joints;
-  pmap<const string, PT(EggVertexPool)> _vertex_pools;
-  pvector<string> _skeletons;
-  int _frame_rate;
-  
+  DaeCharacter::JointMap _joints;
+
+  typedef pvector<PT(DaeCharacter)> Characters;
+  Characters _characters;
+
   void process_asset();
-  void preprocess(const FCDSceneNode* node = NULL);
-  void process_node(PT(EggGroupNode) parent, const FCDSceneNode* node, bool forced = false);
-  void process_instance(PT(EggGroup) parent, const FCDEntityInstance* instance);
-  void process_mesh(PT(EggGroup) parent, const FCDGeometryMesh* mesh, PT(DaeMaterials) materials);
-  void process_spline(PT(EggGroup) parent, const string group_name, FCDGeometrySpline* geometry_spline);
-  void process_spline(PT(EggGroup) parent, const FCDSpline* spline);
-  void process_controller(PT(EggGroup) parent, const FCDControllerInstance* instance);
-  //void process_table_joint(PT(EggTable) parent, FCDSceneNode* node);
-  void process_extra(PT(EggGroup) group, const FCDExtra* extra);
-  
+  void process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced = false);
+  void process_instance(EggGroup *parent, const FCDEntityInstance* instance);
+  void process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
+                    DaeMaterials *materials, DaeCharacter *character = NULL);
+  void process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline);
+  void process_spline(EggGroup *parent, const FCDSpline* spline);
+  void process_controller(EggGroup *parent, const FCDControllerInstance* instance);
+  void process_extra(EggGroup *group, const FCDExtra* extra);
+
   static LMatrix4d convert_matrix(const FMMatrix44& matrix);
-  void apply_transform(const PT(EggGroup) to, const FCDTransform* from);
-  
+  void apply_transform(EggGroup *to, const FCDTransform* from);
+
   friend class DaeCharacter;
 };
 

+ 8 - 0
pandatool/src/daeprogs/daeToEgg.cxx

@@ -26,6 +26,7 @@ DAEToEgg::
 DAEToEgg():
   SomethingToEgg("COLLADA", ".dae")
 {
+  add_animation_options();
   add_units_options();
   add_normals_options();
   add_transform_options();
@@ -42,6 +43,7 @@ DAEToEgg():
     ("This program converts .dae files (COLLADA Digital Asset Exchange) to .egg.");
 
   _coordinate_system = CS_yup_right;
+  _animation_convert = AC_both;
 }
 
 ////////////////////////////////////////////////////////////////////
@@ -51,6 +53,12 @@ DAEToEgg():
 ////////////////////////////////////////////////////////////////////
 void DAEToEgg::
 run() {
+  if (_animation_convert != AC_both && _animation_convert != AC_none &&
+      _animation_convert != AC_chan && _animation_convert != AC_model) {
+    cerr << "Unsupported animation convert option.\n";
+    exit(1);
+  }
+
   nout << "Reading " << _input_filename << "\n";
 
   _data->set_coordinate_system(_coordinate_system);