// Filename: eggToObjConverter.cxx
// Created by:  drose (19Dec12)
//
////////////////////////////////////////////////////////////////////
//
// 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 "eggToObjConverter.h"
#include "config_objegg.h"
#include "eggData.h"
#include "string_utils.h"
#include "streamReader.h"
#include "virtualFileSystem.h"
#include "eggPolygon.h"
#include "dcast.h"

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
EggToObjConverter::
EggToObjConverter() {
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::Copy Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
EggToObjConverter::
EggToObjConverter(const EggToObjConverter &copy) :
  EggToSomethingConverter(copy)
{
}

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

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::make_copy
//       Access: Public, Virtual
//  Description: Allocates and returns a new copy of the converter.
////////////////////////////////////////////////////////////////////
EggToSomethingConverter *EggToObjConverter::
make_copy() {
  return new EggToObjConverter(*this);
}


////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::get_name
//       Access: Public, Virtual
//  Description: Returns the English name of the file type this
//               converter supports.
////////////////////////////////////////////////////////////////////
string EggToObjConverter::
get_name() const {
  return "obj";
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::get_extension
//       Access: Public, Virtual
//  Description: Returns the common extension of the file type this
//               converter supports.
////////////////////////////////////////////////////////////////////
string EggToObjConverter::
get_extension() const {
  return "obj";
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::supports_compressed
//       Access: Published, Virtual
//  Description: Returns true if this file type can transparently save
//               compressed files (with a .pz extension), false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool EggToObjConverter::
supports_compressed() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::write_file
//       Access: Public, Virtual
//  Description: Handles the conversion of the internal EggData to the
//               target file format, written to the specified
//               filename.
////////////////////////////////////////////////////////////////////
bool EggToObjConverter::
write_file(const Filename &filename) {
  clear_error();

  if (_egg_data->get_coordinate_system() == CS_default) {
    _egg_data->set_coordinate_system(CS_zup_right);
  }

  if (!process(filename)) {
    _error = true;
  }
  return !had_error();
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::process
//       Access: Private
//  Description:
////////////////////////////////////////////////////////////////////
bool EggToObjConverter::
process(const Filename &filename) {
  _egg_data->flatten_transforms();
  collect_vertices(_egg_data);

  VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
  Filename obj_filename = Filename::text_filename(filename);
  vfs->delete_file(obj_filename);
  ostream *file = vfs->open_write_file(obj_filename, true, true);
  if (file == (ostream *)NULL) {
    return false;
  }

  _current_group = NULL;

  /*
  (*file) << "\n#\n"
          << "# obj file generated by the following command:\n"
          << "# " << get_exec_command() << "\n"
          << "#\n\n";
  */

  write_vertices(*file, "v", 3, _unique_vert3);
  write_vertices(*file, "v", 4, _unique_vert4);
  write_vertices(*file, "vt", 2, _unique_uv2);
  write_vertices(*file, "vt", 3, _unique_uv3);
  write_vertices(*file, "vn", 3, _unique_norm);

  write_faces(*file, _egg_data);

  bool success = (void *)(*file) != NULL;
  vfs->close_write_file(file);

  return success;
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::collect_vertices
//       Access: Private
//  Description: Recursively walks the egg structure, looking for
//               vertices referenced by polygons.  Any such vertices
//               are added to the vertex tables for writing to the obj
//               file.
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
collect_vertices(EggNode *egg_node) {
  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);
    EggPolygon::iterator pi;
    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
      record_vertex(*pi);
    }

  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);

    EggGroupNode::iterator ci;
    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
      collect_vertices(*ci);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::write_faces
//       Access: Private
//  Description: Recursively walks the egg structure again, this time
//               writing out the face records for any polygons
//               encountered.
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
write_faces(ostream &out, EggNode *egg_node) {
  if (egg_node->is_of_type(EggPolygon::get_class_type())) {
    write_group_reference(out, egg_node);

    EggPolygon *egg_poly = DCAST(EggPolygon, egg_node);

    out << "f";
    EggPolygon::iterator pi;
    for (pi = egg_poly->begin(); pi != egg_poly->end(); ++pi) {
      VertexDef &vdef = _vmap[(*pi)];
      int vert_index = -1;
      int uv_index = -1;
      int norm_index = -1;

      if (vdef._vert3_index != -1) {
        vert_index = vdef._vert3_index + 1;
      } else if (vdef._vert4_index != -1) {
        vert_index = vdef._vert4_index + 1 + (int)_unique_vert3.size();
      }

      if (vdef._uv2_index != -1) {
        uv_index = vdef._uv2_index + 1;
      } else if (vdef._uv3_index != -1) {
        uv_index = vdef._uv3_index + 1 + (int)_unique_uv2.size();
      }

      if (vdef._norm_index != -1) {
        norm_index = vdef._norm_index + 1;
      }

      if (vert_index == -1) {
        continue;
      }

      if (norm_index != -1) {
        if (uv_index != -1) {
          out << " " << vert_index << "/" << uv_index << "/" << norm_index;
        } else {
          out << " " << vert_index << "//" << norm_index;
        }
      } else if (uv_index != -1) {
        out << " " << vert_index << "/" << uv_index;
      } else {
        out << " " << vert_index;
      }
    }
    out << "\n";

  } else if (egg_node->is_of_type(EggGroupNode::get_class_type())) {
    EggGroupNode *egg_group = DCAST(EggGroupNode, egg_node);

    EggGroupNode::iterator ci;
    for (ci = egg_group->begin(); ci != egg_group->end(); ++ci) {
      write_faces(out, *ci);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::write_group_reference
//       Access: Private
//  Description: Writes the "g" tag to describe this polygon's group,
//               if needed.
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
write_group_reference(ostream &out, EggNode *egg_node) {
  EggGroupNode *egg_group = egg_node->get_parent();
  if (egg_group == _current_group) {
    // Same group we wrote last time.
    return;
  }

  string group_name;
  get_group_name(group_name, egg_group);
  if (group_name.empty()) {
    out << "g default\n";
  } else {
    out << "g" << group_name << "\n";
  }
  _current_group = egg_group;
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::get_group_name
//       Access: Private
//  Description: Recursively determines the appropriate string to
//               write for the "g" tag to describe a particular
//               EggGroupNode.
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
get_group_name(string &group_name, EggGroupNode *egg_group) {
  string name = trim(egg_group->get_name());
  if (!name.empty()) {
    group_name += ' ';

    // Remove nonstandard characters.
    for (string::const_iterator ni = name.begin(); ni != name.end(); ++ni) {
      char c = (*ni);
      if (!isalnum(c)) {
        c = '_';
      }
      group_name += c;
    }
  }

  // Now recurse.
  EggGroupNode *egg_parent = egg_group->get_parent();
  if (egg_parent != NULL) {
    get_group_name(group_name, egg_parent);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::record_vertex
//       Access: Private
//  Description: Adds the indicated EggVertex to the unique vertex
//               tables, for writing later by write_vertices().
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
record_vertex(EggVertex *vertex) {
  VertexDef &vdef = _vmap[vertex];

  switch (vertex->get_num_dimensions()) {
  case 1:
    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos1());
    break;
  case 2:
    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos2());
    break;
  case 3:
    vdef._vert3_index = record_unique(_unique_vert3, vertex->get_pos3());
    break;
  case 4:
    vdef._vert4_index = record_unique(_unique_vert4, vertex->get_pos4());
    break;
  }

  if (vertex->has_uv("")) {
    vdef._uv2_index = record_unique(_unique_uv2, vertex->get_uv(""));
  } else if (vertex->has_uvw("")) {
    vdef._uv3_index = record_unique(_unique_uv3, vertex->get_uvw(""));
  }

  if (vertex->has_normal()) {
    vdef._norm_index = record_unique(_unique_norm, vertex->get_normal());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::record_unique
//       Access: Private
//  Description: Records the indicated vertex value, returning the
//               shared index if this value already appears elsewhere
//               in the table, or the new unique index if this is the
//               first time this value appears.
////////////////////////////////////////////////////////////////////
int EggToObjConverter::
record_unique(UniqueVertices &unique, const LVecBase4d &vec) {
  // We record a zero-based index.  Note that we will actually write
  // out a one-based index to the obj file, as required by the
  // standard.
  int index = unique.size();
  UniqueVertices::iterator ui = unique.insert(UniqueVertices::value_type(vec, index)).first;
  return (*ui).second;
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::record_unique
//       Access: Private
//  Description: Records the indicated vertex value, returning the
//               shared index if this value already appears elsewhere
//               in the table, or the new unique index if this is the
//               first time this value appears.
////////////////////////////////////////////////////////////////////
int EggToObjConverter::
record_unique(UniqueVertices &unique, const LVecBase3d &vec) {
  return record_unique(unique, LVecBase4d(vec[0], vec[1], vec[2], 0.0));
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::record_unique
//       Access: Private
//  Description: Records the indicated vertex value, returning the
//               shared index if this value already appears elsewhere
//               in the table, or the new unique index if this is the
//               first time this value appears.
////////////////////////////////////////////////////////////////////
int EggToObjConverter::
record_unique(UniqueVertices &unique, const LVecBase2d &vec) {
  return record_unique(unique, LVecBase4d(vec[0], vec[1], 0.0, 0.0));
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::record_unique
//       Access: Private
//  Description: Records the indicated vertex value, returning the
//               shared index if this value already appears elsewhere
//               in the table, or the new unique index if this is the
//               first time this value appears.
////////////////////////////////////////////////////////////////////
int EggToObjConverter::
record_unique(UniqueVertices &unique, double pos) {
  return record_unique(unique, LVecBase4d(pos, 0.0, 0.0, 0.0));
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::write_vertices
//       Access: Private
//  Description: Actually writes the vertex values recorded in the
//               indicated table to the obj output stream.
////////////////////////////////////////////////////////////////////
void EggToObjConverter::
write_vertices(ostream &out, const string &prefix, int num_components, 
               const UniqueVertices &unique) {
  // First, sort the list into numeric order.
  int num_vertices = (int)unique.size();
  const LVecBase4d **vertices = (const LVecBase4d **)alloca(num_vertices * sizeof(LVecBase4d *));
  memset(vertices, 0, num_vertices * sizeof(LVecBase4d *));
  UniqueVertices::const_iterator ui;
  for (ui = unique.begin(); ui != unique.end(); ++ui) {
    int index = (*ui).second;
    const LVecBase4d &vec = (*ui).first;
    nassertv(index >= 0 && index < num_vertices);
    nassertv(vertices[index] == NULL);
    vertices[index] = &vec;
  }

  for (int i = 0; i < num_vertices; ++i) {
    out << prefix;
    const LVecBase4d &vec = *(vertices[i]);
    for (int ci = 0; ci < num_components; ++ci) {
      out << " " << vec[ci];
    }
    out << "\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggToObjConverter::VertexDef::Constructor
//       Access: Private
//  Description: 
////////////////////////////////////////////////////////////////////
EggToObjConverter::VertexDef::
VertexDef() :
  _vert3_index(-1),
  _vert4_index(-1),
  _uv2_index(-1),
  _uv3_index(-1),
  _norm_index(-1)
{
}
