LODNode should test its bounding volumes

This commit is contained in:
David Rose 2006-09-12 00:15:59 +00:00
parent 1ea1517e59
commit 2ddcce6ad2
8 changed files with 358 additions and 101 deletions

View File

@ -219,6 +219,14 @@ ConfigVariableDouble lod_fade_time
PRC_DESC("The default amount of time (in seconds) over which a FadeLODNode "
"transitions between its different levels."));
ConfigVariableBool verify_lods
("verify-lods", true,
PRC_DESC("When this is true, LODNodes will test when they are rendered to "
"ensure that each child's geometry fits entirely within the radius "
"defined by its switch-out distance. When it is false, LODNodes "
"may have any switch in and out distances, regardless of the "
"actual size of their geometry. This test is only made in NDEBUG "
"mode (the variable is ignored in a production build)."));
ConfigVariableBool show_vertex_animation
("show-vertex-animation", false,

View File

@ -51,6 +51,7 @@ extern ConfigVariableInt max_collect_indices;
extern ConfigVariableBool polylight_info;
extern ConfigVariableDouble lod_fade_time;
extern ConfigVariableBool verify_lods;
extern ConfigVariableBool show_vertex_animation;
extern ConfigVariableBool show_transparency;

View File

@ -77,6 +77,8 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return show_switches_cull_callback(trav, data);
}
consider_verify_lods(trav, data);
Camera *camera = trav->get_scene()->get_camera_node();
NodePath this_np = data._node_path.get_node_path();
FadeLODNodeData *ldata =
@ -126,21 +128,27 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
// Fade the new LOD in with z writing off
// Keep drawing the old LOD opaque with z writing on
if (ldata->_fade_out >= 0 && ldata->_fade_out < get_num_children()) {
CullTraverserData next_data_out(data, get_child(ldata->_fade_out));
trav->traverse(next_data_out);
PandaNode *child = get_child(ldata->_fade_out);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data_out(data, child);
trav->traverse(next_data_out);
}
}
if (ldata->_fade_in >= 0 && ldata->_fade_in < get_num_children()) {
CullTraverserData next_data_in(data, get_child(ldata->_fade_in));
float in_alpha = elapsed / half_fade_time;
LVecBase4f alpha_scale(1.0f, 1.0f, 1.0f, in_alpha);
next_data_in._state =
next_data_in._state->compose(get_fade_out_state())->compose
(RenderState::make(ColorScaleAttrib::make(alpha_scale)));
trav->traverse(next_data_in);
PandaNode *child = get_child(ldata->_fade_in);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data_in(data, child);
float in_alpha = elapsed / half_fade_time;
LVecBase4f alpha_scale(1.0f, 1.0f, 1.0f, in_alpha);
next_data_in._state =
next_data_in._state->compose(get_fade_out_state())->compose
(RenderState::make(ColorScaleAttrib::make(alpha_scale)));
trav->traverse(next_data_in);
}
}
} else if (elapsed < _fade_time) {
@ -148,21 +156,27 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
//Fade out the old LOD with z write off and
//draw the opaque new LOD with z write on
if (ldata->_fade_in >= 0 && ldata->_fade_in < get_num_children()) {
CullTraverserData next_data_in(data, get_child(ldata->_fade_in));
trav->traverse(next_data_in);
PandaNode *child = get_child(ldata->_fade_in);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data_in(data, child);
trav->traverse(next_data_in);
}
}
if (ldata->_fade_out >= 0 && ldata->_fade_out < get_num_children()) {
CullTraverserData next_data_out(data, get_child(ldata->_fade_out));
PandaNode *child = get_child(ldata->_fade_out);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data_out(data, child);
float out_alpha = 1.0f - (elapsed - half_fade_time) / half_fade_time;
LVecBase4f alpha_scale(1.0f, 1.0f, 1.0f, out_alpha);
next_data_out._state =
next_data_out._state->compose(get_fade_out_state())->compose
(RenderState::make(ColorScaleAttrib::make(alpha_scale)));
trav->traverse(next_data_out);
float out_alpha = 1.0f - (elapsed - half_fade_time) / half_fade_time;
LVecBase4f alpha_scale(1.0f, 1.0f, 1.0f, out_alpha);
next_data_out._state =
next_data_out._state->compose(get_fade_out_state())->compose
(RenderState::make(ColorScaleAttrib::make(alpha_scale)));
trav->traverse(next_data_out);
}
}
} else {
@ -177,8 +191,11 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
// transition; we're just drawing one child of the LOD.
int index = ldata->_fade_in;
if (index >= 0 && index < get_num_children()) {
CullTraverserData next_data(data, get_child(index));
trav->traverse(next_data);
PandaNode *child = get_child(index);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data(data, child);
trav->traverse(next_data);
}
}
}

View File

@ -56,6 +56,8 @@ LODNode(const LODNode &copy) :
////////////////////////////////////////////////////////////////////
INLINE void LODNode::
add_switch(float in, float out) {
nassertv(in >= out);
CDWriter cdata(_cycler);
cdata->_switch_vector.push_back(Switch(in, out));
cdata->check_limits();
@ -73,6 +75,8 @@ add_switch(float in, float out) {
////////////////////////////////////////////////////////////////////
INLINE bool LODNode::
set_switch(int index, float in, float out) {
nassertr(in >= out, false);
CDWriter cdata(_cycler);
nassertr(index >= 0 && index < (int)cdata->_switch_vector.size(), false);
cdata->_switch_vector[index].set_range(in, out);
@ -244,6 +248,23 @@ is_any_shown() const {
return (cdata->_num_shown != 0);
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::consider_verify_lods
// Access: Protected
// Description: To be called internally when the node is rendered,
// this will raise an assertion if verify-lods is
// configured true, and verify_child_bounds() returns
// false.
////////////////////////////////////////////////////////////////////
INLINE void LODNode::
consider_verify_lods(CullTraverser *trav, CullTraverserData &data) {
#ifndef NDEBUG
if (verify_lods) {
do_auto_verify_lods(trav, data);
}
#endif // NDEBUG
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::CData::Constructor
// Access: Public

View File

@ -35,7 +35,9 @@
#include "cullFaceAttrib.h"
#include "textureAttrib.h"
#include "boundingSphere.h"
#include "geometricBoundingVolume.h"
#include "look_at.h"
#include "nodePath.h"
TypeHandle LODNode::_type_handle;
@ -132,10 +134,32 @@ cull_callback(CullTraverser *trav, CullTraverserData &data) {
return show_switches_cull_callback(trav, data);
}
int index = compute_child(trav, data);
if (index >= 0 && index < get_num_children()) {
CullTraverserData next_data(data, get_child(index));
trav->traverse(next_data);
consider_verify_lods(trav, data);
CDReader cdata(_cycler);
CPT(TransformState) rel_transform = get_rel_transform(trav, data);
LPoint3f center = cdata->_center * rel_transform->get_mat();
float dist2 = center.dot(center);
int num_children = min(get_num_children(), (int)cdata->_switch_vector.size());
for (int index = 0; index < num_children; ++index) {
const Switch &sw = cdata->_switch_vector[index];
bool in_range;
if (cdata->_got_force_switch) {
in_range = (cdata->_force_switch == index);
} else {
in_range = sw.in_range_2(dist2);
}
if (in_range) {
// This switch level is in range. Draw its children.
PandaNode *child = get_child(index);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data(data, child);
trav->traverse(next_data);
}
}
}
// Now return false indicating that we have already taken care of
@ -269,6 +293,35 @@ hide_all_switches() {
mark_internal_bounds_stale();
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::verify_child_bounds
// Access: Published
// Description: Returns true if the bounding volumes for the geometry
// of each fhild node entirely fits within the
// switch_in radius for that child, or false otherwise.
// It is almost always a mistake for the geometry of an
// LOD level to be larger than its switch_in radius.
////////////////////////////////////////////////////////////////////
bool LODNode::
verify_child_bounds() const {
bool okflag = true;
CDReader cdata(_cycler);
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
float suggested_radius;
if (!do_verify_child_bounds(cdata, index, suggested_radius)) {
const Switch &sw = cdata->_switch_vector[index];
pgraph_cat.warning()
<< "Level " << index << " geometry of " << *this
<< " is larger than its switch radius; suggest radius of "
<< suggested_radius << " instead of " << sw.get_in() << "\n";
okflag = false;
}
}
return okflag;
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::compute_child
// Access: Protected
@ -290,36 +343,11 @@ compute_child(CullTraverser *trav, CullTraverserData &data) {
return cdata->_force_switch;
}
// Get a pointer to the camera node.
Camera *camera = trav->get_scene()->get_camera_node();
// Get the LOD center in camera space. If the camera has a special
// LOD center defined, use that; otherwise, if it has a cull center,
// use that; otherwise, use the modelview transform (which is camera
// space).
CPT(TransformState) rel_transform;
NodePath lod_center = camera->get_lod_center();
if (!lod_center.is_empty()) {
rel_transform =
lod_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
NodePath cull_center = camera->get_cull_center();
if (!cull_center.is_empty()) {
rel_transform =
cull_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
rel_transform = data.get_modelview_transform(trav);
}
}
CPT(TransformState) rel_transform = get_rel_transform(trav, data);
LPoint3f center = cdata->_center * rel_transform->get_mat();
// Now measure the distance to the LOD center, and use that to
// determine which child to display.
float dist2 = center.dot(center);
for (int index = 0; index < (int)cdata->_switch_vector.size(); index++) {
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
if (cdata->_switch_vector[index].in_range_2(dist2)) {
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
@ -353,27 +381,7 @@ bool LODNode::
show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
CDReader cdata(_cycler);
// Get a pointer to the camera node.
Camera *camera = trav->get_scene()->get_camera_node();
// Get the camera space transform. This bit is the same as the code
// in compute_child(), above.
CPT(TransformState) rel_transform;
NodePath lod_center = camera->get_lod_center();
if (!lod_center.is_empty()) {
rel_transform =
lod_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
NodePath cull_center = camera->get_cull_center();
if (!cull_center.is_empty()) {
rel_transform =
cull_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
rel_transform = data.get_modelview_transform(trav);
}
}
CPT(TransformState) rel_transform = get_rel_transform(trav, data);
LPoint3f center = cdata->_center * rel_transform->get_mat();
float dist2 = center.dot(center);
@ -382,22 +390,12 @@ show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
LMatrix4f mat;
look_at(mat, -center, LVector3f(0.0f, 0.0f, 1.0f));
mat.set_row(3, center);
CPT(TransformState) viz_transform = TransformState::make_mat(mat);
CPT(TransformState) viz_transform =
rel_transform->invert_compose(TransformState::make_mat(mat));
viz_transform = rel_transform->invert_compose(viz_transform);
viz_transform = data.get_net_transform(trav)->compose(viz_transform);
SwitchVector::const_iterator si;
for (si = cdata->_switch_vector.begin();
si != cdata->_switch_vector.end();
++si) {
const Switch &sw = (*si);
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
const Switch &sw = cdata->_switch_vector[index];
if (sw.is_shown()) {
CullTraverserData next_data(data, sw.get_ring_viz());
next_data._net_transform = viz_transform;
trav->traverse(next_data);
int index = (si - cdata->_switch_vector.begin());
bool in_range;
if (cdata->_got_force_switch) {
in_range = (cdata->_force_switch == index);
@ -406,19 +404,34 @@ show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data) {
}
if (in_range) {
// This switch level is in range. Draw the spindle in this
// color.
CullTraverserData next_data2(data, sw.get_spindle_viz());
next_data2._net_transform = viz_transform;
trav->traverse(next_data2);
// And draw its children in the funny wireframe mode.
// This switch level is in range. Draw its children in the
// funny wireframe mode.
if (index < get_num_children()) {
CullTraverserData next_data3(data, get_child(index));
next_data3._state = next_data3._state->compose(sw.get_viz_model_state());
trav->traverse(next_data3);
PandaNode *child = get_child(index);
if (child != (PandaNode *)NULL) {
CullTraverserData next_data3(data, child);
next_data3._state = next_data3._state->compose(sw.get_viz_model_state());
trav->traverse(next_data3);
}
}
// And draw the spindle in this color.
CullTraverserData next_data2(data, sw.get_spindle_viz());
next_data2.apply_transform_and_state(trav, viz_transform,
RenderState::make_empty(),
RenderEffects::make_empty(),
ClipPlaneAttrib::make());
trav->traverse(next_data2);
}
// Draw the rings for this switch level. We do this after we
// have drawn the geometry and the spindle.
CullTraverserData next_data(data, sw.get_ring_viz());
next_data.apply_transform_and_state(trav, viz_transform,
RenderState::make_empty(),
RenderEffects::make_empty(),
ClipPlaneAttrib::make());
trav->traverse(next_data);
}
}
@ -471,6 +484,37 @@ compute_internal_bounds(int pipeline_stage, Thread *current_thread) const {
return bound;
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::get_rel_transform
// Access: Protected
// Description: Returns the relative transform to convert from the
// LODNode space to the camera space.
////////////////////////////////////////////////////////////////////
CPT(TransformState) LODNode::
get_rel_transform(CullTraverser *trav, CullTraverserData &data) {
// Get a pointer to the camera node.
Camera *camera = trav->get_scene()->get_camera_node();
// Get the camera space transform.
CPT(TransformState) rel_transform;
NodePath lod_center = camera->get_lod_center();
if (!lod_center.is_empty()) {
rel_transform =
lod_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
NodePath cull_center = camera->get_cull_center();
if (!cull_center.is_empty()) {
rel_transform =
cull_center.get_net_transform()->invert_compose(data.get_net_transform(trav));
} else {
rel_transform = data.get_modelview_transform(trav);
}
}
return rel_transform;
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::do_show_switch
// Access: Private
@ -501,6 +545,124 @@ do_hide_switch(LODNode::CData *cdata, int index) {
cdata->_switch_vector[index].hide();
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::do_verify_child_bounds
// Access: Private
// Description: The private implementation of verify_child_bounds(),
// this checks the bounding volume of just one child.
//
// If the return value is false, suggested_radius is
// filled with a radius that ought to be large enough to
// include the child.
////////////////////////////////////////////////////////////////////
bool LODNode::
do_verify_child_bounds(const LODNode::CData *cdata, int index,
float &suggested_radius) const {
suggested_radius = 0.0f;
if (index < get_num_children()) {
PandaNode *child = get_child(index);
if (child != (PandaNode *)NULL) {
CPT(BoundingVolume) bv = child->get_bounds();
if (bv->is_empty()) {
// This child has no geometry, so no one cares anyway.
return true;
}
const Switch &sw = cdata->_switch_vector[index];
const GeometricBoundingVolume *gbv;
DCAST_INTO_R(gbv, bv, false);
BoundingSphere sphere(cdata->_center, sw.get_in());
sphere.local_object();
int flags = sphere.contains(gbv);
if ((flags & BoundingVolume::IF_all) != 0) {
// This child's radius completely encloses its bounding volume.
// Perfect. (And this is the most common case.)
return true;
}
if (flags == 0) {
// This child's radius doesn't even come close to containing
// its volume.
sphere.extend_by(gbv);
suggested_radius = sphere.get_radius();
return false;
}
// This child's radius partially encloses its (loose) bounding
// volume. We have to look closer to determine whether it, in
// fact, fully encloses its geometry.
LPoint3f min_point(0.0f, 0.0f, 0.0f);
LPoint3f max_point(0.0f, 0.0f, 0.0f);
bool found_any = false;
child->calc_tight_bounds(min_point, max_point, found_any,
TransformState::make_identity(),
Thread::get_current_thread());
if (!found_any) {
// Hmm, the child has no geometry after all.
return true;
}
// Now we have a bounding box. Define the largest sphere we can
// that fits within this box. All we can say about this sphere
// is that it should definitely fit entirely within a bounding
// sphere that contains all the points of the child.
LPoint3f box_center = (min_point + max_point) / 2.0f;
float box_radius = min(min(max_point[0] - box_center[0],
max_point[1] - box_center[1]),
max_point[2] - box_center[2]);
BoundingSphere box_sphere(box_center, box_radius);
box_sphere.local_object();
// So if any part of this inscribed sphere is outside of the
// radius, then the radius is bad.
flags = sphere.contains(&box_sphere);
if (flags != BoundingVolume::IF_all) {
// No good.
sphere.extend_by(gbv);
suggested_radius = sphere.get_radius();
return false;
}
}
}
return true;
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::do_auto_verify_lods
// Access: Private
// Description: Called internally by consider_verify_lods().
////////////////////////////////////////////////////////////////////
void LODNode::
do_auto_verify_lods(CullTraverser *trav, CullTraverserData &data) {
UpdateSeq seq;
get_bounds(seq);
CDLockedReader cdata(_cycler);
if (seq != cdata->_bounds_seq) {
// Time to validate the children again.
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
float suggested_radius;
if (!do_verify_child_bounds(cdata, index, suggested_radius)) {
const Switch &sw = cdata->_switch_vector[index];
ostringstream strm;
strm
<< "Level " << index << " geometry of " << data._node_path
<< " is larger than its switch radius; suggest radius of "
<< suggested_radius << " instead of " << sw.get_in()
<< " (configure verify-lods 0 to ignore this error)";
nassert_raise(strm.str());
}
}
CDWriter cdataw(_cycler, cdata);
cdataw->_bounds_seq = seq;
}
}
////////////////////////////////////////////////////////////////////
// Function: LODNode::get_default_show_color
// Access: Private, Static

View File

@ -79,16 +79,25 @@ PUBLISHED:
void hide_all_switches();
INLINE bool is_any_shown() const;
bool verify_child_bounds() const;
protected:
int compute_child(CullTraverser *trav, CullTraverserData &data);
bool show_switches_cull_callback(CullTraverser *trav, CullTraverserData &data);
virtual PT(BoundingVolume) compute_internal_bounds(int pipeline_stage, Thread *current_thread) const;
INLINE void consider_verify_lods(CullTraverser *trav, CullTraverserData &data);
CPT(TransformState) get_rel_transform(CullTraverser *trav, CullTraverserData &data);
private:
class CData;
void do_show_switch(CData *cdata, int index, const Colorf &color);
void do_hide_switch(CData *cdata, int index);
bool do_verify_child_bounds(const CData *cdata, int index,
float &suggested_radius) const;
void do_auto_verify_lods(CullTraverser *trav, CullTraverserData &data);
static const Colorf &get_default_show_color(int index);
@ -152,6 +161,7 @@ private:
LPoint3f _center;
SwitchVector _switch_vector;
size_t _lowest, _highest;
UpdateSeq _bounds_seq;
bool _got_force_switch;
int _force_switch;
@ -161,6 +171,7 @@ private:
PipelineCycler<CData> _cycler;
typedef CycleDataReader<CData> CDReader;
typedef CycleDataWriter<CData> CDWriter;
typedef CycleDataLockedReader<CData> CDLockedReader;
typedef CycleDataStageReader<CData> CDStageReader;
typedef CycleDataStageWriter<CData> CDStageWriter;

View File

@ -1821,6 +1821,41 @@ get_bounds(Thread *current_thread) const {
return cdata->_external_bounds;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::get_bounds
// Access: Published
// Description: This flavor of get_bounds() return the external
// bounding volume, and also fills in seq with the
// bounding volume's current sequence number. When this
// sequence number changes, it indicates that the
// bounding volume might have changed, e.g. because some
// nested child's bounding volume has changed.
//
// Although this might occasionally increment without
// changing the bounding volume, the bounding volume
// will never change without incrementing this counter,
// so as long as this counter remains unchanged you can
// be confident the bounding volume is also unchanged.
////////////////////////////////////////////////////////////////////
CPT(BoundingVolume) PandaNode::
get_bounds(UpdateSeq &seq, Thread *current_thread) const {
int pipeline_stage = current_thread->get_pipeline_stage();
CDLockedStageReader cdata(_cycler, pipeline_stage, current_thread);
if (cdata->_last_update != cdata->_next_update) {
// The cache is stale; it needs to be rebuilt.
CPT(BoundingVolume) result;
{
CDStageWriter cdataw =
((PandaNode *)this)->update_bounds(pipeline_stage, cdata);
result = cdataw->_external_bounds;
seq = cdataw->_last_update;
}
return result;
}
seq = cdata->_last_update;
return cdata->_external_bounds;
}
////////////////////////////////////////////////////////////////////
// Function: PandaNode::mark_bounds_stale
// Access: Published
@ -3079,6 +3114,7 @@ update_bounds(int pipeline_stage, PandaNode::CDLockedStageReader &cdata) {
drawmask_cat.debug(false)
<< "} " << *this << "::update_bounds();\n";
}
return cdataw;
}

View File

@ -240,6 +240,7 @@ PUBLISHED:
void set_bound(const BoundingVolume *volume);
INLINE void clear_bounds();
CPT(BoundingVolume) get_bounds(Thread *current_thread = Thread::get_current_thread()) const;
CPT(BoundingVolume) get_bounds(UpdateSeq &seq, Thread *current_thread = Thread::get_current_thread()) const;
INLINE CPT(BoundingVolume) get_internal_bounds() const;
void mark_bounds_stale(Thread *current_thread = Thread::get_current_thread()) const;