// Filename: billboardEffect.cxx
// Created by:  drose (14Mar02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "billboardEffect.h"
#include "cullTraverser.h"
#include "cullTraverserData.h"
#include "nodePath.h"
#include "look_at.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "datagram.h"
#include "datagramIterator.h"

TypeHandle BillboardEffect::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::make
//       Access: Published, Static
//  Description: Constructs a new BillboardEffect object with the
//               indicated properties.
////////////////////////////////////////////////////////////////////
CPT(RenderEffect) BillboardEffect::
make(const LVector3f &up_vector, bool eye_relative,
     bool axial_rotate, float offset, const NodePath &look_at,
     const LPoint3f &look_at_point) {
  BillboardEffect *effect = new BillboardEffect;
  effect->_up_vector = up_vector;
  effect->_eye_relative = eye_relative;
  effect->_axial_rotate = axial_rotate;
  effect->_offset = offset;
  effect->_look_at = look_at;
  effect->_look_at_point = look_at_point;
  effect->_off = false;
  return return_new(effect);
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::safe_to_transform
//       Access: Public, Virtual
//  Description: Returns true if it is generally safe to transform
//               this particular kind of RenderEffect by calling the
//               xform() method, false otherwise.
////////////////////////////////////////////////////////////////////
bool BillboardEffect::
safe_to_transform() const {
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::prepare_flatten_transform
//       Access: Public, Virtual
//  Description: Preprocesses the accumulated transform that is about
//               to be applied to (or through) this node due to a
//               flatten operation.  The returned value will be used
//               instead.
////////////////////////////////////////////////////////////////////
CPT(TransformState) BillboardEffect::
prepare_flatten_transform(const TransformState *net_transform) const {
  // We don't want any flatten operation to rotate the billboarded
  // node, since the billboard effect should eat any rotation that
  // comes in from above.
  return net_transform->set_hpr(LVecBase3f(0, 0, 0));
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::output
//       Access: Public, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
void BillboardEffect::
output(ostream &out) const {
  out << get_type() << ":";
  if (is_off()) {
    out << "(off)";
  } else {
    if (_axial_rotate) {
      out << "(axis";
    } else {
      out << "(point";
    }
    if (!_up_vector.almost_equal(LVector3f::up())) {
      out << " up " << _up_vector;
    }
    if (_eye_relative) {
      out << " eye";
    }
    if (_offset != 0.0f) {
      out << " offset " << _offset;
    }
    if (!_look_at.is_empty()) {
      out << " look at " << _look_at;
    }
    if (!_look_at_point.almost_equal(LPoint3f(0.0f, 0.0f, 0.0f))) {
      out << " look at point " << _look_at_point;
    }
    out << ")";
  }
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::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 BillboardEffect::
has_cull_callback() const {
  return true;
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::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 BillboardEffect::
cull_callback(CullTraverser *trav, CullTraverserData &data,
              CPT(TransformState) &node_transform,
              CPT(RenderState) &) const {
  CPT(TransformState) modelview_transform = data.get_modelview_transform(trav);
  if (modelview_transform->is_singular()) {
    // If we're under a singular transform, never mind.
    return;
  }

  // Since the "modelview" transform from the cull traverser already
  // includes the inverse camera transform, the camera transform is
  // identity.
  CPT(TransformState) camera_transform = TransformState::make_identity();

  // But if we're rotating to face something other than the camera, we
  // have to compute the "camera" transform to compensate for that.
  if (!_look_at.is_empty()) {
    camera_transform = trav->get_camera_transform()->invert_compose(_look_at.get_net_transform());
  }

  compute_billboard(node_transform, modelview_transform, camera_transform);
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::has_adjust_transform
//       Access: Public, Virtual
//  Description: Should be overridden by derived classes to return
//               true if adjust_transform() has been defined, and
//               therefore the RenderEffect has some effect on the
//               node's apparent local and net transforms.
////////////////////////////////////////////////////////////////////
bool BillboardEffect::
has_adjust_transform() const {
  // A BillboardEffect can only affect the net transform when it is to
  // a particular node.  A billboard to a camera is camera-dependent,
  // of course, so it has no effect in the absence of any particular
  // camera viewing it.
  return !_look_at.is_empty();
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::adjust_transform
//       Access: Public, Virtual
//  Description: Performs some operation on the node's apparent net
//               and/or local transforms.  This will only be called if
//               has_adjust_transform() is redefined to return true.
//
//               Both parameters are in/out.  The original transforms
//               will be passed in, and they may (or may not) be
//               modified in-place by the RenderEffect.
////////////////////////////////////////////////////////////////////
void BillboardEffect::
adjust_transform(CPT(TransformState) &net_transform,
                 CPT(TransformState) &node_transform,
                 PandaNode *) const {
  // A BillboardEffect can only affect the net transform when it is to
  // a particular node.  A billboard to a camera is camera-dependent,
  // of course, so it has no effect in the absence of any particular
  // camera viewing it.
  if (_look_at.is_empty()) {
    return;
  }

  CPT(TransformState) camera_transform = _look_at.get_net_transform();

  compute_billboard(node_transform, net_transform, camera_transform);
}


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

  if (_axial_rotate != ta->_axial_rotate) {
    return (int)_axial_rotate - (int)ta->_axial_rotate;
  }
  if (_eye_relative != ta->_eye_relative) {
    return (int)_eye_relative - (int)ta->_eye_relative;
  }
  if (_offset != ta->_offset) {
    return _offset < ta->_offset ? -1 : 1;
  }
  int compare = _up_vector.compare_to(ta->_up_vector);
  if (compare != 0) {
    return compare;
  }
  compare = _look_at.compare_to(ta->_look_at);
  if (compare != 0) {
    return compare;
  }
  compare = _look_at_point.compare_to(ta->_look_at_point);
  if (compare != 0) {
    return compare;
  }
  return 0;
}

////////////////////////////////////////////////////////////////////
//     Function: BillboardEffect::compute_billboard
//       Access: Private
//  Description: Computes the billboard operation given the parent's
//               net transform and the camera transform.
//
//               The result is applied to node_transform, which is
//               modified in-place.
////////////////////////////////////////////////////////////////////
void BillboardEffect::
compute_billboard(CPT(TransformState) &node_transform, 
                  const TransformState *net_transform, 
                  const TransformState *camera_transform) const {
  // First, extract out just the translation component of the node's
  // local transform.  This gets applied to the net transform, to
  // compute the look-at direction properly.
  CPT(TransformState) translate = TransformState::make_pos(node_transform->get_pos());

  // And then the translation gets removed from the node, but we keep
  // its rotation etc., which gets applied after the billboard
  // operation.
  node_transform = node_transform->set_pos(LPoint3f(0.0f, 0.0f, 0.0f));

  CPT(TransformState) rel_transform =
    net_transform->compose(translate)->invert_compose(camera_transform);
  const LMatrix4f &rel_mat = rel_transform->get_mat();

  // Determine the look_at point in the camera space.
  LVector3f camera_pos, up;

  // If this is an eye-relative Billboard, then (a) the up vector is
  // relative to the camera, not to the world, and (b) the look
  // direction is towards the plane that contains the camera,
  // perpendicular to the forward direction, not directly to the
  // camera.

  if (_eye_relative) {
    up = _up_vector * rel_mat;
    camera_pos = LVector3f::forward() * rel_mat;

  } else {
    up = _up_vector;
    camera_pos = -(_look_at_point * rel_mat);
  }

  // Now determine the rotation matrix for the Billboard.
  LMatrix4f rotate;
  if (_axial_rotate) {
    heads_up(rotate, camera_pos, up);
  } else {
    look_at(rotate, camera_pos, up);
  }

  // Also slide the billboard geometry towards the camera according to
  // the offset factor.
  if (_offset != 0.0f) {
    LVector3f translate(rel_mat(3, 0), rel_mat(3, 1), rel_mat(3, 2));
    translate.normalize();
    translate *= _offset;
    rotate.set_row(3, translate);
  }

  node_transform = translate->compose(TransformState::make_mat(rotate))->compose(node_transform);
}

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

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

  dg.add_bool(_off);
  _up_vector.write_datagram(dg);
  dg.add_bool(_eye_relative);
  dg.add_bool(_axial_rotate);
  dg.add_float32(_offset);
  _look_at_point.write_datagram(dg);

  // *** We don't write out the _look_at NodePath right now.  Maybe
  // we should.
}

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

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

  return effect;
}

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

  _off = scan.get_bool();
  _up_vector.read_datagram(scan);
  _eye_relative = scan.get_bool();
  _axial_rotate = scan.get_bool();
  _offset = scan.get_float32();
  _look_at_point.read_datagram(scan);
}
