// Filename: lodNode.cxx
// Created by:  drose (06Mar02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "lodNode.h"
#include "fadeLodNode.h"
#include "cullTraverserData.h"
#include "cullTraverser.h"
#include "config_pgraphnodes.h"
#include "geomVertexData.h"
#include "geomVertexWriter.h"
#include "geomVertexFormat.h"
#include "geomTristrips.h"
#include "mathNumbers.h"
#include "geom.h"
#include "geomNode.h"
#include "transformState.h"
#include "material.h"
#include "materialAttrib.h"
#include "materialPool.h"
#include "renderState.h"
#include "cullFaceAttrib.h"
#include "textureAttrib.h"
#include "boundingSphere.h"
#include "geometricBoundingVolume.h"
#include "look_at.h"
#include "nodePath.h"
#include "shaderAttrib.h"
#include "colorAttrib.h"
#include "clipPlaneAttrib.h"

TypeHandle LODNode::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: LODNode::make_default_lod
//       Access: Published, Static
//  Description: Creates a new LODNode of the type specified by the
//               default-lod-type config variable.
////////////////////////////////////////////////////////////////////
PT(LODNode) LODNode::
make_default_lod(const string &name) {
  switch (default_lod_type.get_value()) {
  case LNT_pop:
    return new LODNode(name);

  case LNT_fade:
    return new FadeLODNode(name);
    
  default:
    pgraph_cat.error()
      << "Invalid LODNodeType value: " << (int)default_lod_type << "\n";
    return new LODNode(name);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::make_copy
//       Access: Public, Virtual
//  Description: Returns a newly-allocated Node that is a shallow copy
//               of this one.  It will be a different Node pointer,
//               but its internal data may or may not be shared with
//               that of the original Node.
////////////////////////////////////////////////////////////////////
PandaNode *LODNode::
make_copy() const {
  return new LODNode(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::safe_to_combine
//       Access: Public, Virtual
//  Description: Returns true if it is generally safe to combine this
//               particular kind of PandaNode with other kinds of
//               PandaNodes of compatible type, adding children or
//               whatever.  For instance, an LODNode should not be
//               combined with any other PandaNode, because its set of
//               children is meaningful.
////////////////////////////////////////////////////////////////////
bool LODNode::
safe_to_combine() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::safe_to_combine_children
//       Access: Public, Virtual
//  Description: Returns true if it is generally safe to combine the
//               children of this PandaNode with each other.  For
//               instance, an LODNode's children should not be
//               combined with each other, because the set of children
//               is meaningful.
////////////////////////////////////////////////////////////////////
bool LODNode::
safe_to_combine_children() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::xform
//       Access: Public, Virtual
//  Description: Transforms the contents of this PandaNode by the
//               indicated matrix, if it means anything to do so.  For
//               most kinds of PandaNodes, this does nothing.
////////////////////////////////////////////////////////////////////
void LODNode::
xform(const LMatrix4 &mat) {
  CDWriter cdata(_cycler);

  cdata->_center = cdata->_center * mat;

  // We'll take just the length of the y axis as the matrix's scale.
  LVector3 y;
  mat.get_row3(y, 1);
  PN_stdfloat factor = y.length();

  SwitchVector::iterator si;
  for (si = cdata->_switch_vector.begin(); 
       si != cdata->_switch_vector.end(); 
       ++si) {
    (*si).rescale(factor);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::cull_callback
//       Access: Public, Virtual
//  Description: This function will be called during the cull
//               traversal to perform any additional operations that
//               should be performed at cull time.  This may include
//               additional manipulation of render state or additional
//               visible/invisible decisions, or any other arbitrary
//               operation.
//
//               Note that this function will *not* be called unless
//               set_cull_callback() is called in the constructor of
//               the derived class.  It is necessary to call
//               set_cull_callback() to indicated that we require
//               cull_callback() to be called.
//
//               By the time this function is called, the node has
//               already passed the bounding-volume test for the
//               viewing frustum, and the node's transform and state
//               have already been applied to the indicated
//               CullTraverserData object.
//
//               The return value is true if this node should be
//               visible, or false if it should be culled.
////////////////////////////////////////////////////////////////////
bool LODNode::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
  if (is_any_shown()) {
    return show_switches_cull_callback(trav, data);
  }

  consider_verify_lods(trav, data);

  CDReader cdata(_cycler);

  CPT(TransformState) rel_transform = get_rel_transform(trav, data);
  LPoint3 center = cdata->_center * rel_transform->get_mat();
  PN_stdfloat dist2 = center.dot(center);

  int num_children = min(get_num_children(), (int)cdata->_switch_vector.size());
  for (int index = 0; index < num_children; ++index) {
    const Switch &sw = cdata->_switch_vector[index];
    bool in_range;
    if (cdata->_got_force_switch) {
      in_range = (cdata->_force_switch == index);
    } else {
      in_range = sw.in_range_2(dist2 * cdata->_lod_scale
                   * trav->get_scene()->get_camera_node()->get_lod_scale());
    }
    
    if (in_range) {
      // This switch level is in range.  Draw its children.
      PandaNode *child = get_child(index);
      if (child != (PandaNode *)NULL) {
        CullTraverserData next_data(data, child);
        trav->traverse(next_data);
      }
    }
  }

  // Now return false indicating that we have already taken care of
  // the traversal from here.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::output
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
void LODNode::
output(ostream &out) const {
  PandaNode::output(out);
  CDReader cdata(_cycler);
  out << " center(" << cdata->_center << ") ";
  if (cdata->_switch_vector.empty()) {
    out << "no switches.";
  } else {
    SwitchVector::const_iterator si;
    si = cdata->_switch_vector.begin();
    out << "(" << (*si).get_in() << "/" << (*si).get_out() << ")";
    ++si;
    while (si != cdata->_switch_vector.end()) {
      out << " (" << (*si).get_in() << "/" << (*si).get_out() << ")";
      ++si;
    }
  }
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::is_lod_node
//       Access: Published, Virtual
//  Description: A simple downcast check.  Returns true if this kind
//               of node happens to inherit from LODNode, false
//               otherwise.
//
//               This is provided as a a faster alternative to calling
//               is_of_type(LODNode::get_class_type()).
////////////////////////////////////////////////////////////////////
bool LODNode::
is_lod_node() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::show_switch
//       Access: Published
//  Description: This is provided as a debugging aid.  show_switch()
//               will put the LODNode into a special mode where rather
//               than computing and drawing the appropriate level of
//               the LOD, a ring is drawn around the LODNode center
//               indicating the switch distances from the camera for
//               the indicated level, and the geometry of the
//               indicated level is drawn in wireframe.
//
//               Multiple different levels can be visualized this way
//               at once.  Call hide_switch() or hide_all_switches() to
//               undo this mode and restore the LODNode to its normal
//               behavior.
////////////////////////////////////////////////////////////////////
void LODNode::
show_switch(int index) {
  CDWriter cdata(_cycler);
  do_show_switch(cdata, index, get_default_show_color(index));
  mark_internal_bounds_stale();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::show_switch
//       Access: Published
//  Description: This is provided as a debugging aid.  show_switch()
//               will put the LODNode into a special mode where rather
//               than computing and drawing the appropriate level of
//               the LOD, a ring is drawn around the LODNode center
//               indicating the switch distances from the camera for
//               the indicated level, and the geometry of the
//               indicated level is drawn in wireframe.
//
//               Multiple different levels can be visualized this way
//               at once.  Call hide_switch() or hide_all_switches() to
//               undo this mode and restore the LODNode to its normal
//               behavior.
////////////////////////////////////////////////////////////////////
void LODNode::
show_switch(int index, const LColor &color) {
  CDWriter cdata(_cycler);
  do_show_switch(cdata, index, color);
  mark_internal_bounds_stale();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::hide_switch
//       Access: Published
//  Description: Disables a previous call to show_switch().
////////////////////////////////////////////////////////////////////
void LODNode::
hide_switch(int index) {
  CDWriter cdata(_cycler);
  do_hide_switch(cdata, index);
  mark_internal_bounds_stale();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::show_all_switches
//       Access: Published
//  Description: Shows all levels in their default colors.
////////////////////////////////////////////////////////////////////
void LODNode::
show_all_switches() {
  CDWriter cdata(_cycler);
  for (int i = 0; i < (int)cdata->_switch_vector.size(); ++i) {
    do_show_switch(cdata, i, get_default_show_color(i));
  }
  mark_internal_bounds_stale();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::hide_all_switches
//       Access: Published
//  Description: Hides all levels, restoring the LODNode to normal
//               operation.
////////////////////////////////////////////////////////////////////
void LODNode::
hide_all_switches() {
  CDWriter cdata(_cycler);
  for (int i = 0; i < (int)cdata->_switch_vector.size(); ++i) {
    do_hide_switch(cdata, i);
  }
  mark_internal_bounds_stale();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::verify_child_bounds
//       Access: Published
//  Description: Returns true if the bounding volumes for the geometry
//               of each fhild node entirely fits within the
//               switch_in radius for that child, or false otherwise.
//               It is almost always a mistake for the geometry of an
//               LOD level to be larger than its switch_in radius.
////////////////////////////////////////////////////////////////////
bool LODNode::
verify_child_bounds() const {
  bool okflag = true;
  CDReader cdata(_cycler);

  for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
    PN_stdfloat suggested_radius;
    if (!do_verify_child_bounds(cdata, index, suggested_radius)) { 
      const Switch &sw = cdata->_switch_vector[index];
      pgraph_cat.warning()
        << "Level " << index << " geometry of " << *this
        << " is larger than its switch radius; suggest radius of "
        << suggested_radius << " instead of " << sw.get_in() << "\n";
      okflag = false;
    }
  }

  return okflag;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::compute_child
//       Access: Protected
//  Description: Determines which child should be visible according to
//               the current camera position.  If a child is visible,
//               returns its index number; otherwise, returns -1.
////////////////////////////////////////////////////////////////////
int LODNode::
compute_child(CullTraverser *trav, CullTraverserData &data) {
  if (data.get_net_transform(trav)->is_singular()) {
    // If we're under a singular transform, we can't compute the LOD;
    // select none of them instead.
    return -1;
  }
   
  CDReader cdata(_cycler);

  if (cdata->_got_force_switch) {
    return cdata->_force_switch;
  }

  CPT(TransformState) rel_transform = get_rel_transform(trav, data);
  LPoint3 center = cdata->_center * rel_transform->get_mat();
  PN_stdfloat dist2 = center.dot(center);

  for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
    if (cdata->_switch_vector[index].in_range_2(dist2 * cdata->_lod_scale
         * trav->get_scene()->get_camera_node()->get_lod_scale())) { 
      if (pgraph_cat.is_debug()) {
        pgraph_cat.debug()
          << data._node_path << " at distance " << sqrt(dist2)
          << ", selected child " << index << "\n";
      }

      return index;
    }
  }

  if (pgraph_cat.is_debug()) {
    pgraph_cat.debug()
      << data._node_path << " at distance " << sqrt(dist2)
      << ", no children in range.\n";
  }

  return -1;
}


////////////////////////////////////////////////////////////////////
//     Function: LODNode::show_switches_cull_callback
//       Access: Protected
//  Description: A special version of cull_callback() that is to be
//               invoked when the LODNode is in show_switch() mode.
//               This just draws the rings and the wireframe geometry
//               for the selected switches.
////////////////////////////////////////////////////////////////////
bool LODNode::
show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
  CDReader cdata(_cycler);

  CPT(TransformState) rel_transform = get_rel_transform(trav, data);
  LPoint3 center = cdata->_center * rel_transform->get_mat();
  PN_stdfloat dist2 = center.dot(center);

  // Now orient the disk(s) in camera space such that their origin is
  // at center, and the (0, 0, 0) point in camera space is on the disk.
  LMatrix4 mat;
  look_at(mat, -center, LVector3(0.0f, 0.0f, 1.0f));
  mat.set_row(3, center);
  CPT(TransformState) viz_transform = 
    rel_transform->invert_compose(TransformState::make_mat(mat));

  for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
    const Switch &sw = cdata->_switch_vector[index];
    if (sw.is_shown()) {
      bool in_range;
      if (cdata->_got_force_switch) {
        in_range = (cdata->_force_switch == index);
      } else {
        in_range = sw.in_range_2(dist2);
      }

      if (in_range) {
        // This switch level is in range.  Draw its children in the
        // funny wireframe mode.
        if (index < get_num_children()) {
          PandaNode *child = get_child(index);
          if (child != (PandaNode *)NULL) {
            CullTraverserData next_data3(data, child);
            next_data3._state = next_data3._state->compose(sw.get_viz_model_state());
            trav->traverse(next_data3);
          }
        }

        // And draw the spindle in this color.
        CullTraverserData next_data2(data, sw.get_spindle_viz());
        next_data2.apply_transform_and_state(trav, viz_transform,
                                             RenderState::make_empty(),
                                             RenderEffects::make_empty(),
                                             ClipPlaneAttrib::make());
        trav->traverse(next_data2);
      }

      // Draw the rings for this switch level.  We do this after we
      // have drawn the geometry and the spindle.
      CullTraverserData next_data(data, sw.get_ring_viz());
      next_data.apply_transform_and_state(trav, viz_transform,
                                          RenderState::make_empty(),
                                          RenderEffects::make_empty(),
                                          ClipPlaneAttrib::make());
      trav->traverse(next_data);
    }
  }

  // Now return false indicating that we have already taken care of
  // the traversal from here.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::compute_internal_bounds
//       Access: Protected, Virtual
//  Description: Returns a newly-allocated BoundingVolume that
//               represents the internal contents of the node.  Should
//               be overridden by PandaNode classes that contain
//               something internally.
////////////////////////////////////////////////////////////////////
void LODNode::
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
                        int &internal_vertices,
                        int pipeline_stage,
                        Thread *current_thread) const {
  // First, get ourselves a fresh, empty bounding volume.
  PT(BoundingVolume) bound = new BoundingSphere;

  // If we have any visible rings, those count in the bounding volume.
  if (is_any_shown()) {
    // Now actually compute the bounding volume by putting it around all
    // of our geoms' bounding volumes.
    pvector<const BoundingVolume *> child_volumes;
    pvector<PT(BoundingVolume) > pt_volumes;

    CDStageReader cdata(_cycler, pipeline_stage, current_thread);

    SwitchVector::const_iterator si;
    for (si = cdata->_switch_vector.begin();
         si != cdata->_switch_vector.end();
         ++si) {
      const Switch &sw = (*si);
      if (sw.is_shown()) {
        PT(BoundingVolume) sphere = new BoundingSphere(cdata->_center, sw.get_in());
        child_volumes.push_back(sphere);
        pt_volumes.push_back(sphere);
      }
    }
    
    const BoundingVolume **child_begin = &child_volumes[0];
    const BoundingVolume **child_end = child_begin + child_volumes.size();
    
    bound->around(child_begin, child_end);
  }

  internal_bounds = bound;
  internal_vertices = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::get_rel_transform
//       Access: Protected
//  Description: Returns the relative transform to convert from the
//               LODNode space to the camera space.
////////////////////////////////////////////////////////////////////
CPT(TransformState) LODNode::
get_rel_transform(CullTraverser *trav, CullTraverserData &data) {
  // Get a pointer to the camera node.
  Camera *camera = trav->get_scene()->get_camera_node();
  
  // Get the camera space transform.
  CPT(TransformState) rel_transform;

  NodePath lod_center = camera->get_lod_center();
  if (!lod_center.is_empty()) {
    rel_transform = 
      lod_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
  } else {
    NodePath cull_center = camera->get_cull_center();
    if (!cull_center.is_empty()) {
      rel_transform = 
        cull_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
    } else {
      rel_transform = data.get_modelview_transform(trav);
    }
  }

  return rel_transform;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::do_show_switch
//       Access: Private
//  Description: The private implementation of show_switch().
////////////////////////////////////////////////////////////////////
void LODNode::
do_show_switch(LODNode::CData *cdata, int index, const LColor &color) {
  nassertv(index >= 0 && index < (int)cdata->_switch_vector.size());

  if (!cdata->_switch_vector[index].is_shown()) {
    ++cdata->_num_shown;
  }
  cdata->_switch_vector[index].show(color);
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::do_hide_switch
//       Access: Private
//  Description: The private implementation of hide_switch().
////////////////////////////////////////////////////////////////////
void LODNode::
do_hide_switch(LODNode::CData *cdata, int index) {
  nassertv(index >= 0 && index < (int)cdata->_switch_vector.size());

  if (cdata->_switch_vector[index].is_shown()) {
    --cdata->_num_shown;
  }
  cdata->_switch_vector[index].hide();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::do_verify_child_bounds
//       Access: Private
//  Description: The private implementation of verify_child_bounds(),
//               this checks the bounding volume of just one child.
//
//               If the return value is false, suggested_radius is
//               filled with a radius that ought to be large enough to
//               include the child.
////////////////////////////////////////////////////////////////////
bool LODNode::
do_verify_child_bounds(const LODNode::CData *cdata, int index,
                       PN_stdfloat &suggested_radius) const {
  suggested_radius = 0.0f;

  if (index < get_num_children()) {
    const Switch &sw = cdata->_switch_vector[index];
    PandaNode *child = get_child(index);
    if (child != (PandaNode *)NULL) {
      UpdateSeq seq;
      CPT(BoundingVolume) bv = child->get_bounds(seq);

      if (seq == sw._bounds_seq) {
        // We previously verified this child, and it hasn't changed
        // since then.
        return sw._verify_ok;
      }

      ((Switch &)sw)._bounds_seq = seq;
      ((Switch &)sw)._verify_ok = true;

      if (bv->is_empty()) {
        // This child has no geometry, so no one cares anyway.
        return true;
      }
      if (bv->is_infinite()) {
        // To be strict, we ought to look closer if the child has an
        // infinite bounding volume, but in practice this is probably
        // just a special case (e.g. the child contains the camera)
        // that we don't really want to check.
        return true;
      }
      
      const Switch &sw = cdata->_switch_vector[index];
      
      const GeometricBoundingVolume *gbv;
      DCAST_INTO_R(gbv, bv, false);
      BoundingSphere sphere(cdata->_center, sw.get_in());
      sphere.local_object();
      
      int flags = sphere.contains(gbv);
      if ((flags & BoundingVolume::IF_all) != 0) {
        // This child's radius completely encloses its bounding volume.
        // Perfect.  (And this is the most common case.)
        return true;
      }
      
      if (flags == 0) {
        // This child's radius doesn't even come close to containing
        // its volume.
        nassertr(!gbv->is_infinite(), false);
        sphere.extend_by(gbv);
        suggested_radius = sphere.get_radius();
        ((Switch &)sw)._verify_ok = false;
        return false;
      }
      
      // This child's radius partially encloses its (loose) bounding
      // volume.  We have to look closer to determine whether it, in
      // fact, fully encloses its geometry.
      LPoint3 min_point(0.0f, 0.0f, 0.0f);
      LPoint3 max_point(0.0f, 0.0f, 0.0f);
      
      bool found_any = false;
      child->calc_tight_bounds(min_point, max_point, found_any, 
                               TransformState::make_identity(),
                               Thread::get_current_thread());
      if (!found_any) {
        // Hmm, the child has no geometry after all.
        return true;
      }
      
      // Now we have a bounding box.  Define the largest sphere we can
      // that fits within this box.  All we can say about this sphere
      // is that it should definitely fit entirely within a bounding
      // sphere that contains all the points of the child.
      LPoint3 box_center = (min_point + max_point) / 2.0f;
      PN_stdfloat box_radius = min(min(max_point[0] - box_center[0],
                                 max_point[1] - box_center[1]),
                             max_point[2] - box_center[2]);
      
      BoundingSphere box_sphere(box_center, box_radius);
      box_sphere.local_object();
      
      // So if any part of this inscribed sphere is outside of the
      // radius, then the radius is bad.
      flags = sphere.contains(&box_sphere);
      if ((flags & BoundingVolume::IF_all) == 0) {
        // No good.  
        if (gbv->is_infinite()) {
          sphere.extend_by(&box_sphere);
        } else {
          sphere.extend_by(gbv);
        }
        suggested_radius = sphere.get_radius();
        ((Switch &)sw)._verify_ok = false;
        return false;
      }
    }
  }    

  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::do_auto_verify_lods
//       Access: Private
//  Description: Called internally by consider_verify_lods().
////////////////////////////////////////////////////////////////////
void LODNode::
do_auto_verify_lods(CullTraverser *trav, CullTraverserData &data) {
  UpdateSeq seq;
  get_bounds(seq);
  CDLockedReader cdata(_cycler);

  if (cdata->_got_force_switch) {
    // If we're forcing a particular switch, don't verify the LOD
    // sizes, since they don't really apply anymore anyway.  Assume
    // the user knows what he's doing.
    return;
  }

  if (seq != cdata->_bounds_seq) {
    // Time to validate the children again.
    for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
      PN_stdfloat suggested_radius;
      if (!do_verify_child_bounds(cdata, index, suggested_radius)) { 
        const Switch &sw = cdata->_switch_vector[index];
        ostringstream strm;
        strm
          << "Level " << index << " geometry of " << data._node_path
          << " is larger than its switch radius; suggest radius of "
          << suggested_radius << " instead of " << sw.get_in() 
          << " (configure verify-lods 0 to ignore this error)";
        nassert_raise(strm.str());
      }
    }
    CDWriter cdataw(_cycler, cdata);
    cdataw->_bounds_seq = seq;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::get_default_show_color
//       Access: Private, Static
//  Description: Returns a default color appropriate for showing the
//               indicated level.
////////////////////////////////////////////////////////////////////
const LColor &LODNode::
get_default_show_color(int index) {
  static LColor default_colors[] = {
    LColor(1.0f, 0.0f, 0.0f, 0.7f),
    LColor(0.0f, 1.0f, 0.0f, 0.7f),
    LColor(0.0f, 0.0f, 1.0f, 0.7f),
    LColor(0.0f, 1.0f, 1.0f, 0.7f),
    LColor(1.0f, 0.0f, 1.0f, 0.7f),
    LColor(1.0f, 1.0f, 0.0f, 0.7f),
  };
  static const int num_default_colors = sizeof(default_colors) / sizeof(LColor);

  return default_colors[index % num_default_colors];
}


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

////////////////////////////////////////////////////////////////////
//     Function: LODNode::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void LODNode::
write_datagram(BamWriter *manager, Datagram &dg) {
  PandaNode::write_datagram(manager, dg);
  manager->write_cdata(dg, _cycler);
}

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

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

  return node;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::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 LODNode.
////////////////////////////////////////////////////////////////////
void LODNode::
fillin(DatagramIterator &scan, BamReader *manager) {
  PandaNode::fillin(scan, manager);
  manager->read_cdata(scan, _cycler);
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::CData::make_copy
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
CycleData *LODNode::CData::
make_copy() const {
  return new CData(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::CData::check_limits
//       Access: Public
//  Description: Ensures that the _lowest and _highest members are set
//               appropriately after a change to the set of switches.
////////////////////////////////////////////////////////////////////
void LODNode::CData::
check_limits() {
  _lowest = 0;
  _highest = 0;
  for (size_t i = 1; i < _switch_vector.size(); ++i) {
    if (_switch_vector[i].get_out() > _switch_vector[_lowest].get_out()) {
      _lowest = i;
    }
    if (_switch_vector[i].get_in() < _switch_vector[_highest].get_in()) {
      _highest = i;
    }
  }
}

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

  dg.add_uint16(_switch_vector.size());

  SwitchVector::const_iterator si;
  for (si = _switch_vector.begin();
       si != _switch_vector.end();
       ++si) {
    (*si).write_datagram(dg);
  }
}

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

  _switch_vector.clear();

  int num_switches = scan.get_uint16();
  _switch_vector.reserve(num_switches);
  for (int i = 0; i < num_switches; i++) {
    Switch sw(0, 0);
    sw.read_datagram(scan);

    _switch_vector.push_back(sw);
  }
  _lod_scale = 1;
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::Switch::compute_ring_viz
//       Access: Private
//  Description: Computes a Geom suitable for rendering the ring
//               associated with this switch.
////////////////////////////////////////////////////////////////////
void LODNode::Switch::
compute_ring_viz() {
  // We render the ring as a series of concentric ring-shaped triangle
  // strips, each of which has num_slices quads.
  static const int num_slices = 50;
  static const int num_rings = 1;

  // There are also two more triangle strips, one for the outer edge,
  // and one for the inner edge.
  static const PN_stdfloat edge_ratio = 0.1;  // ratio of edge height to diameter.

  const GeomVertexFormat *format = GeomVertexFormat::get_v3n3cp();
  PT(GeomVertexData) vdata = new GeomVertexData("LOD_ring", format, Geom::UH_static);

  // Fill up the vertex table with all of the vertices.
  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
  GeomVertexWriter normal(vdata, InternalName::get_normal());
  GeomVertexWriter color(vdata, InternalName::get_color());

  // First, the vertices for the flat ring.
  int ri, si;
  for (ri = 0; ri <= num_rings; ++ri) {
    // r is in the range [0.0, 1.0].
    PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;

    // d is in the range [_out, _in].
    PN_stdfloat d = r * (_in - _out) + _out;

    for (si = 0; si < num_slices; ++si) {
      // s is in the range [0.0, 1.0).
      PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;

      // t is in the range [0.0, 2pi).
      PN_stdfloat t = MathNumbers::pi * 2.0f * s;

      PN_stdfloat x = ccos(t);
      PN_stdfloat y = csin(t);
      vertex.add_data3(x * d, y * d, 0.0f);
      normal.add_data3(0.0f, 0.0f, 1.0f);
      color.add_data4(_show_color);
    }
  }

  // Next, the vertices for the inner and outer edges.
  for (ri = 0; ri <= 1; ++ri) {
    PN_stdfloat r = (PN_stdfloat)ri;
    PN_stdfloat d = r * (_in - _out) + _out;

    for (si = 0; si < num_slices; ++si) {
      PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
      PN_stdfloat t = MathNumbers::pi * 2.0f * s;
      
      PN_stdfloat x = ccos(t);
      PN_stdfloat y = csin(t);

      vertex.add_data3(x * d, y * d, 0.5f * edge_ratio * d);
      normal.add_data3(x, y, 0.0f);
      color.add_data4(_show_color);
    }

    for (si = 0; si < num_slices; ++si) {
      PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
      PN_stdfloat t = MathNumbers::pi * 2.0f * s;
      
      PN_stdfloat x = ccos(t);
      PN_stdfloat y = csin(t);

      vertex.add_data3(x * d, y * d, -0.5f * edge_ratio * d);
      normal.add_data3(x, y, 0.0f);
      color.add_data4(_show_color);
    }
  }

  // Now create the triangle strips.  One tristrip for each ring.
  PT(GeomTristrips) strips = new GeomTristrips(Geom::UH_static);
  for (ri = 0; ri < num_rings; ++ri) {
    for (si = 0; si < num_slices; ++si) {
      strips->add_vertex(ri * num_slices + si);
      strips->add_vertex((ri + 1) * num_slices + si);
    }
    strips->add_vertex(ri * num_slices);
    strips->add_vertex((ri + 1) * num_slices);
    strips->close_primitive();
  }

  // And then one triangle strip for each of the inner and outer
  // edges.
  for (ri = 0; ri <= 1; ++ri) {
    for (si = 0; si < num_slices; ++si) {
      strips->add_vertex((num_rings + 1 + ri * 2) * num_slices + si);
      strips->add_vertex((num_rings + 1 + ri * 2 + 1) * num_slices + si);
    }
    strips->add_vertex((num_rings + 1 + ri * 2) * num_slices);
    strips->add_vertex((num_rings + 1 + ri * 2 + 1) * num_slices);
    strips->close_primitive();
  }

  PT(Geom) ring_geom = new Geom(vdata);
  ring_geom->add_primitive(strips);

  PT(GeomNode) geom_node = new GeomNode("ring");
  geom_node->add_geom(ring_geom);

  // Get a material for two-sided lighting.
  PT(Material) material = new Material();
  material->set_twoside(true);
  material = MaterialPool::get_material(material);

  CPT(RenderState) viz_state = 
    RenderState::make(CullFaceAttrib::make(CullFaceAttrib::M_cull_none),
                      TextureAttrib::make_off(),
                      ShaderAttrib::make_off(),
                      MaterialAttrib::make(material),
                      RenderState::get_max_priority());
  if (_show_color[3] != 1.0f) {
    viz_state = viz_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha),
                                        RenderState::get_max_priority());
  }

  geom_node->set_state(viz_state);

  _ring_viz = geom_node.p();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::Switch::compute_spindle_viz
//       Access: Private
//  Description: Computes a Geom suitable for rendering the LODNode
//               spindle in the color of this switch.
////////////////////////////////////////////////////////////////////
void LODNode::Switch::
compute_spindle_viz() {
  // We render the spindle as a cylinder, which consists of num_rings
  // rings stacked vertically, each of which is a triangle strip of
  // num_slices quads.  The scale is -10 .. 10 vertically, with a radius
  // of 1.0.
  static const int num_slices = 10;
  static const int num_rings = 10;

  const GeomVertexFormat *format = GeomVertexFormat::get_v3n3cp();
  PT(GeomVertexData) vdata = new GeomVertexData("LOD_spindle", format, Geom::UH_static);

  // Fill up the vertex table with all of the vertices.
  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
  GeomVertexWriter normal(vdata, InternalName::get_normal());
  GeomVertexWriter color(vdata, InternalName::get_color());

  int ri, si;
  for (ri = 0; ri <= num_rings; ++ri) {
    // r is in the range [0.0, 1.0].
    PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;

    // z is in the range [100.0, -100.0]
    PN_stdfloat z = 100.0f - r * 200.0f;

    for (si = 0; si < num_slices; ++si) {
      // s is in the range [0.0, 1.0).
      PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;

      // t is in the range [0.0, 2pi).
      PN_stdfloat t = MathNumbers::pi * 2.0f * s;

      PN_stdfloat x = ccos(t);
      PN_stdfloat y = csin(t);
      vertex.add_data3(x, y, z);
      normal.add_data3(x, y, 0.0f);
      color.add_data4(_show_color);
    }
  }

  // Now create the triangle strips.  One tristrip for each ring.
  PT(GeomTristrips) strips = new GeomTristrips(Geom::UH_static);
  for (ri = 0; ri < num_rings; ++ri) {
    for (si = 0; si < num_slices; ++si) {
      strips->add_vertex(ri * num_slices + si);
      strips->add_vertex((ri + 1) * num_slices + si);
    }
    strips->add_vertex(ri * num_slices);
    strips->add_vertex((ri + 1) * num_slices);
    strips->close_primitive();
  }

  PT(Geom) spindle_geom = new Geom(vdata);
  spindle_geom->add_primitive(strips);

  PT(GeomNode) geom_node = new GeomNode("spindle");
  geom_node->add_geom(spindle_geom);

  CPT(RenderState) viz_state = 
    RenderState::make(CullFaceAttrib::make(CullFaceAttrib::M_cull_clockwise),
                      TextureAttrib::make_off(),
                      ShaderAttrib::make_off(),
                      RenderState::get_max_priority());
  if (_show_color[3] != 1.0f) {
    viz_state = viz_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha),
                                      RenderState::get_max_priority());
  }

  geom_node->set_state(viz_state);

  _spindle_viz = geom_node.p();
}

////////////////////////////////////////////////////////////////////
//     Function: LODNode::Switch::compute_viz_model_state
//       Access: Private
//  Description: Computes a RenderState for rendering the children of
//               this switch in colored wireframe mode.
////////////////////////////////////////////////////////////////////
void LODNode::Switch::
compute_viz_model_state() {
  // The RenderState::make() function only takes up to four attribs at
  // once.  Since we need more attribs than that, we have to make up
  // our state in two steps.
  _viz_model_state = RenderState::make(RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
                                       TextureAttrib::make_off(),
                                       ShaderAttrib::make_off(),
                                       ColorAttrib::make_flat(_show_color),
                                       RenderState::get_max_priority());
  CPT(RenderState) st2 = RenderState::make(TransparencyAttrib::make(TransparencyAttrib::M_none),
                                           RenderState::get_max_priority());
  _viz_model_state = _viz_model_state->compose(st2);
}
