// Filename: xFileMaker.cxx
// Created by:  drose (19Jun01)
//
////////////////////////////////////////////////////////////////////
//
// 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."
//
////////////////////////////////////////////////////////////////////

#include "xFileMaker.h"
#include "xFileMesh.h"
#include "xFileMaterial.h"
#include "config_xfile.h"

#include "pnotify.h"
#include "eggGroupNode.h"
#include "eggGroup.h"
#include "eggBin.h"
#include "eggPolysetMaker.h"
#include "eggVertexPool.h"
#include "eggVertex.h"
#include "eggPolygon.h"
#include "eggData.h"
#include "pvector.h"
#include "vector_int.h"
#include "string_utils.h"
#include "datagram.h"

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
XFileMaker::
XFileMaker() {
  _mesh_index = 0;
  _x_file = new XFile;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::Destructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
XFileMaker::
~XFileMaker() {
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::write
//       Access: Public
//  Description: Writes the .x file data to the indicated filename;
//               returns true on success, false otherwise.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
write(const Filename &filename) {
  return _x_file->write(filename);
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::add_tree
//       Access: Public
//  Description: Adds the egg tree rooted at the indicated node to the
//               X structure.  This may be somewhat destructive of
//               the egg tree.  Returns true on success, false on
//               failure.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
add_tree(EggData *egg_data) {
  _meshes.clear();

  // Now collect all the polygons together into polysets.
  EggPolysetMaker pmaker;
  pmaker.make_bins(egg_data);

  // And now we're ready to traverse the egg hierarchy.
  if (!recurse_nodes(egg_data, _x_file)) {
    return false;
  }

  // Create X structures for all of the meshes we built up.
  Meshes::iterator mi;
  for (mi = _meshes.begin(); mi != _meshes.end(); ++mi) {
    if (!finalize_mesh((*mi).first, (*mi).second)) {
      return false;
    }
  }
  _meshes.clear();

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::add_node
//       Access: Private
//  Description: Adds the node to the DX structure, in whatever form
//               it is supported.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
add_node(EggNode *egg_node, XFileNode *x_parent) {
  if (egg_node->is_of_type(EggBin::get_class_type())) {
    return add_bin(DCAST(EggBin, egg_node), x_parent);

  } else if (egg_node->is_of_type(EggGroup::get_class_type())) {
    return add_group(DCAST(EggGroup, egg_node), x_parent);

  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
    // A grouping node of some kind.
    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);

    if (xfile_one_mesh) {
      // Don't create any additional frames representing the egg
      // hierarchy.
      if (!recurse_nodes(egg_group, x_parent)) {
        return false;
      }

    } else {
      // Create a Frame for each EggGroup.
      XFileDataNode *x_frame = x_parent->add_Frame(egg_group->get_name());

      if (!recurse_nodes(egg_group, x_frame)) {
        return false;
      }
    }
    
    return true;
  }

  // Some unsupported node type.  Ignore it.
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::add_group
//       Access: Private
//  Description: Adds a frame for the indicated group node.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
add_group(EggGroup *egg_group, XFileNode *x_parent) {
  if (xfile_one_mesh) {
    // Don't create any additional frames representing the egg
    // hierarchy.
    if (!recurse_nodes(egg_group, x_parent)) {
      return false;
    }

  } else {
    // Create a frame for each EggGroup.
    XFileDataNode *x_frame = x_parent->add_Frame(egg_group->get_name());

    // Set the transform on the frame, if we have one.
    if (egg_group->has_transform()) {
      x_frame->add_FrameTransformMatrix(egg_group->get_transform3d());
    }

    if (!recurse_nodes(egg_group, x_frame)) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::add_bin
//       Access: Private
//  Description: Determines what kind of object needs to be added for
//               the indicated bin node.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
add_bin(EggBin *egg_bin, XFileNode *x_parent) {
  switch (egg_bin->get_bin_number()) {
  case EggPolysetMaker::BN_polyset:
    return add_polyset(egg_bin, x_parent);
  }

  xfile_cat.error()
    << "Unexpected bin type " << egg_bin->get_bin_number() << "\n";
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::add_polyset
//       Access: Private
//  Description: Adds a mesh object corresponding to the collection of
//               polygons within the indicated bin.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
add_polyset(EggBin *egg_bin, XFileNode *x_parent) {
  // Make sure that all our polygons are reasonable.
  egg_bin->remove_invalid_primitives(true);

  XFileMesh *mesh = get_mesh(x_parent);

  EggGroupNode::iterator ci;
  for (ci = egg_bin->begin(); ci != egg_bin->end(); ++ci) {
    EggPolygon *poly;
    DCAST_INTO_R(poly, *ci, false);

    mesh->add_polygon(poly);
  }

  return true;
}

  
////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::recurse_nodes
//       Access: Private
//  Description: Adds each child of the indicated Node as a child of
//               the indicated DX object.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
recurse_nodes(EggGroupNode *egg_node, XFileNode *x_parent) {
  EggGroupNode::iterator ci;
  for (ci = egg_node->begin(); ci != egg_node->end(); ++ci) {
    EggNode *child = (*ci);
    if (!add_node(child, x_parent)) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::get_mesh
//       Access: Private
//  Description: Returns a suitable XFileMesh object for creating
//               meshes within the indicated x_parent object.
////////////////////////////////////////////////////////////////////
XFileMesh *XFileMaker::
get_mesh(XFileNode *x_parent) {
  Meshes::iterator mi = _meshes.find(x_parent);
  if (mi != _meshes.end()) {
    // We've already started working on this x_parent before; use the
    // same mesh object.
    return (*mi).second;
  }

  // We haven't seen this x_parent before; create a new mesh object.
  XFileMesh *mesh = new XFileMesh;
  _meshes.insert(Meshes::value_type(x_parent, mesh));
  return mesh;
}


////////////////////////////////////////////////////////////////////
//     Function: XFileMaker::finalize_mesh
//       Access: Private
//  Description: Creates the actual X structures corresponding to
//               the indicated XFileMesh object.
////////////////////////////////////////////////////////////////////
bool XFileMaker::
finalize_mesh(XFileNode *x_parent, XFileMesh *mesh) {
  // Get a unique number for each mesh.
  _mesh_index++;
  string mesh_index = format_string(_mesh_index);

  // Finally, create the Mesh object.
  mesh->make_x_mesh(x_parent, mesh_index);

  return true;
}
