/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file daeToEggConverter.cxx
 * @author rdb
 * @date 2008-05-08
 */

#include "daeToEggConverter.h"
#include "fcollada_utils.h"
#include "config_daeegg.h"
#include "daeCharacter.h"
#include "dcast.h"
#include "string_utils.h"
#include "eggData.h"
#include "eggPrimitive.h"
#include "eggLine.h"
#include "eggPolygon.h"
#include "eggTriangleFan.h"
#include "eggTriangleStrip.h"
#include "eggPoint.h"
#include "eggXfmSAnim.h"
#include "eggSAnimData.h"
#include "pt_EggVertex.h"

#include <FCDocument/FCDAsset.h>
#include <FCDocument/FCDocumentTools.h>
#include <FCDocument/FCDSceneNode.h>
#include <FCDocument/FCDSceneNodeTools.h>
#include <FCDocument/FCDGeometry.h>
#include <FCDocument/FCDGeometryInstance.h>
#include <FCDocument/FCDGeometryPolygons.h>
#include <FCDocument/FCDGeometrySource.h>
#include <FCDocument/FCDSkinController.h>
#include <FCDocument/FCDController.h>
#include <FCDocument/FCDControllerInstance.h>
#include <FCDocument/FCDMorphController.h>
#include <FCDocument/FCDMaterialInstance.h>
#include <FCDocument/FCDExtra.h>
#include <FCDocument/FCDEffect.h>
#include <FCDocument/FCDEffectStandard.h>
#if FCOLLADA_VERSION >= 0x00030005
  #include <FCDocument/FCDGeometryPolygonsInput.h>
#endif

using std::endl;
using std::string;

/**
 *
 */
DAEToEggConverter::
DAEToEggConverter() {
  _unit_name = "meter";
  _unit_meters = 1.0;
  _document = nullptr;
  _table = nullptr;
  _error_handler = nullptr;
  _invert_transparency = false;
}

/**
 *
 */
DAEToEggConverter::
DAEToEggConverter(const DAEToEggConverter &copy) :
  SomethingToEggConverter(copy)
{
}

/**
 *
 */
DAEToEggConverter::
~DAEToEggConverter() {
  delete _error_handler;
}

/**
 * Allocates and returns a new copy of the converter.
 */
SomethingToEggConverter *DAEToEggConverter::
make_copy() {
  return new DAEToEggConverter(*this);
}


/**
 * Returns the English name of the file type this converter supports.
 */
string DAEToEggConverter::
get_name() const {
  return "COLLADA";
}

/**
 * Returns the common extension of the file type this converter supports.
 */
string DAEToEggConverter::
get_extension() const {
  return "dae";
}

/**
 * Handles the reading of the input file and converting it to egg.  Returns
 * true if successful, false otherwise.
 */
bool DAEToEggConverter::
convert_file(const Filename &filename) {
  // Reset stuff
  clear_error();
  _joints.clear();
  if (_error_handler == nullptr) {
    _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());
  if (_document == nullptr) {
    daeegg_cat.error() << "Failed to load document: " << _error_handler->GetErrorString() << endl;
    FCollada::Release();
    return false;
  }
  // Make sure the file uses consistent coordinate system and length
  if (_document->GetAsset() != nullptr) {
    FCDocumentTools::StandardizeUpAxisAndLength(_document);
  }

  // Process the scene
  process_asset();
  PT(EggGroup) scene_group;
  string model_name = _character_name;

  FCDSceneNode* visual_scene = _document->GetVisualSceneInstance();
  if (visual_scene != nullptr) {
    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(scene_group, visual_scene->GetChild(ch));
    }
  } else {
    daeegg_cat.warning()
      << "No visual scene instance found in COLLADA document.\n";
  }

  // 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 != nullptr) {
          PT(DaeMaterials) materials = new DaeMaterials(character->_instance);
          if (daeegg_cat.is_spam()) {
            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 != nullptr) {
      // 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) {
        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;
}

/**
 * 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() == nullptr) {
    return;
  }

  _unit_name = FROM_FSTRING(asset->GetUnitName());
  _unit_meters = asset->GetUnitConversionFactor();

  // Read out the coordinate system
  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";
  }
}

// Process the node.  If forced is true, it will even process it if its known
// to be a skeleton root.
void DAEToEggConverter::
process_node(EggGroupNode *parent, const FCDSceneNode* node, bool forced) {
  nassertv(node != nullptr);
  string node_id = FROM_FSTRING(node->GetDaeId());
  if (daeegg_cat.is_spam()) {
    daeegg_cat.spam() << "Processing node with ID '" << node_id << "'" << endl;
  }

  // Create an egg group for this node
  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);

    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 (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) {
    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(EggGroup *parent, const FCDEntityInstance* instance) {
  nassertv(instance != nullptr);
  nassertv(instance->GetEntity() != nullptr);
  // Check what kind of instance this is
  switch (instance->GetType()) {
  case FCDEntityInstance::GEOMETRY:
    {
      if (get_animation_convert() != AC_chan) {
        const FCDGeometry* geometry = (const FCDGeometry*) instance->GetEntity();
        assert(geometry != nullptr);
        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 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;
  }
}

// Processes the given mesh.
void DAEToEggConverter::
process_mesh(EggGroup *parent, const FCDGeometryMesh* mesh,
             DaeMaterials *materials, DaeCharacter *character) {

  nassertv(mesh != nullptr);
  if (daeegg_cat.is_debug()) {
    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);

  // First retrieve the vertex source
  if (mesh->GetSourceCount() == 0) {
    if (daeegg_cat.is_debug()) {
      daeegg_cat.debug() << "Mesh with id " << FROM_FSTRING(mesh->GetDaeId()) << " has no sources" << endl;
    }
    return;
  }
  const FCDGeometrySource* vsource = mesh->FindSourceByType(FUDaeGeometryInput::POSITION);
  if (vsource == nullptr) {
    if (daeegg_cat.is_debug()) {
      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
  if (daeegg_cat.is_spam()) {
    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 != nullptr && (!polygons->GetMaterialSemantic().empty()) && mesh->GetPolygonsCount() > 1) {
      // primitiveholder = new EggGroup(FROM_FSTRING(mesh->GetDaeId()) + "." +
      // material_semantic);
      primitiveholder = new EggGroup;
      mesh_group->add_child(primitiveholder);
    } else {
      primitiveholder = mesh_group;
    }
    primitive_holders[gr] = primitiveholder;
    // Apply the per-group data of the materials, if we have it.
    if (materials != nullptr) {
      materials->apply_to_group(material_semantic, primitiveholder, _invert_transparency);
    }
    // Find the position sources
    const FCDGeometryPolygonsInput* pinput = polygons->FindInput(FUDaeGeometryInput::POSITION);
    assert(pinput != nullptr);
    const uint32* indices = pinput->GetIndices();
    // Find the normal sources
    const FCDGeometrySource* nsource = mesh->FindSourceByType(FUDaeGeometryInput::NORMAL);
    const FCDGeometryPolygonsInput* ninput = polygons->FindInput(FUDaeGeometryInput::NORMAL);
    const uint32* nindices;
    if (ninput != nullptr) nindices = ninput->GetIndices();
    // Find texcoord sources
    const FCDGeometrySource* tcsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXCOORD);
    const FCDGeometryPolygonsInput* tcinput = polygons->FindInput(FUDaeGeometryInput::TEXCOORD);
    const uint32* tcindices;
    if (tcinput != nullptr) tcindices = tcinput->GetIndices();
    // Find vcolor sources
    const FCDGeometrySource* csource = mesh->FindSourceByType(FUDaeGeometryInput::COLOR);
    const FCDGeometryPolygonsInput* cinput = polygons->FindInput(FUDaeGeometryInput::COLOR);
    const uint32* cindices;
    if (cinput != nullptr) cindices = cinput->GetIndices();
    // Find binormal sources
    const FCDGeometrySource* bsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXBINORMAL);
    const FCDGeometryPolygonsInput* binput = polygons->FindInput(FUDaeGeometryInput::TEXBINORMAL);
    const uint32* bindices;
    if (binput != nullptr) bindices = binput->GetIndices();
    // Find tangent sources
    const FCDGeometrySource* tsource = mesh->FindSourceByType(FUDaeGeometryInput::TEXTANGENT);
    const FCDGeometryPolygonsInput* tinput = polygons->FindInput(FUDaeGeometryInput::TEXTANGENT);
    const uint32* tindices;
    if (tinput != nullptr) tindices = tinput->GetIndices();
    // Get a name for potential coordinate sets
    string tcsetname;
    if (materials != nullptr && tcinput != nullptr) {
      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;
    if (materials != nullptr && binput != nullptr) {
      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;
    if (materials != nullptr && tinput != nullptr) {
      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 != nullptr) {
        // If this is skinned geometry, add the vertex influences.
        character->influence_vertex(indices[ix], vertex);
      }

      // Process the normal
      if (nsource != nullptr && ninput != nullptr) {
        assert(nsource->GetStride() == 3);
        data = &nsource->GetData()[nindices[ix]*3];
        vertex->set_normal(LVecBase3d(data[0], data[1], data[2]));
      }
      // Process the texcoords
      if (tcsource != nullptr && tcinput != nullptr) {
        assert(tcsource->GetStride() == 2 || tcsource->GetStride() == 3);
        data = &tcsource->GetData()[tcindices[ix]*tcsource->GetStride()];
        if (tcsource->GetStride() == 2) {
          vertex->set_uv(tcsetname, LPoint2d(data[0], data[1]));
        } else {
          vertex->set_uvw(tcsetname, LPoint3d(data[0], data[1], data[2]));
        }
      }
      // Process the color
      if (csource != nullptr && cinput != nullptr) {
        assert(csource->GetStride() == 3 || csource->GetStride() == 4);
        if (csource->GetStride() == 3) {
          data = &csource->GetData()[cindices[ix]*3];
          vertex->set_color(LColor(data[0], data[1], data[2], 1.0f));
        } else {
          data = &csource->GetData()[cindices[ix]*4];
          vertex->set_color(LColor(data[0], data[1], data[2], data[3]));
        }
      }
      // Possibly add a UV object
      if ((bsource != nullptr && binput != nullptr) || (tsource != nullptr && tinput != nullptr)) {
        if (bsource != nullptr && binput != nullptr) {
          assert(bsource->GetStride() == 3);
          data = &bsource->GetData()[bindices[ix]*3];
          PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(tbsetname);
          if (uv_obj == nullptr) {
            uv_obj = new EggVertexUV(tbsetname, LTexCoordd());
          }
          uv_obj->set_binormal(LVecBase3d(data[0], data[1], data[2]));
        }
        if (tsource != nullptr && tinput != nullptr) {
          assert(tsource->GetStride() == 3);
          data = &tsource->GetData()[tindices[ix]*3];
          PT(EggVertexUV) uv_obj = vertex->modify_uv_obj(ttsetname);
          if (uv_obj == nullptr) {
            uv_obj = new EggVertexUV(ttsetname, LTexCoordd());
          }
          uv_obj->set_tangent(LVecBase3d(data[0], data[1], data[2]));
        }
      }
      vertex->transform(parent->get_node_to_vertex());
    }
  }
  // Loop again for the polygons
  for (size_t gr = 0; gr < mesh->GetPolygonsCount(); ++gr) {
    const FCDGeometryPolygons* polygons = mesh->GetPolygons(gr);
    // Now loop through the faces
    uint32 offset = 0;
    for (size_t fa = 0; fa < polygons->GetFaceVertexCountCount(); ++fa) {
      PT(EggPrimitive) primitive = nullptr;
      // 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;
      }
      if (primitive != nullptr) {
        primitive_holders[gr]->add_child(primitive);
        if (materials != nullptr) {
          materials->apply_to_primitive(FROM_FSTRING(polygons->GetMaterialSemantic()), primitive);
        }
        for (size_t ve = 0; ve < polygons->GetFaceVertexCount(fa); ++ve) {
          assert(mesh_pool->has_vertex(ve + polygons->GetFaceVertexOffset() + offset));
          primitive->add_vertex(mesh_pool->get_vertex(ve + polygons->GetFaceVertexOffset() + offset));
        }
      }
      offset += polygons->GetFaceVertexCount(fa);
    }
  }
  delete[] primitive_holders;
}

void DAEToEggConverter::
process_spline(EggGroup *parent, const string group_name, FCDGeometrySpline* geometry_spline) {
  assert(geometry_spline != nullptr);
  PT(EggGroup) result = new EggGroup(group_name);
  parent->add_child(result);
  // TODO: if its not a nurbs, make it convert between the types
  if (geometry_spline->GetType() != FUDaeSplineType::NURBS) {
    daeegg_cat.warning() << "Only NURBS curves are supported (yet)!" << endl;
  } else {
    // Loop through the splines
    for (size_t sp = 0; sp < geometry_spline->GetSplineCount(); ++sp) {
      process_spline(result, geometry_spline->GetSpline(sp));
    }
  }
}

void DAEToEggConverter::
process_spline(EggGroup *parent, const FCDSpline* spline) {
  assert(spline != nullptr);
  nassertv(spline->GetSplineType() == FUDaeSplineType::NURBS);
  // Now load in the nurbs curve to the egg library
  PT(EggNurbsCurve) nurbs_curve = new EggNurbsCurve(FROM_FSTRING(spline->GetName()));
  parent->add_child(nurbs_curve);
  // TODO: what value is this?
  nurbs_curve->setup(0, ((const FCDNURBSSpline*) spline)->GetKnotCount());
  for (size_t kn = 0; kn < ((const FCDNURBSSpline*) spline)->GetKnotCount(); ++kn) {
    const float* knot = ((const FCDNURBSSpline*) spline)->GetKnot(kn);
    assert(knot != nullptr);
    nurbs_curve->set_knot(kn, *knot);
  }
  for (size_t cv = 0; cv < spline->GetCVCount(); ++cv) {
    PT_EggVertex c_vtx = new EggVertex();
    c_vtx->set_pos(TO_VEC3(*spline->GetCV(cv)));
    c_vtx->transform(parent->get_node_to_vertex());
    nurbs_curve->add_vertex(c_vtx);
  }
}

void DAEToEggConverter::
process_controller(EggGroup *parent, const FCDControllerInstance *instance) {
  assert(instance != nullptr);
  const FCDController* controller = (const FCDController *)instance->GetEntity();
  assert(controller != nullptr);

  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 != nullptr) {
      PT(DaeMaterials) materials = new DaeMaterials(instance);
      if (daeegg_cat.is_spam()) {
        daeegg_cat.spam() << "Processing mesh for controller\n";
      }
      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 != nullptr);
    const FCDMorphController* morph_controller = controller->GetMorphController();
    assert(morph_controller != nullptr);
    PT(EggTable) bundle = new EggTable(parent->get_name());
    bundle->set_table_type(EggTable::TT_bundle);
    PT(EggTable) morph = new EggTable("morph");
    morph->set_table_type(EggTable::TT_table);
    bundle->add_child(morph);
    // Loop through the morph targets.
    for (size_t mt = 0; mt < morph_controller->GetTargetCount(); ++mt) {
      const FCDMorphTarget* morph_target = morph_controller->GetTarget(mt);
      assert(morph_target != nullptr);
      PT(EggSAnimData) target = new EggSAnimData(FROM_FSTRING(morph_target->GetGeometry()->GetName()));
      if (morph_target->IsAnimated()) {
        // TODO
      } else {
        target->add_data(morph_target->GetWeight());
      }
      morph->add_child(target);
    }
  }
}

void DAEToEggConverter::
process_extra(EggGroup *group, const FCDExtra* extra) {
  if (extra == nullptr) {
    return;
  }
  nassertv(group != nullptr);

  const FCDEType* etype = extra->GetDefaultType();
  if (etype == nullptr) {
    return;
  }

  const FCDENode* enode = (const FCDENode*) etype->FindTechnique("PANDA3D");
  if (enode == nullptr) {
    return;
  }

  FCDENodeList tags;
  enode->FindChildrenNodes("param", tags);
  for (FCDENodeList::iterator it = tags.begin(); it != tags.end(); ++it) {
    const FCDEAttribute* attr = (*it)->FindAttribute("sid");
    if (attr) {
      group->set_tag(FROM_FSTRING(attr->GetValue()), (*it)->GetContent());
    }
  }
}

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(EggGroup *to, const FCDTransform* from) {
  assert(from != nullptr);
  assert(to != nullptr);
  // 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;
  }
}
