// Filename: SceneGraphReducer.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 "sceneGraphReducer.h" #include "config_pgraph.h" #include "accumulatedAttribs.h" #include "modelNode.h" #include "pointerTo.h" #include "plist.h" #include "pmap.h" //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::flatten // Access: Published // Description: Simplifies the graph by removing unnecessary nodes // and nodes. // // In general, a node (and its parent node) is a // candidate for removal if the node has no siblings and // the node has no special properties. // // If combine_siblings_bits is nonzero, some sibling // nodes (according to the bits set in // combine_siblings_bits) may also be collapsed into a // single node. This will further reduce scene graph // complexity, sometimes substantially, at the cost of // reduced spatial separation. // // Returns the number of nodes removed from the graph. //////////////////////////////////////////////////////////////////// int SceneGraphReducer:: flatten(PandaNode *root, int combine_siblings_bits) { int num_total_nodes = 0; int num_pass_nodes; do { num_pass_nodes = 0; // Get a copy of the children list, so we don't have to worry // about self-modifications. PandaNode::ChildrenCopy cr = root->get_children_copy(); // Now visit each of the children in turn. int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { PandaNode *child_node = cr.get_child(i); num_pass_nodes += r_flatten(root, child_node, combine_siblings_bits); } if (combine_siblings_bits != 0 && root->get_num_children() >= 2) { num_pass_nodes += flatten_siblings(root, combine_siblings_bits); } num_total_nodes += num_pass_nodes; // If combine_siblings_bits has CS_recurse set, we should repeat // the above until we don't get any more benefit from flattening, // because each pass could convert cousins into siblings, which // may get flattened next pass. } while ((combine_siblings_bits & CS_recurse) != 0 && num_pass_nodes != 0); return num_total_nodes; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::r_apply_attribs // Access: Protected // Description: The recursive implementation of apply_attribs(). //////////////////////////////////////////////////////////////////// void SceneGraphReducer:: r_apply_attribs(PandaNode *node, const AccumulatedAttribs &attribs, int attrib_types, GeomTransformer &transformer) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "r_apply_attribs(" << *node << "), node's attribs are:\n"; node->get_transform()->write(pgraph_cat.spam(false), 2); node->get_state()->write(pgraph_cat.spam(false), 2); node->get_effects()->write(pgraph_cat.spam(false), 2); } AccumulatedAttribs next_attribs(attribs); next_attribs.collect(node, attrib_types); if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Got attribs from " << *node << "\n" << "Accumulated attribs are:\n"; next_attribs.write(pgraph_cat.spam(false), attrib_types, 2); } // Check to see if we can't propagate any of these attribs past // this node for some reason. int apply_types = 0; const RenderEffects *effects = node->get_effects(); if (!effects->safe_to_transform()) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Node " << *node << " contains a non-transformable effect; leaving transform here.\n"; } next_attribs._transform = effects->prepare_flatten_transform(next_attribs._transform); apply_types |= TT_transform; } if (!node->safe_to_transform()) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Cannot safely transform nodes of type " << node->get_type() << "; leaving a transform here but carrying on otherwise.\n"; } apply_types |= TT_transform; } apply_types |= node->get_unsafe_to_apply_attribs(); // Also, check the children of this node. If any of them indicates // it is not safe to modify its transform, we must drop our // transform here. int num_children = node->get_num_children(); int i; if ((apply_types & TT_transform) == 0) { bool children_transform_friendly = true; for (i = 0; i < num_children && children_transform_friendly; i++) { PandaNode *child_node = node->get_child(i); children_transform_friendly = child_node->safe_to_modify_transform(); } if (!children_transform_friendly) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Node " << *node << " has a child that cannot modify its transform; leaving transform here.\n"; } apply_types |= TT_transform; } } // Directly store whatever attributes we must, next_attribs.apply_to_node(node, attrib_types & apply_types); // And apply the rest to the vertices. node->apply_attribs_to_vertices(next_attribs, attrib_types, transformer); // Do we need to copy any children to flatten instances? bool resist_copy = false; for (i = 0; i < num_children; i++) { PandaNode *child_node = node->get_child(i); if (child_node->get_num_parents() > 1) { if (!child_node->safe_to_flatten()) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Cannot duplicate nodes of type " << child_node->get_type() << ".\n"; resist_copy = true; } } else { PT(PandaNode) new_node = child_node->make_copy(); if (new_node->get_type() != child_node->get_type()) { pgraph_cat.error() << "Don't know how to copy nodes of type " << child_node->get_type() << "\n"; resist_copy = true; } else { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Duplicated " << *child_node << "\n"; } new_node->copy_children(child_node); node->replace_child(child_node, new_node); child_node = new_node; } } } } if (resist_copy) { // If any of our children should have been copied but weren't, we // need to drop the state here before continuing. next_attribs.apply_to_node(node, attrib_types); } // Now it's safe to traverse through all of our children. nassertv(num_children == node->get_num_children()); for (i = 0; i < num_children; i++) { PandaNode *child_node = node->get_child(i); r_apply_attribs(child_node, next_attribs, attrib_types, transformer); } } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::r_flatten // Access: Protected // Description: The recursive implementation of flatten(). //////////////////////////////////////////////////////////////////// int SceneGraphReducer:: r_flatten(PandaNode *grandparent_node, PandaNode *parent_node, int combine_siblings_bits) { int num_nodes = 0; if (parent_node->safe_to_flatten_below()) { // First, recurse on each of the children. { PandaNode::ChildrenCopy cr = parent_node->get_children_copy(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { PandaNode *child_node = cr.get_child(i); num_nodes += r_flatten(parent_node, child_node, combine_siblings_bits); } } // Now that the above loop has removed some children, the child // list saved above is no longer accurate, so hereafter we must // ask the node for its real child list. // If we have CS_recurse set, then we flatten siblings before // trying to flatten children. Otherwise, we flatten children // first, and then flatten siblings, which avoids overly // enthusiastic flattening. if ((combine_siblings_bits & CS_recurse) != 0 && parent_node->get_num_children() >= 2) { if (parent_node->safe_to_combine()) { num_nodes += flatten_siblings(parent_node, combine_siblings_bits); } } if (parent_node->get_num_children() == 1) { // If we now have exactly one child, consider flattening the node // out. PT(PandaNode) child_node = parent_node->get_child(0); int child_sort = parent_node->get_child_sort(0); if (consider_child(grandparent_node, parent_node, child_node)) { // Ok, do it. parent_node->remove_child(0); if (do_flatten_child(grandparent_node, parent_node, child_node)) { // Done! num_nodes++; } else { // Chicken out. parent_node->add_child(child_node, child_sort); } } } if ((combine_siblings_bits & CS_recurse) == 0 && (combine_siblings_bits & ~CS_recurse) != 0 && parent_node->get_num_children() >= 2) { if (parent_node->safe_to_combine()) { num_nodes += flatten_siblings(parent_node, combine_siblings_bits); } } } return num_nodes; } class SortByState { public: INLINE bool operator () (const PandaNode *node1, const PandaNode *node2) const; }; INLINE bool SortByState:: operator () (const PandaNode *node1, const PandaNode *node2) const { if (node1->get_transform() != node2->get_transform()) { return node1->get_transform() < node2->get_transform(); } if (node1->get_state() != node2->get_state()) { return node1->get_state() < node2->get_state(); } if (node1->get_effects() != node2->get_effects()) { return node1->get_effects() < node2->get_effects(); } if (node1->get_draw_mask() != node2->get_draw_mask()) { return node1->get_draw_mask() < node2->get_draw_mask(); } return 0; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::flatten_siblings // Access: Protected // Description: Attempts to collapse together any pairs of siblings // of the indicated node that share the same properties. //////////////////////////////////////////////////////////////////// int SceneGraphReducer:: flatten_siblings(PandaNode *parent_node, int combine_siblings_bits) { int num_nodes = 0; // First, collect the children into groups of nodes with common // properties. typedef plist< PT(PandaNode) > NodeList; typedef pmap Collected; Collected collected; { // Protect this within a local scope, so the Children member will // destruct and free the read pointer before we try to write to // these nodes. PandaNode::Children cr = parent_node->get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { PandaNode *child_node = cr.get_child(i); bool safe_to_combine = child_node->safe_to_combine(); if (safe_to_combine) { if (child_node->is_geom_node()) { safe_to_combine = (combine_siblings_bits & CS_geom_node) != 0; } else { safe_to_combine = (combine_siblings_bits & CS_other) != 0; } } if (safe_to_combine) { collected[child_node].push_back(child_node); } } } // Now visit each of those groups and try to collapse them together. // A O(n^2) operation, but presumably the number of nodes in each // group is small. And if each node in the group can collapse with // any other node, it becomes a O(n) operation. Collected::iterator ci; for (ci = collected.begin(); ci != collected.end(); ++ci) { const RenderEffects *effects = (*ci).first->get_effects(); if (effects->safe_to_combine()) { NodeList &nodes = (*ci).second; NodeList::iterator ai1; ai1 = nodes.begin(); while (ai1 != nodes.end()) { NodeList::iterator ai1_hold = ai1; PandaNode *child1 = (*ai1); ++ai1; NodeList::iterator ai2 = ai1; while (ai2 != nodes.end()) { NodeList::iterator ai2_hold = ai2; PandaNode *child2 = (*ai2); ++ai2; if (consider_siblings(parent_node, child1, child2)) { PT(PandaNode) new_node = do_flatten_siblings(parent_node, child1, child2); if (new_node != (PandaNode *)NULL) { // We successfully collapsed a node. nodes.erase(ai2_hold); nodes.erase(ai1_hold); nodes.push_back(new_node); ai1 = nodes.begin(); ai2 = nodes.end(); num_nodes++; } } } } } } return num_nodes; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::consider_child // Access: Protected // Description: Decides whether or not the indicated child node is a // suitable candidate for removal. Returns true if the // node may be removed, false if it should be kept. //////////////////////////////////////////////////////////////////// bool SceneGraphReducer:: consider_child(PandaNode *grandparent_node, PandaNode *parent_node, PandaNode *child_node) { if (!parent_node->safe_to_combine() || !child_node->safe_to_combine()) { // One or both nodes cannot be safely combined with another node; // do nothing. return false; } if (parent_node->get_transform() != child_node->get_transform() || parent_node->get_state() != child_node->get_state() || parent_node->get_effects() != child_node->get_effects() || parent_node->get_draw_mask() != child_node->get_draw_mask()) { // The two nodes have a different state; too bad. return false; } if (!parent_node->get_effects()->safe_to_combine()) { // The effects don't want to be combined. return false; } return true; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::consider_siblings // Access: Protected // Description: Decides whether or not the indicated sibling nodes // should be collapsed into a single node or not. // Returns true if the nodes may be collapsed, false if // they should be kept distinct. //////////////////////////////////////////////////////////////////// bool SceneGraphReducer:: consider_siblings(PandaNode *parent_node, PandaNode *child1, PandaNode *child2) { // We don't have to worry about the states being different betweeen // child1 and child2, since the SortByState object already // guaranteed we only consider children that have the same state. return true; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::do_flatten_child // Access: Protected // Description: Collapses together the indicated parent node and // child node and leaves the result attached to the // grandparent. The return value is true if the node is // successfully collapsed, false if we chickened out. //////////////////////////////////////////////////////////////////// bool SceneGraphReducer:: do_flatten_child(PandaNode *grandparent_node, PandaNode *parent_node, PandaNode *child_node) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Collapsing " << *parent_node << " and " << *child_node << "\n"; } PT(PandaNode) new_parent = collapse_nodes(parent_node, child_node, false); if (new_parent == (PandaNode *)NULL) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Decided not to collapse " << *parent_node << " and " << *child_node << "\n"; } return false; } choose_name(new_parent, parent_node, child_node); if (new_parent != child_node) { new_parent->steal_children(child_node); new_parent->copy_tags(child_node); } if (new_parent != parent_node) { grandparent_node->replace_child(parent_node, new_parent); new_parent->copy_tags(parent_node); } return true; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::do_flatten_siblings // Access: Protected // Description: Performs the work of collapsing two sibling nodes // together into a single node, leaving the resulting // node attached to the parent. // // Returns a pointer to a PandaNode that reflects the // combined node (which may be either of the source nodes, // or a new node altogether) if the siblings are // successfully collapsed, or NULL if we chickened out. //////////////////////////////////////////////////////////////////// PandaNode *SceneGraphReducer:: do_flatten_siblings(PandaNode *parent_node, PandaNode *child1, PandaNode *child2) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Collapsing " << *child1 << " and " << *child2 << "\n"; } PT(PandaNode) new_child = collapse_nodes(child2, child1, true); if (new_child == (PandaNode *)NULL) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Decided not to collapse " << *child1 << " and " << *child2 << "\n"; } return NULL; } choose_name(new_child, child2, child1); if (new_child == child1) { new_child->steal_children(child2); parent_node->remove_child(child2); new_child->copy_tags(child2); } else if (new_child == child2) { new_child->steal_children(child1); parent_node->remove_child(child1); new_child->copy_tags(child1); } else { new_child->steal_children(child1); new_child->steal_children(child2); parent_node->remove_child(child2); parent_node->replace_child(child1, new_child); new_child->copy_tags(child1); new_child->copy_tags(child2); } return new_child; } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::collapse_nodes // Access: Protected // 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. //////////////////////////////////////////////////////////////////// PT(PandaNode) SceneGraphReducer:: collapse_nodes(PandaNode *node1, PandaNode *node2, bool siblings) { return node2->combine_with(node1); } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::choose_name // Access: Protected // Description: Chooses a suitable name for the collapsed node, based // on the names of the two sources nodes. //////////////////////////////////////////////////////////////////// void SceneGraphReducer:: choose_name(PandaNode *preserve, PandaNode *source1, PandaNode *source2) { string name; bool got_name = false; name = source1->get_name(); got_name = !name.empty() || source1->preserve_name(); if (source2->preserve_name() || !got_name) { name = source2->get_name(); got_name = !name.empty() || source2->preserve_name(); } if (got_name) { preserve->set_name(name); } } //////////////////////////////////////////////////////////////////// // Function: SceneGraphReducer::r_collect_vertex_data // Access: Private // Description: The recursive implementation of // collect_vertex_data(). //////////////////////////////////////////////////////////////////// int SceneGraphReducer:: r_collect_vertex_data(PandaNode *node, int collect_bits, GeomTransformer &transformer) { int num_collected = 0; if ((collect_bits & CVD_model) != 0 && node->is_of_type(ModelNode::get_class_type())) { // When we come to a model node, start a new collection. GeomTransformer new_transformer(transformer); PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_collected += r_collect_vertex_data(children.get_child(i), collect_bits, new_transformer); } return num_collected; } if (node->is_geom_node()) { // When we come to geom node, collect. bool keep_names = ((collect_bits & SceneGraphReducer::CVD_name) != 0); if (transformer.collect_vertex_data(DCAST(GeomNode, node), keep_names)) { ++num_collected; } } // Then recurse. PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_collected += r_collect_vertex_data(children.get_child(i), collect_bits, transformer); } return num_collected; }