// Filename: planeNode.cxx
// Created by:  drose (11Jul02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "planeNode.h"
#include "geometricBoundingVolume.h"
#include "bamWriter.h"
#include "bamReader.h"
#include "datagram.h"
#include "datagramIterator.h"
#include "geomVertexWriter.h"
#include "geomVertexData.h"
#include "geomLines.h"
#include "geom.h"
#include "cullableObject.h"
#include "cullHandler.h"
#include "boundingPlane.h"

UpdateSeq PlaneNode::_sort_seq;

TypeHandle PlaneNode::_type_handle;

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

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

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::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 Light.
////////////////////////////////////////////////////////////////////
void PlaneNode::CData::
fillin(DatagramIterator &scan, BamReader *) {
  _plane.read_datagram(scan);
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
PlaneNode::
PlaneNode(const string &name, const LPlane &plane) :
  PandaNode(name),
  _priority(0),
  _clip_effect(~0)
{
  set_cull_callback();

  // PlaneNodes are hidden by default.
  set_overall_hidden(true);

  set_plane(plane);
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::Copy Constructor
//       Access: Protected
//  Description:
////////////////////////////////////////////////////////////////////
PlaneNode::
PlaneNode(const PlaneNode &copy) :
  PandaNode(copy),
  _priority(copy._priority),
  _clip_effect(copy._clip_effect),
  _cycler(copy._cycler)
{
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void PlaneNode::
output(ostream &out) const {
  PandaNode::output(out);
  out << " " << get_plane();
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::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 *PlaneNode::
make_copy() const {
  return new PlaneNode(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::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 PlaneNode::
xform(const LMatrix4 &mat) {
  PandaNode::xform(mat);
  CDWriter cdata(_cycler);
  cdata->_plane = cdata->_plane * mat;
  cdata->_front_viz = NULL;
  cdata->_back_viz = NULL;
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::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 PlaneNode::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
  // Normally, a PlaneNode is invisible.  But if someone shows it, we
  // will draw a visualization, a nice yellow wireframe.

  CullableObject *plane_viz = 
    new CullableObject(get_viz(trav, data), data._state, 
                       data.get_net_transform(trav),
                       data.get_modelview_transform(trav),
                       trav->get_scene());
  trav->get_cull_handler()->record_object(plane_viz, trav);

  // Now carry on to render our child nodes.
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::is_renderable
//       Access: Public, Virtual
//  Description: Returns true if there is some value to visiting this
//               particular node during the cull traversal for any
//               camera, false otherwise.  This will be used to
//               optimize the result of get_net_draw_show_mask(), so
//               that any subtrees that contain only nodes for which
//               is_renderable() is false need not be visited.
////////////////////////////////////////////////////////////////////
bool PlaneNode::
is_renderable() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::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 PlaneNode::
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
                        int &internal_vertices,
                        int pipeline_stage,
                        Thread *current_thread) const {
  CDStageReader cdata(_cycler, pipeline_stage, current_thread);
  internal_bounds = new BoundingPlane(cdata->_plane);
  internal_vertices = 0;
}

////////////////////////////////////////////////////////////////////
//     Function: PlaneNode::get_viz
//       Access: Protected
//  Description: Returns a Geom that represents the visualization of
//               the PlaneNode.
////////////////////////////////////////////////////////////////////
PT(Geom) PlaneNode::
get_viz(CullTraverser *trav, CullTraverserData &data) {
  CDLockedReader cdata(_cycler);
  
  // Figure out whether we are looking at the front or the back of the
  // plane.
  const Lens *lens = trav->get_scene()->get_lens();
  LPlane eye_plane = cdata->_plane * data.get_modelview_transform(trav)->get_mat();
  bool front = (eye_plane.dist_to_plane(lens->get_nodal_point()) >= 0.0f);

  if (cdata->_front_viz != (Geom *)NULL) {
    return front ? cdata->_front_viz : cdata->_back_viz;
  }

  if (pgraph_cat.is_debug()) {
    pgraph_cat.debug()
      << "Recomputing viz for " << *this << "\n";
  }

  CDWriter cdataw(_cycler, cdata, false);
  const LPlane &plane = cdataw->_plane;

  PT(GeomVertexData) vdata = new GeomVertexData
    (get_name(), GeomVertexFormat::get_v3cp(), Geom::UH_static);

  GeomVertexWriter vertex(vdata, InternalName::get_vertex());
  PT(GeomLines) lines = new GeomLines(Geom::UH_static);

  LVector3 a, b;

  if (fabs(plane[0]) > fabs(plane[1])) {
    // X > Y
    if (fabs(plane[0]) > fabs(plane[2])) {
      // X > Y && X > Z.  X is the largest.
      a.set(0, 1, 0);
      b.set(0, 0, 1);
    } else {
      // X > Y && Z > X.  Z is the largest.
      a.set(1, 0, 0);
      b.set(0, 1, 0);
    }
  } else {
    // Y > X
    if (fabs(plane[1]) > fabs(plane[2])) {
      // Y > X && Y > Z.  Y is the largest.
      a.set(1, 0, 0);
      b.set(0, 0, 1);
    } else {
      // Y > X && Z > Y.  Z is the largest.
      a.set(1, 0, 0);
      b.set(0, 1, 0);
    }
  }

  static const int num_segs = 10;
  a *= cdataw->_viz_scale / (num_segs * 2);
  b *= cdataw->_viz_scale / (num_segs * 2);
  
  for (int x = -num_segs; x <= num_segs; ++x) {
    vertex.add_data3(plane.project(a * x - b * num_segs));
    vertex.add_data3(plane.project(a * x + b * num_segs));
    lines->add_next_vertices(2);
    lines->close_primitive();
  }
  for (int y = -num_segs; y <= num_segs; ++y) {
    vertex.add_data3(plane.project(b * y - a * num_segs));
    vertex.add_data3(plane.project(b * y + a * num_segs));
    lines->add_next_vertices(2);
    lines->close_primitive();
  }

  cdataw->_front_viz = new Geom(vdata->set_color(LColor(1.0f, 1.0f, 0.0f, 1.0f)));
  cdataw->_front_viz->add_primitive(lines);

  cdataw->_back_viz = new Geom(vdata->set_color(LColor(0.4, 0.4, 0.0f, 1.0f)));
  cdataw->_back_viz->add_primitive(lines);

  return front ? cdataw->_front_viz : cdataw->_back_viz;
}

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

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

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

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

  return node;
}

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

  if (manager->get_file_minor_ver() < 9) {
    _clip_effect = ~0;
  } else {
    _clip_effect = scan.get_uint8();
  }
}
