// Filename: sceneGraphReducer.cxx
// Created by:  drose (22May00)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001, 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://www.panda3d.org/license.txt .
//
// To contact the maintainers of this program write to
// panda3d@yahoogroups.com .
//
////////////////////////////////////////////////////////////////////

#include "sceneGraphReducer.h"
#include "config_sgraphutil.h"

#include <geomNode.h>
#include <geom.h>
#include <indent.h>
#include <billboardTransition.h>
#include <decalTransition.h>


////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::AccumulatedTransitions::apply_to_arc
//       Access: Public
//  Description: Stores the relevant transitions on the indicated arc,
//               and clears the transitions for continuing.
////////////////////////////////////////////////////////////////////
void SceneGraphReducer::AccumulatedTransitions::
apply_to_arc(NodeRelation *arc, int transition_types) {
  if ((transition_types & TT_transform) != 0) {
    if (!_transform->get_matrix().almost_equal(LMatrix4f::ident_mat())) {
      arc->set_transition(_transform);
    }
    _transform = new TransformTransition;
  }

  if ((transition_types & TT_color) != 0) {
    if (!_color->is_identity()) {
      arc->set_transition(_color);
      _color = new ColorTransition;
    }
  }

  if ((transition_types & TT_texture_matrix) != 0) {
    if (!_texture_matrix->get_matrix().almost_equal(LMatrix4f::ident_mat())) {
      arc->set_transition(_texture_matrix);
    }
    _texture_matrix = new TexMatrixTransition;
  }
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::AccumulatedTransitions::write
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
void SceneGraphReducer::AccumulatedTransitions::
write(ostream &out, int transition_types, int indent_level) const {
  if ((transition_types & TT_transform) != 0) {
    _transform->write(out, indent_level);
  }
  if ((transition_types & TT_color) != 0) {
    _color->write(out, indent_level);
  }
  if ((transition_types & TT_texture_matrix) != 0) {
    _texture_matrix->write(out, indent_level);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::Constructor
//       Access: Public
//  Description:
////////////////////////////////////////////////////////////////////
SceneGraphReducer::
SceneGraphReducer(TypeHandle graph_type) :
  GraphReducer(graph_type)
{
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::apply_transitions
//       Access: Public
//  Description: Walks the scene graph, accumulating transitions of
//               the indicated types, applying them to the vertices,
//               and removing them from the scene graph.  This has a
//               performance optimization benefit in itself, but is
//               especially useful to pave the way for a call to
//               flatten() and greatly improve the effectiveness of
//               the flattening operation.
//
//               Multiply instanced geometry is duplicated before the
//               transitions are applied.
//
//               Of course, this operation does make certain dynamic
//               operations impossible.
////////////////////////////////////////////////////////////////////
void SceneGraphReducer::
apply_transitions(NodeRelation *arc, int transition_types) {
  AccumulatedTransitions trans;
  if ((transition_types & TT_transform) != 0) {
    trans._transform = new TransformTransition;
  }
  if ((transition_types & TT_color) != 0) {
    trans._color = new ColorTransition;
  }
  if ((transition_types & TT_texture_matrix) != 0) {
    trans._texture_matrix = new TexMatrixTransition;
  }

  r_apply_transitions(arc, transition_types, trans, false);
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::r_apply_transitions
//       Access: Protected
//  Description: The recursive implementation of apply_transitions().
////////////////////////////////////////////////////////////////////
void SceneGraphReducer::
r_apply_transitions(NodeRelation *arc, int transition_types,
                    SceneGraphReducer::AccumulatedTransitions trans,
                    bool duplicate) {
  if (sgraphutil_cat.is_debug()) {
    sgraphutil_cat.debug()
      << "r_apply_transitions(" << *arc << "), arc's transitions are:\n";
    arc->write_transitions(sgraphutil_cat.debug(false), 2);
  }

  if ((transition_types & TT_transform) != 0) {
    nassertv(trans._transform != (TransformTransition *)NULL);
    TransformTransition *tt;
    if (get_transition_into(tt, arc)) {
      trans._transform =
        DCAST(TransformTransition, trans._transform->compose(tt));
      arc->clear_transition(TransformTransition::get_class_type());
    }
  }

  if ((transition_types & TT_color) != 0) {
    nassertv(trans._color != (ColorTransition *)NULL);
    ColorTransition *ct;
    if (get_transition_into(ct, arc)) {
      trans._color =
        DCAST(ColorTransition, trans._color->compose(ct));
      arc->clear_transition(ColorTransition::get_class_type());
    }
  }

  if ((transition_types & TT_texture_matrix) != 0) {
    nassertv(trans._texture_matrix != (TexMatrixTransition *)NULL);
    TexMatrixTransition *tmt;
    if (get_transition_into(tmt, arc)) {
      trans._texture_matrix =
        DCAST(TexMatrixTransition, trans._texture_matrix->compose(tmt));
      arc->clear_transition(TexMatrixTransition::get_class_type());
    }
  }

  PT(Node) node = arc->get_child();
  nassertv(node != (Node *)NULL);

  if (sgraphutil_cat.is_debug()) {
    sgraphutil_cat.debug()
      << "Applying transitions to " << *node << "\n"
      << "Accumulated transitions are:\n";
    trans.write(sgraphutil_cat.debug(false), transition_types, 2);
  }


  if (node->get_num_parents(_graph_type) > 1) {
    // This node has multiple instances; we need to duplicate
    // everything in the graph from this point down.
    duplicate = true;
  }

  if (duplicate) {
    // Make another copy of the Node.

    if (!node->safe_to_flatten()) {
      // Oops!  We *can't* flatten below this node.  We'll have to stop
      // here.  Drop transitions onto this arc to reflect what's been
      // accumulated so far.
      if (sgraphutil_cat.is_debug()) {
        sgraphutil_cat.debug()
          << "Cannot duplicate nodes of type " << node->get_type()
          << "; dropping transitions here and stopping.\n";
      }

      trans.apply_to_arc(arc, transition_types);
      return;
    }

    PT(Node) new_node = node->make_copy();
    if (new_node->get_type() != node->get_type()) {
      sgraphutil_cat.error()
        << "Cannot apply transitions to " << *node
        << "; don't know how to copy nodes of this type.\n";

      trans.apply_to_arc(arc, transition_types);
      return;
    }

    if (sgraphutil_cat.is_debug()) {
      sgraphutil_cat.debug()
        << "Duplicated " << *node << "\n";
    }

    copy_children(new_node, node);
    node = new_node.p();
    arc->change_child(node);
  }

  // Check to see if we can't propagate any of these transitions past
  // this arc for some reason.  A little bit kludgey, since we have to
  // know about all the special kinds of transitions, but attempting
  // to make this general and also work correctly is kind of tricky.
  int apply_types = 0;
  if (arc->has_transition(BillboardTransition::get_class_type())) {
    if (sgraphutil_cat.is_debug()) {
      sgraphutil_cat.debug()
        << "Arc " << *arc
        << " contains a BillboardTransition; leaving transform here.\n";
    }
    apply_types |= TT_transform;
  }
  if (!node->safe_to_transform()) {
    if (sgraphutil_cat.is_debug()) {
      sgraphutil_cat.debug()
        << "Cannot safely transform nodes of type " << node->get_type()
        << "; leaving a transform here but carrying on otherwise.\n";
    }
    apply_types |= TT_transform;
  }
  trans.apply_to_arc(arc, transition_types & apply_types);

  // Now apply what's left to the vertices.
  if (node->is_of_type(GeomNode::get_class_type())) {
    if (sgraphutil_cat.is_debug()) {
      sgraphutil_cat.debug()
        << "Transforming geometry.\n";
    }

    // We treat GeomNodes as a special case, since we can apply more
    // than just a transform matrix and so we can share vertex arrays
    // across different GeomNodes.
    GeomNode *gnode = DCAST(GeomNode, node);
    if ((transition_types & TT_transform) != 0) {
      if (trans._transform->get_matrix() != LMatrix4f::ident_mat()) {
        _transformer.transform_vertices(gnode, trans._transform->get_matrix());
      }
    }
    if ((transition_types & TT_color) != 0) {
      if (trans._color->is_on() && trans._color->is_real()) {
        _transformer.set_color(gnode, trans._color->get_color());
      }
    }
    if ((transition_types & TT_texture_matrix) != 0) {
      if (trans._texture_matrix->get_matrix() != LMatrix4f::ident_mat()) {
        _transformer.transform_texcoords(gnode,
                                         trans._texture_matrix->get_matrix());
      }
    }

  } else {
    // This handles any kind of node other than a GeomNode.
    if ((transition_types & TT_transform) != 0) {
      node->xform(trans._transform->get_matrix());
    }
  }

  const DownRelationPointers &drp =
    node->find_connection(_graph_type).get_down();
  DownRelationPointers drp_copy = drp;

  DownRelationPointers::const_iterator drpi;
  for (drpi = drp_copy.begin(); drpi != drp_copy.end(); ++drpi) {
    NodeRelation *child_arc = (*drpi);
    r_apply_transitions(child_arc, transition_types, trans, duplicate);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::consider_siblings
//       Access: Protected, Virtual
//  Description: Decides whether or not the indicated sibling nodes
//               (and their associated arcs) should be collapsed into
//               a single node or not.  Returns true if the arcs may
//               be collapsed, false if they should be kept distinct.
////////////////////////////////////////////////////////////////////
bool SceneGraphReducer::
consider_siblings(Node *parent, NodeRelation *arc1, NodeRelation *arc2) {
  // We can't collapse siblings with arcs that contain the
  // DecalTransition; however, other transitions are OK.
  if (arc1->has_transition(DecalTransition::get_class_type())) {
    return false;
  }

  return GraphReducer::consider_siblings(parent, arc1, arc2);
}

////////////////////////////////////////////////////////////////////
//     Function: SceneGraphReducer::collapse_nodes
//       Access: Protected, Virtual
//  Description: Collapses the two nodes into a single node, if
//               possible.  The 'siblings' flag is true if the two
//               nodes are siblings nodes; otherwise, node1 is a
//               parent of node2.  The return value is the resulting
//               node, which may be either one of the source nodes, or
//               a new node altogether, or it may be NULL to indicate
//               that the collapse operation could not take place.
//
//               This function may be extended in a user class to
//               handle combining special kinds of nodes.
////////////////////////////////////////////////////////////////////
Node *SceneGraphReducer::
collapse_nodes(Node *node1, Node *node2, bool siblings) {
  // We can collapse two GeomNodes easily, if they're siblings.  If
  // they're parent-child, we'd probably better not (it might
  // interfere with decaling).
  if (node1->is_exact_type(GeomNode::get_class_type()) &&
      node2->is_exact_type(GeomNode::get_class_type())) {
    if (!siblings) {
      return (Node *)NULL;
    }
  }

  return GraphReducer::collapse_nodes(node1, node2, siblings);
}
