text: // Filename: transformBlend.cxx
  // Created by:  drose (24Mar05)
  //
  ////////////////////////////////////////////////////////////////////
  //
  // 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 "transformBlend.h"
  #include "indent.h"
  #include "bamReader.h"
  #include "bamWriter.h"
  
  TypeHandle TransformBlend::_type_handle;
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::compare_to
  //       Access: Published
  //  Description: Defines an arbitrary ordering for TransformBlend
  //               objects.
  ////////////////////////////////////////////////////////////////////
  int TransformBlend::
  compare_to(const TransformBlend &other) const {
    if (_entries.size() != other._entries.size()) {
      return (int)_entries.size() - (int)other._entries.size();
    }
  
    Entries::const_iterator ai, bi;
    ai = _entries.begin();
    bi = other._entries.begin();
    while (ai != _entries.end() && bi != other._entries.end()) {
      if ((*ai)._transform != (*bi)._transform) {
        return (*ai)._transform < (*bi)._transform ? -1 : 1;
      }
      if (!IS_NEARLY_EQUAL((*ai)._weight, (*bi)._weight)) {
        return (*ai)._weight < (*bi)._weight ? -1 : 1;
      }
      ++ai;
      ++bi;
    }
  
    return 0;
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::add_transform
  //       Access: Published
  //  Description: Adds a new transform to the blend.  If the transform
  //               already existed, increases its weight factor.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  add_transform(const VertexTransform *transform, PN_stdfloat weight) {
    if (!IS_NEARLY_ZERO(weight)) {
      TransformEntry entry;
      entry._transform = transform;
      entry._weight = weight;
      pair<Entries::iterator, bool> result = _entries.insert(entry);
      if (!result.second) {
        // If the new value was not inserted, it was already there;
        // increment the existing weight factor.
        Entries::iterator ei = result.first;
        (*ei)._weight += weight;
        if (IS_NEARLY_ZERO((*ei)._weight)) {
          // If we have just zeroed out the weight, remove it.
          _entries.erase(ei);
        }
      }
      Thread *current_thread = Thread::get_current_thread();
      clear_result(current_thread);
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::remove_transform
  //       Access: Published
  //  Description: Removes the indicated transform from the blend.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  remove_transform(const VertexTransform *transform) {
    TransformEntry entry;
    entry._transform = transform;
    entry._weight = 0.0f;
    Entries::iterator ei = _entries.find(entry);
    if (ei != _entries.end()) {
      _entries.erase(ei);
    }
    Thread *current_thread = Thread::get_current_thread();
    clear_result(current_thread);
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::limit_transforms
  //       Access: Published
  //  Description: If the total number of transforms in the blend
  //               exceeds max_transforms, removes the n least-important
  //               transforms as needed to reduce the number of
  //               transforms to max_transforms.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  limit_transforms(int max_transforms) {
    if (max_transforms <= 0) {
      _entries.clear();
      return;
    }
  
    while ((int)_entries.size() > max_transforms) {
      // Repeatedly find and remove the least-important transform.
      nassertv(!_entries.empty());
      Entries::iterator ei_least = _entries.begin();
      Entries::iterator ei = ei_least;
      ++ei;
      while (ei != _entries.end()) {
        if ((*ei)._weight < (*ei_least)._weight) {
          ei_least = ei;
        }
        ++ei;
      }
  
      _entries.erase(ei_least);
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::normalize_weights
  //       Access: Published
  //  Description: Rescales all of the weights on the various transforms
  //               so that they sum to 1.0.  It is generally a good idea
  //               to call this after adding or removing transforms from
  //               the blend.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  normalize_weights() {
    PN_stdfloat net_weight = 0.0f;
    Entries::iterator ei;
    for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
      net_weight += (*ei)._weight;
    }
    if (net_weight != 0.0f) {
      for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
        (*ei)._weight /= net_weight;
      }
    }
    Thread *current_thread = Thread::get_current_thread();
    clear_result(current_thread);
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::has_transform
  //       Access: Published
  //  Description: Returns true if the blend has the indicated
  //               transform, false otherwise.
  ////////////////////////////////////////////////////////////////////
  bool TransformBlend::
  has_transform(const VertexTransform *transform) const {
    TransformEntry entry;
    entry._transform = transform;
    entry._weight = 0.0f;
    Entries::const_iterator ei = _entries.find(entry);
    return (ei != _entries.end());
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::get_weight
  //       Access: Published
  //  Description: Returns the weight associated with the indicated
  //               transform, or 0 if there is no entry for the
  //               transform.
  ////////////////////////////////////////////////////////////////////
  PN_stdfloat TransformBlend::
  get_weight(const VertexTransform *transform) const {
    TransformEntry entry;
    entry._transform = transform;
    entry._weight = 0.0f;
    Entries::const_iterator ei = _entries.find(entry);
    if (ei != _entries.end()) {
      return (*ei)._weight;
    }
    return 0.0f;
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::output
  //       Access: Published
  //  Description: 
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  output(ostream &out) const {
    if (_entries.empty()) {
      out << "empty";
    } else {
      Entries::const_iterator ei = _entries.begin();
      out << *(*ei)._transform << ":" << (*ei)._weight;
      ++ei;
      while (ei != _entries.end()) {
        out << " " << *(*ei)._transform << ":" << (*ei)._weight;
        ++ei;
      }
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::write
  //       Access: Published
  //  Description: 
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  write(ostream &out, int indent_level) const {
    Thread *current_thread = Thread::get_current_thread();
    Entries::const_iterator ei;
    for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
      indent(out, indent_level)
        << *(*ei)._transform << ":" << (*ei)._weight << "\n";
      LMatrix4 mat;
      (*ei)._transform->get_matrix(mat);
      mat.write(out, indent_level + 4);
    }
    LMatrix4 blend;
    update_blend(current_thread);
    get_blend(blend, current_thread);
    indent(out, indent_level)
      << "Blended result =\n";
    blend.write(out, indent_level + 2);
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::recompute_result
  //       Access: Private
  //  Description: Recomputes the blend result from the various
  //               VertexTransform objects, if necessary.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  recompute_result(CData *cdata, Thread *current_thread) {
    // Update the global_modified sequence number first, to prevent race
    // conditions.
    cdata->_global_modified = VertexTransform::get_global_modified(current_thread);
  
    // Now see if we really need to recompute.
    UpdateSeq seq;
    Entries::const_iterator ei;
    for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
      seq = max(seq, (*ei)._transform->get_modified(current_thread));
    }
  
    if (cdata->_modified != seq) {
      // We do need to recompute.
      cdata->_modified = seq;
  
      cdata->_result = LMatrix4::zeros_mat();
      for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
        (*ei)._transform->accumulate_matrix(cdata->_result, (*ei)._weight);
      }
    }    
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::clear_result
  //       Access: Private
  //  Description: Removes the computed result to force it to be
  //               recomputed.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  clear_result(Thread *current_thread) {
    CDWriter cdata(_cycler, true, current_thread);
    cdata->_global_modified = UpdateSeq();
    if (cdata->_modified != UpdateSeq()) {
      cdata->_modified = UpdateSeq();
      cdata->_result = LMatrix4::ident_mat();
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::write_datagram
  //       Access: Public
  //  Description: Writes the contents of this object to the datagram
  //               for shipping out to a Bam file.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  write_datagram(BamWriter *manager, Datagram &dg) const {
    dg.add_uint16(_entries.size());
  
    Entries::const_iterator ei;
    for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
      manager->write_pointer(dg, (*ei)._transform);
      dg.add_stdfloat((*ei)._weight);
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::complete_pointers
  //       Access: Public
  //  Description: Receives an array of pointers, one for each time
  //               manager->read_pointer() was called in fillin().
  //               Returns the number of pointers processed.
  ////////////////////////////////////////////////////////////////////
  int TransformBlend::
  complete_pointers(TypedWritable **p_list, BamReader *manager) {
    int pi = 0;
  
    Entries::iterator ei;
    for (ei = _entries.begin(); ei != _entries.end(); ++ei) {
      (*ei)._transform = DCAST(VertexTransform, p_list[pi++]);
    }
  
    // Now that we have actual pointers, we can sort the list of
    // entries.
    _entries.sort();
  
    return pi;
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::fillin
  //       Access: Public
  //  Description: This internal function is called by make_from_bam to
  //               read in all of the relevant data from the BamFile for
  //               the new PandaNode.
  ////////////////////////////////////////////////////////////////////
  void TransformBlend::
  fillin(DatagramIterator &scan, BamReader *manager) {
    size_t num_entries = scan.get_uint16();
    _entries.reserve(num_entries);
    for (size_t i = 0; i < num_entries; ++i) {
      TransformEntry entry;
      manager->read_pointer(scan);
      entry._weight = scan.get_stdfloat();
      _entries.push_back(entry);
    }
  }
  
  ////////////////////////////////////////////////////////////////////
  //     Function: TransformBlend::CData::make_copy
  //       Access: Public, Virtual
  //  Description:
  ////////////////////////////////////////////////////////////////////
  CycleData *TransformBlend::CData::
  make_copy() const {
    return new CData(*this);
  }
  