// Filename: eggVertex.cxx
// Created by:  drose (16Jan99)
//
////////////////////////////////////////////////////////////////////
//
// 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 "eggVertex.h"
#include "eggVertexPool.h"
#include "eggParameters.h"
#include "eggGroup.h"
#include "eggMiscFuncs.h"
#include "eggPrimitive.h"

#include "indent.h"
#include "luse.h"
#include "lmatrix.h"
#include "pandabase.h"

#include <math.h>
#include <algorithm>

TypeHandle EggVertex::_type_handle;


////////////////////////////////////////////////////////////////////
//     Function: EggVertex::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggVertex::
EggVertex() {
  _pool = NULL;
  _forward_reference = false;
  _index = -1;
  _external_index = -1;
  set_pos(LPoint3d(0.0, 0.0, 0.0));
  test_pref_integrity();
  test_gref_integrity();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::Copy constructor
//       Access: Published
//  Description: Copies all properties of the vertex except its vertex
//               pool, index number, and group membership.
////////////////////////////////////////////////////////////////////
EggVertex::
EggVertex(const EggVertex &copy)
  : EggObject(copy), EggAttributes(copy),
    _dxyzs(copy._dxyzs),
    _external_index(copy._external_index),
    _pos(copy._pos),
    _num_dimensions(copy._num_dimensions),
    _uv_map(copy._uv_map)
{
  _pool = NULL;
  _forward_reference = false;
  _index = -1;
  test_pref_integrity();
  test_gref_integrity();
}


////////////////////////////////////////////////////////////////////
//     Function: EggVertex::Copy assignment operator
//       Access: Published
//  Description: Copies all properties of the vertex except its vertex
//               pool, index number, and group membership.
////////////////////////////////////////////////////////////////////
EggVertex &EggVertex::
operator = (const EggVertex &copy) {
  EggObject::operator = (copy);
  EggAttributes::operator = (copy);
  _dxyzs = copy._dxyzs;
  _external_index = copy._external_index;
  _pos = copy._pos;
  _num_dimensions = copy._num_dimensions;
  _uv_map = copy._uv_map;

  test_pref_integrity();
  test_gref_integrity();

  return *this;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::Destructor
//       Access: Published, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
EggVertex::
~EggVertex() {
  // We should never destruct a vertex while it still thinks it
  // belongs to a VertexPool.  If we do, we've probably lost a
  // reference count somewhere.
  nassertv(_pool == NULL);

  // Also, a vertex shouldn't be destructed while it's being
  // referenced by a group or a primitive, for the same reason.
  nassertv(_gref.empty());
  nassertv(_pref.empty());
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::has_uv
//       Access: Published
//  Description: Returns true if the vertex has the named UV
//               coordinate pair, and the named UV coordinate pair is
//               2-d, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggVertex::
has_uv(const string &name) const {
  UVMap::const_iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  if (ui != _uv_map.end()) {
    EggVertexUV *uv_obj = (*ui).second;
    return !uv_obj->has_w();
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::has_uvw
//       Access: Published
//  Description: Returns true if the vertex has the named UV
//               coordinate triple, and the named UV coordinate triple is
//               3-d, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggVertex::
has_uvw(const string &name) const {
  UVMap::const_iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  if (ui != _uv_map.end()) {
    EggVertexUV *uv_obj = (*ui).second;
    return uv_obj->has_w();
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::get_uv
//       Access: Published
//  Description: Returns the named UV coordinate pair on the vertex.
//               vertex.  It is an error to call this if has_uv(name)
//               returned false.
////////////////////////////////////////////////////////////////////
TexCoordd EggVertex::
get_uv(const string &name) const {
  UVMap::const_iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  nassertr(ui != _uv_map.end(), TexCoordd::zero());
  return (*ui).second->get_uv();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::get_uvw
//       Access: Published
//  Description: Returns the named UV coordinate triple on the vertex.
//               vertex.  It is an error to call this if has_uvw(name)
//               returned false.
////////////////////////////////////////////////////////////////////
const TexCoord3d &EggVertex::
get_uvw(const string &name) const {
  UVMap::const_iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  nassertr(ui != _uv_map.end(), TexCoord3d::zero());
  return (*ui).second->get_uvw();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::set_uv
//       Access: Published
//  Description: Sets the indicated UV coordinate pair on the vertex.
//               This replaces any UV coordinate pair with the same
//               name already on the vertex, but preserves UV morphs.
////////////////////////////////////////////////////////////////////
void EggVertex::
set_uv(const string &name, const TexCoordd &uv) {
  string fname = EggVertexUV::filter_name(name);
  PT(EggVertexUV) &uv_obj = _uv_map[fname];

  if (uv_obj.is_null()) {
    uv_obj = new EggVertexUV(fname, uv);
  } else {
    uv_obj = new EggVertexUV(*uv_obj);
    uv_obj->set_uv(uv);
  }

  nassertv(get_uv(fname) == uv);
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::set_uvw
//       Access: Published
//  Description: Sets the indicated UV coordinate triple on the vertex.
//               This replaces any UV coordinate pair or triple with
//               the same name already on the vertex, but preserves UV
//               morphs.
////////////////////////////////////////////////////////////////////
void EggVertex::
set_uvw(const string &name, const TexCoord3d &uvw) {
  string fname = EggVertexUV::filter_name(name);
  PT(EggVertexUV) &uv_obj = _uv_map[fname];

  if (uv_obj.is_null()) {
    uv_obj = new EggVertexUV(fname, uvw);
  } else {
    uv_obj = new EggVertexUV(*uv_obj);
    uv_obj->set_uvw(uvw);
  }

  nassertv(get_uvw(fname) == uvw);
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::get_uv_obj
//       Access: Published
//  Description: Returns the named EggVertexUV object, which defines
//               both the UV coordinate pair for this name and the UV
//               morphs.  This object might be shared between multiple
//               vertices.  You should not attempt to modify this
//               object; instead, call modify_uv_object to return a
//               modifiable pointer.
////////////////////////////////////////////////////////////////////
const EggVertexUV *EggVertex::
get_uv_obj(const string &name) const {
  UVMap::const_iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  if (ui != _uv_map.end()) {
    return (*ui).second;
  }
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::modify_uv_obj
//       Access: Published
//  Description: Returns a modifiable pointer to the named EggVertexUV
//               object, which defines both the UV coordinate pair for
//               this name and the UV morphs.  Returns NULL if there
//               is no such named UV object.
////////////////////////////////////////////////////////////////////
EggVertexUV *EggVertex::
modify_uv_obj(const string &name) {
  UVMap::iterator ui = _uv_map.find(EggVertexUV::filter_name(name));
  if (ui != _uv_map.end()) {
    if ((*ui).second->get_ref_count() != 1) {
      // Copy on write.
      (*ui).second = new EggVertexUV(*(*ui).second);
    }
    return (*ui).second;
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::set_uv_obj
//       Access: Published
//  Description: Sets the indicated EggVertexUV on the vertex.
//               This replaces any UV coordinate pair with the same
//               name already on the vertex, including UV morphs.
////////////////////////////////////////////////////////////////////
void EggVertex::
set_uv_obj(EggVertexUV *uv) {
  _uv_map[uv->get_name()] = uv;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::clear_uv
//       Access: Published
//  Description: Removes the named UV coordinate pair from the vertex,
//               along with any UV morphs.
///////////////////////////////////////////////////////////////////
void EggVertex::
clear_uv(const string &name) {
  _uv_map.erase(EggVertexUV::filter_name(name));
}



///////////////////////////////////////////////////////////////////
//       Class : GroupRefEntry
// Description : A temporary class used in EggVertex::write(), below,
//               to hold the groups that reference each vertex prior
//               to outputting them as a formatted list.
////////////////////////////////////////////////////////////////////
class GroupRefEntry {
public:
  GroupRefEntry(EggGroup *group, double membership)
    : _group(group), _membership(membership) { }

  bool operator < (const GroupRefEntry &other) const {
    return _group->get_name() < other._group->get_name();
  }
  void output(ostream &out) const {
    out << _group->get_name() << ":" << _membership;
  }

  EggGroup *_group;
  double _membership;
};

INLINE ostream &operator << (ostream &out, const GroupRefEntry &gre) {
  gre.output(out);
  return out;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::write
//       Access: Published
//  Description: Writes the vertex to the indicated output stream in
//               Egg format.
////////////////////////////////////////////////////////////////////
void EggVertex::
write(ostream &out, int indent_level) const {
  test_pref_integrity();
  test_gref_integrity();

  indent(out, indent_level)
    << "<Vertex> " << _index << " {\n";

  // Now output the position.  This might have any number of
  // dimensions up to 4.
  indent(out, indent_level+1);
  for (int i = 0; i < _num_dimensions; i++) {
    out << " " << _pos[i];
  }
  out << "\n";

  UVMap::const_iterator ui;
  for (ui = _uv_map.begin(); ui != _uv_map.end(); ++ui) {
    (*ui).second->write(out, indent_level + 2);
  }

  EggAttributes::write(out, indent_level+2);

  _dxyzs.write(out, indent_level + 2, "<Dxyz>", 3);

  // If the vertex is referenced by one or more groups, write that as
  // a helpful comment.
  if (!_gref.empty()) {
    // We need to build a list of group entries.
    pset<GroupRefEntry> gre;

    GroupRef::const_iterator gi;
    for (gi = _gref.begin(); gi != _gref.end(); ++gi) {
      gre.insert(GroupRefEntry(*gi, (*gi)->get_vertex_membership(this)));
    }

    // Now output the list.
    write_long_list(out, indent_level + 2, gre.begin(), gre.end(), "// ",
                    "", 72);
  }

  indent(out, indent_level)
    << "}\n";
}


////////////////////////////////////////////////////////////////////
//     Function: EggVertex::sorts_less_than
//       Access: Published
//  Description: An ordering operator to compare two vertices for
//               sorting order.  This imposes an arbitrary ordering
//               useful to identify unique vertices.
//
//               Group membership is not considered in this
//               comparison.  This is somewhat problematic, but cannot
//               easily be helped, because considering group
//               membership would make it difficult to add and remove
//               groups from vertices.  It also makes it impossible to
//               meaningfully compare with a concrete EggVertex object
//               (which cannot have group memberships).
//
//               However, this is not altogether bad, because two
//               vertices that are identical in all other properties
//               should generally also be identical in group
//               memberships, else the vertices will tend to fly apart
//               when the joints animate.
////////////////////////////////////////////////////////////////////
bool EggVertex::
sorts_less_than(const EggVertex &other) const {
  if (_external_index != other._external_index) {
    return _external_index < other._external_index;
  }
  if (_num_dimensions != other._num_dimensions) {
    return _num_dimensions < other._num_dimensions;
  }

  int compare =
    _pos.compare_to(other._pos, egg_parameters->_pos_threshold);
  if (compare != 0) {
    return compare < 0;
  }
  if (_dxyzs != other._dxyzs) {
    return _dxyzs < other._dxyzs;
  }

  // Merge-compare the uv maps.
  UVMap::const_iterator ai, bi;
  ai = _uv_map.begin();
  bi = other._uv_map.begin();
  while (ai != _uv_map.end() && bi != other._uv_map.end()) {
    if ((*ai).first < (*bi).first) {
      return true;

    } else if ((*bi).first < (*ai).first) {
      return false;

    } else {
      int compare = (*ai).second->compare_to(*(*bi).second);
      if (compare != 0) {
        return compare < 0;
      }
    }
    ++ai;
    ++bi;
  }
  if (bi != other._uv_map.end()) {
    return true;
  }
  if (ai != _uv_map.end()) {
    return false;
  }

  return EggAttributes::sorts_less_than(other);
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::get_num_local_coord
//       Access: Published
//  Description: Returns the number of primitives that own this vertex
//               whose vertices are interpreted to be in a local
//               coordinate system.
////////////////////////////////////////////////////////////////////
int EggVertex::
get_num_local_coord() const {
  test_pref_integrity();

  PrimitiveRef::const_iterator pri;

  int count = 0;
  for (pri = pref_begin(); pri != pref_end(); ++pri) {
    EggPrimitive *prim = *pri;
    count += (prim->is_local_coord() ? 1 : 0);
  }
  return count;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::get_num_global_coord
//       Access: Published
//  Description: Returns the number of primitives that own this vertex
//               whose vertices are interpreted in the global
//               coordinate system.
////////////////////////////////////////////////////////////////////
int EggVertex::
get_num_global_coord() const {
  test_pref_integrity();

  PrimitiveRef::const_iterator pri;

  int count = 0;
  for (pri = pref_begin(); pri != pref_end(); ++pri) {
    EggPrimitive *prim = *pri;
    count += (prim->is_local_coord() ? 0 : 1);
  }
  return count;
}


////////////////////////////////////////////////////////////////////
//     Function: EggVertex::transform
//       Access: Published, Virtual
//  Description: Applies the indicated transformation matrix to the
//               vertex.
////////////////////////////////////////////////////////////////////
void EggVertex::
transform(const LMatrix4d &mat) {
  _pos = _pos * mat;

  EggMorphVertexList::iterator mi;
  for (mi = _dxyzs.begin(); mi != _dxyzs.end(); ++mi) {
    // We can safely cast the morph object to a non-const, because
    // we're not changing its name, which is the only thing the set
    // cares about preserving.
    EggMorphVertex &morph = (EggMorphVertex &)(*mi);

    morph.set_offset((*mi).get_offset() * mat);
  }

  UVMap::iterator ui;
  for (ui = _uv_map.begin(); ui != _uv_map.end(); ++ui) {
    (*ui).second->transform(mat);
  }

  EggAttributes::transform(mat);
}


////////////////////////////////////////////////////////////////////
//     Function: EggVertex::gref_begin
//       Access: Public
//  Description: Returns an iterator that can, in conjunction with
//               gref_end(), be used to traverse the entire set of
//               groups that reference this vertex.  Each iterator
//               returns a pointer to a group.
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::GroupRef::const_iterator EggVertex::
gref_begin() const {
  return _gref.begin();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::gref_end
//       Access: Public
//  Description: Returns an iterator that can, in conjunction with
//               gref_begin(), be used to traverse the entire set of
//               groups that reference this vertex.  Each iterator
//               returns a pointer to a group.
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::GroupRef::const_iterator EggVertex::
gref_end() const {
  return _gref.end();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::gref_size
//       Access: Public
//  Description: Returns the number of elements between gref_begin()
//               and gref_end().
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::GroupRef::size_type EggVertex::
gref_size() const {
  return _gref.size();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::has_gref
//       Access: Published
//  Description: Returns true if the indicated group references this
//               vertex, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggVertex::
has_gref(const EggGroup *group) const {
  return _gref.count((EggGroup *)group) != 0;
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::copy_grefs_from
//       Access: Published
//  Description: Copies all the group references from the other vertex
//               onto this one.  This assigns the current vertex to
//               exactly the same groups, with exactly the same
//               memberships, as the given one.
//
//               Warning: only an EggVertex allocated from the free
//               store may have groups assigned to it.  Do not attempt
//               to call this on a temporary concrete EggVertex
//               object; a core dump will certainly result.
////////////////////////////////////////////////////////////////////
void EggVertex::
copy_grefs_from(const EggVertex &other) {
  if (&other == this) {
    return;
  }
  test_gref_integrity();
  other.test_gref_integrity();

  clear_grefs();
  test_gref_integrity();

  GroupRef::const_iterator gri;

  for (gri = other.gref_begin(); gri != other.gref_end(); ++gri) {
    EggGroup *group = *gri;
    nassertv(group != NULL);

    group->ref_vertex(this, group->get_vertex_membership(&other));
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::clear_grefs
//       Access: Published
//  Description: Removes all group references from the vertex, so that
//               it is not assigned to any group.
////////////////////////////////////////////////////////////////////
void EggVertex::
clear_grefs() {
  GroupRef gref_copy = _gref;
  GroupRef::const_iterator gri;
  for (gri = gref_copy.begin(); gri != gref_copy.end(); ++gri) {
    EggGroup *group = *gri;
    nassertv(group != NULL);
    group->unref_vertex(this);
  }

  // Now we should have no more refs.
  nassertv(_gref.empty());
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::pref_begin
//       Access: Public
//  Description: Returns an iterator that can, in conjunction with
//               pref_end(), be used to traverse the entire set of
//               primitives that reference this vertex.  Each iterator
//               returns a pointer to a primitive.
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::PrimitiveRef::const_iterator EggVertex::
pref_begin() const {
  return _pref.begin();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::pref_end
//       Access: Public
//  Description: Returns an iterator that can, in conjunction with
//               pref_begin(), be used to traverse the entire set of
//               primitives that reference this vertex.  Each iterator
//               returns a pointer to a primitive.
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::PrimitiveRef::const_iterator EggVertex::
pref_end() const {
  return _pref.end();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::pref_size
//       Access: Public
//  Description: Returns the number of elements between pref_begin()
//               and pref_end().
//
//               This interface is not safe to use outside of
//               PANDAEGG.DLL.
////////////////////////////////////////////////////////////////////
EggVertex::GroupRef::size_type EggVertex::
pref_size() const {
  return _pref.size();
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::has_pref
//       Access: Published
//  Description: Returns the number of times the vertex appears in the
//               indicated primitive, or 0 if it does not appear.
////////////////////////////////////////////////////////////////////
int EggVertex::
has_pref(const EggPrimitive *prim) const {
  return _pref.count((EggPrimitive *)prim);
}

#ifndef NDEBUG

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::test_gref_integrity
//       Access: Published
//  Description: Verifies that the gref list is correct and that all
//               the groups included actually exist and do reference
//               the vertex.
////////////////////////////////////////////////////////////////////
void EggVertex::
test_gref_integrity() const {
  test_ref_count_integrity();

  GroupRef::const_iterator gri;

  for (gri = gref_begin(); gri != gref_end(); ++gri) {
    EggGroup *group = *gri;
    nassertv(group != NULL);
    group->test_ref_count_integrity();

    double membership = group->get_vertex_membership(this);
    nassertv(membership != 0.0);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::test_pref_integrity
//       Access: Published
//  Description: Verifies that the pref list is correct and that all
//               the primitives included actually exist and do
//               reference the vertex.
////////////////////////////////////////////////////////////////////
void EggVertex::
test_pref_integrity() const {
  test_ref_count_integrity();

  PrimitiveRef::const_iterator pri;

  for (pri = pref_begin(); pri != pref_end(); ++pri) {
    EggPrimitive *prim = *pri;
    nassertv(prim != NULL);
    prim->test_ref_count_integrity();

    EggPrimitive::iterator vi;
    vi = find(prim->begin(), prim->end(), this);
    nassertv(vi != prim->end());
  }
}

#endif  // NDEBUG

////////////////////////////////////////////////////////////////////
//     Function: EggVertex::output
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
void EggVertex::
output(ostream &out) const {
  if (get_pool() == NULL) {
    out << "(null):" << get_index();
  } else {
    out << get_pool()->get_name() << ":" << get_index();
  }
}
