// Filename: eggGroup.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 "eggGroup.h"
#include "eggMiscFuncs.h"
#include "eggVertexPool.h"
#include "eggBin.h"
#include "lexerDefs.h"

#include "indent.h"
#include "string_utils.h"
#include "lmatrix.h"
#include "dcast.h"


TypeHandle EggGroup::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroup::
EggGroup(const string &name) : EggGroupNode(name) {
  _flags = 0;
  _flags2 = 0;
  _fps = 0.0;
  _blend_mode = BM_unspecified;
  _blend_operand_a = BO_unspecified;
  _blend_operand_b = BO_unspecified;
  _blend_color = Colorf::zero();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::Copy Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroup::
EggGroup(const EggGroup &copy) {
  (*this) = copy;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::Copy assignment operator
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroup &EggGroup::
operator = (const EggGroup &copy) {
  EggTransform::operator = (copy);
  _flags = copy._flags;
  _flags2 = copy._flags2;
  _collide_mask = copy._collide_mask;
  _from_collide_mask = copy._from_collide_mask;
  _into_collide_mask = copy._into_collide_mask;
  _billboard_center = copy._billboard_center;
  _object_types = copy._object_types;
  _collision_name = copy._collision_name;
  _fps = copy._fps;
  _lod = copy._lod;
  _blend_mode = copy._blend_mode;
  _blend_operand_a = copy._blend_operand_a;
  _blend_operand_b = copy._blend_operand_b;
  _blend_color = copy._blend_color;
  _tag_data = copy._tag_data;
  _default_pose = copy._default_pose;

  unref_all_vertices();
  _vref = copy._vref;

  // We must walk through the vertex ref list, and flag each vertex as
  // now reffed by this group.
  VertexRef::iterator vri;
  for (vri = _vref.begin(); vri != _vref.end(); ++vri) {
    EggVertex *vert = (*vri).first;

    bool inserted = vert->_gref.insert(this).second;
    // Did the group not exist previously in the vertex's gref list?
    // If it was there already, we must be out of sync between
    // vertices and groups.
    nassertr(inserted, *this);
  }

  // These must be down here, because the EggNode assignment operator
  // will force an update_under().  Therefore, we can't call it until
  // all the attributes that affect adjust_under() are in place.
  EggGroupNode::operator = (copy);
  EggRenderMode::operator = (copy);

  return *this;
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroup::Destructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
EggGroup::
~EggGroup() {
  unref_all_vertices();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::set_group_type
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
void EggGroup::
set_group_type(GroupType type) {
  if (type != get_group_type()) {
#ifndef NDEBUG
    if (type != GT_instance) {
      // Only valid to change to a non-instance type if we have no
      // group refs.
      nassertv(_group_refs.empty());
    }
#endif 

    // Make sure the user didn't give us any stray bits.
    nassertv((type & ~F_group_type)==0);
    _flags = (_flags & ~F_group_type) | type;

    // Now we might have changed the type to or from an instance node,
    // so we have to recompute the under_flags.
    update_under(0);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::has_object_type
//       Access: Published
//  Description: Returns true if the indicated object type has been
//               added to the group, or false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroup::
has_object_type(const string &object_type) const {
  vector_string::const_iterator oi;
  for (oi = _object_types.begin(); oi != _object_types.end(); ++oi) {
    if (cmp_nocase_uh((*oi), object_type) == 0) {
      return true;
    }
  }
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::remove_object_type
//       Access: Published
//  Description: Removes the first instance of the indicated object
//               type from the group if it is present.  Returns true
//               if the object type was found and removed, false
//               otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroup::
remove_object_type(const string &object_type) {
  vector_string::iterator oi;
  for (oi = _object_types.begin(); oi != _object_types.end(); ++oi) {
    if (cmp_nocase_uh((*oi), object_type) == 0) {
      _object_types.erase(oi);
      return true;
    }
  }
  return false;
}

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

  switch (get_group_type()) {
  case GT_group:
    write_header(out, indent_level, "<Group>");
    break;

  case GT_instance:
    write_header(out, indent_level, "<Instance>");
    break;

  case GT_joint:
    write_header(out, indent_level, "<Joint>");
    break;

  default:
    // invalid group type
    nassertv(false);
  }

  if (is_of_type(EggBin::get_class_type())) {
    indent(out, indent_level + 2)
      << "// Bin " << DCAST(EggBin, this)->get_bin_number() << "\n";
  }

  if (has_lod()) {
    get_lod().write(out, indent_level + 2);
  }

  write_billboard_flags(out, indent_level + 2);
  write_collide_flags(out, indent_level + 2);
  write_model_flags(out, indent_level + 2);
  write_switch_flags(out, indent_level + 2);

  if (has_transform()) {
    EggTransform::write(out, indent_level + 2, "<Transform>");
  }

  if (get_group_type() == GT_joint && _default_pose.has_transform()) {
    _default_pose.write(out, indent_level + 2, "<DefaultPose>");
  }

  write_object_types(out, indent_level + 2);
  write_decal_flags(out, indent_level + 2);
  write_tags(out, indent_level + 2);
  write_render_mode(out, indent_level + 2);

  if (get_portal_flag()) {
    indent(out, indent_level) << "<Scalar> portal { 1 }\n";
  }

  if (get_polylight_flag()) {
    indent(out, indent_level) << "<Scalar> polylight { 1 }\n";
  }

  if (has_indexed_flag()) {
    indent(out, indent_level) 
      << "<Scalar> indexed { " << get_indexed_flag() << " }\n";
  }

  if (get_blend_mode() != BM_unspecified) {
    indent(out, indent_level)
      << "<Scalar> blend { " << get_blend_mode() << " }\n";
  }

  if (get_blend_operand_a() != BO_unspecified) {
    indent(out, indent_level)
      << "<Scalar> blendop-a { " << get_blend_operand_a() << " }\n";
  }

  if (get_blend_operand_b() != BO_unspecified) {
    indent(out, indent_level)
      << "<Scalar> blendop-b { " << get_blend_operand_b() << " }\n";
  }

  if (has_blend_color()) {
    const Colorf &c = get_blend_color();
    indent(out, indent_level)
      << "<Scalar> blendr { " << c[0] << " }\n";
    indent(out, indent_level)
      << "<Scalar> blendg { " << c[1] << " }\n";
    indent(out, indent_level)
      << "<Scalar> blendb { " << c[2] << " }\n";
    indent(out, indent_level)
      << "<Scalar> blenda { " << c[3] << " }\n";
  }

  GroupRefs::const_iterator gri;
  for (gri = _group_refs.begin(); gri != _group_refs.end(); ++gri) {
    EggGroup *group_ref = (*gri);
    indent(out, indent_level + 2)
      << "<Ref> { " << group_ref->get_name() << " }\n";
  }

  // We have to write the children nodes before we write the vertex
  // references, since we might be referencing a vertex that's defined
  // in one of those children nodes!
  EggGroupNode::write(out, indent_level + 2);
  write_vertex_ref(out, indent_level + 2);

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

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_billboard_flags
//       Access: Published
//  Description: Writes just the <Billboard> entry and related fields to
//               the indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_billboard_flags(ostream &out, int indent_level) const {
  if (get_billboard_type() != BT_none) {
    indent(out, indent_level)
      << "<Billboard> { " << get_billboard_type() << " }\n";
  }

  if (has_billboard_center()) {
    indent(out, indent_level)
      << "<BillboardCenter> { " << get_billboard_center() << " }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_collide_flags
//       Access: Published
//  Description: Writes just the <Collide> entry and related fields to
//               the indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_collide_flags(ostream &out, int indent_level) const {
  if (get_cs_type() != CST_none) {
    indent(out, indent_level) << "<Collide> ";
    if (has_collision_name()) {
      enquote_string(out, get_collision_name()) << " ";
    }
    out << "{ " << get_cs_type();
    if (get_collide_flags() != CF_none) {
      out << " " << get_collide_flags();
    }
    out << " }\n";
  }

  if (has_collide_mask()) {
    indent(out, indent_level)
      << "<Scalar> collide-mask { 0x";
    get_collide_mask().output_hex(out, 0);
    out << " }\n";
  }

  if (has_from_collide_mask()) {
    indent(out, indent_level)
      << "<Scalar> from-collide-mask { 0x";
    get_from_collide_mask().output_hex(out, 0);
    out << " }\n";
  }

  if (has_into_collide_mask()) {
    indent(out, indent_level)
      << "<Scalar> into-collide-mask { 0x";
    get_into_collide_mask().output_hex(out, 0);
    out << " }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_model_flags
//       Access: Published
//  Description: Writes the <Model> flag and related flags to the
//               indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_model_flags(ostream &out, int indent_level) const {
  if (get_dcs_type() != DC_unspecified) {
    indent(out, indent_level) 
      << "<DCS> { " << get_dcs_type() << " }\n";
  }

  if (get_dart_type() != DT_none) {
    indent(out, indent_level)
      << "<Dart> { " << get_dart_type() << " }\n";
  }

  if (get_model_flag()) {
    indent(out, indent_level) << "<Model> { 1 }\n";
  }

  if (get_texlist_flag()) {
    indent(out, indent_level) << "<TexList> { 1 }\n";
  }

  if (get_direct_flag()) {
    indent(out, indent_level) << "<Scalar> direct { 1 }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_switch_flags
//       Access: Published
//  Description: Writes the <Switch> flag and related flags to the
//               indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_switch_flags(ostream &out, int indent_level) const {
  if (get_switch_flag()) {
    indent(out, indent_level) << "<Switch> { 1 }\n";
    if (get_switch_fps() != 0.0) {
      indent(out, indent_level)
        << "<Scalar> fps { " << get_switch_fps() << " }\n";
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_object_types
//       Access: Published
//  Description: Writes just the <ObjectTypes> entries, if any, to the
//               indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_object_types(ostream &out, int indent_level) const {
  vector_string::const_iterator oi;
  for (oi = _object_types.begin(); oi != _object_types.end(); ++oi) {
    indent(out, indent_level)
      << "<ObjectType> { ";
    enquote_string(out, (*oi)) << " }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_decal_flags
//       Access: Published
//  Description: Writes the flags related to decaling, if any.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_decal_flags(ostream &out, int indent_level) const {
  if (get_decal_flag()) {
    indent(out, indent_level) << "<Scalar> decal { 1 }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_tags
//       Access: Published
//  Description: Writes just the <Tag> entries, if any, to the
//               indicated ostream.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_tags(ostream &out, int indent_level) const {
  TagData::const_iterator ti;
  for (ti = _tag_data.begin(); ti != _tag_data.end(); ++ti) {
    const string &key = (*ti).first;
    const string &value = (*ti).second;

    indent(out, indent_level) << "<Tag> ";
    enquote_string(out, key) << " {\n";
    enquote_string(out, value, indent_level + 2) << "\n";
    indent(out, indent_level) << "}\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_render_mode
//       Access: Published
//  Description: Writes the flags inherited from EggRenderMode and
//               similar flags that control obscure render effects.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_render_mode(ostream &out, int indent_level) const {
  EggRenderMode::write(out, indent_level);

  if (get_nofog_flag()) {
    indent(out, indent_level) << "<Scalar> no-fog { 1 }\n";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::is_joint
//       Access: Published, Virtual
//  Description: Returns true if this particular node represents a
//               <Joint> entry or not.  This is a handy thing to know
//               since Joints are sorted to the end of their sibling
//               list when writing an egg file.  See
//               EggGroupNode::write().
////////////////////////////////////////////////////////////////////
bool EggGroup::
is_joint() const {
  return (get_group_type() == GT_joint);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_alpha_mode
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has an alpha_mode other than
//               AM_unspecified.  Returns a valid EggRenderMode pointer
//               if one is found, or NULL otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_alpha_mode() {
  if (get_alpha_mode() != AM_unspecified) {
    return this;
  }
  return EggGroupNode::determine_alpha_mode();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_depth_write_mode
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has a depth_write_mode other
//               than DWM_unspecified.  Returns a valid EggRenderMode
//               pointer if one is found, or NULL otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_depth_write_mode() {
  if (get_depth_write_mode() != DWM_unspecified) {
    return this;
  }
  return EggGroupNode::determine_depth_write_mode();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_depth_test_mode
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has a depth_test_mode other
//               than DTM_unspecified.  Returns a valid EggRenderMode
//               pointer if one is found, or NULL otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_depth_test_mode() {
  if (get_depth_test_mode() != DTM_unspecified) {
    return this;
  }
  return EggGroupNode::determine_depth_test_mode();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_visibility_mode
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has a visibility_mode other
//               than VM_unspecified.  Returns a valid EggRenderMode
//               pointer if one is found, or NULL otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_visibility_mode() {
  if (get_visibility_mode() != VM_unspecified) {
    return this;
  }
  return EggGroupNode::determine_visibility_mode();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_draw_order
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has a draw_order specified.
//               Returns a valid EggRenderMode pointer if one is found,
//               or NULL otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_draw_order() {
  if (has_draw_order()) {
    return this;
  }
  return EggGroupNode::determine_draw_order();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_bin
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               or EggPrimitive or some such object at this level or
//               above this group that has a bin specified.  Returns a
//               valid EggRenderMode pointer if one is found, or NULL
//               otherwise.
////////////////////////////////////////////////////////////////////
EggRenderMode *EggGroup::
determine_bin() {
  if (has_bin()) {
    return this;
  }
  return EggGroupNode::determine_bin();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_indexed
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               at this level or above that has the "indexed" scalar
//               set.  Returns the value of the indexed scalar if it
//               is found, or false if it is not.
//
//               In other words, returns true if the "indexed" flag is
//               in effect for the indicated node, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroup::
determine_indexed() {
  if (has_indexed_flag()) {
    return get_indexed_flag();
  }
  return EggGroupNode::determine_indexed();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::determine_decal
//       Access: Published, Virtual
//  Description: Walks back up the hierarchy, looking for an EggGroup
//               at this level or above that has the "decal" flag
//               set.  Returns the value of the decal flag if it
//               is found, or false if it is not.
//
//               In other words, returns true if the "decal" flag is
//               in effect for the indicated node, false otherwise.
////////////////////////////////////////////////////////////////////
bool EggGroup::
determine_decal() {
  if (get_decal_flag()) {
    return true;
  }
  return EggGroupNode::determine_decal();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::ref_vertex
//       Access: Published
//  Description: Adds the vertex to the set of those referenced by the
//               group, at the indicated membership level.  If the
//               vertex is already being referenced, increases the
//               membership amount by the indicated amount.
////////////////////////////////////////////////////////////////////
void EggGroup::
ref_vertex(EggVertex *vert, double membership) {
  VertexRef::iterator vri = _vref.find(vert);

  if (vri != _vref.end()) {
    // The vertex was already being reffed; increment its membership
    // amount.
    (*vri).second += membership;

    // If that takes us down to zero, go ahead and unref the vertex.
    if ((*vri).second == 0.0) {
      unref_vertex(vert);
    }

  } else {
    // The vertex was not already reffed; ref it.
    if (membership != 0.0) {
      _vref[vert] = membership;

      bool inserted = vert->_gref.insert(this).second;
      // Did the group not exist previously in the vertex's gref list?
      // If it was there already, we must be out of sync between
      // vertices and groups.
      nassertv(inserted);
    }
  }
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroup::unref_vertex
//       Access: Published
//  Description: Removes the vertex from the set of those referenced
//               by the group.  Does nothing if the vertex is not
//               already reffed.
////////////////////////////////////////////////////////////////////
void EggGroup::
unref_vertex(EggVertex *vert) {
  VertexRef::iterator vri = _vref.find(vert);

  if (vri != _vref.end()) {
    _vref.erase(vri);
    int count = vert->_gref.erase(this);
    // Did the group exist in the vertex's gref list?  If it didn't,
    // we must be out of sync between vertices and groups.
    nassertv(count == 1);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::unref_all_vertices
//       Access: Published
//  Description: Removes all vertices from the reference list.
////////////////////////////////////////////////////////////////////
void EggGroup::
unref_all_vertices() {
  // We must walk through the vertex ref list, and flag each vertex as
  // unreffed in its own structure.
  VertexRef::iterator vri;
  for (vri = _vref.begin(); vri != _vref.end(); ++vri) {
    EggVertex *vert = (*vri).first;
    int count = vert->_gref.erase(this);
    // Did the group exist in the vertex's gref list?  If it didn't,
    // we must be out of sync between vertices and groups.
    nassertv(count == 1);
  }

  _vref.clear();
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroup::get_vertex_membership
//       Access: Published
//  Description: Returns the amount of membership of the indicated
//               vertex in this group.  If the vertex is not reffed by
//               the group, returns 0.
////////////////////////////////////////////////////////////////////
double EggGroup::
get_vertex_membership(const EggVertex *vert) const {
  VertexRef::const_iterator vri = _vref.find((EggVertex *)vert);

  if (vri != _vref.end()) {
    return (*vri).second;
  } else {
    return 0.0;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::set_vertex_membership
//       Access: Published
//  Description: Explicitly sets the net membership of the indicated
//               vertex in this group to the given value.
////////////////////////////////////////////////////////////////////
void EggGroup::
set_vertex_membership(EggVertex *vert, double membership) {
  if (membership == 0.0) {
    unref_vertex(vert);
    return;
  }

  VertexRef::iterator vri = _vref.find(vert);

  if (vri != _vref.end()) {
    // The vertex was already being reffed; just change its membership
    // amount.
    (*vri).second = membership;

  } else {
    // The vertex was not already reffed; ref it.
    _vref[vert] = membership;

    bool inserted = vert->_gref.insert(this).second;
    // Did the group not exist previously in the vertex's gref list?
    // If it was there already, we must be out of sync between
    // vertices and groups.
    nassertv(inserted);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::steal_vrefs
//       Access: Published
//  Description: Moves all of the vertex references from the indicated
//               other group into this one.  If a given vertex was
//               previously shared by both groups, the relative
//               memberships will be summed.
////////////////////////////////////////////////////////////////////
void EggGroup::
steal_vrefs(EggGroup *other) {
  nassertv(other != this);
  VertexRef::const_iterator vri;
  for (vri = other->vref_begin(); vri != other->vref_end(); ++vri) {
    EggVertex *vert = (*vri).first;
    double membership = (*vri).second;
    ref_vertex(vert, membership);
  }
  other->unref_all_vertices();
}


#ifndef NDEBUG

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::test_vref_integrity
//       Access: Published
//  Description: Verifies that each vertex in the group exists and
//               that it knows it is referenced by the group.
////////////////////////////////////////////////////////////////////
void EggGroup::
test_vref_integrity() const {
  test_ref_count_integrity();

  VertexRef::const_iterator vri;
  for (vri = vref_begin(); vri != vref_end(); ++vri) {
    const EggVertex *vert = (*vri).first;
    vert->test_ref_count_integrity();

    nassertv(vert->has_gref(this));
  }
}

#endif  // NDEBUG

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::add_group_ref
//       Access: Published
//  Description: Adds a new <Ref> entry to the group.  This declares
//               an internal reference to another node, and is used to
//               implement scene-graph instancing; it is only valid if
//               the group_type is GT_instance.
////////////////////////////////////////////////////////////////////
void EggGroup::
add_group_ref(EggGroup *group) {
  nassertv(get_group_type() == GT_instance);
  _group_refs.push_back(group);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::get_num_group_refs
//       Access: Published
//  Description: Returns the number of <Ref> entries within this
//               group.  See add_group_ref().
////////////////////////////////////////////////////////////////////
int EggGroup::
get_num_group_refs() const {
  return _group_refs.size();
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::get_group_ref
//       Access: Published
//  Description: Returns the nth <Ref> entry within this group.  See
//               add_group_ref().
////////////////////////////////////////////////////////////////////
EggGroup *EggGroup::
get_group_ref(int n) const {
  nassertr(n >= 0 && n < (int)_group_refs.size(), NULL);
  return _group_refs[n];
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::remove_group_ref
//       Access: Published
//  Description: Removes the nth <Ref> entry within this group.  See
//               add_group_ref().
////////////////////////////////////////////////////////////////////
void EggGroup::
remove_group_ref(int n) {
  nassertv(n >= 0 && n < (int)_group_refs.size());
  _group_refs.erase(_group_refs.begin() + n);
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::clear_group_refs
//       Access: Published
//  Description: Removes all of the <Ref> entries within this group.
//               See add_group_ref().
////////////////////////////////////////////////////////////////////
void EggGroup::
clear_group_refs() {
  _group_refs.clear();
}



////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_group_type
//       Access: Published, Static
//  Description: Returns the GroupType value associated with the given
//               string representation, or GT_invalid if the string
//               does not match any known GroupType value.
////////////////////////////////////////////////////////////////////
EggGroup::GroupType EggGroup::
string_group_type(const string &strval) {
  if (cmp_nocase_uh(strval, "group") == 0) {
    return GT_group;
  } else if (cmp_nocase_uh(strval, "instance") == 0) {
    return GT_instance;
  } else if (cmp_nocase_uh(strval, "joint") == 0) {
    return GT_joint;
  } else {
    return GT_invalid;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_dart_type
//       Access: Published, Static
//  Description: Returns the DartType value associated with the given
//               string representation, or DT_none if the string
//               does not match any known DartType value.
////////////////////////////////////////////////////////////////////
EggGroup::DartType EggGroup::
string_dart_type(const string &strval) {
  if (cmp_nocase_uh(strval, "sync") == 0) {
    return DT_sync;
  } else if (cmp_nocase_uh(strval, "nosync") == 0) {
    return DT_nosync;
  } else if (cmp_nocase_uh(strval, "default") == 0) {
    return DT_default;
  } else {
    return DT_none;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_dcs_type
//       Access: Published, Static
//  Description: Returns the DCSType value associated with the given
//               string representation, or DC_unspecified if the
//               string does not match any known DCSType value.
////////////////////////////////////////////////////////////////////
EggGroup::DCSType EggGroup::
string_dcs_type(const string &strval) {
  if (cmp_nocase_uh(strval, "none") == 0) {
    return DC_none;
  } else if (cmp_nocase_uh(strval, "local") == 0) {
    return DC_local;
  } else if (cmp_nocase_uh(strval, "net") == 0) {
    return DC_net;
  } else if (cmp_nocase_uh(strval, "no_touch") == 0) {
    return DC_no_touch;
  } else if (cmp_nocase_uh(strval, "default") == 0) {
    return DC_default;
  } else {
    return DC_unspecified;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_billboard_type
//       Access: Published, Static
//  Description: Returns the BillboardType value associated with the
//               given string representation, or BT_none if the string
//               does not match any known BillboardType value.
////////////////////////////////////////////////////////////////////
EggGroup::BillboardType EggGroup::
string_billboard_type(const string &strval) {
  if (cmp_nocase_uh(strval, "axis") == 0) {
    return BT_axis;
  } else if (cmp_nocase_uh(strval, "point_eye") == 0) {
    return BT_point_camera_relative;
  } else if (cmp_nocase_uh(strval, "point_world") == 0) {
    return BT_point_world_relative;
  } else if (cmp_nocase_uh(strval, "point") == 0) {
    return BT_point_world_relative;
  } else {
    return BT_none;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_cs_type
//       Access: Published, Static
//  Description: Returns the CollisionSolidType value associated with the
//               given string representation, or CST_none if the string
//               does not match any known CollisionSolidType value.
////////////////////////////////////////////////////////////////////
EggGroup::CollisionSolidType EggGroup::
string_cs_type(const string &strval) {
  if (cmp_nocase_uh(strval, "plane") == 0) {
    return CST_plane;
  } else if (cmp_nocase_uh(strval, "polygon") == 0) {
    return CST_polygon;
  } else if (cmp_nocase_uh(strval, "polyset") == 0) {
    return CST_polyset;
  } else if (cmp_nocase_uh(strval, "sphere") == 0) {
    return CST_sphere;
  } else if (cmp_nocase_uh(strval, "inv-sphere") == 0 ||
             cmp_nocase_uh(strval, "invsphere") == 0) {
    return CST_inv_sphere;
  } else if (cmp_nocase_uh(strval, "tube") == 0) {
    return CST_tube;
  } else if (cmp_nocase_uh(strval, "floor-mesh") == 0) {    
    return CST_floor_mesh;
  } else {
    return CST_none;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_collide_flags
//       Access: Published, Static
//  Description: Returns the CollideFlags value associated with the
//               given string representation, or CF_none if the string
//               does not match any known CollideFlags value.  This
//               only recognizes a single keyword; it does not attempt
//               to parse a string of keywords.
////////////////////////////////////////////////////////////////////
EggGroup::CollideFlags EggGroup::
string_collide_flags(const string &strval) {
  if (cmp_nocase_uh(strval, "intangible") == 0) {
    return CF_intangible;
  } else if (cmp_nocase_uh(strval, "event") == 0) {
    return CF_event;
  } else if (cmp_nocase_uh(strval, "descend") == 0) {
    return CF_descend;
  } else if (cmp_nocase_uh(strval, "keep") == 0) {
    return CF_keep;
  } else if (cmp_nocase_uh(strval, "solid") == 0) {
    return CF_solid;
  } else if (cmp_nocase_uh(strval, "center") == 0) {
    return CF_center;
  } else if (cmp_nocase_uh(strval, "turnstile") == 0) {
    return CF_turnstile;
  } else if (cmp_nocase_uh(strval, "level") == 0) {
    return CF_level;
  } else {
    return CF_none;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_blend_mode
//       Access: Published, Static
//  Description: Returns the BlendMode value associated with the
//               given string representation, or BM_none if the string
//               does not match any known BlendMode.
////////////////////////////////////////////////////////////////////
EggGroup::BlendMode EggGroup::
string_blend_mode(const string &strval) {
  if (cmp_nocase_uh(strval, "none") == 0) {
    return BM_none;
  } else if (cmp_nocase_uh(strval, "add") == 0) {
    return BM_add;
  } else if (cmp_nocase_uh(strval, "subtract") == 0) {
    return BM_subtract;
  } else if (cmp_nocase_uh(strval, "inv_subtract") == 0) {
    return BM_inv_subtract;
  } else if (cmp_nocase_uh(strval, "min") == 0) {
    return BM_min;
  } else if (cmp_nocase_uh(strval, "max") == 0) {
    return BM_max;
  } else {
    return BM_unspecified;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::string_blend_operand
//       Access: Published, Static
//  Description: Returns the BlendOperand value associated with the
//               given string representation, or BO_none if the string
//               does not match any known BlendOperand.
////////////////////////////////////////////////////////////////////
EggGroup::BlendOperand EggGroup::
string_blend_operand(const string &strval) {
  if (cmp_nocase_uh(strval, "zero") == 0) {
    return BO_zero;
  } else if (cmp_nocase_uh(strval, "one") == 0) {
    return BO_one;
  } else if (cmp_nocase_uh(strval, "incoming_color") == 0) {
    return BO_incoming_color;
  } else if (cmp_nocase_uh(strval, "one_minus_incoming_color") == 0) {
    return BO_one_minus_incoming_color;
  } else if (cmp_nocase_uh(strval, "fbuffer_color") == 0) {
    return BO_fbuffer_color;
  } else if (cmp_nocase_uh(strval, "one_minus_fbuffer_color") == 0) {
    return BO_one_minus_fbuffer_color;
  } else if (cmp_nocase_uh(strval, "incoming_alpha") == 0) {
    return BO_incoming_alpha;
  } else if (cmp_nocase_uh(strval, "one_minus_incoming_alpha") == 0) {
    return BO_one_minus_incoming_alpha;
  } else if (cmp_nocase_uh(strval, "fbuffer_alpha") == 0) {
    return BO_fbuffer_alpha;
  } else if (cmp_nocase_uh(strval, "one_minus_fbuffer_alpha") == 0) {
    return BO_one_minus_fbuffer_alpha;
  } else if (cmp_nocase_uh(strval, "constant_color") == 0) {
    return BO_constant_color;
  } else if (cmp_nocase_uh(strval, "one_minus_constant_color") == 0) {
    return BO_one_minus_constant_color;
  } else if (cmp_nocase_uh(strval, "constant_alpha") == 0) {
    return BO_constant_alpha;
  } else if (cmp_nocase_uh(strval, "one_minus_constant_alpha") == 0) {
    return BO_one_minus_constant_alpha;
  } else if (cmp_nocase_uh(strval, "incoming_color_saturate") == 0) {
    return BO_incoming_color_saturate;
  } else if (cmp_nocase_uh(strval, "color_scale") == 0) {
    return BO_color_scale;
  } else if (cmp_nocase_uh(strval, "one_minus_color_scale") == 0) {
    return BO_one_minus_color_scale;
  } else if (cmp_nocase_uh(strval, "alpha_scale") == 0) {
    return BO_alpha_scale;
  } else if (cmp_nocase_uh(strval, "one_minus_alpha_scale") == 0) {
    return BO_one_minus_alpha_scale;
  } else {
    return BO_unspecified;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::as_transform
//       Access: Public, Virtual
//  Description: Returns this object cross-cast to an EggTransform
//               pointer, if it inherits from EggTransform, or NULL if
//               it does not.
////////////////////////////////////////////////////////////////////
EggTransform *EggGroup::
as_transform() {
  return this;
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroup::write_vertex_ref
//       Access: Protected
//  Description: Writes out the vertex ref component of the group body
//               only.  This may consist of a number of <VertexRef>
//               entries, each with its own membership value.
////////////////////////////////////////////////////////////////////
void EggGroup::
write_vertex_ref(ostream &out, int indent_level) const {
  // We want to put the vertices together into groups first by vertex
  // pool, then by membership value.  Each of these groups becomes a
  // separate VertexRef entry.  Within each group, we'll sort the
  // vertices by index number.

  typedef pset<int> Indices;
  typedef pmap<double, Indices> Memberships;
  typedef pmap<EggVertexPool *, Memberships> Pools;

  Pools _entries;
  bool all_membership_one = true;

  VertexRef::const_iterator vri;
  for (vri = _vref.begin(); vri != _vref.end(); ++vri) {
    EggVertex *vert = (*vri).first;
    double membership = (*vri).second;

    if (membership != 1.0) {
      all_membership_one = false;
    }

    _entries[vert->get_pool()][membership].insert(vert->get_index());
  }

  // Now that we've reordered them, we can simply traverse the entries
  // and write them out.
  Pools::const_iterator pi;
  for (pi = _entries.begin(); pi != _entries.end(); ++pi) {
    EggVertexPool *pool = (*pi).first;
    const Memberships &memberships = (*pi).second;
    Memberships::const_iterator mi;
    for (mi = memberships.begin(); mi != memberships.end(); ++mi) {
      double membership = (*mi).first;
      const Indices &indices = (*mi).second;

      indent(out, indent_level)
        << "<VertexRef> {\n";
      write_long_list(out, indent_level+2, indices.begin(), indices.end(),
                      "", "", 72);

      // If all vrefs in this group have membership of 1, don't bother
      // to write out the membership scalar.
      if (!all_membership_one) {
        indent(out, indent_level + 2)
          << "<Scalar> membership { " << membership << " }\n";
      }
      if (pool == (EggVertexPool *)NULL) {
        indent(out, indent_level + 2)
          << "// Invalid NULL vertex pool.\n";
      } else {
        indent(out, indent_level + 2)
          << "<Ref> { " << pool->get_name() << " }\n";
      }
      indent(out, indent_level)
        << "}\n";
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::egg_start_parse_body
//       Access: Protected, Virtual
//  Description: This function is called within parse_egg().  It
//               should call the appropriate function on the lexer to
//               initialize the parser into the state associated with
//               this object.  If the object cannot be parsed into
//               directly, it should return false.
////////////////////////////////////////////////////////////////////
bool EggGroup::
egg_start_parse_body() {
  egg_start_group_body();
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::adjust_under
//       Access: Protected, Virtual
//  Description: This is called within update_under() after all the
//               various under settings have been inherited directly
//               from the parent node.  It is responsible for
//               adjusting these settings to reflect states local to
//               the current node; for instance, an <Instance> node
//               will force the UF_under_instance bit on.
////////////////////////////////////////////////////////////////////
void EggGroup::
adjust_under() {
  // If we have our own transform, it carries forward.

  // As of 4/18/01, this now also affects the local_coord flag, below.
  // This means that a <Transform> entry within an <Instance> node
  // transforms the instance itself.
  if (has_transform()) {
    _under_flags |= UF_under_transform;

    // Our own transform also affects our node frame.
    _node_frame =
      new MatrixFrame(get_transform3d() * get_node_frame());
    _node_frame_inv =
      new MatrixFrame(invert(get_node_frame()));
    _vertex_to_node =
      new MatrixFrame(get_vertex_frame() * get_node_frame_inv());
    _node_to_vertex =
      new MatrixFrame(get_node_frame() * get_vertex_frame_inv());
  }

  if (is_instance_type()) {
    _under_flags |= UF_under_instance;
    if (_under_flags & UF_under_transform) {
      // If we've reached an instance node and we're under a
      // transform, that means we've just defined a local coordinate
      // system.
      _under_flags |= UF_local_coord;
    }

    // An instance node means that from this point and below, vertices
    // are defined relative to this node.  Thus, the node frame
    // becomes the vertex frame.
    _vertex_frame = _node_frame;
    _vertex_frame_inv = _node_frame_inv;
    _vertex_to_node = NULL;
    _node_to_vertex = NULL;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::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 EggGroup::
r_transform(const LMatrix4d &mat, const LMatrix4d &inv,
            CoordinateSystem to_cs) {
  if (has_transform() || get_group_type() == GT_joint) {
    // Since we want to apply this transform to all matrices,
    // including nested matrices, we can't simply premult it in and
    // leave it, because that would leave the rotational component in
    // the scene graph's matrix, and all nested matrices would inherit
    // the same rotational component.  So we have to premult and then
    // postmult by the inverse to undo the rotational component each
    // time.

    LMatrix4d mat1 = mat;
    LMatrix4d inv1 = inv;

    // If we have a translation component, we should only apply
    // it to the top matrix.  All subsequent matrices get just the
    // rotational component.
    mat1.set_row(3, LVector3d(0.0, 0.0, 0.0));
    inv1.set_row(3, LVector3d(0.0, 0.0, 0.0));

    internal_set_transform(inv1 * get_transform3d() * mat);

    if (_default_pose.has_transform()) {
      LMatrix4d t = _default_pose.get_transform3d();
      _default_pose.clear_transform();
      _default_pose.add_matrix4(inv1 * t * mat);
    }

    EggGroupNode::r_transform(mat1, inv1, to_cs);
  } else {
    EggGroupNode::r_transform(mat, inv, to_cs);
  }

  // Convert the LOD description too.
  if (has_lod()) {
    _lod->transform(mat);
  }
  if (has_billboard_center()) {
    _billboard_center = _billboard_center * mat;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: EggGroup::r_flatten_transforms
//       Access: Protected, Virtual
//  Description: The recursive implementation of flatten_transforms().
////////////////////////////////////////////////////////////////////
void EggGroup::
r_flatten_transforms() {
  EggGroupNode::r_flatten_transforms();

  if (is_local_coord()) {
    LMatrix4d mat = get_vertex_frame();
    if (has_lod()) {
      _lod->transform(mat);
    }

    if (get_billboard_type() != BT_none && !has_billboard_center()) {
      // If we had a billboard without an explicit center, it was an
      // implicit instance.  Now it's not any more.
      set_billboard_center(LPoint3d(0.0, 0.0, 0.0) * mat);

    } else if (has_billboard_center()) {
      _billboard_center = _billboard_center * mat;
    }
  }

  if (get_group_type() == GT_instance) {
    set_group_type(GT_group);
  }

  if (get_group_type() != GT_joint) {
    internal_clear_transform();
  }
}


////////////////////////////////////////////////////////////////////
//     Function: EggGroup::transform_changed
//       Access: Protected, Virtual
//  Description: This virtual method is inherited by EggTransform3d;
//               it is called whenever the transform is changed.
////////////////////////////////////////////////////////////////////
void EggGroup::
transform_changed() {
  // Recompute all of the cached transforms at this node and below.
  // We should probably make this smarter and do lazy evaluation of
  // these transforms, rather than having to recompute the whole tree
  // with every change to a parent node's transform.
  update_under(0);
}



////////////////////////////////////////////////////////////////////
//     Function: GroupType output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::GroupType t) {
  switch (t) {
  case EggGroup::GT_invalid:
    return out << "invalid group";
  case EggGroup::GT_group:
    return out << "group";
  case EggGroup::GT_instance:
    return out << "instance";
  case EggGroup::GT_joint:
    return out << "joint";
  }

  nassertr(false, out);
  return out << "(**invalid**)";
}

////////////////////////////////////////////////////////////////////
//     Function: DartType output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::DartType t) {
  switch (t) {
  case EggGroup::DT_none:
    return out << "none";
  case EggGroup::DT_sync:
    return out << "sync";
  case EggGroup::DT_nosync:
    return out << "nosync";
  case EggGroup::DT_default:
    return out << "1";
  }

  nassertr(false, out);
  return out << "(**invalid**)";
}

////////////////////////////////////////////////////////////////////
//     Function: DCSType output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::DCSType t) {
  switch (t) {
  case EggGroup::DC_unspecified:
    return out << "unspecified";
  case EggGroup::DC_none:
    return out << "none";
  case EggGroup::DC_local:
    return out << "local";
  case EggGroup::DC_net:
    return out << "net";
  case EggGroup::DC_no_touch:
    return out << "no_touch";
  case EggGroup::DC_default:
    return out << "1";
  }

  nassertr(false, out);
  return out << "(**invalid**)";
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardType output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::BillboardType t) {
  switch (t) {
  case EggGroup::BT_none:
    return out << "none";
  case EggGroup::BT_axis:
    return out << "axis";
  case EggGroup::BT_point_camera_relative:
    return out << "point_eye";
  case EggGroup::BT_point_world_relative:
    return out << "point_world";
  }

  nassertr(false, out);
  return out << "(**invalid**)";
}

////////////////////////////////////////////////////////////////////
//     Function: CollisionSolidType output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::CollisionSolidType t) {
  switch (t) {
  case EggGroup::CST_none:
    return out << "None";
  case EggGroup::CST_plane:
    return out << "Plane";
  case EggGroup::CST_polygon:
    return out << "Polygon";
  case EggGroup::CST_polyset:
    return out << "Polyset";
  case EggGroup::CST_sphere:
    return out << "Sphere";
  case EggGroup::CST_inv_sphere:
    return out << "InvSphere";
  case EggGroup::CST_tube:
    return out << "Tube";
  }

  nassertr(false, out);
  return out << "(**invalid**)";
}

////////////////////////////////////////////////////////////////////
//     Function: CollideFlags output operator
//  Description:
////////////////////////////////////////////////////////////////////
ostream &operator << (ostream &out, EggGroup::CollideFlags t) {
  if (t == EggGroup::CF_none) {
    return out << "none";
  }
  int bits = (int)t;
  const char *space = "";

  if (bits & EggGroup::CF_intangible) {
    out << space << "intangible";
    space = " ";
  }
  if (bits & EggGroup::CF_event) {
    out << space << "event";
    space = " ";
  }
  if (bits & EggGroup::CF_descend) {
    out << space << "descend";
    space = " ";
  }
  if (bits & EggGroup::CF_keep) {
    out << space << "keep";
    space = " ";
  }
  if (bits & EggGroup::CF_solid) {
    out << space << "solid";
    space = " ";
  }
  if (bits & EggGroup::CF_center) {
    out << space << "center";
    space = " ";
  }
  if (bits & EggGroup::CF_turnstile) {
    out << space << "turnstile";
    space = " ";
  }
  if (bits & EggGroup::CF_level) {
    out << space << "level";
    space = " ";
  }
  return out;
}

////////////////////////////////////////////////////////////////////
//     Function: ostream << EggGroup::BlendMode
//  Description: 
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, EggGroup::BlendMode t) {
  switch (t) {
  case EggGroup::BM_unspecified:
    return out << "unspecified";

  case EggGroup::BM_none:
    return out << "none";

  case EggGroup::BM_add:
    return out << "add";

  case EggGroup::BM_subtract:
    return out << "subtract";

  case EggGroup::BM_inv_subtract:
    return out << "inv_subtract";

  case EggGroup::BM_min:
    return out << "min";

  case EggGroup::BM_max:
    return out << "max";
  }

  return out << "**invalid EggGroup::BlendMode(" << (int)t << ")**";
}

////////////////////////////////////////////////////////////////////
//     Function: ostream << EggGroup::BlendOperand
//  Description: 
////////////////////////////////////////////////////////////////////
ostream &
operator << (ostream &out, EggGroup::BlendOperand t) {
  switch (t) {
  case EggGroup::BO_unspecified:
    return out << "unspecified";

  case EggGroup::BO_zero:
    return out << "zero";

  case EggGroup::BO_one:
    return out << "one";

  case EggGroup::BO_incoming_color:
    return out << "incomfing_color";

  case EggGroup::BO_one_minus_incoming_color:
    return out << "one_minus_incoming_color";

  case EggGroup::BO_fbuffer_color:
    return out << "fbuffer_color";

  case EggGroup::BO_one_minus_fbuffer_color:
    return out << "one_minus_fbuffer_color";

  case EggGroup::BO_incoming_alpha:
    return out << "incoming_alpha";

  case EggGroup::BO_one_minus_incoming_alpha:
    return out << "one_minus_incoming_alpha";

  case EggGroup::BO_fbuffer_alpha:
    return out << "fbuffer_alpha";

  case EggGroup::BO_one_minus_fbuffer_alpha:
    return out << "one_minus_fbuffer_alpha";

  case EggGroup::BO_constant_color:
    return out << "constant_color";

  case EggGroup::BO_one_minus_constant_color:
    return out << "one_minus_constant_color";

  case EggGroup::BO_constant_alpha:
    return out << "constant_alpha";

  case EggGroup::BO_one_minus_constant_alpha:
    return out << "one_minus_constant_alpha";

  case EggGroup::BO_incoming_color_saturate:
    return out << "incoming_color_saturate";

  case EggGroup::BO_color_scale:
    return out << "color_scale";

  case EggGroup::BO_one_minus_color_scale:
    return out << "one_minus_color_scale";

  case EggGroup::BO_alpha_scale:
    return out << "alpha_scale";

  case EggGroup::BO_one_minus_alpha_scale:
    return out << "one_minus_alpha_scale";
  }

  return out << "**invalid EggGroup::BlendOperand(" << (int)t << ")**";
}
