/** * 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." * * @file sceneGraphReducer.cxx * @author drose * @date 2002-03-14 */ #include "sceneGraphReducer.h" #include "config_pgraph.h" #include "accumulatedAttribs.h" #include "boundingSphere.h" #include "modelNode.h" #include "pointerTo.h" #include "plist.h" #include "pmap.h" #include "geomNode.h" #include "config_gobj.h" #include "thread.h" PStatCollector SceneGraphReducer::_flatten_collector("*:Flatten:flatten"); PStatCollector SceneGraphReducer::_apply_collector("*:Flatten:apply"); PStatCollector SceneGraphReducer::_remove_column_collector("*:Flatten:remove column"); PStatCollector SceneGraphReducer::_compatible_state_collector("*:Flatten:compatible colors"); PStatCollector SceneGraphReducer::_collect_collector("*:Flatten:collect"); PStatCollector SceneGraphReducer::_make_nonindexed_collector("*:Flatten:make nonindexed"); PStatCollector SceneGraphReducer::_unify_collector("*:Flatten:unify"); PStatCollector SceneGraphReducer::_remove_unused_collector("*:Flatten:remove unused vertices"); PStatCollector SceneGraphReducer::_premunge_collector("*:Premunge"); /** * Specifies the particular GraphicsStateGuardian that this object will * attempt to optimize to. The GSG may specify parameters such as maximum * number of vertices per vertex data, max number of vertices per primitive, * and whether triangle strips are preferred. It also affects the types of * vertex column data that is created by premunge(). */ void SceneGraphReducer:: set_gsg(GraphicsStateGuardianBase *gsg) { if (gsg != nullptr) { _gsg = gsg; } else { _gsg = GraphicsStateGuardianBase::get_default_gsg(); } int max_vertices = max_collect_vertices; if (_gsg != nullptr) { max_vertices = std::min(max_vertices, _gsg->get_max_vertices_per_array()); } _transformer.set_max_collect_vertices(max_vertices); } /** * Specifies that no particular GraphicsStateGuardian will be used to guide * the optimization. The SceneGraphReducer will instead use config variables * such as max-collect-vertices and max-collect-indices. */ void SceneGraphReducer:: clear_gsg() { _gsg = nullptr; _transformer.set_max_collect_vertices(max_collect_vertices); } /** * 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) { nassertr(check_live_flatten(root), 0); PStatTimer timer(_flatten_collector); 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::Children cr = root->get_children(); // Now visit each of the children in turn. int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { PT(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 && root->safe_to_combine_children()) { 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; } /** * Removes the indicated data column from any GeomVertexDatas found at the * indicated root and below. Returns the number of GeomNodes modified. */ int SceneGraphReducer:: remove_column(PandaNode *root, const InternalName *column) { nassertr(check_live_flatten(root), 0); PStatTimer timer(_remove_column_collector); int count = r_remove_column(root, column, _transformer); _transformer.finish_apply(); return count; } /** * Searches for GeomNodes that contain multiple Geoms that differ only in * their ColorAttribs. If such a GeomNode is found, then all the colors are * pushed down into the vertices. This makes it feasible for the geoms to be * unified later. */ int SceneGraphReducer:: make_compatible_state(PandaNode *root) { nassertr(check_live_flatten(root), 0); PStatTimer timer(_compatible_state_collector); int count = r_make_compatible_state(root, _transformer); _transformer.finish_apply(); return count; } /** * Calls decompose() on every GeomNode at this level and below. * * There is usually no reason to call this explicitly, since unify() will do * this anyway if it needs to be done. However, calling it ahead of time can * make that future call to unify() run a little bit faster. * * This operation has no effect if the config variable preserve-triangle- * strips has been set true. */ void SceneGraphReducer:: decompose(PandaNode *root) { nassertv(check_live_flatten(root)); if (!preserve_triangle_strips) { PStatTimer timer(_unify_collector); r_decompose(root); } } /** * Calls unify() on every GeomNode at this level and below. This attempts to * reduce the total number of individual Geoms and GeomPrimitives by combining * these objects wherever possible. See GeomNode::unify(). */ void SceneGraphReducer:: unify(PandaNode *root, bool preserve_order) { nassertv(check_live_flatten(root)); PStatTimer timer(_unify_collector); int max_indices = max_collect_indices; if (_gsg != nullptr) { max_indices = std::min(max_indices, _gsg->get_max_vertices_per_primitive()); } r_unify(root, max_indices, preserve_order); } /** * Removes any vertices in GeomVertexDatas that are no longer used at this * level and below. This requires remapping vertex indices in all of the * GeomPrimitives, to remove holes in the GeomVertexDatas. It is normally not * necessary to call this explicitly. */ void SceneGraphReducer:: remove_unused_vertices(PandaNode *root) { nassertv(check_live_flatten(root)); PStatTimer timer(_remove_unused_collector); r_register_vertices(root, _transformer); _transformer.finish_apply(); Thread::consider_yield(); } /** * In a non-release build, returns false if the node is correctly not in a * live scene graph. (Calling flatten on a node that is part of a live scene * graph, for instance, a node somewhere under render, can cause problems in a * multithreaded environment.) * * If allow_live_flatten is true, or in a release build, this always returns * true. */ bool SceneGraphReducer:: check_live_flatten(PandaNode *node) { #ifndef NDEBUG if (allow_live_flatten) { return true; } if (node->is_under_scene_root()) { return false; } #endif // NDEBUG return true; } /** * 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. if (!node->safe_to_flatten_below()) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Not applying further; " << *node << " doesn't allow flattening below itself.\n"; } next_attribs.apply_to_node(node, attrib_types); return; } 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->dupe_for_flatten(); 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"; if (no_unsupported_copy) { nassert_raise("unsupported copy"); return; } 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); } Thread::consider_yield(); } /** * The recursive implementation of flatten(). */ int SceneGraphReducer:: r_flatten(PandaNode *grandparent_node, PandaNode *parent_node, int combine_siblings_bits) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "SceneGraphReducer::r_flatten(" << *grandparent_node << ", " << *parent_node << ", " << std::hex << combine_siblings_bits << std::dec << ")\n"; } if ((combine_siblings_bits & (CS_geom_node | CS_other | CS_recurse)) != 0) { // Unset CS_within_radius, since we're going to flatten everything anyway. // This avoids needlessly calculating the bounding volume. combine_siblings_bits &= ~CS_within_radius; } int num_nodes = 0; if (!parent_node->safe_to_flatten_below()) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Not traversing further; " << *parent_node << " doesn't allow flattening below itself.\n"; } } else { if ((combine_siblings_bits & CS_within_radius) != 0) { CPT(BoundingVolume) bv = parent_node->get_bounds(); if (bv->is_of_type(BoundingSphere::get_class_type())) { const BoundingSphere *bs = DCAST(BoundingSphere, bv); if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "considering radius of " << *parent_node << ": " << *bs << " vs. " << _combine_radius << "\n"; } if (!bs->is_infinite() && (bs->is_empty() || bs->get_radius() <= _combine_radius)) { // This node fits within the specified radius; from here on down, we // will have CS_other set, instead of CS_within_radius. if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "node fits within radius; flattening tighter.\n"; } combine_siblings_bits &= ~CS_within_radius; combine_siblings_bits |= (CS_geom_node | CS_other | CS_recurse); } } } // First, recurse on each of the children. { PandaNode::Children cr = parent_node->get_children(); int num_children = cr.get_num_children(); for (int i = 0; i < num_children; i++) { PT(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 && parent_node->safe_to_combine_children()) { 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(child_node); 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 && parent_node->safe_to_combine_children()) { num_nodes += flatten_siblings(parent_node, combine_siblings_bits); } // Finally, if any of our remaining children are plain PandaNodes with no // children, just remove them. if (parent_node->safe_to_combine_children()) { for (int i = parent_node->get_num_children() - 1; i >= 0; --i) { PandaNode *child_node = parent_node->get_child(i); if (child_node->is_exact_type(PandaNode::get_class_type()) && child_node->get_num_children() == 0 && child_node->get_transform()->is_identity() && child_node->get_effects()->is_empty()) { parent_node->remove_child(child_node); ++num_nodes; } } } } 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_control_mask() != node2->get_draw_control_mask()) { return node1->get_draw_control_mask() < node2->get_draw_control_mask(); } if (node1->get_draw_show_mask() != node2->get_draw_show_mask()) { return node1->get_draw_show_mask() < node2->get_draw_show_mask(); } int cmp = (node1->compare_tags(node2)); if (cmp != 0) { return cmp < 0; } return 0; } /** * 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 != nullptr) { // We successfully collapsed a node. (*ai1_hold) = new_node; nodes.erase(ai2_hold); ai1 = nodes.begin(); ai2 = nodes.end(); num_nodes++; } } } } } } return num_nodes; } /** * 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_control_mask() != child_node->get_draw_control_mask() || parent_node->get_draw_show_mask() != child_node->get_draw_show_mask() || parent_node->compare_tags(child_node) != 0) { // 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; } /** * 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; } /** * 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 == nullptr) { 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); new_parent->replace_node(child_node); new_parent->replace_node(parent_node); return true; } /** * 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 == nullptr) { if (pgraph_cat.is_spam()) { pgraph_cat.spam() << "Decided not to collapse " << *child1 << " and " << *child2 << "\n"; } return nullptr; } choose_name(new_child, child2, child1); // Make sure the new child list has child1's children first, followed by // child2's children. child1->replace_node(child2); new_child->replace_node(child1); return new_child; } /** * 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) { PT(PandaNode) result = node2->combine_with(node1); if (result == nullptr) { result = node1->combine_with(node2); } return result; } /** * 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) { std::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); } } /** * The recursive implementation of remove_column(). */ int SceneGraphReducer:: r_remove_column(PandaNode *node, const InternalName *column, GeomTransformer &transformer) { int num_changed = 0; if (node->is_geom_node()) { if (transformer.remove_column(DCAST(GeomNode, node), column)) { ++num_changed; } } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_changed += r_remove_column(children.get_child(i), column, transformer); } return num_changed; } /** * The recursive implementation of make_compatible_state(). */ int SceneGraphReducer:: r_make_compatible_state(PandaNode *node, GeomTransformer &transformer) { int num_changed = 0; if (node->is_geom_node()) { if (transformer.make_compatible_state(DCAST(GeomNode, node))) { ++num_changed; } } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_changed += r_make_compatible_state(children.get_child(i), transformer); } return num_changed; } /** * The recursive implementation of collect_vertex_data(). */ int SceneGraphReducer:: r_collect_vertex_data(PandaNode *node, int collect_bits, GeomTransformer &transformer, bool format_only) { int num_adjusted = 0; int this_node_bits = 0; if (node->is_of_type(ModelNode::get_class_type())) { this_node_bits |= CVD_model; } if (!node->get_transform()->is_identity()) { this_node_bits |= CVD_transform; } if (node->is_geom_node()) { this_node_bits |= CVD_one_node_only; } if ((collect_bits & this_node_bits) != 0) { // We need to start a unique collection here. GeomTransformer new_transformer(transformer); if (node->is_geom_node()) { // When we come to a geom node, collect. num_adjusted += new_transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits, format_only); } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_adjusted += r_collect_vertex_data(children.get_child(i), collect_bits, new_transformer, format_only); } num_adjusted += new_transformer.finish_collect(format_only); } else { // Keep the same collection. if (node->is_geom_node()) { num_adjusted += transformer.collect_vertex_data(DCAST(GeomNode, node), collect_bits, format_only); } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_adjusted += r_collect_vertex_data(children.get_child(i), collect_bits, transformer, format_only); } } Thread::consider_yield(); return num_adjusted; } /** * The recursive implementation of make_nonindexed(). */ int SceneGraphReducer:: r_make_nonindexed(PandaNode *node, int nonindexed_bits) { int num_changed = 0; if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); int num_geoms = geom_node->get_num_geoms(); for (int i = 0; i < num_geoms; ++i) { const Geom *geom = geom_node->get_geom(i); // Check whether the geom is animated or dynamic, and skip it if the // user specified so. const GeomVertexData *data = geom->get_vertex_data(); int this_geom_bits = 0; if (data->get_format()->get_animation().get_animation_type() != Geom::AT_none) { this_geom_bits |= MN_avoid_animated; } if (data->get_usage_hint() != Geom::UH_static || geom->get_usage_hint() != Geom::UH_static) { this_geom_bits |= MN_avoid_dynamic; } if ((nonindexed_bits & this_geom_bits) == 0) { // The geom meets the user's qualifications for making nonindexed, so // do it. PT(Geom) mgeom = geom_node->modify_geom(i); num_changed += mgeom->make_nonindexed((nonindexed_bits & MN_composite_only) != 0); } } } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { num_changed += r_make_nonindexed(children.get_child(i), nonindexed_bits); } return num_changed; } /** * The recursive implementation of unify(). */ void SceneGraphReducer:: r_unify(PandaNode *node, int max_indices, bool preserve_order) { if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); geom_node->unify(max_indices, preserve_order); } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { r_unify(children.get_child(i), max_indices, preserve_order); } Thread::consider_yield(); } /** * Recursively calls GeomTransformer::register_vertices() on all GeomNodes at * the indicated root and below. */ void SceneGraphReducer:: r_register_vertices(PandaNode *node, GeomTransformer &transformer) { if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); transformer.register_vertices(geom_node, true); } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { r_register_vertices(children.get_child(i), transformer); } } /** * The recursive implementation of decompose(). */ void SceneGraphReducer:: r_decompose(PandaNode *node) { if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); geom_node->decompose(); } PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (int i = 0; i < num_children; ++i) { r_decompose(children.get_child(i)); } } /** * The recursive implementation of premunge(). */ void SceneGraphReducer:: r_premunge(PandaNode *node, const RenderState *state) { CPT(RenderState) next_state = state->compose(node->get_state()); if (node->is_geom_node()) { GeomNode *geom_node = DCAST(GeomNode, node); geom_node->do_premunge(_gsg, next_state, _transformer); } int i; PandaNode::Children children = node->get_children(); int num_children = children.get_num_children(); for (i = 0; i < num_children; ++i) { r_premunge(children.get_child(i), next_state); } PandaNode::Stashed stashed = node->get_stashed(); int num_stashed = stashed.get_num_stashed(); for (i = 0; i < num_stashed; ++i) { r_premunge(stashed.get_stashed(i), next_state); } }