panda3d/panda/src/pgraph/cullTraverser.cxx
2007-10-25 21:19:22 +00:00

900 lines
32 KiB
C++

// Filename: cullTraverser.cxx
// Created by: drose (23Feb02)
//
////////////////////////////////////////////////////////////////////
//
// 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 "cullTraverser.h"
#include "cullTraverserData.h"
#include "transformState.h"
#include "renderState.h"
#include "fogAttrib.h"
#include "colorAttrib.h"
#include "renderModeAttrib.h"
#include "cullFaceAttrib.h"
#include "depthOffsetAttrib.h"
#include "cullHandler.h"
#include "dcast.h"
#include "geomNode.h"
#include "config_pgraph.h"
#include "boundingSphere.h"
#include "boundingBox.h"
#include "boundingHexahedron.h"
#include "portalClipper.h"
#include "geom.h"
#include "geomTristrips.h"
#include "geomTriangles.h"
#include "geomLinestrips.h"
#include "geomVertexWriter.h"
PStatCollector CullTraverser::_nodes_pcollector("Nodes");
PStatCollector CullTraverser::_geom_nodes_pcollector("Nodes:GeomNodes");
PStatCollector CullTraverser::_geoms_pcollector("Geoms");
PStatCollector CullTraverser::_geoms_occluded_pcollector("Geoms:Occluded");
TypeHandle CullTraverser::_type_handle;
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullTraverser::
CullTraverser() :
_gsg(NULL),
_current_thread(Thread::get_current_thread())
{
_camera_mask = DrawMask::all_on();
_has_tag_state_key = false;
_initial_state = RenderState::make_empty();
_cull_handler = (CullHandler *)NULL;
_portal_clipper = (PortalClipper *)NULL;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::Copy Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullTraverser::
CullTraverser(const CullTraverser &copy) :
_gsg(copy._gsg),
_current_thread(copy._current_thread),
_scene_setup(copy._scene_setup),
_camera_mask(copy._camera_mask),
_has_tag_state_key(copy._has_tag_state_key),
_tag_state_key(copy._tag_state_key),
_initial_state(copy._initial_state),
_depth_offset_decals(copy._depth_offset_decals),
_view_frustum(copy._view_frustum),
_cull_handler(copy._cull_handler),
_portal_clipper(copy._portal_clipper)
{
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::set_scene
// Access: Public, Virtual
// Description: Sets the SceneSetup object that indicates the initial
// camera position, etc. This must be called before
// traversal begins.
////////////////////////////////////////////////////////////////////
void CullTraverser::
set_scene(SceneSetup *scene_setup, GraphicsStateGuardianBase *gsg) {
_scene_setup = scene_setup;
_gsg = gsg;
_initial_state = scene_setup->get_initial_state();
_depth_offset_decals = _gsg->depth_offset_decals() && depth_offset_decals;
_current_thread = Thread::get_current_thread();
const Camera *camera = scene_setup->get_camera_node();
_tag_state_key = camera->get_tag_state_key();
_has_tag_state_key = !_tag_state_key.empty();
_camera_mask = camera->get_camera_mask();
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::traverse
// Access: Public
// Description: Begins the traversal from the indicated node.
////////////////////////////////////////////////////////////////////
void CullTraverser::
traverse(const NodePath &root) {
nassertv(_cull_handler != (CullHandler *)NULL);
nassertv(_scene_setup != (SceneSetup *)NULL);
if (allow_portal_cull) {
// This _view_frustum is in cull_center space
PT(GeometricBoundingVolume) vf = _view_frustum;
GeometricBoundingVolume *local_frustum = NULL;
PT(BoundingVolume) bv = _scene_setup->get_lens()->make_bounds();
if (bv != (BoundingVolume *)NULL &&
bv->is_of_type(GeometricBoundingVolume::get_class_type())) {
local_frustum = DCAST(GeometricBoundingVolume, bv);
}
// This local_frustum is in camera space
PortalClipper portal_viewer(local_frustum, _scene_setup);
portal_viewer.draw_camera_frustum();
// store this pointer in this
set_portal_clipper(&portal_viewer);
CullTraverserData data(root, TransformState::make_identity(),
_initial_state, _view_frustum,
_current_thread);
traverse(data);
// finally add the lines to be drawn
portal_viewer.draw_lines();
// Render the frustum relative to the cull center.
NodePath cull_center = _scene_setup->get_cull_center();
CPT(TransformState) transform = cull_center.get_transform(root);
CullTraverserData my_data(data, portal_viewer._previous);
my_data._net_transform = my_data._net_transform->compose(transform);
traverse(my_data);
} else {
CullTraverserData data(root, TransformState::make_identity(),
_initial_state, _view_frustum,
_current_thread);
traverse(data);
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::traverse
// Access: Public
// Description: Traverses from the next node with the given
// data, which has been constructed with the node but
// has not yet been converted into the node's space.
////////////////////////////////////////////////////////////////////
void CullTraverser::
traverse(CullTraverserData &data) {
if (is_in_view(data)) {
if (pgraph_cat.is_spam()) {
pgraph_cat.spam()
<< "\n" << data._node_path
<< " " << data._draw_mask << "\n";
}
PandaNodePipelineReader *node_reader = data.node_reader();
int fancy_bits = node_reader->get_fancy_bits();
if ((fancy_bits & (PandaNode::FB_transform |
PandaNode::FB_state |
PandaNode::FB_effects |
PandaNode::FB_tag |
PandaNode::FB_draw_mask |
PandaNode::FB_cull_callback)) == 0 &&
data._cull_planes->is_empty()) {
// Nothing interesting in this node; just move on.
traverse_below(data);
} else {
// Something in this node is worth taking a closer look.
const RenderEffects *node_effects = node_reader->get_effects();
if (node_effects->has_show_bounds()) {
// If we should show the bounding volume for this node, make it
// up now.
show_bounds(data, node_effects->has_show_tight_bounds());
}
data.apply_transform_and_state(this);
const FogAttrib *fog = node_reader->get_state()->get_fog();
if (fog != (const FogAttrib *)NULL && fog->get_fog() != (Fog *)NULL) {
// If we just introduced a FogAttrib here, call adjust_to_camera()
// now. This maybe isn't the perfect time to call it, but it's
// good enough; and at this time we have all the information we
// need for it.
fog->get_fog()->adjust_to_camera(get_camera_transform());
}
if (fancy_bits & PandaNode::FB_cull_callback) {
PandaNode *node = data.node();
if (!node->cull_callback(this, data)) {
return;
}
}
traverse_below(data);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::traverse_below
// Access: Public, Virtual
// Description: Traverses all the children of the indicated node,
// with the given data, which has been converted into
// the node's space.
////////////////////////////////////////////////////////////////////
void CullTraverser::
traverse_below(CullTraverserData &data) {
_nodes_pcollector.add_level(1);
PandaNodePipelineReader *node_reader = data.node_reader();
PandaNode *node = data.node();
bool this_node_hidden = data.is_this_node_hidden(this);
const RenderEffects *node_effects = node_reader->get_effects();
bool has_decal = !this_node_hidden && node_effects->has_decal();
if (has_decal && !_depth_offset_decals) {
// Start the three-pass decal rendering if we're not using
// DepthOffsetAttribs to implement decals.
start_decal(data);
} else {
if (!this_node_hidden && node->is_geom_node()) {
_geom_nodes_pcollector.add_level(1);
GeomNode *geom_node = DCAST(GeomNode, node);
if (pgraph_cat.is_spam()) {
pgraph_cat.spam()
<< "Found " << *geom_node << " in state " << *data._state
<< " draw_mask = " << data._draw_mask << "\n";
}
// Get all the Geoms, with no decalling.
GeomNode::Geoms geoms = geom_node->get_geoms(_current_thread);
int num_geoms = geoms.get_num_geoms();
_geoms_pcollector.add_level(num_geoms);
CPT(TransformState) net_transform = data.get_net_transform(this);
CPT(TransformState) modelview_transform = data.get_modelview_transform(this);
CPT(TransformState) internal_transform = get_gsg()->get_cs_transform()->compose(modelview_transform);
for (int i = 0; i < num_geoms; i++) {
const Geom *geom = geoms.get_geom(i);
if (geom->is_empty()) {
continue;
}
CPT(RenderState) state = data._state->compose(geoms.get_geom_state(i));
if (state->has_cull_callback() && !state->cull_callback(this, data)) {
// Cull.
continue;
}
// Cull the Geom bounding volume against the view frustum
// and/or the cull planes. Don't bother unless we've got more
// than one Geom, since otherwise the bounding volume of the
// GeomNode is (probably) the same as that of the one Geom,
// and we've already culled against that.
if (num_geoms > 1) {
if (data._view_frustum != (GeometricBoundingVolume *)NULL) {
// Cull the individual Geom against the view frustum.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result = data._view_frustum->contains(geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull this Geom.
continue;
}
}
if (!data._cull_planes->is_empty()) {
// Also cull the Geom against the cull planes.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result;
data._cull_planes->do_cull(result, state, geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull.
continue;
}
}
}
CullableObject *object =
new CullableObject(geom, state, net_transform,
modelview_transform, internal_transform);
_cull_handler->record_object(object, this);
}
}
if (has_decal) {
// If we *are* implementing decals with DepthOffsetAttribs,
// apply it now, so that each child of this node gets offset by
// a tiny amount.
data._state = data._state->compose(get_depth_offset_state());
#ifndef NDEBUG
// This is just a sanity check message.
if (!node->is_geom_node()) {
pgraph_cat.error()
<< "DecalEffect applied to " << *node << ", not a GeomNode.\n";
}
#endif
}
// Now visit all the node's children.
PandaNode::Children children = node_reader->get_children();
node_reader->release();
int num_children = children.get_num_children();
if (node->has_selective_visibility()) {
int i = node->get_first_visible_child();
while (i < num_children) {
CullTraverserData next_data(data, children.get_child(i));
traverse(next_data);
i = node->get_next_visible_child(i);
}
} else {
for (int i = 0; i < num_children; i++) {
CullTraverserData next_data(data, children.get_child(i));
traverse(next_data);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::end_traverse
// Access: Public, Virtual
// Description: Should be called when the traverser has finished
// traversing its scene, this gives it a chance to do
// any necessary finalization.
////////////////////////////////////////////////////////////////////
void CullTraverser::
end_traverse() {
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::draw_bounding_volume
// Access: Public
// Description: Draws an appropriate visualization of the indicated
// bounding volume.
////////////////////////////////////////////////////////////////////
void CullTraverser::
draw_bounding_volume(const BoundingVolume *vol,
const TransformState *net_transform,
const TransformState *modelview_transform) {
PT(Geom) bounds_viz = make_bounds_viz(vol);
if (bounds_viz != (Geom *)NULL) {
_geoms_pcollector.add_level(2);
CullableObject *outer_viz =
new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
net_transform, modelview_transform, get_gsg());
_cull_handler->record_object(outer_viz, this);
CullableObject *inner_viz =
new CullableObject(bounds_viz, get_bounds_inner_viz_state(),
net_transform, modelview_transform, get_gsg());
_cull_handler->record_object(inner_viz, this);
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::is_in_view
// Access: Protected, Virtual
// Description: Returns true if the current node is fully or
// partially within the viewing area and should be
// drawn, or false if it (and all of its children)
// should be pruned.
////////////////////////////////////////////////////////////////////
bool CullTraverser::
is_in_view(CullTraverserData &data) {
return data.is_in_view(_camera_mask);
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::show_bounds
// Access: Private
// Description: Draws an appropriate visualization of the node's
// external bounding volume.
////////////////////////////////////////////////////////////////////
void CullTraverser::
show_bounds(CullTraverserData &data, bool tight) {
PandaNode *node = data.node();
CPT(TransformState) net_transform = data.get_net_transform(this);
CPT(TransformState) modelview_transform = data.get_modelview_transform(this);
if (tight) {
PT(Geom) bounds_viz = make_tight_bounds_viz(node);
if (bounds_viz != (Geom *)NULL) {
_geoms_pcollector.add_level(1);
CullableObject *outer_viz =
new CullableObject(bounds_viz, get_bounds_outer_viz_state(),
net_transform, modelview_transform,
get_gsg());
_cull_handler->record_object(outer_viz, this);
}
} else {
draw_bounding_volume(node->get_bounds(),
net_transform, modelview_transform);
if (node->is_geom_node()) {
// Also show the bounding volumes of included Geoms.
GeomNode *gnode = DCAST(GeomNode, node);
int num_geoms = gnode->get_num_geoms();
for (int i = 0; i < num_geoms; ++i) {
draw_bounding_volume(gnode->get_geom(i)->get_bounds(),
net_transform, modelview_transform);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::make_bounds_viz
// Access: Private
// Description: Returns an appropriate visualization of the indicated
// bounding volume.
////////////////////////////////////////////////////////////////////
PT(Geom) CullTraverser::
make_bounds_viz(const BoundingVolume *vol) {
PT(Geom) geom;
if (vol->is_infinite() || vol->is_empty()) {
// No way to draw an infinite or empty bounding volume.
} else if (vol->is_of_type(BoundingSphere::get_class_type())) {
const BoundingSphere *sphere = DCAST(BoundingSphere, vol);
static const int num_slices = 16;
static const int num_stacks = 8;
PT(GeomVertexData) vdata = new GeomVertexData
("bounds", GeomVertexFormat::get_v3(),
Geom::UH_stream);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
PT(GeomTristrips) strip = new GeomTristrips(Geom::UH_stream);
for (int sl = 0; sl < num_slices; ++sl) {
float longitude0 = (float)sl / (float)num_slices;
float longitude1 = (float)(sl + 1) / (float)num_slices;
vertex.add_data3f(compute_point(sphere, 0.0, longitude0));
for (int st = 1; st < num_stacks; ++st) {
float latitude = (float)st / (float)num_stacks;
vertex.add_data3f(compute_point(sphere, latitude, longitude0));
vertex.add_data3f(compute_point(sphere, latitude, longitude1));
}
vertex.add_data3f(compute_point(sphere, 1.0, longitude0));
strip->add_next_vertices(num_stacks * 2);
strip->close_primitive();
}
geom = new Geom(vdata);
geom->add_primitive(strip);
} else if (vol->is_of_type(FiniteBoundingVolume::get_class_type())) {
const FiniteBoundingVolume *fvol = DCAST(FiniteBoundingVolume, vol);
BoundingBox box(fvol->get_min(), fvol->get_max());
box.local_object();
PT(GeomVertexData) vdata = new GeomVertexData
("bounds", GeomVertexFormat::get_v3(),
Geom::UH_stream);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
for (int i = 0; i < 8; ++i ) {
vertex.add_data3f(box.get_point(i));
}
PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_stream);
tris->add_vertices(0, 4, 5);
tris->close_primitive();
tris->add_vertices(0, 5, 1);
tris->close_primitive();
tris->add_vertices(4, 6, 7);
tris->close_primitive();
tris->add_vertices(4, 7, 5);
tris->close_primitive();
tris->add_vertices(6, 2, 3);
tris->close_primitive();
tris->add_vertices(6, 3, 7);
tris->close_primitive();
tris->add_vertices(2, 0, 1);
tris->close_primitive();
tris->add_vertices(2, 1, 3);
tris->close_primitive();
tris->add_vertices(1, 5, 7);
tris->close_primitive();
tris->add_vertices(1, 7, 3);
tris->close_primitive();
tris->add_vertices(2, 6, 4);
tris->close_primitive();
tris->add_vertices(2, 4, 0);
tris->close_primitive();
geom = new Geom(vdata);
geom->add_primitive(tris);
} else {
pgraph_cat.warning()
<< "Don't know how to draw a representation of "
<< vol->get_class_type() << "\n";
}
return geom;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::make_tight_bounds_viz
// Access: Private
// Description: Returns a bounding-box visualization of the indicated
// node's "tight" bounding volume.
////////////////////////////////////////////////////////////////////
PT(Geom) CullTraverser::
make_tight_bounds_viz(PandaNode *node) {
PT(Geom) geom;
NodePath np = NodePath::any_path(node);
LPoint3f n, x;
bool found_any = false;
node->calc_tight_bounds(n, x, found_any, TransformState::make_identity(),
_current_thread);
if (found_any) {
PT(GeomVertexData) vdata = new GeomVertexData
("bounds", GeomVertexFormat::get_v3(),
Geom::UH_stream);
GeomVertexWriter vertex(vdata, InternalName::get_vertex(),
_current_thread);
vertex.add_data3f(n[0], n[1], n[2]);
vertex.add_data3f(n[0], n[1], x[2]);
vertex.add_data3f(n[0], x[1], n[2]);
vertex.add_data3f(n[0], x[1], x[2]);
vertex.add_data3f(x[0], n[1], n[2]);
vertex.add_data3f(x[0], n[1], x[2]);
vertex.add_data3f(x[0], x[1], n[2]);
vertex.add_data3f(x[0], x[1], x[2]);
PT(GeomLinestrips) strip = new GeomLinestrips(Geom::UH_stream);
// We wind one long linestrip around the wireframe cube. This
// does require backtracking a few times here and there.
strip->add_vertex(0);
strip->add_vertex(1);
strip->add_vertex(3);
strip->add_vertex(2);
strip->add_vertex(0);
strip->add_vertex(4);
strip->add_vertex(5);
strip->add_vertex(7);
strip->add_vertex(6);
strip->add_vertex(4);
strip->add_vertex(6);
strip->add_vertex(2);
strip->add_vertex(3);
strip->add_vertex(7);
strip->add_vertex(5);
strip->add_vertex(1);
strip->close_primitive();
geom = new Geom(vdata);
geom->add_primitive(strip);
}
return geom;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::compute_point
// Access: Private, Static
// Description: Returns a point on the surface of the sphere.
// latitude and longitude range from 0.0 to 1.0.
////////////////////////////////////////////////////////////////////
Vertexf CullTraverser::
compute_point(const BoundingSphere *sphere,
float latitude, float longitude) {
float s1, c1;
csincos(latitude * MathNumbers::pi_f, &s1, &c1);
float s2, c2;
csincos(longitude * 2.0f * MathNumbers::pi_f, &s2, &c2);
Vertexf p(s1 * c2, s1 * s2, c1);
return p * sphere->get_radius() + sphere->get_center();
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::get_bounds_outer_viz_state
// Access: Private, Static
// Description: Returns a RenderState for rendering the outside
// surfaces of the bounding volume visualizations.
////////////////////////////////////////////////////////////////////
CPT(RenderState) CullTraverser::
get_bounds_outer_viz_state() {
// Once someone asks for this pointer, we hold its reference count
// and never free it.
static CPT(RenderState) state = (const RenderState *)NULL;
if (state == (const RenderState *)NULL) {
state = RenderState::make
(ColorAttrib::make_flat(Colorf(0.3f, 1.0f, 0.5f, 1.0f)),
RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
CullFaceAttrib::make(CullFaceAttrib::M_cull_clockwise));
}
return state;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::get_bounds_inner_viz_state
// Access: Private, Static
// Description: Returns a RenderState for rendering the inside
// surfaces of the bounding volume visualizations.
////////////////////////////////////////////////////////////////////
CPT(RenderState) CullTraverser::
get_bounds_inner_viz_state() {
// Once someone asks for this pointer, we hold its reference count
// and never free it.
static CPT(RenderState) state = (const RenderState *)NULL;
if (state == (const RenderState *)NULL) {
state = RenderState::make
(ColorAttrib::make_flat(Colorf(0.15f, 0.5f, 0.25f, 1.0f)),
RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
CullFaceAttrib::make(CullFaceAttrib::M_cull_counter_clockwise));
}
return state;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::get_depth_offset_state
// Access: Private, Static
// Description: Returns a RenderState for increasing the DepthOffset
// by one.
////////////////////////////////////////////////////////////////////
CPT(RenderState) CullTraverser::
get_depth_offset_state() {
// Once someone asks for this pointer, we hold its reference count
// and never free it.
static CPT(RenderState) state = (const RenderState *)NULL;
if (state == (const RenderState *)NULL) {
state = RenderState::make
(DepthOffsetAttrib::make(1));
}
return state;
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::start_decal
// Access: Private
// Description: Collects a base node and all of the decals applied to
// it. This involves recursing below the base GeomNode
// to find all the decal geoms.
////////////////////////////////////////////////////////////////////
void CullTraverser::
start_decal(const CullTraverserData &data) {
PandaNode *node = data.node();
if (!node->is_geom_node()) {
pgraph_cat.error()
<< "DecalEffect applied to " << *node << ", not a GeomNode.\n";
return;
}
const PandaNodePipelineReader *node_reader = data.node_reader();
// Build a chain of CullableObjects. The head of the chain will be
// all of the base Geoms in order, followed by an empty
// CullableObject node, followed by all of the decal Geoms, in
// order.
// Since the CullableObject is a linked list which gets built in
// LIFO order, we start with the decals.
CullableObject *decals = (CullableObject *)NULL;
PandaNode::Children cr = node_reader->get_children();
int num_children = cr.get_num_children();
if (node->has_selective_visibility()) {
int i = node->get_first_visible_child();
while (i < num_children) {
CullTraverserData next_data(data, cr.get_child(i));
decals = r_get_decals(next_data, decals);
i = node->get_next_visible_child(i);
}
} else {
for (int i = num_children - 1; i >= 0; i--) {
CullTraverserData next_data(data, cr.get_child(i));
decals = r_get_decals(next_data, decals);
}
}
// Now create a new, empty CullableObject to separate the decals
// from the non-decals.
CullableObject *separator = new CullableObject(decals);
// And now get the base Geoms, again in reverse order.
CullableObject *object = separator;
GeomNode *geom_node = DCAST(GeomNode, node);
GeomNode::Geoms geoms = geom_node->get_geoms();
int num_geoms = geoms.get_num_geoms();
_geoms_pcollector.add_level(num_geoms);
CPT(TransformState) net_transform = data.get_net_transform(this);
CPT(TransformState) modelview_transform = data.get_modelview_transform(this);
CPT(TransformState) internal_transform = get_gsg()->get_cs_transform()->compose(modelview_transform);
for (int i = num_geoms - 1; i >= 0; i--) {
const Geom *geom = geoms.get_geom(i);
if (geom->is_empty()) {
continue;
}
CPT(RenderState) state = data._state->compose(geoms.get_geom_state(i));
if (state->has_cull_callback() && !state->cull_callback(this, data)) {
// Cull.
continue;
}
// Cull the Geom bounding volume against the view frustum
// and/or the cull planes. Don't bother unless we've got more
// than one Geom, since otherwise the bounding volume of the
// GeomNode is (probably) the same as that of the one Geom,
// and we've already culled against that.
if (num_geoms > 1) {
if (data._view_frustum != (GeometricBoundingVolume *)NULL) {
// Cull the individual Geom against the view frustum.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result = data._view_frustum->contains(geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull this Geom.
continue;
}
}
if (!data._cull_planes->is_empty()) {
// Also cull the Geom against the cull planes.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result;
data._cull_planes->do_cull(result, state, geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull.
continue;
}
}
}
object =
new CullableObject(geom, state, net_transform,
modelview_transform, internal_transform,
object);
}
if (object != separator) {
// Finally, send the whole list down to the CullHandler for
// processing. The first Geom in the node now represents the
// overall state.
_cull_handler->record_object(object, this);
} else {
// Never mind; there's nothing to render.
delete object;
}
}
////////////////////////////////////////////////////////////////////
// Function: CullTraverser::r_get_decals
// Access: Private
// Description: Recursively gets all the decals applied to a
// particular GeomNode. These are built into a
// CullableObject list in LIFO order (so that the
// traversing the list will extract them in the order
// they were encountered in the scene graph).
////////////////////////////////////////////////////////////////////
CullableObject *CullTraverser::
r_get_decals(CullTraverserData &data, CullableObject *decals) {
if (is_in_view(data)) {
PandaNodePipelineReader *node_reader = data.node_reader();
PandaNode *node = data.node();
const RenderEffects *node_effects = node_reader->get_effects();
if (node_effects->has_show_bounds()) {
// If we should show the bounding volume for this node, make it
// up now.
show_bounds(data, node_effects->has_show_tight_bounds());
}
data.apply_transform_and_state(this);
// First, visit all of the node's children.
int num_children = node_reader->get_num_children();
if (node->has_selective_visibility()) {
int i = node->get_first_visible_child();
while (i < num_children) {
CullTraverserData next_data(data, node_reader->get_child(i));
decals = r_get_decals(next_data, decals);
i = node->get_next_visible_child(i);
}
} else {
for (int i = num_children - 1; i >= 0; i--) {
CullTraverserData next_data(data, node_reader->get_child(i));
decals = r_get_decals(next_data, decals);
}
}
// Now, tack on any geoms within the node.
if (node->is_geom_node()) {
GeomNode *geom_node = DCAST(GeomNode, node);
GeomNode::Geoms geoms = geom_node->get_geoms();
int num_geoms = geoms.get_num_geoms();
_geoms_pcollector.add_level(num_geoms);
CPT(TransformState) net_transform = data.get_net_transform(this);
CPT(TransformState) modelview_transform = data.get_modelview_transform(this);
CPT(TransformState) internal_transform = get_gsg()->get_cs_transform()->compose(modelview_transform);
for (int i = num_geoms - 1; i >= 0; i--) {
const Geom *geom = geoms.get_geom(i);
if (geom->is_empty()) {
continue;
}
CPT(RenderState) state = data._state->compose(geoms.get_geom_state(i));
if (state->has_cull_callback() && !state->cull_callback(this, data)) {
// Cull.
continue;
}
// Cull the Geom bounding volume against the view frustum
// and/or the cull planes. Don't bother unless we've got more
// than one Geom, since otherwise the bounding volume of the
// GeomNode is (probably) the same as that of the one Geom,
// and we've already culled against that.
if (num_geoms > 1) {
if (data._view_frustum != (GeometricBoundingVolume *)NULL) {
// Cull the individual Geom against the view frustum.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result = data._view_frustum->contains(geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull this Geom.
continue;
}
}
if (!data._cull_planes->is_empty()) {
// Also cull the Geom against the cull planes.
CPT(BoundingVolume) geom_volume = geom->get_bounds();
const GeometricBoundingVolume *geom_gbv =
DCAST(GeometricBoundingVolume, geom_volume);
int result;
data._cull_planes->do_cull(result, state, geom_gbv);
if (result == BoundingVolume::IF_no_intersection) {
// Cull.
continue;
}
}
}
decals =
new CullableObject(geom, state, net_transform,
modelview_transform, internal_transform,
decals);
}
}
}
return decals;
}