// Filename: glGeomMunger_src.cxx
// Created by:  drose (10Mar05)
//
////////////////////////////////////////////////////////////////////
//
// 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 "dcast.h"

TypeHandle CLP(GeomMunger)::_type_handle;

ALLOC_DELETED_CHAIN_DEF(CLP(GeomMunger));

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
CLP(GeomMunger)::
CLP(GeomMunger)(GraphicsStateGuardian *gsg, const RenderState *state) :
  StandardMunger(gsg, state, 4, NT_uint8, C_color),
  _texture(DCAST(TextureAttrib, state->get_attrib(TextureAttrib::get_class_slot()))),
  _tex_gen(DCAST(TexGenAttrib, state->get_attrib(TexGenAttrib::get_class_slot())))
{
  // Set a callback to unregister ourselves when either the Texture or
  // the TexGen object gets deleted.
  _texture.set_callback(this);
  _tex_gen.set_callback(this);

  _flags = 0;

  if (CLP(interleaved_arrays)) {
    _flags |= F_interleaved_arrays;
  } else if (CLP(parallel_arrays)) {
    _flags |= F_parallel_arrays;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::Destructor
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
CLP(GeomMunger)::
~CLP(GeomMunger)() {
  // We need to remove this pointer from all of the geom contexts that
  // reference this object.
  GeomContexts::iterator gci;
  for (gci = _geom_contexts.begin(); gci != _geom_contexts.end(); ++gci) {
    (*gci)->remove_munger(this);
  }
  _geom_contexts.clear();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::wp_callback
//       Access: Public, Virtual
//  Description: This callback is set to be made whenever the
//               associated _texture or _tex_gen attributes are
//               destructed, in which case the GeomMunger is invalid
//               and should no longer be used.
////////////////////////////////////////////////////////////////////
void CLP(GeomMunger)::
wp_callback(void *) {
  unregister_myself();
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::munge_format_impl
//       Access: Protected, Virtual
//  Description: Given a source GeomVertexFormat, converts it if
//               necessary to the appropriate format for rendering.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexFormat) CLP(GeomMunger)::
munge_format_impl(const GeomVertexFormat *orig,
                  const GeomVertexAnimationSpec &animation) {
  PT(GeomVertexFormat) new_format = new GeomVertexFormat(*orig);
  new_format->set_animation(animation);

  const GeomVertexColumn *color_type = orig->get_color_column();
  if (color_type != (GeomVertexColumn *)NULL &&
      color_type->get_numeric_type() == NT_packed_dabc) {
    // We need to convert the color format; OpenGL doesn't support the
    // byte order of DirectX's packed ARGB format.
    int color_array = orig->get_array_with(InternalName::get_color());

    PT(GeomVertexArrayFormat) new_array_format = new_format->modify_array(color_array);

    // Replace the existing color format with the new format.
    new_array_format->add_column
      (InternalName::get_color(), 4, NT_uint8,
       C_color, color_type->get_start(), color_type->get_column_alignment());
  }

  if (animation.get_animation_type() == AT_hardware) {
    // If we want hardware animation, we need to reserve space for the
    // blend weights.
      
    // Make sure the old weights and indices are removed, just in
    // case.
    new_format->remove_column(InternalName::get_transform_weight());
    new_format->remove_column(InternalName::get_transform_index());

    // And we don't need the transform_blend table any more.
    new_format->remove_column(InternalName::get_transform_blend());

    if (animation.get_num_transforms() > 1) {
      PT(GeomVertexArrayFormat) new_array_format = new GeomVertexArrayFormat;
      new_array_format->add_column
        (InternalName::get_transform_weight(), animation.get_num_transforms() - 1,
         NT_stdfloat, C_other);
      
      if (animation.get_indexed_transforms()) {
        // Also, if we'll be indexing into the transform table, reserve
        // space for the index.
        
        // TODO: We should examine the maximum palette index so we can
        // decide whether we need 16-bit indices.  That implies saving
        // the maximum palette index, presumably in the AnimationSpec.
        // At the moment, I don't think any existing hardware supports
        // more than 255 indices anyway.
        new_array_format->add_column
          (InternalName::get_transform_index(), animation.get_num_transforms(),
           NT_uint8, C_index);
      }                                    

      new_format->add_array(new_array_format);
    }
  }

  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(new_format);

  if ((_flags & F_parallel_arrays) != 0) {
    // Split out the interleaved array into n parallel arrays.
    new_format = new GeomVertexFormat;
    for (int i = 0; i < format->get_num_columns(); ++i) {
      const GeomVertexColumn *column = format->get_column(i);
      PT(GeomVertexArrayFormat) new_array_format = new GeomVertexArrayFormat;
      new_array_format->add_column(column->get_name(), column->get_num_components(),
                                   column->get_numeric_type(), column->get_contents(),
                                   -1, column->get_column_alignment());
      new_format->add_array(new_array_format);
    }
    format = GeomVertexFormat::register_format(new_format);
  
  } else if ((_flags & F_interleaved_arrays) != 0) {
    // Combine the primary data columns into a single array.
    new_format = new GeomVertexFormat(*format);
    PT(GeomVertexArrayFormat) new_array_format = new GeomVertexArrayFormat;
  
    const GeomVertexColumn *column = format->get_vertex_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    column = format->get_normal_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    column = format->get_color_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    // Put only the used texture coordinates into the interleaved
    // array.
    if (_texture != (TextureAttrib *)NULL) {
      typedef pset<const InternalName *> UsedStages;
      UsedStages used_stages;
      
      int num_stages = _texture->get_num_on_stages();
      for (int i = 0; i < num_stages; ++i) {
        TextureStage *stage = _texture->get_on_stage(i);
        if (_tex_gen == (TexGenAttrib *)NULL ||
            !_tex_gen->has_stage(stage)) {
          InternalName *name = stage->get_texcoord_name();
          if (used_stages.insert(name).second) {
            // This is the first time we've encountered this texcoord name.
            const GeomVertexColumn *texcoord_type = format->get_column(name);
            
            if (texcoord_type != (const GeomVertexColumn *)NULL) {
              new_array_format->add_column
                (name, texcoord_type->get_num_values(), NT_stdfloat, C_texcoord,
                 -1, texcoord_type->get_column_alignment());
            } else {
              // We have to add something as a placeholder, even if the
              // texture coordinates aren't defined.
              new_array_format->add_column(name, 2, NT_stdfloat, C_texcoord);
            }
            new_format->remove_column(name);
          }
        }
      }
    }

    new_format->insert_array(0, new_array_format);
    format = GeomVertexFormat::register_format(new_format);
  }

  return format;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::premunge_format_impl
//       Access: Protected, Virtual
//  Description: Given a source GeomVertexFormat, converts it if
//               necessary to the appropriate format for rendering.
////////////////////////////////////////////////////////////////////
CPT(GeomVertexFormat) CLP(GeomMunger)::
premunge_format_impl(const GeomVertexFormat *orig) {
  PT(GeomVertexFormat) new_format = new GeomVertexFormat(*orig);

  const GeomVertexColumn *color_type = orig->get_color_column();
  if (color_type != (GeomVertexColumn *)NULL &&
      color_type->get_numeric_type() == NT_packed_dabc) {
    // We need to convert the color format; OpenGL doesn't support the
    // byte order of DirectX's packed ARGB format.
    int color_array = orig->get_array_with(InternalName::get_color());

    PT(GeomVertexArrayFormat) new_array_format = new_format->modify_array(color_array);

    // Replace the existing color format with the new format.
    new_array_format->add_column
      (InternalName::get_color(), 4, NT_uint8,
       C_color, color_type->get_start(), color_type->get_column_alignment());
  }

  CPT(GeomVertexFormat) format = GeomVertexFormat::register_format(new_format);

  if ((_flags & F_parallel_arrays) != 0) {
    // Split out the interleaved array into n parallel arrays.
    new_format = new GeomVertexFormat;
    for (int i = 0; i < format->get_num_columns(); ++i) {
      const GeomVertexColumn *column = format->get_column(i);
      PT(GeomVertexArrayFormat) new_array_format = new GeomVertexArrayFormat;
      new_array_format->add_column(column->get_name(), column->get_num_components(),
                                   column->get_numeric_type(), column->get_contents(),
                                   -1, column->get_column_alignment());
      new_format->add_array(new_array_format);
    }
    format = GeomVertexFormat::register_format(new_format);
  
  } else {
    // Combine the primary data columns into a single array.  Unlike
    // the munge case, above, in the premunge case, we do this even if
    // F_interleaved_arrays is not set (unless F_parallel_arrays is
    // set), since the presumption is that you're more willing to pay
    // the overhead of doing this step at load time than you might be
    // at run time.
    new_format = new GeomVertexFormat(*format);
    PT(GeomVertexArrayFormat) new_array_format = new GeomVertexArrayFormat;
  
    const GeomVertexColumn *column = format->get_vertex_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    column = format->get_normal_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    column = format->get_color_column();
    if (column != (const GeomVertexColumn *)NULL) {
      new_array_format->add_column
        (column->get_name(), column->get_num_components(), 
         column->get_numeric_type(), column->get_contents(),
         -1, column->get_column_alignment());
      new_format->remove_column(column->get_name());
    }

    // Put only the used texture coordinates into the interleaved
    // array.  The others will be kept around, but in a parallel
    // array.
    if (_texture != (TextureAttrib *)NULL) {
      typedef pset<const InternalName *> UsedStages;
      UsedStages used_stages;
      
      int num_stages = _texture->get_num_on_stages();
      for (int i = 0; i < num_stages; ++i) {
        TextureStage *stage = _texture->get_on_stage(i);
        if (_tex_gen == (TexGenAttrib *)NULL ||
            !_tex_gen->has_stage(stage)) {
          InternalName *name = stage->get_texcoord_name();
          if (used_stages.insert(name).second) {
            // This is the first time we've encountered this texcoord name.
            const GeomVertexColumn *texcoord_type = format->get_column(name);
            
            if (texcoord_type != (const GeomVertexColumn *)NULL) {
              new_array_format->add_column
                (name, texcoord_type->get_num_values(), NT_stdfloat, C_texcoord,
                 -1, texcoord_type->get_column_alignment());
            } else {
              // We have to add something as a placeholder, even if the
              // texture coordinates aren't defined.
              new_array_format->add_column(name, 2, NT_stdfloat, C_texcoord);
            }
            new_format->remove_column(name);
          }
        }
      }
    }

    // Now go through the remaining arrays and make sure they are
    // tightly packed (with the column alignment restrictions).  If
    // not, repack them.
    for (int i = 0; i < new_format->get_num_arrays(); ++i) {
      CPT(GeomVertexArrayFormat) orig_a = new_format->get_array(i);
      if (orig_a->count_unused_space() != 0) {
        PT(GeomVertexArrayFormat) new_a = new GeomVertexArrayFormat;
        for (int j = 0; j < orig_a->get_num_columns(); ++j) {
          const GeomVertexColumn *column = orig_a->get_column(j);
          new_a->add_column(column->get_name(), column->get_num_components(),
                            column->get_numeric_type(), column->get_contents(),
                            -1, column->get_column_alignment());
        }
        new_format->set_array(i, new_a);
      }
    }

    // Finally, insert the interleaved array first in the format.
    new_format->insert_array(0, new_array_format);
    format = GeomVertexFormat::register_format(new_format);
  }

  return format;
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::compare_to_impl
//       Access: Protected, Virtual
//  Description: Called to compare two GeomMungers who are known to be
//               of the same type, for an apples-to-apples comparison.
//               This will never be called on two pointers of a
//               different type.
////////////////////////////////////////////////////////////////////
int CLP(GeomMunger)::
compare_to_impl(const GeomMunger *other) const {
  const CLP(GeomMunger) *om = DCAST(CLP(GeomMunger), other);
  if (_texture != om->_texture) {
    return _texture < om->_texture ? -1 : 1;
  }
  if (_tex_gen != om->_tex_gen) {
    return _tex_gen < om->_tex_gen ? -1 : 1;
  }
  if (_flags != om->_flags) {
    return _flags < om->_flags ? -1 : 1;
  }

  return StandardMunger::compare_to_impl(other);
}

////////////////////////////////////////////////////////////////////
//     Function: CLP(GeomMunger)::geom_compare_to_impl
//       Access: Protected, Virtual
//  Description: Called to compare two GeomMungers who are known to be
//               of the same type, for an apples-to-apples comparison.
//               This will never be called on two pointers of a
//               different type.
////////////////////////////////////////////////////////////////////
int CLP(GeomMunger)::
geom_compare_to_impl(const GeomMunger *other) const {
  const CLP(GeomMunger) *om = DCAST(CLP(GeomMunger), other);
  if (_texture != om->_texture) {
    return _texture < om->_texture ? -1 : 1;
  }
  if (_tex_gen != om->_tex_gen) {
    return _tex_gen < om->_tex_gen ? -1 : 1;
  }
  if (_flags != om->_flags) {
    return _flags < om->_flags ? -1 : 1;
  }

  return StandardMunger::compare_to_impl(other);
}
