// Filename: textureAttrib.cxx
// Created by:  drose (21Feb02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "textureAttrib.h"
#include "attribSlots.h"
#include "graphicsStateGuardianBase.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "datagram.h"
#include "datagramIterator.h"
#include "dcast.h"

CPT(RenderAttrib) TextureAttrib::_empty_attrib;
CPT(RenderAttrib) TextureAttrib::_all_off_attrib;
TypeHandle TextureAttrib::_type_handle;

// This STL Function object is used in filter_to_max(), below, to sort
// a list of TextureStages in reverse order by priority and, within
// priority, in order by sort.
class CompareTextureStagePriorities {
public:
  bool operator ()(const TextureStage *a, const TextureStage *b) const {
    if (a->get_priority() != b->get_priority()) {
      return a->get_priority() > b->get_priority();
    }
    return a->get_sort() < b->get_sort();
  }
};



////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make
//       Access: Published, Static
//  Description: Constructs a new TextureAttrib object suitable for
//               rendering the indicated texture onto geometry, using
//               the default TextureStage.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
make(Texture *texture) {
  return DCAST(TextureAttrib, make())->add_on_stage(TextureStage::get_default(), texture);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make_off
//       Access: Published, Static
//  Description: Constructs a new TextureAttrib object suitable for
//               rendering untextured geometry.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
make_off() {
  return make_all_off();
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make
//       Access: Published, Static
//  Description: Constructs a new TextureAttrib object that does
//               nothing.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
make() {
  // We make it a special case and store a pointer to the empty attrib
  // forever once we find it the first time, as an optimization.
  if (_empty_attrib == (RenderAttrib *)NULL) {
    _empty_attrib = return_new(new TextureAttrib);
  }

  return _empty_attrib;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make_all_off
//       Access: Published, Static
//  Description: Constructs a new TextureAttrib object that turns off
//               all stages (and hence disables texturing).
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
make_all_off() {
  // We make it a special case and store a pointer to the off attrib
  // forever once we find it the first time, as an optimization.
  if (_all_off_attrib == (RenderAttrib *)NULL) {
    TextureAttrib *attrib = new TextureAttrib;
    attrib->_off_all_stages = true;
    _all_off_attrib = return_new(attrib);
  }

  return _all_off_attrib;
}


////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::find_on_stage
//       Access: Published
//  Description: Returns the index number of the indicated
//               TextureStage within the list of on_stages, or -1 if
//               the indicated stage is not listed.
////////////////////////////////////////////////////////////////////
int TextureAttrib::
find_on_stage(const TextureStage *stage) const {
  for (int n = 0; n < (int)_on_stages.size(); ++n) {
    if (_on_stages[n] == stage) {
      return n;
    }
  }

  return -1;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::add_on_stage
//       Access: Published
//  Description: Returns a new TextureAttrib, just like this one, but
//               with the indicated stage added to the list of stages
//               turned on by this attrib.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
add_on_stage(TextureStage *stage, Texture *tex) const {
  TextureAttrib *attrib = new TextureAttrib(*this);
  pair<OnTextures::iterator, bool> insert_result = 
    attrib->_on_textures.insert(OnTextures::value_type(stage, tex));
  if (insert_result.second) {
    // If the insert was successful--we have added a new stage that
    // wasn't present before--then add the stage to the linear list
    // also.
    attrib->_on_stages.push_back(stage);
    attrib->_sort_seq = UpdateSeq::old();

    // Also ensure it is removed from the off_stages list.
    attrib->_off_stages.erase(stage);

  } else {
    // If the insert was unsuccessful, it means there was already a
    // definition for that stage.  Replace it.
    (*insert_result.first).second = tex;
  }
  return return_new(attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::remove_on_stage
//       Access: Published
//  Description: Returns a new TextureAttrib, just like this one, but
//               with the indicated stage removed from the list of
//               stages turned on by this attrib.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
remove_on_stage(TextureStage *stage) const {
  TextureAttrib *attrib = new TextureAttrib(*this);

  OnTextures::iterator ti = attrib->_on_textures.find(stage);
  if (ti != attrib->_on_textures.end()) {
    attrib->_on_textures.erase(ti);
    OnStages::iterator si = 
      find(attrib->_on_stages.begin(), attrib->_on_stages.end(), 
           PT(TextureStage)(stage));
    if (si != attrib->_on_stages.end()) {
      attrib->_on_stages.erase(si);
    }
  }

  return return_new(attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::add_off_stage
//       Access: Published
//  Description: Returns a new TextureAttrib, just like this one, but
//               with the indicated stage added to the list of stages
//               turned off by this attrib.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
add_off_stage(TextureStage *stage) const {
  TextureAttrib *attrib = new TextureAttrib(*this);
  if (!_off_all_stages) {
    attrib->_off_stages.insert(stage);

    // Also ensure it is removed from the on_stages list.
    OnTextures::iterator ti = attrib->_on_textures.find(stage);
    if (ti != attrib->_on_textures.end()) {
      attrib->_on_textures.erase(ti);
      OnStages::iterator si = 
        find(attrib->_on_stages.begin(), attrib->_on_stages.end(), 
             PT(TextureStage)(stage));
      if (si != attrib->_on_stages.end()) {
        attrib->_on_stages.erase(si);
      }
    }
  }
  return return_new(attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::remove_off_stage
//       Access: Published
//  Description: Returns a new TextureAttrib, just like this one, but
//               with the indicated stage removed from the list of
//               stages turned off by this attrib.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
remove_off_stage(TextureStage *stage) const {
  TextureAttrib *attrib = new TextureAttrib(*this);
  attrib->_off_stages.erase(stage);
  return return_new(attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::unify_texture_stages
//       Access: Published
//  Description: Returns a new TextureAttrib, just like this one, but
//               with any included TextureAttribs that happen to have
//               the same name as the given object replaced with the
//               object.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
unify_texture_stages(TextureStage *stage) const {
  PT(TextureAttrib) attrib = new TextureAttrib;

  attrib->_off_all_stages = _off_all_stages;
  bool any_changed = false;

  OnTextures::const_iterator nti;
  for (nti = _on_textures.begin(); nti != _on_textures.end(); ++nti) {
    if ((*nti).first != stage && 
        (*nti).first->get_name() == stage->get_name()) {
      attrib->_on_textures[stage] = (*nti).second;
      any_changed = true;
    } else {
      attrib->_on_textures.insert(*nti);
    }
  }

  // Now copy from _on_textures to the _on_stages list.  We can't do
  // this as we walk through the list the first pass, since we might
  // have collapsed together multiple different stages.
  for (nti = attrib->_on_textures.begin(); 
       nti != attrib->_on_textures.end(); 
       ++nti) {
    attrib->_on_stages.push_back((*nti).first);
  }

  OffStages::const_iterator fsi;
  for (fsi = _off_stages.begin(); fsi != _off_stages.end(); ++fsi) {
    if ((*fsi) != stage && 
        (*fsi)->get_name() == stage->get_name()) {
      attrib->_off_stages.insert(stage);
      any_changed = true;
    } else {
      attrib->_off_stages.insert(*fsi);
    }
  }

  if (!any_changed) {
    return this;
  }

  return return_new(attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::filter_to_max
//       Access: Public
//  Description: Returns a new TextureAttrib, very much like this one,
//               but with the number of on_stages reduced to be no
//               more than max_texture_stages.  The number of
//               off_stages in the new TextureAttrib is undefined.
////////////////////////////////////////////////////////////////////
CPT(TextureAttrib) TextureAttrib::
filter_to_max(int max_texture_stages) const {
  if ((int)_on_stages.size() <= max_texture_stages) {
    // Trivial case: this TextureAttrib qualifies.
    return this;
  }

  // Since check_sorted() will clear the _filtered list if we are out
  // of date, we should call it first.
  check_sorted();

  Filtered::const_iterator fi;
  fi = _filtered.find(max_texture_stages);
  if (fi != _filtered.end()) {
    // Easy case: we have already computed this for this particular
    // TextureAttrib.
    return (*fi).second;
  }

  // Harder case: we have to compute it now.  We must choose the n
  // stages with the highest priority in our list of stages.  In the
  // case of equal priority, we prefer the stage with the lower sort.
  OnStages priority_stages = _on_stages;

  // This sort function uses the STL function object defined above.
  sort(priority_stages.begin(), priority_stages.end(), 
       CompareTextureStagePriorities());

  // Now lop off all of the stages after the first max_texture_stages.
  priority_stages.erase(priority_stages.begin() + max_texture_stages,
                        priority_stages.end());

  // And create a new attrib reflecting these stages.
  PT(TextureAttrib) attrib = new TextureAttrib;

  OnStages::const_iterator si;
  for (si = priority_stages.begin(); si != priority_stages.end(); ++si) {
    TextureStage *stage = (*si);
    attrib->_on_textures[stage] = get_on_texture(stage);
  }

  attrib->_on_stages.swap(priority_stages);

  CPT(RenderAttrib) new_attrib = return_new(attrib);

  // Finally, record this newly-created attrib in the map for next
  // time.
  CPT(TextureAttrib) tex_attrib = (const TextureAttrib *)new_attrib.p();
  ((TextureAttrib *)this)->_filtered[max_texture_stages] = tex_attrib;
  return tex_attrib;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void TextureAttrib::
output(ostream &out) const {
  check_sorted();

  out << get_type() << ":";
  if (_off_stages.empty()) {
    if (_on_stages.empty()) {
      if (_off_all_stages) {
        out << "all off";
      } else {
        out << "identity";
      }
    } else {
      if (_off_all_stages) {
        out << "set";
      } else {
        out << "on";
      }
    }

  } else {
    out << "off";
    OffStages::const_iterator fi;
    for (fi = _off_stages.begin(); fi != _off_stages.end(); ++fi) {
      TextureStage *stage = (*fi);
      out << " " << stage->get_name();
    }

    if (!_on_stages.empty()) {
      out << " on";
    }
  }
    
  OnStages::const_iterator li;
  for (li = _on_stages.begin(); li != _on_stages.end(); ++li) {
    TextureStage *stage = (*li);
    OnTextures::const_iterator ti = _on_textures.find(stage);
    if (ti != _on_textures.end()) {
      Texture *tex = (*ti).second;
      out << " " << stage->get_name() << ":" << tex->get_name();
    } else {
      out << " " << stage->get_name();
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::has_cull_callback
//       Access: Public, Virtual
//  Description: Should be overridden by derived classes to return
//               true if cull_callback() has been defined.  Otherwise,
//               returns false to indicate cull_callback() does not
//               need to be called for this node during the cull
//               traversal.
////////////////////////////////////////////////////////////////////
bool TextureAttrib::
has_cull_callback() const {
  OnTextures::const_iterator nti;
  for (nti = _on_textures.begin(); nti != _on_textures.end(); ++nti) {
    Texture *texture = (*nti).second;
    if (texture->has_cull_callback()) {
      return true;
    }
  }

  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::cull_callback
//       Access: Public, Virtual
//  Description: If has_cull_callback() returns true, this function
//               will be called during the cull traversal to perform
//               any additional operations that should be performed at
//               cull time.
//
//               This is called each time the RenderAttrib is
//               discovered applied to a Geom in the traversal.  It
//               should return true if the Geom is visible, false if
//               it should be omitted.
////////////////////////////////////////////////////////////////////
bool TextureAttrib::
cull_callback(CullTraverser *trav, const CullTraverserData &data) const {
  OnTextures::const_iterator nti;
  for (nti = _on_textures.begin(); nti != _on_textures.end(); ++nti) {
    Texture *texture = (*nti).second;
    if (!texture->cull_callback(trav, data)) {
      return false;
    }
  }

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::compare_to_impl
//       Access: Protected, Virtual
//  Description: Intended to be overridden by derived TextureAttrib
//               types to return a unique number indicating whether
//               this TextureAttrib is equivalent to the other one.
//
//               This should return 0 if the two TextureAttrib objects
//               are equivalent, a number less than zero if this one
//               should be sorted before the other one, and a number
//               greater than zero otherwise.
//
//               This will only be called with two TextureAttrib
//               objects whose get_type() functions return the same.
////////////////////////////////////////////////////////////////////
int TextureAttrib::
compare_to_impl(const RenderAttrib *other) const {
  const TextureAttrib *ta;
  DCAST_INTO_R(ta, other, 0);

  if (_off_all_stages != ta->_off_all_stages) {
    return (int)_off_all_stages - (int)ta->_off_all_stages;
  }

  OnTextures::const_iterator li = _on_textures.begin();
  OnTextures::const_iterator oli = ta->_on_textures.begin();

  while (li != _on_textures.end() && oli != ta->_on_textures.end()) {
    TextureStage *stage = (*li).first;
    TextureStage *other_stage = (*oli).first;

    if (stage != other_stage) {
      return stage < other_stage ? -1 : 1;
    }

    Texture *tex = (*li).second;
    Texture *other_tex = (*oli).second;
    if (tex != other_tex) {
      return tex < other_tex ? -1 : 1;
    }

    ++li;
    ++oli;
  }

  if (li != _on_textures.end()) {
    return 1;
  }
  if (oli != ta->_on_textures.end()) {
    return -1;
  }

  OffStages::const_iterator fi = _off_stages.begin();
  OffStages::const_iterator ofi = ta->_off_stages.begin();

  while (fi != _off_stages.end() && ofi != ta->_off_stages.end()) {
    TextureStage *stage = (*fi);
    TextureStage *other_stage = (*ofi);

    if (stage != other_stage) {
      return stage < other_stage ? -1 : 1;
    }

    ++fi;
    ++ofi;
  }

  if (fi != _off_stages.end()) {
    return 1;
  }
  if (ofi != ta->_off_stages.end()) {
    return -1;
  }
  
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::compose_impl
//       Access: Protected, Virtual
//  Description: Intended to be overridden by derived RenderAttrib
//               types to specify how two consecutive RenderAttrib
//               objects of the same type interact.
//
//               This should return the result of applying the other
//               RenderAttrib to a node in the scene graph below this
//               RenderAttrib, which was already applied.  In most
//               cases, the result is the same as the other
//               RenderAttrib (that is, a subsequent RenderAttrib
//               completely replaces the preceding one).  On the other
//               hand, some kinds of RenderAttrib (for instance,
//               ColorTransformAttrib) might combine in meaningful
//               ways.
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
compose_impl(const RenderAttrib *other) const {
  const TextureAttrib *ta;
  DCAST_INTO_R(ta, other, 0);

  if (ta->_off_all_stages) {
    // If the other type turns off all stages, it doesn't matter what
    // we are.
    return ta;
  }

  // This is a three-way merge between ai, bi, and ci, except that bi
  // and ci should have no intersection and therefore needn't be
  // compared to each other.
  OnTextures::const_iterator ai = _on_textures.begin();
  OnTextures::const_iterator bi = ta->_on_textures.begin();
  OffStages::const_iterator ci = ta->_off_stages.begin();

  // Create a new TextureAttrib that will hold the result.
  TextureAttrib *new_attrib = new TextureAttrib;

  while (ai != _on_textures.end() && 
         bi != ta->_on_textures.end() && 
         ci != ta->_off_stages.end()) {
    if ((*ai).first < (*bi).first) {
      if ((*ai).first < (*ci)) {
        // Here is a stage that we have in the original, which is not
        // present in the secondary.
        new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *ai);
        new_attrib->_on_stages.push_back((*ai).first);
        ++ai;

      } else if ((*ci) < (*ai).first) {
        // Here is a stage that is turned off in the secondary, but
        // was not present in the original.
        ++ci;

      } else { // (*ci) == (*ai).first
        // Here is a stage that is turned off in the secondary, and
        // was present in the original.
        ++ai;
        ++ci;
      }

    } else if ((*bi).first < (*ai).first) {
      // Here is a new stage we have in the secondary, that was not
      // present in the original.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *bi);
      new_attrib->_on_stages.push_back((*bi).first);
      ++bi;

    } else {  // (*bi).first == (*ai).first
      // Here is a stage we have in both.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *bi);
      new_attrib->_on_stages.push_back((*ai).first);
      ++ai;
      ++bi;
    }
  }

  while (ai != _on_textures.end() && bi != ta->_on_textures.end()) {
    if ((*ai).first < (*bi).first) {
      // Here is a stage that we have in the original, which is not
      // present in the secondary.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *ai);
      new_attrib->_on_stages.push_back((*ai).first);
      ++ai;

    } else if ((*bi).first < (*ai).first) {
      // Here is a new stage we have in the secondary, that was not
      // present in the original.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *bi);
      new_attrib->_on_stages.push_back((*bi).first);
      ++bi;

    } else {
      // Here is a stage we have in both.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *bi);
      new_attrib->_on_stages.push_back((*ai).first);
      ++ai;
      ++bi;
    }
  }

  while (ai != _on_textures.end() && ci != ta->_off_stages.end()) {
    if ((*ai).first < (*ci)) {
      // Here is a stage that we have in the original, which is not
      // present in the secondary.
      new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *ai);
      new_attrib->_on_stages.push_back((*ai).first);
      ++ai;
      
    } else if ((*ci) < (*ai).first) {
      // Here is a stage that is turned off in the secondary, but
      // was not present in the original.
      ++ci;
      
    } else { // (*ci) == (*ai).first
      // Here is a stage that is turned off in the secondary, and
      // was present in the original.
      ++ai;
      ++ci;
    }
  }

  while (ai != _on_textures.end()) {
    new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *ai);
    new_attrib->_on_stages.push_back((*ai).first);
    ++ai;
  }

  while (bi != ta->_on_textures.end()) {
    new_attrib->_on_textures.insert(new_attrib->_on_textures.end(), *bi);
    new_attrib->_on_stages.push_back((*bi).first);
    ++bi;
  }

  return return_new(new_attrib);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::invert_compose_impl
//       Access: Protected, Virtual
//  Description: Intended to be overridden by derived RenderAttrib
//               types to specify how two consecutive RenderAttrib
//               objects of the same type interact.
//
//               See invert_compose() and compose_impl().
////////////////////////////////////////////////////////////////////
CPT(RenderAttrib) TextureAttrib::
invert_compose_impl(const RenderAttrib *other) const {
  // I think in this case the other attrib always wins.  Maybe this
  // needs a bit more thought.  It's hard to imagine that it's even
  // important to compute this properly.
  return other;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make_default_impl
//       Access: Protected, Virtual
//  Description: Intended to be overridden by derived TextureAttrib
//               types to specify what the default property for a
//               TextureAttrib of this type should be.
//
//               This should return a newly-allocated TextureAttrib of
//               the same type that corresponds to whatever the
//               standard default for this kind of TextureAttrib is.
////////////////////////////////////////////////////////////////////
RenderAttrib *TextureAttrib::
make_default_impl() const {
  return new TextureAttrib;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::store_into_slot
//       Access: Public, Virtual
//  Description: Stores this attrib into the appropriate slot of
//               an object of class AttribSlots.
////////////////////////////////////////////////////////////////////
void TextureAttrib::
store_into_slot(AttribSlots *slots) const {
  slots->_texture = this;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::register_with_read_factory
//       Access: Public, Static
//  Description: Tells the BamReader how to create objects of type
//               TextureAttrib.
////////////////////////////////////////////////////////////////////
void TextureAttrib::
register_with_read_factory() {
  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void TextureAttrib::
write_datagram(BamWriter *manager, Datagram &dg) {
  RenderAttrib::write_datagram(manager, dg);

#if 1
  // TODO: write the multitexture data.
  // write the boolean if _off_all_stages
  dg.add_bool(_off_all_stages);
  // write the number of off_stages
  dg.add_uint16(get_num_off_stages());
  // write the off stages pointers if any
  OffStages::const_iterator fi;
  for (fi = _off_stages.begin(); fi != _off_stages.end(); ++fi) {
    TextureStage *stage = (*fi);
    manager->write_pointer(dg, stage);
  }
  // write the number of on stages
  dg.add_uint16(get_num_on_stages());
  // write the on stages pointers if any
  OnTextures::const_iterator nti;
  for (nti = _on_textures.begin(); nti != _on_textures.end(); ++nti) {
    TextureStage *stage = (*nti).first;
    manager->write_pointer(dg, stage);
    Texture *texture = (*nti).second;
    manager->write_pointer(dg,texture);
  }
#else
  manager->write_pointer(dg, get_texture());
#endif
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::complete_pointers
//       Access: Public, Virtual
//  Description: Receives an array of pointers, one for each time
//               manager->read_pointer() was called in fillin().
//               Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int TextureAttrib::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
  int pi = RenderAttrib::complete_pointers(p_list, manager);

  OffStages::iterator ci = _off_stages.begin();
  while (ci != _off_stages.end()) {
    TextureStage *ts = DCAST(TextureStage, p_list[pi++]);
    *ci = ts;
    ++ci;
  }
  
  // read the pointers of the on_textures
  _on_stages.reserve(_num_on_textures);
  
  for (int i = 0; i < _num_on_textures; ++i) {
    TextureStage *ts = DCAST(TextureStage, p_list[pi++]);
    Texture *tx = DCAST(Texture, p_list[pi++]);
    if (tx != (Texture *)NULL) {
      _on_textures[ts] = tx;
      _on_stages.push_back(ts);
      
    } else {
      // If we couldn't load a texture pointer, turn off that
      // particular texture stage.
      _off_stages.push_back(ts);
    }
  }
  _sort_seq = UpdateSeq::old();

  return pi;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::make_from_bam
//       Access: Protected, Static
//  Description: This function is called by the BamReader's factory
//               when a new object of type TextureAttrib is encountered
//               in the Bam file.  It should create the TextureAttrib
//               and extract its information from the file.
////////////////////////////////////////////////////////////////////
TypedWritable *TextureAttrib::
make_from_bam(const FactoryParams &params) {
  TextureAttrib *attrib = new TextureAttrib;
  DatagramIterator scan;
  BamReader *manager;

  parse_params(params, scan, manager);
  attrib->fillin(scan, manager);

  return attrib;
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::fillin
//       Access: Protected
//  Description: This internal function is called by make_from_bam to
//               read in all of the relevant data from the BamFile for
//               the new TextureAttrib.
////////////////////////////////////////////////////////////////////
void TextureAttrib::
fillin(DatagramIterator &scan, BamReader *manager) {
  RenderAttrib::fillin(scan, manager);

  // read the boolean if _off_all_stages
  _off_all_stages = scan.get_bool();
  // read the number of off_stages
  int num_off_stages = scan.get_uint16();
  
  // Push back a NULL pointer for each off TextureStage for now, until
  // we get the actual list of pointers later in complete_pointers().
  int i;
  _off_stages.reserve(num_off_stages);
  for (i = 0; i < num_off_stages; i++) {
    manager->read_pointer(scan);
    _off_stages.push_back(NULL);
  }
  // read the number of on stages
  _num_on_textures = scan.get_uint16();

  // just read the pointers, all allocation will happen from
  // complete_pointers because it is a map template we get the actual
  // list of pointers later in complete_pointers().
  for (i = 0; i < _num_on_textures; i++) {
    manager->read_pointer(scan);
    manager->read_pointer(scan);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: TextureAttrib::sort_on_stages
//       Access: Private
//  Description: Sorts the list of stages so that they are listed in
//               render order.  Also clears the _filtered map.
////////////////////////////////////////////////////////////////////
void TextureAttrib::
sort_on_stages() {
  sort(_on_stages.begin(), _on_stages.end(), IndirectLess<TextureStage>());

  _sort_seq = TextureStage::get_sort_seq();

  // Also clear the _filtered map, so we'll have to recompute those
  // (in case the priority orders have changed as well).
  _filtered.clear();
}
