// Filename: eggGroupNode.cxx
// Created by:  drose (16Jan99)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "eggGroupNode.h"
#include "eggCoordinateSystem.h"
#include "eggData.h"
#include "eggFilenameNode.h"
#include "eggExternalReference.h"
#include "eggPrimitive.h"
#include "eggPolygon.h"
#include "eggVertexPool.h"
#include "eggVertex.h"
#include "eggTextureCollection.h"
#include "eggMaterialCollection.h"
#include "pt_EggTexture.h"
#include "pt_EggMaterial.h"
#include "config_egg.h"

#include "dSearchPath.h"
#include "deg_2_rad.h"

#include <algorithm>

TypeHandle EggGroupNode::_type_handle;


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::Copy constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::
EggGroupNode(const EggGroupNode &copy) : EggNode(copy) {
  if (!copy.empty()) {
    egg_cat.warning()
      << "The EggGroupNode copy constructor does not copy children!\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::Copy assignment operator
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode &EggGroupNode::
operator =(const EggGroupNode &copy) {
  if (!copy.empty()) {
    egg_cat.warning()
      << "The EggGroupNode copy assignment does not copy children!\n";
  }
  EggNode::operator =(copy);
  return *this;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::Destructor
//       Access: Published, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::
~EggGroupNode() {
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::write
//       Access: Published, Virtual
//  Description: Writes the group and all of its children to the
//               indicated output stream in Egg format.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
write(ostream &out, int indent_level) const {
  iterator i;

  // Since joints tend to reference vertex pools, which sometimes
  // appear later in the file, and since generally non-joints don't
  // reference joints, we try to maximize our chance of writing out a
  // one-pass readable egg file by writing joints at the end of the
  // list of children of a particular node.

  for (i = begin(); i != end(); ++i) {
    PT(EggNode) child = (*i);
    if (!child->is_joint()) {
      child->write(out, indent_level);
    }
  }

  for (i = begin(); i != end(); ++i) {
    PT(EggNode) child = (*i);
    if (child->is_joint()) {
      child->write(out, indent_level);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::begin
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
begin() const {
  return _children.begin();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::end
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
end() const {
  return _children.end();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::rbegin
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::reverse_iterator EggGroupNode::
rbegin() const {
  return _children.rbegin();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::rend
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::reverse_iterator EggGroupNode::
rend() const {
  return _children.rend();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::insert
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
insert(iterator position, PT(EggNode) x) {
  prepare_add_child(x);
  return _children.insert((Children::iterator &)position, x);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::erase
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
erase(iterator position) {
  prepare_remove_child(*position);
  return _children.erase((Children::iterator &)position);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::erase
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::iterator EggGroupNode::
erase(iterator first, iterator last) {
  iterator i;
  for (i = first; i != last; ++i) {
    prepare_remove_child(*i);
  }
  return _children.erase((Children::iterator &)first,
                         (Children::iterator &)last);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::replace
//       Access: Published
//  Description: Replaces the node at the indicated position with
//               the indicated node.  It is an error to call this
//               with an invalid position iterator (e.g. end()).
////////////////////////////////////////////////////////////////////
void EggGroupNode::
replace(iterator position, PT(EggNode) x) {
  nassertv(position != end());

  prepare_remove_child(*position);
  prepare_add_child(x);
  *(Children::iterator &)position = x;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::empty
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
empty() const {
  return _children.empty();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::size
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroupNode::size_type EggGroupNode::
size() const {
  return _children.size();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::clear
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
void EggGroupNode::
clear() {
  erase(begin(), end());
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::get_first_child
//       Access: Published
//  Description: Returns the first child in the group's list of
//               children, or NULL if the list of children is empty.
//               Can be used with get_next_child() to return the
//               complete list of children without using the iterator
//               class; however, this is non-thread-safe, and so is
//               not recommended except for languages other than C++
//               which cannot use the iterators.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
get_first_child() {
  _gnc_iterator = begin();
  return get_next_child();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::get_next_child
//       Access: Published
//  Description: Returns the next child in the group's list of
//               children since the last call to get_first_child() or
//               get_next_child(), or NULL if the last child has been
//               returned.  Can be used with get_first_child() to
//               return the complete list of children without using
//               the iterator class; however, this is non-thread-safe,
//               and so is not recommended except for languages other
//               than C++ which cannot use the iterators.
//
//               It is an error to call this without previously
//               calling get_first_child().
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
get_next_child() {
  if (_gnc_iterator != end()) {
    return *_gnc_iterator++;
  }
  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::add_child
//       Access: Published
//  Description: Adds the indicated child to the group and returns it.
//               If the child node is already a child of some other
//               node, removes it first.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
add_child(EggNode *node) {
  PT(EggNode) ptnode = node;
  if (node->_parent != NULL) {
    node->_parent->remove_child(node);
  }
  prepare_add_child(node);
  _children.push_back(node);
  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::remove_child
//       Access: Published
//  Description: Removes the indicated child node from the group and
//               returns it.  If the child was not already in the
//               group, does nothing and returns NULL.
////////////////////////////////////////////////////////////////////
PT(EggNode) EggGroupNode::
remove_child(EggNode *node) {
  PT(EggNode) ptnode = node;
  iterator i = find(begin(), end(), ptnode);
  if (i == end()) {
    return PT(EggNode)();
  } else {
    // erase() calls prepare_remove_child().
    erase(i);
    return ptnode;
  }
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::steal_children
//       Access: Published
//  Description: Moves all the children from the other node to this
//               one.  This is especially useful because the group
//               node copy assignment operator does not copy children.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
steal_children(EggGroupNode &other) {
  Children::iterator ci;
  for (ci = other._children.begin();
       ci != other._children.end();
       ++ci) {
    other.prepare_remove_child(*ci);
    prepare_add_child(*ci);
  }

  _children.splice(_children.end(), other._children);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::find_child
//       Access: Published
//  Description: Returns the child of this node whose name is the
//               indicated string, or NULL if there is no child of
//               this node by that name.  Does not search recursively.
////////////////////////////////////////////////////////////////////
EggNode *EggGroupNode::
find_child(const string &name) const {
  Children::const_iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    EggNode *child = (*ci);
    if (child->get_name() == name) {
      return child;
    }
  }

  return NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::has_absolute_pathnames
//       Access: Published
//  Description: Returns true if any nodes at this level and below
//               include a reference to a file via an absolute
//               pathname, or false if all references are relative.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_absolute_pathnames() const {
  Children::const_iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = *ci;
    if (child->is_of_type(EggTexture::get_class_type())) {
      EggTexture *tex = DCAST(EggTexture, child);
      if (!tex->get_filename().is_local()) {
        if (egg_cat.is_debug()) {
          egg_cat.debug()
            << "Absolute pathname: " << tex->get_filename()
            << "\n";
        }
        return true;
      }

      if (tex->has_alpha_filename()) {
        if (!tex->get_alpha_filename().is_local()) {
          if (egg_cat.is_debug()) {
            egg_cat.debug()
              << "Absolute pathname: " << tex->get_alpha_filename()
              << "\n";
          }
          return true;
        }
      }

    } else if (child->is_of_type(EggFilenameNode::get_class_type())) {
      EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
      if (!fnode->get_filename().is_local()) {
        if (egg_cat.is_debug()) {
          egg_cat.debug()
            << "Absolute pathname: " << fnode->get_filename()
            << "\n";
        }
        return true;
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      if (DCAST(EggGroupNode, child)->has_absolute_pathnames()) {
        return true;
      }
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::resolve_filenames
//       Access: Published
//  Description: Walks the tree and attempts to resolve any filenames
//               encountered.  This looks up filenames in the search
//               path, etc.  It does not automatically search the
//               egg_path for missing files.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
resolve_filenames(const DSearchPath &searchpath) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = *ci;
    if (child->is_of_type(EggTexture::get_class_type())) {
      EggTexture *tex = DCAST(EggTexture, child);
      Filename tex_filename = tex->get_filename();
      tex_filename.resolve_filename(searchpath);
      tex->set_filename(tex_filename);

      if (tex->has_alpha_filename()) {
        Filename alpha_filename = tex->get_alpha_filename();
        alpha_filename.resolve_filename(searchpath);
        tex->set_alpha_filename(alpha_filename);
      }

    } else if (child->is_of_type(EggFilenameNode::get_class_type())) {
      EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
      Filename filename = fnode->get_filename();
      filename.resolve_filename(searchpath, fnode->get_default_extension());
      fnode->set_filename(filename);

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      DCAST(EggGroupNode, child)->resolve_filenames(searchpath);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::force_filenames
//       Access: Published
//  Description: Similar to resolve_filenames, but each non-absolute
//               filename encountered is arbitrarily taken to be in
//               the indicated directory, whether or not the so-named
//               filename exists.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
force_filenames(const Filename &directory) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = *ci;
    if (child->is_of_type(EggTexture::get_class_type())) {
      EggTexture *tex = DCAST(EggTexture, child);
      Filename tex_filename = tex->get_filename();
      if (tex_filename.is_local()) {
        tex->set_filename(Filename(directory, tex_filename));
      }

      if (tex->has_alpha_filename()) {
        Filename alpha_filename = tex->get_alpha_filename();
        if (alpha_filename.is_local()) {
          tex->set_alpha_filename(Filename(directory, alpha_filename));
        }
      }

    } else if (child->is_of_type(EggFilenameNode::get_class_type())) {
      EggFilenameNode *fnode = DCAST(EggFilenameNode, child);
      Filename filename = fnode->get_filename();
      if (filename.is_local()) {
        fnode->set_filename(Filename(directory, filename));
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      DCAST(EggGroupNode, child)->force_filenames(directory);
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::reverse_vertex_ordering
//       Access: Published
//  Description: Reverses the vertex ordering of all polygons defined
//               at this node and below.  Does not change the surface
//               normals, if any.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
reverse_vertex_ordering() {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = *ci;
    if (child->is_of_type(EggPrimitive::get_class_type())) {
      EggPrimitive *prim = DCAST(EggPrimitive, child);
      prim->reverse_vertex_ordering();

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      DCAST(EggGroupNode, child)->reverse_vertex_ordering();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::recompute_vertex_normals
//       Access: Published
//  Description: Recomputes all the vertex normals for polygon
//               geometry at this group node and below so that they
//               accurately reflect the vertex positions.  A shared
//               edge between two polygons (even in different groups)
//               is considered smooth if the angle between the two
//               edges is less than threshold degrees.
//
//               This function also removes degenerate polygons that
//               do not have enough vertices to define a normal.  It
//               does not affect normals for other kinds of primitives
//               like Nurbs or Points.
//
//               This function does not remove or adjust vertices in
//               the vertex pool; it only adds new vertices with the
//               correct normals.  Thus, it is a good idea to call
//               remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
recompute_vertex_normals(double threshold, CoordinateSystem cs) {
  // First, collect all the vertices together with their shared
  // polygons.
  NVertexCollection collection;
  r_collect_vertex_normals(collection, threshold, cs);

  // Now bust them into separate groups according to the edge
  // threshold.  Two polygons that share a vertex belong in the same
  // group only if the angle between their normals is within the
  // threshold.

  double cos_angle = cos(deg_2_rad(threshold));

  NVertexCollection::iterator ci;
  for (ci = collection.begin(); ci != collection.end(); ++ci) {
    NVertexGroup &group = (*ci).second;

    // Here's a group of polygons that share a vertex.  Build up a new
    // group that consists of just the first polygon and all the ones
    // that are within threshold degrees from it.
    NVertexGroup::iterator gi;
    gi = group.begin();
    while (gi != group.end()) {
      const NVertexReference &base_ref = (*gi);
      NVertexGroup new_group;
      NVertexGroup leftover_group;
      new_group.push_back(base_ref);
      ++gi;

      while (gi != group.end()) {
        const NVertexReference &ref = (*gi);
        double dot = base_ref._normal.dot(ref._normal);
        if (dot > cos_angle) {
          // These polygons are close enough to the same angle.
          new_group.push_back(ref);
        } else {
          // These polygons are not.
          leftover_group.push_back(ref);
        }
        ++gi;
      }

      // Now new_group is a collection of connected polygons and the
      // vertices that connect them.  Smooth these vertices.
      do_compute_vertex_normals(new_group);

      // And reset the group of remaining polygons.
      group.swap(leftover_group);
      gi = group.begin();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::recompute_polygon_normals
//       Access: Published
//  Description: Recomputes all the polygon normals for polygon
//               geometry at this group node and below so that they
//               accurately reflect the vertex positions.  Normals are
//               removed from the vertices and defined only on
//               polygons, giving the geometry a faceted appearance.
//
//               This function also removes degenerate polygons that
//               do not have enough vertices to define a normal.  It
//               does not affect normals for other kinds of primitives
//               like Nurbs or Points.
//
//               This function does not remove or adjust vertices in
//               the vertex pool; it only adds new vertices with the
//               normals removed.  Thus, it is a good idea to call
//               remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
recompute_polygon_normals(CoordinateSystem cs) {
  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggPolygon::get_class_type())) {
      EggPolygon *polygon = DCAST(EggPolygon, child);

      if (!polygon->recompute_polygon_normal(cs)) {
        // The polygon is degenerate.  Remove it.
        prepare_remove_child(child);
        _children.erase(ci);

      } else {
        // Remove the normal from each polygon vertex.
        size_t num_vertices = polygon->size();
        for (size_t i = 0; i < num_vertices; i++) {
          EggVertex *vertex = polygon->get_vertex(i);
          EggVertexPool *pool = vertex->get_pool();

          if (vertex->has_normal()) {
            EggVertex new_vertex(*vertex);
            new_vertex.clear_normal();
            EggVertex *unique = pool->create_unique_vertex(new_vertex);
            unique->copy_grefs_from(*vertex);

            polygon->set_vertex(i, unique);
          }
        }
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      DCAST(EggGroupNode, child)->recompute_polygon_normals(cs);
    }

    ci = cnext;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::strip_normals
//       Access: Published
//  Description: Removes all normals from primitives, and the vertices
//               they reference, at this node and below.
//
//               This function does not remove or adjust vertices in
//               the vertex pool; it only adds new vertices with the
//               normal removed.  Thus, it is a good idea to call
//               remove_unused_vertices() after calling this.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
strip_normals() {
  Children::iterator ci;
  for (ci = _children.begin(); ci != _children.end(); ++ci) {
    EggNode *child = *ci;

    if (child->is_of_type(EggPrimitive::get_class_type())) {
      EggPrimitive *prim = DCAST(EggPrimitive, child);
      prim->clear_normal();

      // Remove the normal from each prim vertex.
      size_t num_vertices = prim->size();
      for (size_t i = 0; i < num_vertices; i++) {
        EggVertex *vertex = prim->get_vertex(i);
        EggVertexPool *pool = vertex->get_pool();

        if (vertex->has_normal()) {
          EggVertex new_vertex(*vertex);
          new_vertex.clear_normal();
          EggVertex *unique = pool->create_unique_vertex(new_vertex);
          unique->copy_grefs_from(*vertex);

          prim->set_vertex(i, unique);
        }
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      DCAST(EggGroupNode, child)->strip_normals();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::triangulate_polygons
//       Access: Published
//  Description: Replace all higher-order polygons at this point in
//               the scene graph and below with triangles.  Returns
//               the total number of new triangles produced, less
//               degenerate polygons removed.
//
//               If convex_also is true, both concave and convex
//               polygons will be subdivided into triangles;
//               otherwise, only concave polygons will be subdivided,
//               and convex polygons will be largely unchanged.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
triangulate_polygons(bool convex_also) {
  int num_produced = 0;

  Children children_copy = _children;

  Children::iterator ci;
  for (ci = children_copy.begin();
       ci != children_copy.end();
       ++ci) {
    EggNode *child = (*ci);

    if (child->is_of_type(EggPolygon::get_class_type())) {
      EggPolygon *poly = DCAST(EggPolygon, child);
      poly->triangulate_in_place(convex_also);

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      num_produced += DCAST(EggGroupNode, child)->triangulate_polygons(convex_also);
    }
  }

  num_produced += max(0, (int)(_children.size() - children_copy.size()));
  return num_produced;
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::remove_unused_vertices
//       Access: Published
//  Description: Removes all vertices from VertexPools within this
//               group or below that are not referenced by at least
//               one primitive.  Also renumbers all vertices after the
//               operation so their indices are consecutive, beginning
//               at zero.  Returns the total number of vertices removed.
//
//               Note that this operates on the VertexPools within
//               this group level, without respect to primitives that
//               reference these vertices (unlike other functions like
//               strip_normals()).  It is therefore most useful to
//               call this on the EggData root, rather than on a
//               subgroup within the hierarchy, since a VertexPool may
//               appear anywhere in the hierarchy.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
remove_unused_vertices() {
  int num_removed = 0;

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggVertexPool::get_class_type())) {
      EggVertexPool *vpool = DCAST(EggVertexPool, child);
      num_removed += vpool->remove_unused_vertices();

      if (vpool->empty()) {
        // If, after removing all the vertices, there's nothing left
        // in the vertex pool, then remove the whole vertex pool.
        _children.erase(ci);
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      num_removed += DCAST(EggGroupNode, child)->remove_unused_vertices();
    }

    ci = cnext;
  }

  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::remove_invalid_primitives
//       Access: Published
//  Description: Removes primitives at this level and below which
//               appear to be degenerate; e.g. polygons with fewer
//               than 3 vertices, etc.  Returns the number of
//               primitives removed.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
remove_invalid_primitives() {
  int num_removed = 0;

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggPrimitive::get_class_type())) {
      EggPrimitive *prim = DCAST(EggPrimitive, child);
      if (!prim->cleanup()) {
        _children.erase(ci);
        num_removed++;
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      num_removed += DCAST(EggGroupNode, child)->remove_invalid_primitives();
    }

    ci = cnext;
  }

  return num_removed;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::has_primitives
//       Access: Published, Virtual
//  Description: Returns true if there are any primitives
//               (e.g. polygons) defined within this group or below,
//               false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_primitives() const {
  Children::const_iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    if ((*ci)->has_primitives()) {
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::joint_has_primitives
//       Access: Published, Virtual
//  Description: Returns true if there are any primitives
//               (e.g. polygons) defined within this group or below,
//               but the search does not include nested joints.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
joint_has_primitives() const {
  Children::const_iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = (*ci);

    if (!child->is_joint()) {
      if (child->joint_has_primitives()) {
        return true;
      }
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::has_normals
//       Access: Published, Virtual
//  Description: Returns true if any of the primitives (e.g. polygons)
//               defined within this group or below have either face
//               or vertex normals defined, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
has_normals() const {
  Children::const_iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    if ((*ci)->has_normals()) {
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::update_under
//       Access: Protected, Virtual
//  Description: This function is called from within EggGroupNode
//               whenever the parentage of the node has changed.  It
//               should update the depth and under_instance flags
//               accordingly.
//
//               Offset is the difference between the old depth value
//               and the new value.  It should be consistent with the
//               supplied depth value.  If it is not, we have some
//               error.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
update_under(int depth_offset) {
  EggNode::update_under(depth_offset);

  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    nassertv((*ci)->get_parent() == this);
    (*ci)->update_under(depth_offset);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_transform
//       Access: Protected, Virtual
//  Description: This is called from within the egg code by
//               transform().  It applies a transformation matrix
//               to the current node in some sensible way, then
//               continues down the tree.
//
//               The first matrix is the transformation to apply; the
//               second is its inverse.  The third parameter is the
//               coordinate system we are changing to, or CS_default
//               if we are not changing coordinate systems.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_transform(const LMatrix4d &mat, const LMatrix4d &inv,
            CoordinateSystem to_cs) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    (*ci)->r_transform(mat, inv, to_cs);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_transform_vertices
//       Access: Protected, Virtual
//  Description: This is called from within the egg code by
//               transform_vertices_only()().  It applies a
//               transformation matrix to the current node in some
//               sensible way (if the current node is a vertex pool
//               with vertices), then continues down the tree.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_transform_vertices(const LMatrix4d &mat) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    (*ci)->r_transform_vertices(mat);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_mark_coordsys
//       Access: Protected, Virtual
//  Description: This is only called immediately after loading an egg
//               file from disk, to propagate the value found in the
//               CoordinateSystem entry (or the default Y-up
//               coordinate system) to all nodes that care about what
//               the coordinate system is.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_mark_coordsys(CoordinateSystem cs) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    (*ci)->r_mark_coordsys(cs);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_flatten_transforms
//       Access: Protected, Virtual
//  Description: The recursive implementation of flatten_transforms().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_flatten_transforms() {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    (*ci)->r_flatten_transforms();
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_apply_texmats
//       Access: Protected, Virtual
//  Description: The recursive implementation of apply_texmats().
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_apply_texmats(EggTextureCollection &textures) {
  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    (*ci)->r_apply_texmats(textures);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::find_coordsys_entry()
//       Access: Protected
//  Description: Walks the tree, looking for an EggCoordinateSystem
//               entry.  If one is found, extracts it and returns its
//               value.  If multiple entries are found, extracts all
//               of them and returns CS_invalid if they disagree.
////////////////////////////////////////////////////////////////////
CoordinateSystem EggGroupNode::
find_coordsys_entry() {
  CoordinateSystem coordsys = CS_default;

  // We can do this ci/cnext iteration through the list as we modify
  // it, only because we know this works with an STL list type
  // container.  If this were a vector or a set, this wouldn't
  // necessarily work.

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggCoordinateSystem::get_class_type())) {
      CoordinateSystem new_cs =
        DCAST(EggCoordinateSystem, child)->get_value();

      // Now remove the CoordinateSystem entry from our child list.
      prepare_remove_child(child);
      _children.erase(ci);

      if (new_cs != CS_default) {
        if (coordsys != CS_default && coordsys != new_cs) {
          coordsys = CS_invalid;
        } else {
          coordsys = new_cs;
        }
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      CoordinateSystem new_cs =
        DCAST(EggGroupNode, child)->find_coordsys_entry();
      if (new_cs != CS_default) {
        if (coordsys != CS_default && coordsys != new_cs) {
          coordsys = CS_invalid;
        } else {
          coordsys = new_cs;
        }
      }
    }

    ci = cnext;
  }

  return coordsys;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::find_textures()
//       Access: Protected
//  Description: Walks the tree, looking for EggTextures.  Each
//               EggTexture that is found is removed from the
//               hierarchy and added to the EggTextureCollection.
//               Returns the number of EggTextures found.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
find_textures(EggTextureCollection *collection) {
  int num_found = 0;

  // We can do this ci/cnext iteration through the list as we modify
  // it, only because we know this works with an STL list type
  // container.  If this were a vector or a set, this wouldn't
  // necessarily work.

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggTexture::get_class_type())) {
      PT_EggTexture tex = DCAST(EggTexture, child);

      // Now remove the EggTexture entry from our child list.
      prepare_remove_child(tex);
      _children.erase(ci);

      // And add it to the collection.
      collection->add_texture(tex);
      num_found++;

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      num_found +=
        DCAST(EggGroupNode, child)->find_textures(collection);
    }

    ci = cnext;
  }

  return num_found;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::find_materials()
//       Access: Protected
//  Description: Walks the tree, looking for EggMaterials.  Each
//               EggMaterial that is found is removed from the
//               hierarchy and added to the EggMaterialCollection.
//               Returns the number of EggMaterials found.
////////////////////////////////////////////////////////////////////
int EggGroupNode::
find_materials(EggMaterialCollection *collection) {
  int num_found = 0;

  // We can do this ci/cnext iteration through the list as we modify
  // it, only because we know this works with an STL list type
  // container.  If this were a vector or a set, this wouldn't
  // necessarily work.

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggMaterial::get_class_type())) {
      PT_EggMaterial tex = DCAST(EggMaterial, child);

      // Now remove the EggMaterial entry from our child list.
      prepare_remove_child(tex);
      _children.erase(ci);

      // And add it to the collection.
      collection->add_material(tex);
      num_found++;

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      num_found +=
        DCAST(EggGroupNode, child)->find_materials(collection);
    }

    ci = cnext;
  }

  return num_found;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_load_externals
//       Access: Protected
//  Description: Walks the tree and locates unloaded external
//               reference nodes, which it attempts to locate and load
//               in.  The reference node is replaced with the entire
//               subtree loaded.  This is intended to be called from
//               EggData::load_externals().
////////////////////////////////////////////////////////////////////
bool EggGroupNode::
r_load_externals(const DSearchPath &searchpath, CoordinateSystem coordsys) {
  bool success = true;

  Children::iterator ci;
  for (ci = _children.begin();
       ci != _children.end();
       ++ci) {
    EggNode *child = *ci;
    if (child->is_of_type(EggExternalReference::get_class_type())) {
      PT(EggExternalReference) ref = DCAST(EggExternalReference, child);

      // Replace the reference with an empty group node.  When we load
      // the external file successfully, we'll put its contents here.
      Filename filename = ref->get_filename();
      EggGroupNode *new_node =
        new EggGroupNode(filename.get_basename_wo_extension());
      replace(ci, new_node);

      if (!EggData::resolve_egg_filename(filename, searchpath)) {
        egg_cat.error()
          << "Could not locate " << filename << " in "
          << searchpath << "\n";
      } else {
        // Now define a new EggData structure to hold the external
        // reference, and load it.
        EggData ext_data;
        ext_data.set_coordinate_system(coordsys);
        ext_data.set_auto_resolve_externals(true);
        if (ext_data.read(filename)) {
          // The external file was read correctly.  Add its contents
          // into the tree at this point.
          success =
            ext_data.load_externals(searchpath)
            && success;
          new_node->steal_children(ext_data);
        }
      }

    } else if (child->is_of_type(EggGroupNode::get_class_type())) {
      EggGroupNode *group_child = DCAST(EggGroupNode, child);
      success =
        group_child->r_load_externals(searchpath, coordsys)
        && success;
    }
  }
  return success;
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::prepare_add_child
//       Access: Private
//  Description: Marks the node as a child of the group.  This is an
//               internal function called by the STL-like functions
//               push_back() and insert(), in preparation for actually
//               adding the child.
//
//               It is an error to add a node that is already a child
//               of this group or some other group.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
prepare_add_child(EggNode *node) {
  nassertv(node != (EggNode *)NULL);
  // Make sure the node is not already a child of some other group.
  nassertv(node->get_parent() == NULL);
  nassertv(node->get_depth() == 0);
  node->_parent = this;

  node->update_under(get_depth() + 1);
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::prepare_remove_child
//       Access: Private
//  Description: Marks the node as removed from the group.  This is an
//               internal function called by the STL-like functions
//               pop_back() and erase(), in preparation for actually
//               doing the removal.
//
//               It is an error to attempt to remove a node that is
//               not already a child of this group.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
prepare_remove_child(EggNode *node) {
  nassertv(node != (EggNode *)NULL);
  // Make sure the node is in fact a child of this group.
  nassertv(node->get_parent() == this);
  nassertv(node->get_depth() == get_depth() + 1);
  node->_parent = NULL;

  node->update_under(-(get_depth() + 1));
}



////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_collect_vertex_normals
//       Access: Private
//  Description: This is part of the implementation of
//               recompute_vertex_normals().  It walks the scene graph
//               at this group node and below, identifying all the
//               polygons and the vertices they have in common.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
r_collect_vertex_normals(EggGroupNode::NVertexCollection &collection,
                         double threshold, CoordinateSystem cs) {
  // We can do this ci/cnext iteration through the list as we modify
  // it, only because we know this works with an STL list type
  // container.  If this were a vector or a set, this wouldn't
  // necessarily work.

  Children::iterator ci, cnext;
  ci = _children.begin();
  while (ci != _children.end()) {
    cnext = ci;
    ++cnext;
    EggNode *child = *ci;

    if (child->is_of_type(EggPolygon::get_class_type())) {
      EggPolygon *polygon = DCAST(EggPolygon, child);
      polygon->clear_normal();

      NVertexReference ref;
      ref._polygon = polygon;
      if (!polygon->calculate_normal(ref._normal, cs)) {
        // The polygon is degenerate.  Remove it.

        prepare_remove_child(child);
        _children.erase(ci);

      } else {
        // Now add each vertex from the polygon separately to our
        // collection.
        size_t num_vertices = polygon->size();
        for (size_t i = 0; i < num_vertices; i++) {
          EggVertex *vertex = polygon->get_vertex(i);
          ref._vertex = i;
          collection[vertex->get_pos3()].push_back(ref);
        }
      }

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

      // We can't share vertices across an Instance node.  Don't
      // even bother trying.  Instead, just restart.
      if (group->is_under_instance()) {
        group->recompute_vertex_normals(threshold, cs);
      } else {
        group->r_collect_vertex_normals(collection, threshold, cs);
      }
    }

    ci = cnext;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroupNode::r_collect_vertex_normals
//       Access: Private
//  Description: This is part of the implementation of
//               recompute_vertex_normals().  It accepts a group of
//               polygons and their common normals, and computes the
//               same normal for all their shared vertices.
////////////////////////////////////////////////////////////////////
void EggGroupNode::
do_compute_vertex_normals(const NVertexGroup &group) {
  nassertv(!group.empty());

  // Determine the common normal.  This is simply the average of all
  // the polygon normals that share this vertex.
  Normald normal(0.0, 0.0, 0.0);
  NVertexGroup::const_iterator gi;
  for (gi = group.begin(); gi != group.end(); ++gi) {
    const NVertexReference &ref = (*gi);
    normal += ref._normal;
  }

  normal /= (double)group.size();
  normal.normalize();

  // Now we have the common normal; apply it to all the vertices.

  for (gi = group.begin(); gi != group.end(); ++gi) {
    const NVertexReference &ref = (*gi);
    EggVertex *vertex = ref._polygon->get_vertex(ref._vertex);
    EggVertexPool *pool = vertex->get_pool();

    EggVertex new_vertex(*vertex);
    new_vertex.set_normal(normal);
    EggVertex *unique = pool->create_unique_vertex(new_vertex);
    unique->copy_grefs_from(*vertex);

    ref._polygon->set_vertex(ref._vertex, unique);
  }
}

