panda3d/panda/src/cull/cullTraverser.cxx
2001-02-20 04:43:02 +00:00

790 lines
25 KiB
C++

// Filename: cullTraverser.cxx
// Created by: drose (07Apr00)
//
////////////////////////////////////////////////////////////////////
#include "cullTraverser.h"
#include "geomBinTransition.h"
#include "geomBinAttribute.h"
#include "cullStateSubtree.h"
#include "geomBinNormal.h"
#include "geomBinFixed.h"
#include "directRenderTransition.h"
#include "config_cull.h"
#include <wrt.h>
#include <frustumCullTraverser.h>
#include <graphicsStateGuardian.h>
#include <decalTransition.h>
#include <pruneTransition.h>
#include <transformTransition.h>
#include <nodeTransitionWrapper.h>
#include <indent.h>
#include <config_sgraphutil.h> // for implicit_app_traversal
#include <config_sgattrib.h> // for support_decals
#include <pStatTimer.h>
#include <string_utils.h>
TypeHandle CullTraverser::_type_handle;
PStatCollector CullTraverser::_cull_pcollector =
PStatCollector("Cull", RGBColorf(0,1,0), 10);
PStatCollector CullTraverser::_draw_pcollector =
PStatCollector("Draw", RGBColorf(1,0,0), 20);
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullTraverser::
CullTraverser(GraphicsStateGuardian *gsg, TypeHandle graph_type,
const ArcChain &arc_chain) :
RenderTraverser(gsg, graph_type, arc_chain)
{
_nested_count = 0;
setup_initial_bins();
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CullTraverser::
~CullTraverser() {
// We should detach each of our associated bins when we destruct.
clear_bins();
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::has_bin
// Access: Public
// Description: Returns true if a bin by the given name has been
// attached to the CullTraverser, false otherwise.
////////////////////////////////////////////////////////////////////
bool CullTraverser::
has_bin(const string &name) const {
return (_toplevel_bins.count(name) != 0);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::get_bin
// Access: Public
// Description: Returns the GeomBin that was previously attached to
// the CullTraverser that shares the indicated name, or
// NULL if no GeomBin with a matching name was added.
////////////////////////////////////////////////////////////////////
GeomBin *CullTraverser::
get_bin(const string &name) const {
ToplevelBins::const_iterator tbi;
tbi = _toplevel_bins.find(name);
if (tbi == _toplevel_bins.end()) {
return NULL;
}
return (*tbi).second;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::clear_bins
// Access: Public
// Description: Disassociates all the GeomBins previously associated
// with this traverser (and deletes them, if they have
// no other references). You must add new GeomBins
// before rendering by calling set_traverser() on the
// appropriate bins.
////////////////////////////////////////////////////////////////////
void CullTraverser::
clear_bins() {
// We can't just run a for loop, because this is a self-modifying
// operation.
while (!_toplevel_bins.empty()) {
GeomBin *bin = (*_toplevel_bins.begin()).second;
nassertv(bin != (GeomBin *)NULL);
nassertv(bin->get_traverser() == this);
bin->clear_traverser();
}
nassertv(_sub_bins.empty());
nassertv(_default_bin == (GeomBin *)NULL);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::output
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void CullTraverser::
output(ostream &out) const {
int node_count = 0;
int used_states = 0;
States::const_iterator si;
for (si = _states.begin(); si != _states.end(); ++si) {
const CullState *cs = (*si);
int c = cs->count_current_nodes();
if (c != 0) {
node_count += c;
used_states++;
}
}
out << node_count << " nodes with " << used_states << " states; "
<< _states.size() - used_states << " unused states.";
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::output
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void CullTraverser::
write(ostream &out, int indent_level) const {
/*
States::const_iterator si;
for (si = _states.begin(); si != _states.end(); ++si) {
const CullState *cs = (*si);
cs->write(out, indent_level);
out << "\n";
}
*/
ToplevelBins::const_iterator tbi;
for (tbi = _toplevel_bins.begin(); tbi != _toplevel_bins.end(); ++tbi) {
(*tbi).second->write(out, indent_level);
}
_lookup.write(out, indent_level);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::traverse
// Access: Public, Virtual
// Description: This performs a normal, complete cull-and-render
// traversal using this CullTraverser object. State is
// saved within the CullTraverser object so that the
// next frame will be processed much more quickly than
// the first frame.
////////////////////////////////////////////////////////////////////
void CullTraverser::
traverse(Node *root,
const AllAttributesWrapper &initial_state,
const AllTransitionsWrapper &net_trans) {
// Statistics
PStatTimer timer(_cull_pcollector);
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Beginning level " << _nested_count << " cull traversal at "
<< *root << "\n";
}
bool is_initial = (_nested_count == 0);
if (is_initial) {
if (cull_force_update) {
_as_of = UpdateSeq::fresh();
} else {
_as_of = UpdateSeq::initial();
}
}
_now = last_graph_update(_graph_type);
_nested_count++;
if (is_initial) {
_initial_state.apply_from(initial_state, net_trans);
States::iterator si;
for (si = _states.begin(); si != _states.end(); ++si) {
(*si)->clear_current_nodes();
}
}
CullLevelState level_state;
level_state._lookup = &_lookup;
level_state._as_of = _as_of;
// Determine the relative transform matrix from the camera to our
// starting node. This is important for proper view-frustum
// culling.
LMatrix4f rel_from_camera;
NodeTransitionWrapper ntw(TransformTransition::get_class_type());
wrt(_gsg->get_current_projection_node(), root, begin(), end(),
ntw, get_graph_type());
const TransformTransition *tt;
if (get_transition_into(tt, ntw)) {
rel_from_camera = tt->get_matrix();
} else {
// No relative transform.
rel_from_camera = LMatrix4f::ident_mat();
}
fc_traverse(_arc_chain, root, rel_from_camera, *this,
NullAttributeWrapper(), level_state, _gsg, _graph_type);
if (is_initial) {
draw();
clean_out_old_states();
}
_nested_count--;
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Ending level " << _nested_count << " cull traversal at "
<< *root << "\n";
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::setup_initial_bins
// Access: Private
// Description: Creates all the appropriate rendering bins as
// requested from the Configrc file.
////////////////////////////////////////////////////////////////////
void CullTraverser::
setup_initial_bins() {
// We always have "default", "background", and "fixed" hardcoded in,
// although these may be overridden by specifing a new bin with the
// same name in the Configrc file.
GeomBinNormal *default_bin = new GeomBinNormal("default");
GeomBinFixed *background = new GeomBinFixed("background");
GeomBinFixed *fixed = new GeomBinFixed("fixed");
background->set_sort(20);
fixed->set_sort(40);
default_bin->set_traverser(this);
background->set_traverser(this);
fixed->set_traverser(this);
// Now get the config options.
Config::ConfigTable::Symbol cull_bins;
config_cull.GetAll("cull-bin", cull_bins);
Config::ConfigTable::Symbol::iterator bi;
for (bi = cull_bins.begin(); bi != cull_bins.end(); ++bi) {
ConfigString def = (*bi).Val();
// This is a string in three tokens, separated by whitespace:
// bin_name sort type
vector_string words;
extract_words(def, words);
if (words.size() != 3) {
cull_cat.error()
<< "Invalid cull-bin definition: " << def << "\n"
<< "Definition should be three words: bin_name sort type\n";
} else {
int sort;
if (!string_to_int(words[1], sort)) {
cull_cat.error()
<< "Invalid cull-bin definition: " << def << "\n"
<< "Sort token " << words[1] << " is not an integer.\n";
} else {
TypeHandle type = GeomBin::parse_bin_type(words[2]);
if (type == TypeHandle::none()) {
cull_cat.error()
<< "Invalid cull-bin definition: " << def << "\n"
<< "Bin type " << words[2] << " is not known.\n";
} else {
PT(GeomBin) bin = GeomBin::make_bin(type, words[0]);
nassertv(bin != (GeomBin *)NULL);
bin->set_sort(sort);
bin->set_traverser(this);
}
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::draw
// Access: Private
// Description: Puts all of the renderable geometry into the
// appropriate bins, and renders the bins.
////////////////////////////////////////////////////////////////////
void CullTraverser::
draw() {
if (cull_cat.is_debug()) {
// Count up the nonempty states for debug output.
int num_states = 0;
States::iterator si;
for (si = _states.begin(); si != _states.end(); ++si) {
CullState *cs = (*si);
if (!cs->is_empty()) {
num_states++;
}
}
cull_cat.debug()
<< "Initiating draw with " << num_states
<< " nonempty states of " << _states.size() << " total.\n";
}
SubBins::const_iterator sbi;
for (sbi = _sub_bins.begin(); sbi != _sub_bins.end(); ++sbi) {
(*sbi).second->clear_current_states();
}
States::iterator si;
for (si = _states.begin(); si != _states.end(); ++si) {
CullState *cs = (*si);
if (!cs->is_empty()) {
cs->apply_to(_initial_state);
static string default_bin_name = "default";
string bin_name = default_bin_name;
GeomBin *requested_bin = _default_bin;
int draw_order = 0;
// Check the requested bin for the Geoms in this state.
const GeomBinAttribute *bin_attrib;
if (get_attribute_into(bin_attrib, cs->get_attributes(),
GeomBinTransition::get_class_type())) {
draw_order = bin_attrib->get_draw_order();
bin_name = bin_attrib->get_bin();
requested_bin = get_bin(bin_name);
}
if (requested_bin == (GeomBin *)NULL) {
// If we don't have a bin by this name, create one.
cull_cat.warning()
<< "Bin " << bin_name << " is unknown; creating a default bin.\n";
requested_bin = new GeomBinNormal(bin_name);
requested_bin->set_traverser(this);
}
requested_bin->record_current_state(_gsg, cs, draw_order, this);
}
}
if (_gsg != (GraphicsStateGuardian *)NULL) {
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Drawing " << _sub_bins.size() << " bins.\n";
}
for (sbi = _sub_bins.begin(); sbi != _sub_bins.end(); ++sbi) {
GeomBin *bin = (*sbi).second;
if (bin->is_active()) {
bin->draw(this);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::clean_out_old_states
// Access: Private
// Description: Walks through the list of CullStates after a frame
// has been completely rendered and drawn, and removes
// any from the list that don't seem to be in use any
// more.
////////////////////////////////////////////////////////////////////
void CullTraverser::
clean_out_old_states() {
_lookup.clean_out_old_nodes();
States::iterator si, snext;
si = _states.begin();
snext = si;
while (si != _states.end()) {
++snext;
CullState *cs = (*si);
if (cs->get_ref_count() == 1 && cs->is_empty() && !cs->has_bin()) {
// This CullState doesn't seem to be used anywhere else.
_states.erase(si);
}
si = snext;
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::add_geom_node
// Access: Private
// Description: Records the indicated GeomNode as one that is visible
// this frame with the indicated state, setting up an
// appropriate CullState node and adding it to the
// appropriate bin.
////////////////////////////////////////////////////////////////////
void CullTraverser::
add_geom_node(GeomNode *node, const AllTransitionsWrapper &trans,
const CullLevelState &level_state) {
nassertv(node != (GeomNode *)NULL);
const ArcChain &arc_chain = get_arc_chain();
nassertv(!arc_chain.empty());
nassertv(arc_chain.back()->get_child() == node);
AllTransitionsWrapper complete_trans;
level_state._lookup->compose_trans(trans, complete_trans);
// Actually, there should be no need to remove this transition
// explicitly, as we should never encounter it--we'll never traverse
// below a DirectRenderTransition in the CullTraverser. We try to
// remove it here just to be paranoid.
complete_trans.clear_transition(DirectRenderTransition::get_class_type());
CullState *cs = level_state._lookup->find_node
(node, complete_trans, level_state._as_of);
if (cs == (CullState *)NULL) {
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Finding a new bin state\n";
}
// The node didn't have a previously-associated CullState that we
// could use, so determine a new one for it.
cs = find_bin_state(complete_trans);
nassertv(cs != (CullState *)NULL);
level_state._lookup->record_node(node, cs, level_state._as_of);
}
cs->record_current_geom_node(arc_chain);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::add_direct_node
// Access: Private
// Description: Records the indicated Node as one that is immediately
// under a DirectRenderTransition, and thus it and its
// subtree should be rendered directly, in a depth-first
// traversal. This is usually done when the render order
// is very important within a small subtree of nodes.
////////////////////////////////////////////////////////////////////
void CullTraverser::
add_direct_node(Node *node, const AllTransitionsWrapper &trans,
const CullLevelState &level_state) {
nassertv(node != (Node *)NULL);
const ArcChain &arc_chain = get_arc_chain();
nassertv(!arc_chain.empty());
nassertv(arc_chain.back()->get_child() == node);
AllTransitionsWrapper complete_trans;
level_state._lookup->compose_trans(trans, complete_trans);
// Remove this, just so we won't clutter up the state unnecessarily
// and interfere with state-sorting.
complete_trans.clear_transition(DirectRenderTransition::get_class_type());
CullState *cs = level_state._lookup->find_node
(node, complete_trans, level_state._as_of);
if (cs == (CullState *)NULL) {
// The node didn't have a previously-associated CullState that we
// could use, so determine a new one for it.
cs = find_bin_state(complete_trans);
nassertv(cs != (CullState *)NULL);
level_state._lookup->record_node(node, cs, level_state._as_of);
}
cs->record_current_direct_node(arc_chain);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::forward_arc
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
bool CullTraverser::
forward_arc(NodeRelation *arc, NullTransitionWrapper &,
NullAttributeWrapper &, NullAttributeWrapper &,
CullLevelState &level_state) {
nassertr(level_state._lookup != (CullStateLookup *)NULL, false);
if (arc->has_transition(PruneTransition::get_class_type())) {
return false;
}
Node *node = arc->get_child();
if (implicit_app_traversal) {
node->app_traverse();
}
node->draw_traverse();
AllTransitionsWrapper trans;
UpdateSeq last_update = arc->get_last_update();
if (level_state._as_of < last_update) {
level_state._as_of = last_update;
}
bool is_instanced = (node->get_num_parents(_graph_type) > 1);
bool is_geom = node->is_of_type(GeomNode::get_class_type());
bool node_has_sub_render = node->has_sub_render();
int arc_num_sub_render = arc->get_num_sub_render_trans();
bool has_decal = arc->has_transition(DecalTransition::get_class_type());
if (has_decal) {
// For the purposes of cull, we don't consider a DecalTransition
// to be a sub_render transition.
arc_num_sub_render--;
}
bool has_direct_render =
arc->has_transition(DirectRenderTransition::get_class_type());
#ifndef NDEBUG
if (support_decals != SD_on) {
has_direct_render = has_direct_render && !has_decal;
} else
#endif
{
has_direct_render = has_direct_render || has_decal;
}
#ifndef NDEBUG
if (support_subrender == SD_off) {
node_has_sub_render = false;
arc_num_sub_render = 0;
} else if (support_subrender == SD_hide) {
if ((node_has_sub_render || arc_num_sub_render != 0) &&
!arc->has_transition(DecalTransition::get_class_type())) {
return false;
}
node_has_sub_render = false;
arc_num_sub_render = 0;
}
#endif
#ifndef NDEBUG
if (support_direct == SD_off) {
has_direct_render = false;
} else if (support_direct == SD_hide) {
if (has_direct_render) {
return false;
}
}
#endif
if (arc_num_sub_render != 0) {
level_state._as_of = UpdateSeq::fresh();
}
_as_of = level_state._as_of;
mark_forward_arc(arc);
#ifndef NDEBUG
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Reached " << *node << ":\n"
<< " as_of = " << level_state._as_of
<< " now = " << _now
<< " is_instanced = " << is_instanced
<< " is_geom = " << is_geom
<< " node_has_sub_render = " << node_has_sub_render
<< " arc_num_sub_render = " << arc_num_sub_render
<< " has_direct_render = " << has_direct_render
<< "\n";
}
#endif
if (is_instanced || is_geom || node_has_sub_render ||
arc_num_sub_render != 0 || has_direct_render) {
// In any of these cases, we'll need to determine the net
// transition to this node.
wrt_subtree(arc, level_state._lookup->get_top_subtree(),
level_state._as_of, _now,
trans, _graph_type);
}
if (arc_num_sub_render != 0 || node_has_sub_render) {
if (_gsg != (GraphicsStateGuardian *)NULL) {
AllTransitionsWrapper complete_trans;
level_state._lookup->compose_trans(trans, complete_trans);
AllAttributesWrapper attrib;
attrib.apply_from(_initial_state, complete_trans);
AllTransitionsWrapper new_trans;
if (!arc->sub_render_trans(attrib, new_trans, this) ||
!node->sub_render(attrib, new_trans, this)) {
mark_backward_arc(arc);
return false;
}
trans.compose_in_place(new_trans);
}
}
if (has_direct_render) {
add_direct_node(node, trans, level_state);
// Since we're adding the node to be rendered directly, we won't
// traverse any further beyond it--the rest of the subgraph
// beginning at this node will be traversed when the node is
// rendered.
mark_backward_arc(arc);
return false;
}
if (is_instanced || arc_num_sub_render != 0) {
// This node is multiply instanced; thus, it begins a subtree.
level_state._lookup = add_instance(arc, trans, node, level_state);
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Added " << *node << " as instance.\n";
}
// It might also be an instanced GeomNode.
if (is_geom) {
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Added " << *node << "\n";
}
add_geom_node(DCAST(GeomNode, node), AllTransitionsWrapper(), level_state);
}
} else if (is_geom) {
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Added " << *node << "\n";
}
add_geom_node(DCAST(GeomNode, node), trans, level_state);
}
#ifndef NDEBUG
if (support_decals == SD_hide &&
arc->has_transition(DecalTransition::get_class_type())) {
mark_backward_arc(arc);
return false;
}
#endif
return true;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::attach_toplevel_bin
// Access: Private
// Description: This is intended to be called only by
// GeomBin::attach(). It stores the bin in the
// appropriate structures within the traverser, as a
// toplevel bin, e.g. a bin that may be referenced by
// name from outside the traverser, or one that geometry
// may be explicitly assigned to by name.
////////////////////////////////////////////////////////////////////
void CullTraverser::
attach_toplevel_bin(GeomBin *bin) {
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Attaching toplevel bin " << *bin << "\n";
}
const string &bin_name = bin->get_name();
// Insert the new bin by name.
pair<ToplevelBins::iterator, bool> result =
_toplevel_bins.insert(ToplevelBins::value_type(bin_name, bin));
if (!result.second) {
// There was already a bin by the same name name in this
// traverser. We should therefore detach this bin.
GeomBin *other_bin = (*result.first).second;
if (other_bin != bin) {
other_bin->clear_traverser();
}
result =
_toplevel_bins.insert(ToplevelBins::value_type(bin_name, bin));
nassertv(result.second);
}
if (bin_name == "default") {
_default_bin = bin;
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::attach_sub_bin
// Access: Private
// Description: This is intended to be called only by
// GeomBin::attach(). It stores the bin in the
// appropriate structures within the traverser, as a
// sub bin, e.g. a bin that may have geometry assigned
// to it, and may therefore be rendered.
//
// In most cases, a toplevel bin is the same as a sub
// bin, except in the case of a GeomBinGroup, which is a
// toplevel bin that contains a number of sub bins.
////////////////////////////////////////////////////////////////////
void CullTraverser::
attach_sub_bin(GeomBin *bin) {
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Attaching sub bin " << *bin << "\n";
}
_sub_bins.insert(SubBins::value_type(bin->get_sort(), bin));
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::detach_toplevel_bin
// Access: Private
// Description: This is intended to be called only by
// GeomBin::detach(). It removes the bin from the
// appropriate structures within the traverser, as a
// toplevel bin. See attach_toplevel_bin().
////////////////////////////////////////////////////////////////////
void CullTraverser::
detach_toplevel_bin(GeomBin *bin) {
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Detaching toplevel bin " << *bin << "\n";
}
const string &bin_name = bin->get_name();
ToplevelBins::iterator tbi = _toplevel_bins.find(bin_name);
nassertv(tbi != _toplevel_bins.end());
_toplevel_bins.erase(tbi);
if (bin_name == "default") {
_default_bin = (GeomBin *)NULL;
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::detach_sub_bin
// Access: Private
// Description: This is intended to be called only by
// GeomBin::detach(). It removes the bin from the
// appropriate structures within the traverser, as a
// sub bin. See attach_sub_bin().
////////////////////////////////////////////////////////////////////
void CullTraverser::
detach_sub_bin(GeomBin *bin) {
if (cull_cat.is_debug()) {
cull_cat.debug()
<< "Detaching sub bin " << *bin << "\n";
}
// Remove the bin from its place in the sort order list. This will
// be one of the (possibly several) entries in the multimap with the
// same sort value.
pair<SubBins::iterator, SubBins::iterator> range;
range = _sub_bins.equal_range(bin->get_sort());
SubBins::iterator sbi;
for (sbi = range.first; sbi != range.second; ++sbi) {
GeomBin *consider_bin = (*sbi).second;
nassertv(consider_bin->get_sort() == bin->get_sort());
if (consider_bin == bin) {
// Here's the position!
_sub_bins.erase(sbi);
return;
}
}
nassertv(false);
}