474 lines
19 KiB
C++
474 lines
19 KiB
C++
// Filename: cullPlanes.cxx
|
|
// Created by: drose (23Aug05)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "cullPlanes.h"
|
|
#include "cullTraverser.h"
|
|
#include "cullTraverserData.h"
|
|
#include "clipPlaneAttrib.h"
|
|
#include "occluderEffect.h"
|
|
#include "boundingBox.h"
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::make_empty
|
|
// Access: Public, Static
|
|
// Description: Returns a pointer to an empty CullPlanes object.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
make_empty() {
|
|
static CPT(CullPlanes) empty;
|
|
if (empty == NULL) {
|
|
empty = new CullPlanes;
|
|
// Artificially tick the reference count, just to ensure we won't
|
|
// accidentally modify this object in any of the copy-on-write
|
|
// operations below.
|
|
empty->ref();
|
|
}
|
|
return empty;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::xform
|
|
// Access: Public
|
|
// Description: Returns a pointer to a new CullPlanes object that is
|
|
// the same as this one, but with the clip planes
|
|
// modified by the indicated transform.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
xform(const LMatrix4 &mat) const {
|
|
PT(CullPlanes) new_planes;
|
|
if (get_ref_count() == 1) {
|
|
new_planes = (CullPlanes *)this;
|
|
} else {
|
|
new_planes = new CullPlanes(*this);
|
|
}
|
|
|
|
for (Planes::iterator pi = new_planes->_planes.begin();
|
|
pi != new_planes->_planes.end();
|
|
++pi) {
|
|
if ((*pi).second->get_ref_count() != 1) {
|
|
(*pi).second = DCAST(BoundingPlane, (*pi).second->make_copy());
|
|
}
|
|
(*pi).second->xform(mat);
|
|
}
|
|
|
|
for (Occluders::iterator oi = new_planes->_occluders.begin();
|
|
oi != new_planes->_occluders.end();
|
|
++oi) {
|
|
if ((*oi).second->get_ref_count() != 1) {
|
|
(*oi).second = DCAST(BoundingHexahedron, (*oi).second->make_copy());
|
|
}
|
|
(*oi).second->xform(mat);
|
|
}
|
|
|
|
return new_planes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::apply_state
|
|
// Access: Public
|
|
// Description: Returns a pointer to a new CullPlanes object that is
|
|
// the same as this one, but with the indicated
|
|
// attributes applied to the state.
|
|
//
|
|
// In particular, any new ClipPlanes given in
|
|
// net_attrib, if it is not NULL, will be added to the
|
|
// state, unless those ClipPlanes are also listed in
|
|
// off_attrib.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
apply_state(const CullTraverser *trav, const CullTraverserData *data,
|
|
const ClipPlaneAttrib *net_attrib,
|
|
const ClipPlaneAttrib *off_attrib,
|
|
const OccluderEffect *node_effect) const {
|
|
if (net_attrib == (ClipPlaneAttrib *)NULL && node_effect == (OccluderEffect *)NULL) {
|
|
return this;
|
|
}
|
|
|
|
PT(CullPlanes) new_planes;
|
|
if (get_ref_count() == 1) {
|
|
new_planes = (CullPlanes *)this;
|
|
} else {
|
|
new_planes = new CullPlanes(*this);
|
|
}
|
|
|
|
CPT(TransformState) net_transform = NULL;
|
|
|
|
if (net_attrib != (ClipPlaneAttrib *)NULL) {
|
|
int num_on_planes = net_attrib->get_num_on_planes();
|
|
for (int i = 0; i < num_on_planes; ++i) {
|
|
NodePath clip_plane = net_attrib->get_on_plane(i);
|
|
Planes::const_iterator pi = new_planes->_planes.find(clip_plane);
|
|
if (pi == new_planes->_planes.end()) {
|
|
if (!off_attrib->has_off_plane(clip_plane)) {
|
|
// Here's a new clip plane; add it to the list. For this we
|
|
// need the net transform to this node.
|
|
if (net_transform == (TransformState *)NULL) {
|
|
net_transform = data->get_net_transform(trav);
|
|
}
|
|
|
|
PlaneNode *plane_node = DCAST(PlaneNode, clip_plane.node());
|
|
CPT(TransformState) new_transform =
|
|
net_transform->invert_compose(clip_plane.get_net_transform());
|
|
|
|
LPlane plane = plane_node->get_plane() * new_transform->get_mat();
|
|
new_planes->_planes[clip_plane] = new BoundingPlane(-plane);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (node_effect != (OccluderEffect *)NULL) {
|
|
CPT(TransformState) center_transform = NULL;
|
|
// We'll need to know the occluder's frustum in cull-center
|
|
// space.
|
|
SceneSetup *scene = trav->get_scene();
|
|
const Lens *lens = scene->get_lens();
|
|
|
|
int num_on_occluders = node_effect->get_num_on_occluders();
|
|
for (int i = 0; i < num_on_occluders; ++i) {
|
|
NodePath occluder = node_effect->get_on_occluder(i);
|
|
Occluders::const_iterator oi = new_planes->_occluders.find(occluder);
|
|
if (oi == new_planes->_occluders.end()) {
|
|
// Here's a new occluder; consider adding it to the list.
|
|
OccluderNode *occluder_node = DCAST(OccluderNode, occluder.node());
|
|
nassertr(occluder_node->get_num_vertices() == 4, new_planes);
|
|
|
|
CPT(TransformState) occluder_transform = occluder.get_transform(scene->get_cull_center());
|
|
|
|
// And the transform from cull-center space into the current
|
|
// node's coordinate space.
|
|
if (center_transform == (TransformState *)NULL) {
|
|
if (net_transform == (TransformState *)NULL) {
|
|
net_transform = data->get_net_transform(trav);
|
|
}
|
|
|
|
center_transform = net_transform->invert_compose(scene->get_cull_center().get_net_transform());
|
|
}
|
|
|
|
// Compare the occluder node's bounding volume to the view
|
|
// frustum. We construct a new bounding volume because (a)
|
|
// the node's existing bounding volume is in the coordinate
|
|
// space of its parent, which isn't what we have here, and (b)
|
|
// we might as well make a BoundingBox, which is as tight as
|
|
// possible, and creating one isn't any less efficient than
|
|
// transforming the existing bounding volume.
|
|
PT(BoundingBox) occluder_gbv;
|
|
// Get a transform from the occluder directly to this node's
|
|
// space for comparing with the current view frustum.
|
|
CPT(TransformState) composed_transform = center_transform->compose(occluder_transform);
|
|
const LMatrix4 &composed_mat = composed_transform->get_mat();
|
|
LPoint3 ccp[4];
|
|
ccp[0] = occluder_node->get_vertex(0) * composed_mat;
|
|
ccp[1] = occluder_node->get_vertex(1) * composed_mat;
|
|
ccp[2] = occluder_node->get_vertex(2) * composed_mat;
|
|
ccp[3] = occluder_node->get_vertex(3) * composed_mat;
|
|
|
|
LPoint3 ccp_min(min(min(ccp[0][0], ccp[1][0]),
|
|
min(ccp[2][0], ccp[3][0])),
|
|
min(min(ccp[0][1], ccp[1][1]),
|
|
min(ccp[2][1], ccp[3][1])),
|
|
min(min(ccp[0][2], ccp[1][2]),
|
|
min(ccp[2][2], ccp[3][2])));
|
|
LPoint3 ccp_max(max(max(ccp[0][0], ccp[1][0]),
|
|
max(ccp[2][0], ccp[3][0])),
|
|
max(max(ccp[0][1], ccp[1][1]),
|
|
max(ccp[2][1], ccp[3][1])),
|
|
max(max(ccp[0][2], ccp[1][2]),
|
|
max(ccp[2][2], ccp[3][2])));
|
|
|
|
occluder_gbv = new BoundingBox(ccp_min, ccp_max);
|
|
|
|
if (data->_view_frustum != (GeometricBoundingVolume *)NULL) {
|
|
int occluder_result = data->_view_frustum->contains(occluder_gbv);
|
|
if (occluder_result == BoundingVolume::IF_no_intersection) {
|
|
// This occluder is outside the view frustum; ignore it.
|
|
if (pgraph_cat.is_spam()) {
|
|
pgraph_cat.spam()
|
|
<< "Ignoring occluder " << occluder << ": outside view frustum.\n";
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Get the occluder geometry in cull-center space.
|
|
const LMatrix4 &occluder_mat_cull = occluder_transform->get_mat();
|
|
LPoint3 points_near[4];
|
|
points_near[0] = occluder_node->get_vertex(0) * occluder_mat_cull;
|
|
points_near[1] = occluder_node->get_vertex(1) * occluder_mat_cull;
|
|
points_near[2] = occluder_node->get_vertex(2) * occluder_mat_cull;
|
|
points_near[3] = occluder_node->get_vertex(3) * occluder_mat_cull;
|
|
LPlane plane(points_near[0], points_near[1], points_near[2]);
|
|
|
|
if (plane.get_normal().dot(LVector3::forward()) >= 0.0) {
|
|
if (occluder_node->is_double_sided()) {
|
|
swap(points_near[0], points_near[3]);
|
|
swap(points_near[1], points_near[2]);
|
|
plane = LPlane(points_near[0], points_near[1], points_near[2]);
|
|
} else {
|
|
// This occluder is facing the wrong direction. Ignore it.
|
|
if (pgraph_cat.is_spam()) {
|
|
pgraph_cat.spam()
|
|
<< "Ignoring occluder " << occluder << ": wrong direction.\n";
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (occluder_node->get_min_coverage()) {
|
|
LPoint3 coords[4];
|
|
lens->project(points_near[0], coords[0]);
|
|
lens->project(points_near[1], coords[1]);
|
|
lens->project(points_near[2], coords[2]);
|
|
lens->project(points_near[3], coords[3]);
|
|
coords[0][0] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[0][0]));
|
|
coords[0][1] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[0][1]));
|
|
coords[1][0] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[1][0]));
|
|
coords[1][1] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[1][1]));
|
|
coords[2][0] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[2][0]));
|
|
coords[2][1] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[2][1]));
|
|
coords[3][0] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[3][0]));
|
|
coords[3][1] = max((PN_stdfloat)-1.0, min((PN_stdfloat)1.0, coords[3][1]));
|
|
PN_stdfloat coverage = ((coords[0] - coords[1]).cross(coords[0] - coords[2]).length()
|
|
+ (coords[3] - coords[1]).cross(coords[3] - coords[2]).length())
|
|
* 0.125;
|
|
if (coverage < occluder_node->get_min_coverage()) {
|
|
// The occluder does not cover enough screen space. Ignore it.
|
|
if (pgraph_cat.is_spam()) {
|
|
pgraph_cat.spam()
|
|
<< "Ignoring occluder " << occluder << ": coverage less than minimum.\n";
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Also check if the new occluder is completely within any of
|
|
// our existing occluder volumes.
|
|
bool is_enclosed = false;
|
|
Occluders::const_iterator oi;
|
|
for (oi = _occluders.begin(); oi != _occluders.end(); ++oi) {
|
|
int occluder_result = (*oi).second->contains(occluder_gbv);
|
|
if ((occluder_result & BoundingVolume::IF_all) != 0) {
|
|
is_enclosed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (is_enclosed) {
|
|
// No reason to add this occluder; it's behind an existing
|
|
// occluder.
|
|
if (pgraph_cat.is_spam()) {
|
|
pgraph_cat.spam()
|
|
<< "Ignoring occluder " << occluder << ": behind another.\n";
|
|
}
|
|
continue;
|
|
}
|
|
// TODO: perhaps we should also check whether any existing
|
|
// occluders are fully contained within this new one.
|
|
|
|
// Get the occluder coordinates in global space.
|
|
const LMatrix4 &occluder_mat = occluder.get_net_transform()->get_mat();
|
|
points_near[0] = occluder_node->get_vertex(0) * occluder_mat;
|
|
points_near[1] = occluder_node->get_vertex(1) * occluder_mat;
|
|
points_near[2] = occluder_node->get_vertex(2) * occluder_mat;
|
|
points_near[3] = occluder_node->get_vertex(3) * occluder_mat;
|
|
|
|
// For the far points, project PAST the far clip of the lens
|
|
// to ensures we get stuff that might be intersecting the far clip.
|
|
LPoint3 center = scene->get_cull_center().get_net_transform()->get_pos();
|
|
PN_stdfloat far_clip = scene->get_lens()->get_far() * 2.0;
|
|
LPoint3 points_far[4];
|
|
points_far[0] = normalize(points_near[0] - center) * far_clip + points_near[0];
|
|
points_far[1] = normalize(points_near[1] - center) * far_clip + points_near[1];
|
|
points_far[2] = normalize(points_near[2] - center) * far_clip + points_near[2];
|
|
points_far[3] = normalize(points_near[3] - center) * far_clip + points_near[3];
|
|
|
|
// With these points, construct the bounding frustum of the
|
|
// occluded region.
|
|
PT(BoundingHexahedron) frustum =
|
|
new BoundingHexahedron(points_far[1], points_far[2], points_far[3], points_far[0],
|
|
points_near[1], points_near[2], points_near[3], points_near[0]);
|
|
|
|
new_planes->_occluders[occluder] = frustum;
|
|
|
|
if (show_occluder_volumes) {
|
|
// Draw the frustum for visualization.
|
|
nassertr(net_transform != NULL, new_planes);
|
|
trav->draw_bounding_volume(frustum, data->get_internal_transform(trav));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return new_planes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::do_cull
|
|
// Access: Public
|
|
// Description: Tests the indicated bounding volume against all of
|
|
// the clip planes in this object. Sets result to an
|
|
// appropriate union of
|
|
// BoundingVolume::IntersectionFlags, similar to the
|
|
// result of BoundingVolume::contains().
|
|
//
|
|
// Also, if the bounding volume is completely in front
|
|
// of any of the clip planes, removes those planes both
|
|
// from this object and from the indicated state,
|
|
// returning a new CullPlanes object in that case.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
do_cull(int &result, CPT(RenderState) &state,
|
|
const GeometricBoundingVolume *node_gbv) const {
|
|
result =
|
|
BoundingVolume::IF_all | BoundingVolume::IF_possible | BoundingVolume::IF_some;
|
|
|
|
CPT(ClipPlaneAttrib) orig_cpa = DCAST(ClipPlaneAttrib, state->get_attrib(ClipPlaneAttrib::get_class_slot()));
|
|
|
|
CPT(CullPlanes) new_planes = this;
|
|
|
|
if (orig_cpa == (ClipPlaneAttrib *)NULL) {
|
|
// If there are no clip planes in the state, the node is completely
|
|
// in front of all zero of the clip planes. (This can happen if
|
|
// someone directly changes the state during the traversal.)
|
|
CullPlanes *planes = new CullPlanes;
|
|
planes->_occluders = _occluders;
|
|
new_planes = planes;
|
|
|
|
} else {
|
|
CPT(ClipPlaneAttrib) new_cpa = orig_cpa;
|
|
|
|
Planes::const_iterator pi;
|
|
for (pi = _planes.begin(); pi != _planes.end(); ++pi) {
|
|
int plane_result = (*pi).second->contains(node_gbv);
|
|
if (plane_result == BoundingVolume::IF_no_intersection) {
|
|
// The node is completely behind this clip plane and gets
|
|
// culled. Short-circuit the rest of the logic; none of the
|
|
// other planes matter.
|
|
result = plane_result;
|
|
return new_planes;
|
|
} else if ((plane_result & BoundingVolume::IF_all) != 0) {
|
|
// The node is completely in front of this clip plane. We don't
|
|
// need to consider this plane ever again for any descendents of
|
|
// this node.
|
|
new_planes = new_planes->remove_plane((*pi).first);
|
|
nassertr(new_planes != this, new_planes);
|
|
new_cpa = DCAST(ClipPlaneAttrib, new_cpa->remove_on_plane((*pi).first));
|
|
}
|
|
|
|
result &= plane_result;
|
|
}
|
|
|
|
if (new_cpa != orig_cpa) {
|
|
if (new_cpa->is_identity()) {
|
|
state = state->remove_attrib(ClipPlaneAttrib::get_class_slot());
|
|
} else {
|
|
state = state->add_attrib(new_cpa);
|
|
}
|
|
}
|
|
}
|
|
|
|
Occluders::const_iterator oi;
|
|
for (oi = _occluders.begin(); oi != _occluders.end(); ++oi) {
|
|
int occluder_result = (*oi).second->contains(node_gbv);
|
|
if (occluder_result == BoundingVolume::IF_no_intersection) {
|
|
// The node is completely in front of this occluder. We don't
|
|
// need to consider this occluder ever again for any descendents of
|
|
// this node.
|
|
|
|
// Reverse the sense of the test, because an occluder volume is
|
|
// the inverse of a cull plane volume: it describes the volume
|
|
// that is to be culled, not the volume that is to be kept.
|
|
occluder_result = BoundingVolume::IF_all | BoundingVolume::IF_possible | BoundingVolume::IF_some;
|
|
new_planes = new_planes->remove_occluder((*oi).first);
|
|
nassertr(new_planes != this, new_planes);
|
|
|
|
} else if ((occluder_result & BoundingVolume::IF_all) != 0) {
|
|
// The node is completely behind this occluder and gets culled.
|
|
// Short-circuit the rest of the logic; none of the other
|
|
// occluders matter.
|
|
result = BoundingVolume::IF_no_intersection;
|
|
return new_planes;
|
|
}
|
|
|
|
result &= occluder_result;
|
|
}
|
|
|
|
return new_planes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::remove_plane
|
|
// Access: Public
|
|
// Description: Returns a pointer to a new CullPlanes object that is
|
|
// the same as this one, but with the indicated
|
|
// clip plane removed.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
remove_plane(const NodePath &clip_plane) const {
|
|
PT(CullPlanes) new_planes;
|
|
if (get_ref_count() == 1) {
|
|
new_planes = (CullPlanes *)this;
|
|
} else {
|
|
new_planes = new CullPlanes(*this);
|
|
}
|
|
|
|
Planes::iterator pi = new_planes->_planes.find(clip_plane);
|
|
nassertr(pi != new_planes->_planes.end(), new_planes);
|
|
new_planes->_planes.erase(pi);
|
|
|
|
return new_planes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::remove_occluder
|
|
// Access: Public
|
|
// Description: Returns a pointer to a new CullPlanes object that is
|
|
// the same as this one, but with the indicated
|
|
// occluder removed.
|
|
////////////////////////////////////////////////////////////////////
|
|
CPT(CullPlanes) CullPlanes::
|
|
remove_occluder(const NodePath &occluder) const {
|
|
PT(CullPlanes) new_planes;
|
|
if (get_ref_count() == 1) {
|
|
new_planes = (CullPlanes *)this;
|
|
} else {
|
|
new_planes = new CullPlanes(*this);
|
|
}
|
|
|
|
Occluders::iterator pi = new_planes->_occluders.find(occluder);
|
|
nassertr(pi != new_planes->_occluders.end(), new_planes);
|
|
new_planes->_occluders.erase(pi);
|
|
|
|
return new_planes;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: CullPlanes::write
|
|
// Access: Public
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void CullPlanes::
|
|
write(ostream &out) const {
|
|
out << "CullPlanes (" << _planes.size() << " planes and "
|
|
<< _occluders.size() << " occluders):\n";
|
|
Planes::const_iterator pi;
|
|
for (pi = _planes.begin(); pi != _planes.end(); ++pi) {
|
|
out << " " << (*pi).first << " : " << *(*pi).second << "\n";
|
|
}
|
|
|
|
Occluders::const_iterator oi;
|
|
for (oi = _occluders.begin(); oi != _occluders.end(); ++oi) {
|
|
out << " " << (*oi).first << " : " << *(*oi).second << "\n";
|
|
}
|
|
}
|