// Filename: scissorEffect.cxx
// Created by:  drose (30Jul08)
//
////////////////////////////////////////////////////////////////////
//
// 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 "scissorEffect.h"
#include "scissorAttrib.h"
#include "cullTraverser.h"
#include "cullTraverserData.h"
#include "nodePath.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "datagram.h"
#include "datagramIterator.h"
#include "boundingHexahedron.h"

TypeHandle ScissorEffect::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::Constructor
//       Access: Private
//  Description: Use ScissorEffect::make() to construct a new
//               ScissorEffect object.
////////////////////////////////////////////////////////////////////
ScissorEffect::
ScissorEffect(bool screen, const LVecBase4f &frame,
              const PointDef *points, int num_points, bool clip) :
  _screen(screen), _frame(frame), _clip(clip) 
{
  _points.reserve(num_points);
  for (int i = 0; i < num_points; ++i) {
    _points.push_back(points[i]);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::Copy Constructor
//       Access: Private
//  Description: Use ScissorEffect::make() to construct a new
//               ScissorEffect object.
////////////////////////////////////////////////////////////////////
ScissorEffect::
ScissorEffect(const ScissorEffect &copy) :
  _screen(copy._screen), 
  _frame(copy._frame),
  _points(copy._points),
  _clip(copy._clip)
{
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::make_screen
//       Access: Published, Static
//  Description: Constructs a new screen-relative ScissorEffect.  The
//               frame defines a left, right, bottom, top region,
//               relative to the DisplayRegion.  See ScissorAttrib.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
make_screen(const LVecBase4f &frame, bool clip) {
  ScissorEffect *effect = new ScissorEffect(true, frame, NULL, 0, clip);
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::make_node
//       Access: Published, Static
//  Description: Constructs a new node-relative ScissorEffect, with no
//               points.  This empty ScissorEffect does nothing.  You
//               must then call add_point a number of times to add the
//               points you require.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
make_node(bool clip) {
  ScissorEffect *effect = new ScissorEffect(false, LVecBase4f::zero(), NULL, 0, clip);
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::make_node
//       Access: Published, Static
//  Description: Constructs a new node-relative ScissorEffect.  The
//               two points are understood to be relative to the
//               indicated node, or the current node if the NodePath
//               is empty, and determine the diagonally opposite
//               corners of the scissor region.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
make_node(const LPoint3f &a, const LPoint3f &b, const NodePath &node) {
  PointDef points[2];
  points[0]._p = a;
  points[0]._node = node;
  points[1]._p = b;
  points[1]._node = node;
  ScissorEffect *effect = new ScissorEffect(false, LVecBase4f::zero(), points, 2, true);
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::make_node
//       Access: Published, Static
//  Description: Constructs a new node-relative ScissorEffect.  The
//               four points are understood to be relative to the
//               indicated node, or the current node if the indicated
//               NodePath is empty, and determine four points
//               surrounding the scissor region.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
make_node(const LPoint3f &a, const LPoint3f &b, const LPoint3f &c, const LPoint3f &d, const NodePath &node) {
  PointDef points[4];
  points[0]._p = a;
  points[0]._node = node;
  points[1]._p = b;
  points[1]._node = node;
  points[2]._p = c;
  points[2]._node = node;
  points[3]._p = d;
  points[3]._node = node;
  ScissorEffect *effect = new ScissorEffect(false, LVecBase4f::zero(), points, 4, true);
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::add_point
//       Access: Published
//  Description: Returns a new ScissorEffect with the indicated point
//               added.  It is only valid to call this on a "node"
//               type ScissorEffect.  The full set of points,
//               projected into screen space, defines the bounding
//               volume of the rectangular scissor region.
//
//               Each point may be relative to a different node, if
//               desired.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
add_point(const LPoint3f &p, const NodePath &node) const {
  nassertr(!is_screen(), this);
  ScissorEffect *effect = new ScissorEffect(*this);
  PointDef point;
  point._p = p;
  point._node = node;
  effect->_points.push_back(point);
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::xform
//       Access: Public, Virtual
//  Description: Returns a new RenderEffect transformed by the
//               indicated matrix.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) ScissorEffect::
xform(const LMatrix4f &mat) const {
  if (is_screen()) {
    return this;
  }
  ScissorEffect *effect = new ScissorEffect(*this);
  Points::iterator pi;
  for (pi = effect->_points.begin();
       pi != effect->_points.end();
       ++pi) {
    PointDef &point = (*pi);
    if (point._node.is_empty()) {
      point._p = point._p * mat;
    }
  }
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void ScissorEffect::
output(ostream &out) const {
  out << get_type() << ":";
  if (is_screen()) {
    out << "screen [" << _frame << "]";
  } else {
    out << "node";
    Points::const_iterator pi;
    for (pi = _points.begin(); pi != _points.end(); ++pi) {
      const PointDef &point = (*pi);
      if (point._node.is_empty()) {
        out << " (" << point._p << ")";
      } else {
        out << " (" << point._node << ":" << point._p << ")";
      }
    }
  }
  if (!get_clip()) {
    out << " !clip";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::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 effect during the cull
//               traversal.
////////////////////////////////////////////////////////////////////
bool ScissorEffect::
has_cull_callback() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::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 may include additional manipulation
//               of render state or additional visible/invisible
//               decisions, or any other arbitrary operation.
//
//               At the time this function is called, the current
//               node's transform and state have not yet been applied
//               to the net_transform and net_state.  This callback
//               may modify the node_transform and node_state to apply
//               an effective change to the render state at this
//               level.
////////////////////////////////////////////////////////////////////
void ScissorEffect::
cull_callback(CullTraverser *trav, CullTraverserData &data,
              CPT(TransformState) &node_transform,
              CPT(RenderState) &node_state) const {
  LVecBase4f frame;
  const Lens *lens = trav->get_scene()->get_lens();
  CPT(TransformState) modelview_transform = data.get_modelview_transform(trav);
  CPT(TransformState) net_transform = modelview_transform->compose(node_transform);
  if (net_transform->is_singular()) {
    // If we're under a singular transform, never mind.
    return;
  }

  if (is_screen()) {
    frame = _frame;
  } else {
    const LMatrix4f &proj_mat = lens->get_projection_mat();
    LMatrix4f net_mat = net_transform->get_mat() * proj_mat;

    bool any_points = false;

    Points::const_iterator pi;
    for (pi = _points.begin(); pi != _points.end(); ++pi) {
      const PointDef &point = (*pi);
      LVecBase4f pv(point._p[0], point._p[1], point._p[2], 1.0f);
      if (point._node.is_empty()) {
        // Relative to this node.
        pv = pv * net_mat;

      } else {
        // Relative to some other node.
        LMatrix4f other_mat = point._node.get_net_transform()->get_mat() * proj_mat;
        pv = pv * other_mat;
      }

      if (pv[3] == 0) {
        continue;
      }
      LPoint3f pr(pv[0] / pv[3], pv[1] / pv[3], pv[2] / pv[3]);
      if (!any_points) {
        frame[0] = pr[0];
        frame[1] = pr[0];
        frame[2] = pr[1];
        frame[3] = pr[1];
        any_points = true;
      } else {
        frame[0] = min(frame[0], pr[0]);
        frame[1] = max(frame[1], pr[0]);
        frame[2] = min(frame[2], pr[1]);
        frame[3] = max(frame[3], pr[1]);
      }
    }
    
    // Scale from -1..1 to 0..1.
    frame[0] = (frame[0] + 1.0f) * 0.5f;
    frame[1] = (frame[1] + 1.0f) * 0.5f;
    frame[2] = (frame[2] + 1.0f) * 0.5f;
    frame[3] = (frame[3] + 1.0f) * 0.5f;
  }

  // Impose bounding volumes.
  frame[0] = max(min(frame[0], 1.0f), 0.0f);
  frame[1] = max(min(frame[1], 1.0f), frame[0]);
  frame[2] = max(min(frame[2], 1.0f), 0.0f);
  frame[3] = max(min(frame[3], 1.0f), frame[2]);

  if (_clip) {
    CPT(RenderAttrib) scissor_attrib = ScissorAttrib::make(frame);
    CPT(RenderState) state = RenderState::make(scissor_attrib);
    node_state = node_state->compose(state); 
  }

  // Set up the culling.  We do this by extruding the four corners of
  // the frame into the eight corners of the bounding frustum.
  PT(GeometricBoundingVolume) frustum = make_frustum(lens, frame);
  if (frustum != (GeometricBoundingVolume *)NULL) {
    frustum->xform(modelview_transform->get_inverse()->get_mat());
    data._view_frustum = frustum;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::compare_to_impl
//       Access: Protected, Virtual
//  Description: Intended to be overridden by derived ScissorEffect
//               types to return a unique number indicating whether
//               this ScissorEffect is equivalent to the other one.
//
//               This should return 0 if the two ScissorEffect 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 ScissorEffect
//               objects whose get_type() functions return the same.
////////////////////////////////////////////////////////////////////
int ScissorEffect::
compare_to_impl(const RenderEffect *other) const {
  const ScissorEffect *ta;
  DCAST_INTO_R(ta, other, 0);

  if (_screen != ta->_screen) {
    return (int)_screen - (int)ta->_screen;
  }
  if (_clip != ta->_clip) {
    return (int)_clip - (int)ta->_clip;
  }
  if (_screen) {
    int compare = _frame.compare_to(ta->_frame);
    if (compare != 0) {
      return compare;
    }
  } else {
    int compare = (int)_points.size() - (int)ta->_points.size();
    if (compare != 0) {
      return compare;
    }
    for (size_t i = 0; i < _points.size(); ++i) {
      compare = _points[i]._p.compare_to(ta->_points[i]._p);
      if (compare != 0) {
        return compare;
      }
      compare = _points[i]._node.compare_to(ta->_points[i]._node);
      if (compare != 0) {
        return compare;
      }
    }
  }
  return 0;
}

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

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

  dg.add_bool(_screen);
  if (_screen) {
    _frame.write_datagram(dg);
  } else {
    dg.add_uint16(_points.size());
    Points::const_iterator pi;
    for (pi = _points.begin(); pi != _points.end(); ++pi) {
      (*pi)._p.write_datagram(dg);
    }
  }
  dg.add_bool(_clip);
}

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

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

  return effect;
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::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 ScissorEffect.
////////////////////////////////////////////////////////////////////
void ScissorEffect::
fillin(DatagramIterator &scan, BamReader *manager) {
  RenderEffect::fillin(scan, manager);

  _screen = scan.get_bool();
  if (_screen) {
    _frame.read_datagram(scan);
  } else {
    int num_points = scan.get_uint16();
    _points.reserve(num_points);
    for (int i = 0; i < num_points; ++i) {
      PointDef point;
      point._p.read_datagram(scan);
      _points.push_back(point);
    }
  }
  _clip = scan.get_bool();
}

////////////////////////////////////////////////////////////////////
//     Function: ScissorEffect::make_frustum
//       Access: Private
//  Description: Constructs a new bounding frustum from the lens
//               properties, given the indicated scissor frame.
////////////////////////////////////////////////////////////////////
PT(GeometricBoundingVolume) ScissorEffect::
make_frustum(const Lens *lens, const LVecBase4f &frame) const{
  // Scale the frame from 0 .. 1 into -1 .. 1.
  LVecBase4f f2(frame[0] * 2.0f - 1.0f,
                frame[1] * 2.0f - 1.0f,
                frame[2] * 2.0f - 1.0f,
                frame[3] * 2.0f - 1.0f);

  LPoint3f fll, flr, ful, fur;
  LPoint3f nll, nlr, nul, nur;
  LPoint2f corner;

  corner[0] = f2[0]; corner[1] = f2[3];

  // Upper left.
  if (!lens->extrude(corner, nul, ful)) {
    return (GeometricBoundingVolume *)NULL;
  }

  corner[0] = f2[1]; corner[1] = f2[3];

  // Upper right.
  if (!lens->extrude(corner, nur, fur)) {
    return (GeometricBoundingVolume *)NULL;
  }

  corner[0] = f2[1]; corner[1] = f2[2];

  // Lower right.
  if (!lens->extrude(corner, nlr, flr)) {
    return (GeometricBoundingVolume *)NULL;
  }

  corner[0] = f2[0]; corner[1] = f2[2];

  // Lower left.
  if (!lens->extrude(corner, nll, fll)) {
    return (GeometricBoundingVolume *)NULL;
  }

  return new BoundingHexahedron(fll, flr, fur, ful, nll, nlr, nur, nul);
}
