// Filename: rigidBodyCombiner.cxx
// Created by:  drose (22Feb07)
//
////////////////////////////////////////////////////////////////////
//
// 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 "rigidBodyCombiner.h"
#include "nodePath.h"
#include "geomNode.h"
#include "modelNode.h"
#include "geomVertexData.h"
#include "geomVertexFormat.h"
#include "geomVertexArrayFormat.h"
#include "geomVertexAnimationSpec.h"
#include "sceneGraphReducer.h"

TypeHandle RigidBodyCombiner::_type_handle;


////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
RigidBodyCombiner::
RigidBodyCombiner(const string &name) : PandaNode(name) {
  set_cull_callback();

  _internal_root = new PandaNode(name);
}

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::Copy Constructor
//       Access: Published
//  Description:
////////////////////////////////////////////////////////////////////
RigidBodyCombiner::
RigidBodyCombiner(const RigidBodyCombiner &copy) : PandaNode(copy) {
  set_cull_callback();

  _internal_root = copy._internal_root;
  _internal_transforms = copy._internal_transforms;
}

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

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::collect
//       Access: Published
//  Description: Walks through the entire subgraph of nodes rooted at
//               this node, accumulates all of the RenderAttribs and
//               Geoms below this node, flattening them into just one
//               Geom (or as few as possible, if there are multiple
//               different states).
//
//               Nodes that have transforms on them at the time of
//               collect(), or any ModelNodes with the
//               preserve_transform flag, will be identified as
//               "moving" nodes, and their transforms will be
//               monitored as they change in future frames and each
//               new transform directly applied to the vertices.
//               
//               This call must be made after adding any nodes to or
//               removing any nodes from the subgraph rooted at this
//               node.  It should not be made too often, as it is a
//               relatively expensive call.  If you need to hide
//               children of this node, consider scaling them to zero
//               (or very near zero), or moving them behind the
//               camera, instead.
////////////////////////////////////////////////////////////////////
void RigidBodyCombiner::
collect() {
  _internal_root = new GeomNode(get_name());
  _internal_transforms.clear();
  _vd_table.clear();

  Children cr = get_children();
  int num_children = cr.get_num_children();
  for (int i = 0; i < num_children; i++) {
    r_collect(cr.get_child(i), RenderState::make_empty(), NULL);
  }

  _vd_table.clear();

  SceneGraphReducer gr;
  gr.apply_attribs(_internal_root);
  gr.collect_vertex_data(_internal_root, ~(SceneGraphReducer::CVD_format | SceneGraphReducer::CVD_name | SceneGraphReducer::CVD_animation_type));
  gr.unify(_internal_root, false);
}

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::get_internal_scene
//       Access: Published
//  Description: Returns a special NodePath that represents the
//               internal node of this object.  This is the node that
//               is actually sent to the graphics card for rendering;
//               it contains the collection of the children of this
//               node into as few Geoms as possible.  
//
//               This node is filled up by the last call to collect().
////////////////////////////////////////////////////////////////////
NodePath RigidBodyCombiner::
get_internal_scene() {
  return NodePath(_internal_root);
}

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::cull_callback
//       Access: Protected, 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 RigidBodyCombiner::
cull_callback(CullTraverser *trav, CullTraverserData &data) {
  // Pretend that all of our transforms have been modified (since we
  // don't really know which ones have).
  Thread *current_thread = Thread::get_current_thread();
  Transforms::iterator ti;
  for (ti = _internal_transforms.begin(); 
       ti != _internal_transforms.end(); 
       ++ti) {
    (*ti)->mark_modified(current_thread);
  }

  // Render the internal scene only--this is the optimized scene.
  CullTraverserData next_data(data, _internal_root);
  trav->traverse(next_data);

  // Do not directly render the nodes beneath this node.
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::r_collect
//       Access: Private
//  Description: Recursively visits each child or descedant of this
//               node, accumulating state and transform as we go.
//               When GeomNodes are encountered, their Geoms are
//               extracted and added to the _internal_root node.
////////////////////////////////////////////////////////////////////
void RigidBodyCombiner::
r_collect(PandaNode *node, const RenderState *state, 
          const VertexTransform *transform) {
  CPT(RenderState) next_state = state->compose(node->get_state());
  CPT(VertexTransform) next_transform = transform;
  if (!node->get_transform()->is_identity() ||
      node->is_of_type(ModelNode::get_class_type()) &&
      DCAST(ModelNode, node)->get_preserve_transform() != ModelNode::PT_none) {
    // This node has a transform we need to keep.
    PT(NodeVertexTransform) new_transform = new NodeVertexTransform(node, transform);
    _internal_transforms.push_back(new_transform);
    next_transform = new_transform.p();
    
  }

  if (node->is_geom_node()) {
    GeomNode *gnode = DCAST(GeomNode, node);
    GeomNode *root_gnode = DCAST(GeomNode, _internal_root);

    int num_geoms = gnode->get_num_geoms();
    for (int i = 0; i < num_geoms; ++i) {
      PT(Geom) geom = gnode->get_geom(i)->make_copy();
      if (transform != (const VertexTransform *)NULL) {
        geom->set_vertex_data(convert_vd(transform, geom->get_vertex_data()));
      }
      CPT(RenderState) gstate = next_state->compose(gnode->get_geom_state(i));
      root_gnode->add_geom(geom, gstate);
    }
  }

  Children cr = node->get_children();
  int num_children = cr.get_num_children();
  for (int i = 0; i < num_children; i++) {
    r_collect(cr.get_child(i), next_state, next_transform);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: RigidBodyCombiner::convert_vd
//       Access: Private
//  Description: Converts a GeomVertexData to a new form in which all
//               of the vertices are transformed by the node's
//               transform.
////////////////////////////////////////////////////////////////////
PT(GeomVertexData) RigidBodyCombiner::
convert_vd(const VertexTransform *transform, const GeomVertexData *orig) {
  // First, unify this operation for unique transform/data
  // combinations.  If we encounter a given GeomVertexData more than
  // once under the same transform, we should return exactly the same
  // GeomVertexData.
  VDTable::iterator vdti = _vd_table.find(VDUnifier(transform, orig));
  if (vdti != _vd_table.end()) {
    return (*vdti).second;
  }

  PT(GeomVertexFormat) format = new GeomVertexFormat(*orig->get_format());
  if (!orig->get_format()->has_column(InternalName::get_transform_blend())) {
    PT(GeomVertexArrayFormat) af = 
      new GeomVertexArrayFormat(InternalName::get_transform_blend(), 1, 
                                Geom::NT_uint16, Geom::C_index);
    format->add_array(af);
  }

  GeomVertexAnimationSpec spec;
  spec.set_panda();
  format->set_animation(spec);

  CPT(GeomVertexFormat) new_format = GeomVertexFormat::register_format(format);
  CPT(GeomVertexData) converted = orig->convert_to(new_format);
  PT(GeomVertexData) new_data = new GeomVertexData(*converted);
  
  if (new_data->get_transform_blend_table() == (TransformBlendTable *)NULL) {
    // Create a new table that has just the one blend: all vertices
    // hard-assigned to the indicated transform.
    PT(TransformBlendTable) new_table = new TransformBlendTable;
    new_table->add_blend(TransformBlend(transform, 1.0f));
    new_table->set_rows(SparseArray::range(0, new_data->get_num_rows()));
    new_data->set_transform_blend_table(new_table);
  } else {
    // The GeomVertexData already has a TransformBlendTable.  In this
    // case, we'll have to adjust it.  TODO.
  }

  // Store the result for the next time.
  _vd_table[VDUnifier(transform, orig)] = new_data;

  return new_data;
}
