1088 lines
39 KiB
C++
1088 lines
39 KiB
C++
// Filename: lodNode.cxx
|
|
// Created by: drose (06Mar02)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// 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 "lodNode.h"
|
|
#include "cullTraverserData.h"
|
|
#include "cullTraverser.h"
|
|
#include "config_pgraph.h"
|
|
#include "geomVertexData.h"
|
|
#include "geomVertexWriter.h"
|
|
#include "geomVertexFormat.h"
|
|
#include "geomTristrips.h"
|
|
#include "mathNumbers.h"
|
|
#include "geom.h"
|
|
#include "geomNode.h"
|
|
#include "transformState.h"
|
|
#include "material.h"
|
|
#include "materialAttrib.h"
|
|
#include "materialPool.h"
|
|
#include "renderState.h"
|
|
#include "cullFaceAttrib.h"
|
|
#include "textureAttrib.h"
|
|
#include "boundingSphere.h"
|
|
#include "geometricBoundingVolume.h"
|
|
#include "look_at.h"
|
|
#include "nodePath.h"
|
|
#include "shaderAttrib.h"
|
|
#include "colorAttrib.h"
|
|
|
|
TypeHandle LODNode::_type_handle;
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::make_copy
|
|
// Access: Public, Virtual
|
|
// Description: Returns a newly-allocated Node that is a shallow copy
|
|
// of this one. It will be a different Node pointer,
|
|
// but its internal data may or may not be shared with
|
|
// that of the original Node.
|
|
////////////////////////////////////////////////////////////////////
|
|
PandaNode *LODNode::
|
|
make_copy() const {
|
|
return new LODNode(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::safe_to_combine
|
|
// Access: Public, Virtual
|
|
// Description: Returns true if it is generally safe to combine this
|
|
// particular kind of PandaNode with other kinds of
|
|
// PandaNodes of compatible type, adding children or
|
|
// whatever. For instance, an LODNode should not be
|
|
// combined with any other PandaNode, because its set of
|
|
// children is meaningful.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool LODNode::
|
|
safe_to_combine() const {
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::safe_to_combine_children
|
|
// Access: Public, Virtual
|
|
// Description: Returns true if it is generally safe to combine the
|
|
// children of this PandaNode with each other. For
|
|
// instance, an LODNode's children should not be
|
|
// combined with each other, because the set of children
|
|
// is meaningful.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool LODNode::
|
|
safe_to_combine_children() const {
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::xform
|
|
// Access: Public, Virtual
|
|
// Description: Transforms the contents of this PandaNode by the
|
|
// indicated matrix, if it means anything to do so. For
|
|
// most kinds of PandaNodes, this does nothing.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
xform(const LMatrix4f &mat) {
|
|
CDWriter cdata(_cycler);
|
|
|
|
cdata->_center = cdata->_center * mat;
|
|
|
|
// We'll take just the length of the y axis as the matrix's scale.
|
|
LVector3f y;
|
|
mat.get_row3(y, 1);
|
|
float factor = y.length();
|
|
|
|
SwitchVector::iterator si;
|
|
for (si = cdata->_switch_vector.begin();
|
|
si != cdata->_switch_vector.end();
|
|
++si) {
|
|
(*si).rescale(factor);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::cull_callback
|
|
// Access: Public, Virtual
|
|
// Description: This function will be called during the cull
|
|
// traversal to perform any additional operations that
|
|
// should be performed at cull time. This may include
|
|
// additional manipulation of render state or additional
|
|
// visible/invisible decisions, or any other arbitrary
|
|
// operation.
|
|
//
|
|
// Note that this function will *not* be called unless
|
|
// set_cull_callback() is called in the constructor of
|
|
// the derived class. It is necessary to call
|
|
// set_cull_callback() to indicated that we require
|
|
// cull_callback() to be called.
|
|
//
|
|
// By the time this function is called, the node has
|
|
// already passed the bounding-volume test for the
|
|
// viewing frustum, and the node's transform and state
|
|
// have already been applied to the indicated
|
|
// CullTraverserData object.
|
|
//
|
|
// The return value is true if this node should be
|
|
// visible, or false if it should be culled.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool LODNode::
|
|
cull_callback(CullTraverser *trav, CullTraverserData &data) {
|
|
if (is_any_shown()) {
|
|
return show_switches_cull_callback(trav, 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
|
|
// the traversal from here.
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::output
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
output(ostream &out) const {
|
|
PandaNode::output(out);
|
|
CDReader cdata(_cycler);
|
|
out << " center(" << cdata->_center << ") ";
|
|
if (cdata->_switch_vector.empty()) {
|
|
out << "no switches.";
|
|
} else {
|
|
SwitchVector::const_iterator si;
|
|
si = cdata->_switch_vector.begin();
|
|
out << "(" << (*si).get_in() << "/" << (*si).get_out() << ")";
|
|
++si;
|
|
while (si != cdata->_switch_vector.end()) {
|
|
out << " (" << (*si).get_in() << "/" << (*si).get_out() << ")";
|
|
++si;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::is_lod_node
|
|
// Access: Published, Virtual
|
|
// Description: A simple downcast check. Returns true if this kind
|
|
// of node happens to inherit from LODNode, false
|
|
// otherwise.
|
|
//
|
|
// This is provided as a a faster alternative to calling
|
|
// is_of_type(LODNode::get_class_type()).
|
|
////////////////////////////////////////////////////////////////////
|
|
bool LODNode::
|
|
is_lod_node() const {
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::show_switch
|
|
// Access: Published
|
|
// Description: This is provided as a debugging aid. show_switch()
|
|
// will put the LODNode into a special mode where rather
|
|
// than computing and drawing the appropriate level of
|
|
// the LOD, a ring is drawn around the LODNode center
|
|
// indicating the switch distances from the camera for
|
|
// the indicated level, and the geometry of the
|
|
// indicated level is drawn in wireframe.
|
|
//
|
|
// Multiple different levels can be visualized this way
|
|
// at once. Call hide_switch() or hide_all_switches() to
|
|
// undo this mode and restore the LODNode to its normal
|
|
// behavior.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
show_switch(int index) {
|
|
CDWriter cdata(_cycler);
|
|
do_show_switch(cdata, index, get_default_show_color(index));
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::show_switch
|
|
// Access: Published
|
|
// Description: This is provided as a debugging aid. show_switch()
|
|
// will put the LODNode into a special mode where rather
|
|
// than computing and drawing the appropriate level of
|
|
// the LOD, a ring is drawn around the LODNode center
|
|
// indicating the switch distances from the camera for
|
|
// the indicated level, and the geometry of the
|
|
// indicated level is drawn in wireframe.
|
|
//
|
|
// Multiple different levels can be visualized this way
|
|
// at once. Call hide_switch() or hide_all_switches() to
|
|
// undo this mode and restore the LODNode to its normal
|
|
// behavior.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
show_switch(int index, const Colorf &color) {
|
|
CDWriter cdata(_cycler);
|
|
do_show_switch(cdata, index, color);
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::hide_switch
|
|
// Access: Published
|
|
// Description: Disables a previous call to show_switch().
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
hide_switch(int index) {
|
|
CDWriter cdata(_cycler);
|
|
do_hide_switch(cdata, index);
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::show_all_switches
|
|
// Access: Published
|
|
// Description: Shows all levels in their default colors.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
show_all_switches() {
|
|
CDWriter cdata(_cycler);
|
|
for (int i = 0; i < (int)cdata->_switch_vector.size(); ++i) {
|
|
do_show_switch(cdata, i, get_default_show_color(i));
|
|
}
|
|
mark_internal_bounds_stale();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::hide_all_switches
|
|
// Access: Published
|
|
// Description: Hides all levels, restoring the LODNode to normal
|
|
// operation.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
hide_all_switches() {
|
|
CDWriter cdata(_cycler);
|
|
for (int i = 0; i < (int)cdata->_switch_vector.size(); ++i) {
|
|
do_hide_switch(cdata, i);
|
|
}
|
|
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
|
|
// Description: Determines which child should be visible according to
|
|
// the current camera position. If a child is visible,
|
|
// returns its index number; otherwise, returns -1.
|
|
////////////////////////////////////////////////////////////////////
|
|
int LODNode::
|
|
compute_child(CullTraverser *trav, CullTraverserData &data) {
|
|
if (data.get_net_transform(trav)->is_singular()) {
|
|
// If we're under a singular transform, we can't compute the LOD;
|
|
// select none of them instead.
|
|
return -1;
|
|
}
|
|
|
|
CDReader cdata(_cycler);
|
|
|
|
if (cdata->_got_force_switch) {
|
|
return cdata->_force_switch;
|
|
}
|
|
|
|
CPT(TransformState) rel_transform = get_rel_transform(trav, data);
|
|
LPoint3f center = cdata->_center * rel_transform->get_mat();
|
|
float dist2 = center.dot(center);
|
|
|
|
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()
|
|
<< data._node_path << " at distance " << sqrt(dist2)
|
|
<< ", selected child " << index << "\n";
|
|
}
|
|
|
|
return index;
|
|
}
|
|
}
|
|
|
|
if (pgraph_cat.is_debug()) {
|
|
pgraph_cat.debug()
|
|
<< data._node_path << " at distance " << sqrt(dist2)
|
|
<< ", no children in range.\n";
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::show_switches_cull_callback
|
|
// Access: Protected
|
|
// Description: A special version of cull_callback() that is to be
|
|
// invoked when the LODNode is in show_switch() mode.
|
|
// This just draws the rings and the wireframe geometry
|
|
// for the selected switches.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool LODNode::
|
|
show_switches_cull_callback(CullTraverser *trav, CullTraverserData &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);
|
|
|
|
// Now orient the disk(s) in camera space such that their origin is
|
|
// at center, and the (0, 0, 0) point in camera space is on the disk.
|
|
LMatrix4f mat;
|
|
look_at(mat, -center, LVector3f(0.0f, 0.0f, 1.0f));
|
|
mat.set_row(3, center);
|
|
CPT(TransformState) viz_transform =
|
|
rel_transform->invert_compose(TransformState::make_mat(mat));
|
|
|
|
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
|
|
const Switch &sw = cdata->_switch_vector[index];
|
|
if (sw.is_shown()) {
|
|
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 in the
|
|
// funny wireframe mode.
|
|
if (index < get_num_children()) {
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Now return false indicating that we have already taken care of
|
|
// the traversal from here.
|
|
return false;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::compute_internal_bounds
|
|
// Access: Protected, Virtual
|
|
// Description: Returns a newly-allocated BoundingVolume that
|
|
// represents the internal contents of the node. Should
|
|
// be overridden by PandaNode classes that contain
|
|
// something internally.
|
|
////////////////////////////////////////////////////////////////////
|
|
PT(BoundingVolume) LODNode::
|
|
compute_internal_bounds(int pipeline_stage, Thread *current_thread) const {
|
|
// First, get ourselves a fresh, empty bounding volume.
|
|
PT(BoundingVolume) bound = PandaNode::compute_internal_bounds(pipeline_stage, current_thread);
|
|
nassertr(bound != (BoundingVolume *)NULL, bound);
|
|
|
|
// If we have any visible rings, those count in the bounding volume.
|
|
if (is_any_shown()) {
|
|
// Now actually compute the bounding volume by putting it around all
|
|
// of our geoms' bounding volumes.
|
|
pvector<const BoundingVolume *> child_volumes;
|
|
pvector<PT(BoundingVolume) > pt_volumes;
|
|
|
|
CDStageReader cdata(_cycler, pipeline_stage, current_thread);
|
|
|
|
SwitchVector::const_iterator si;
|
|
for (si = cdata->_switch_vector.begin();
|
|
si != cdata->_switch_vector.end();
|
|
++si) {
|
|
const Switch &sw = (*si);
|
|
if (sw.is_shown()) {
|
|
PT(BoundingVolume) sphere = new BoundingSphere(cdata->_center, sw.get_in());
|
|
child_volumes.push_back(sphere);
|
|
pt_volumes.push_back(sphere);
|
|
}
|
|
}
|
|
|
|
const BoundingVolume **child_begin = &child_volumes[0];
|
|
const BoundingVolume **child_end = child_begin + child_volumes.size();
|
|
|
|
bound->around(child_begin, child_end);
|
|
}
|
|
|
|
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
|
|
// Description: The private implementation of show_switch().
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
do_show_switch(LODNode::CData *cdata, int index, const Colorf &color) {
|
|
nassertv(index >= 0 && index < (int)cdata->_switch_vector.size());
|
|
|
|
if (!cdata->_switch_vector[index].is_shown()) {
|
|
++cdata->_num_shown;
|
|
}
|
|
cdata->_switch_vector[index].show(color);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::do_hide_switch
|
|
// Access: Private
|
|
// Description: The private implementation of hide_switch().
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
do_hide_switch(LODNode::CData *cdata, int index) {
|
|
nassertv(index >= 0 && index < (int)cdata->_switch_vector.size());
|
|
|
|
if (cdata->_switch_vector[index].is_shown()) {
|
|
--cdata->_num_shown;
|
|
}
|
|
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()) {
|
|
const Switch &sw = cdata->_switch_vector[index];
|
|
PandaNode *child = get_child(index);
|
|
if (child != (PandaNode *)NULL) {
|
|
UpdateSeq seq;
|
|
CPT(BoundingVolume) bv = child->get_bounds(seq);
|
|
|
|
if (seq == sw._bounds_seq) {
|
|
// We previously verified this child, and it hasn't changed
|
|
// since then.
|
|
return sw._verify_ok;
|
|
}
|
|
|
|
((Switch &)sw)._bounds_seq = seq;
|
|
((Switch &)sw)._verify_ok = true;
|
|
|
|
if (bv->is_empty()) {
|
|
// This child has no geometry, so no one cares anyway.
|
|
return true;
|
|
}
|
|
if (bv->is_infinite()) {
|
|
// To be strict, we ought to look closer if the child has an
|
|
// infinite bounding volume, but in practice this is probably
|
|
// just a special case (e.g. the child contains the camera)
|
|
// that we don't really want to check.
|
|
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.
|
|
nassertr(!gbv->is_infinite(), false);
|
|
sphere.extend_by(gbv);
|
|
suggested_radius = sphere.get_radius();
|
|
((Switch &)sw)._verify_ok = false;
|
|
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) == 0) {
|
|
// No good.
|
|
if (gbv->is_infinite()) {
|
|
sphere.extend_by(&box_sphere);
|
|
} else {
|
|
sphere.extend_by(gbv);
|
|
}
|
|
suggested_radius = sphere.get_radius();
|
|
((Switch &)sw)._verify_ok = false;
|
|
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 (cdata->_got_force_switch) {
|
|
// If we're forcing a particular switch, don't verify the LOD
|
|
// sizes, since they don't really apply anymore anyway. Assume
|
|
// the user knows what he's doing.
|
|
return;
|
|
}
|
|
|
|
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
|
|
// Description: Returns a default color appropriate for showing the
|
|
// indicated level.
|
|
////////////////////////////////////////////////////////////////////
|
|
const Colorf &LODNode::
|
|
get_default_show_color(int index) {
|
|
static Colorf default_colors[] = {
|
|
Colorf(1.0f, 0.0f, 0.0f, 0.7f),
|
|
Colorf(0.0f, 1.0f, 0.0f, 0.7f),
|
|
Colorf(0.0f, 0.0f, 1.0f, 0.7f),
|
|
Colorf(0.0f, 1.0f, 1.0f, 0.7f),
|
|
Colorf(1.0f, 0.0f, 1.0f, 0.7f),
|
|
Colorf(1.0f, 1.0f, 0.0f, 0.7f),
|
|
};
|
|
static const int num_default_colors = sizeof(default_colors) / sizeof(Colorf);
|
|
|
|
return default_colors[index % num_default_colors];
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::register_with_read_factory
|
|
// Access: Public, Static
|
|
// Description: Tells the BamReader how to create objects of type
|
|
// LODNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
register_with_read_factory() {
|
|
BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
write_datagram(BamWriter *manager, Datagram &dg) {
|
|
PandaNode::write_datagram(manager, dg);
|
|
manager->write_cdata(dg, _cycler);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::make_from_bam
|
|
// Access: Protected, Static
|
|
// Description: This function is called by the BamReader's factory
|
|
// when a new object of type LODNode is encountered
|
|
// in the Bam file. It should create the LODNode
|
|
// and extract its information from the file.
|
|
////////////////////////////////////////////////////////////////////
|
|
TypedWritable *LODNode::
|
|
make_from_bam(const FactoryParams ¶ms) {
|
|
LODNode *node = new LODNode("");
|
|
DatagramIterator scan;
|
|
BamReader *manager;
|
|
|
|
parse_params(params, scan, manager);
|
|
node->fillin(scan, manager);
|
|
|
|
return node;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::fillin
|
|
// Access: Protected
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new LODNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
PandaNode::fillin(scan, manager);
|
|
manager->read_cdata(scan, _cycler);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::CData::make_copy
|
|
// Access: Public, Virtual
|
|
// Description:
|
|
////////////////////////////////////////////////////////////////////
|
|
CycleData *LODNode::CData::
|
|
make_copy() const {
|
|
return new CData(*this);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::CData::check_limits
|
|
// Access: Public
|
|
// Description: Ensures that the _lowest and _highest members are set
|
|
// appropriately after a change to the set of switches.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::CData::
|
|
check_limits() {
|
|
_lowest = 0;
|
|
_highest = 0;
|
|
for (size_t i = 1; i < _switch_vector.size(); ++i) {
|
|
if (_switch_vector[i].get_out() > _switch_vector[_lowest].get_out()) {
|
|
_lowest = i;
|
|
}
|
|
if (_switch_vector[i].get_in() < _switch_vector[_highest].get_in()) {
|
|
_highest = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::CData::write_datagram
|
|
// Access: Public, Virtual
|
|
// Description: Writes the contents of this object to the datagram
|
|
// for shipping out to a Bam file.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::CData::
|
|
write_datagram(BamWriter *manager, Datagram &dg) const {
|
|
_center.write_datagram(dg);
|
|
|
|
dg.add_uint16(_switch_vector.size());
|
|
|
|
SwitchVector::const_iterator si;
|
|
for (si = _switch_vector.begin();
|
|
si != _switch_vector.end();
|
|
++si) {
|
|
(*si).write_datagram(dg);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::CData::fillin
|
|
// Access: Public, Virtual
|
|
// Description: This internal function is called by make_from_bam to
|
|
// read in all of the relevant data from the BamFile for
|
|
// the new LODNode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::CData::
|
|
fillin(DatagramIterator &scan, BamReader *manager) {
|
|
_center.read_datagram(scan);
|
|
|
|
_switch_vector.clear();
|
|
|
|
int num_switches = scan.get_uint16();
|
|
_switch_vector.reserve(num_switches);
|
|
for (int i = 0; i < num_switches; i++) {
|
|
Switch sw(0, 0);
|
|
sw.read_datagram(scan);
|
|
|
|
_switch_vector.push_back(sw);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::Switch::compute_ring_viz
|
|
// Access: Private
|
|
// Description: Computes a Geom suitable for rendering the ring
|
|
// associated with this switch.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::Switch::
|
|
compute_ring_viz() {
|
|
// We render the ring as a series of concentric ring-shaped triangle
|
|
// strips, each of which has num_slices quads.
|
|
static const int num_slices = 50;
|
|
static const int num_rings = 1;
|
|
|
|
// There are also two more triangle strips, one for the outer edge,
|
|
// and one for the inner edge.
|
|
static const float edge_ratio = 0.1; // ratio of edge height to diameter.
|
|
|
|
const GeomVertexFormat *format = GeomVertexFormat::get_v3n3cp();
|
|
PT(GeomVertexData) vdata = new GeomVertexData("LOD_ring", format, Geom::UH_static);
|
|
|
|
// Fill up the vertex table with all of the vertices.
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter normal(vdata, InternalName::get_normal());
|
|
GeomVertexWriter color(vdata, InternalName::get_color());
|
|
|
|
// First, the vertices for the flat ring.
|
|
int ri, si;
|
|
for (ri = 0; ri <= num_rings; ++ri) {
|
|
// r is in the range [0.0, 1.0].
|
|
float r = (float)ri / (float)num_rings;
|
|
|
|
// d is in the range [_out, _in].
|
|
float d = r * (_in - _out) + _out;
|
|
|
|
for (si = 0; si < num_slices; ++si) {
|
|
// s is in the range [0.0, 1.0).
|
|
float s = (float)si / (float)num_slices;
|
|
|
|
// t is in the range [0.0, 2pi).
|
|
float t = MathNumbers::pi_f * 2.0f * s;
|
|
|
|
float x = cosf(t);
|
|
float y = sinf(t);
|
|
vertex.add_data3f(x * d, y * d, 0.0f);
|
|
normal.add_data3f(0.0f, 0.0f, 1.0f);
|
|
color.add_data4f(_show_color);
|
|
}
|
|
}
|
|
|
|
// Next, the vertices for the inner and outer edges.
|
|
for (ri = 0; ri <= 1; ++ri) {
|
|
float r = (float)ri;
|
|
float d = r * (_in - _out) + _out;
|
|
|
|
for (si = 0; si < num_slices; ++si) {
|
|
float s = (float)si / (float)num_slices;
|
|
float t = MathNumbers::pi_f * 2.0f * s;
|
|
|
|
float x = cosf(t);
|
|
float y = sinf(t);
|
|
|
|
vertex.add_data3f(x * d, y * d, 0.5f * edge_ratio * d);
|
|
normal.add_data3f(x, y, 0.0f);
|
|
color.add_data4f(_show_color);
|
|
}
|
|
|
|
for (si = 0; si < num_slices; ++si) {
|
|
float s = (float)si / (float)num_slices;
|
|
float t = MathNumbers::pi_f * 2.0f * s;
|
|
|
|
float x = cosf(t);
|
|
float y = sinf(t);
|
|
|
|
vertex.add_data3f(x * d, y * d, -0.5f * edge_ratio * d);
|
|
normal.add_data3f(x, y, 0.0f);
|
|
color.add_data4f(_show_color);
|
|
}
|
|
}
|
|
|
|
// Now create the triangle strips. One tristrip for each ring.
|
|
PT(GeomTristrips) strips = new GeomTristrips(Geom::UH_static);
|
|
for (ri = 0; ri < num_rings; ++ri) {
|
|
for (si = 0; si < num_slices; ++si) {
|
|
strips->add_vertex(ri * num_slices + si);
|
|
strips->add_vertex((ri + 1) * num_slices + si);
|
|
}
|
|
strips->add_vertex(ri * num_slices);
|
|
strips->add_vertex((ri + 1) * num_slices);
|
|
strips->close_primitive();
|
|
}
|
|
|
|
// And then one triangle strip for each of the inner and outer
|
|
// edges.
|
|
for (ri = 0; ri <= 1; ++ri) {
|
|
for (si = 0; si < num_slices; ++si) {
|
|
strips->add_vertex((num_rings + 1 + ri * 2) * num_slices + si);
|
|
strips->add_vertex((num_rings + 1 + ri * 2 + 1) * num_slices + si);
|
|
}
|
|
strips->add_vertex((num_rings + 1 + ri * 2) * num_slices);
|
|
strips->add_vertex((num_rings + 1 + ri * 2 + 1) * num_slices);
|
|
strips->close_primitive();
|
|
}
|
|
|
|
PT(Geom) ring_geom = new Geom(vdata);
|
|
ring_geom->add_primitive(strips);
|
|
|
|
PT(GeomNode) geom_node = new GeomNode("ring");
|
|
geom_node->add_geom(ring_geom);
|
|
|
|
// Get a material for two-sided lighting.
|
|
PT(Material) material = new Material();
|
|
material->set_twoside(true);
|
|
material = MaterialPool::get_material(material);
|
|
|
|
CPT(RenderState) viz_state =
|
|
RenderState::make(CullFaceAttrib::make(CullFaceAttrib::M_cull_none),
|
|
TextureAttrib::make_off(),
|
|
ShaderAttrib::make_off(),
|
|
MaterialAttrib::make(material),
|
|
RenderState::get_max_priority());
|
|
if (_show_color[3] != 1.0f) {
|
|
viz_state = viz_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha),
|
|
RenderState::get_max_priority());
|
|
}
|
|
|
|
geom_node->set_state(viz_state);
|
|
|
|
_ring_viz = geom_node.p();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::Switch::compute_spindle_viz
|
|
// Access: Private
|
|
// Description: Computes a Geom suitable for rendering the LODNode
|
|
// spindle in the color of this switch.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::Switch::
|
|
compute_spindle_viz() {
|
|
// We render the spindle as a cylinder, which consists of num_rings
|
|
// rings stacked vertically, each of which is a triangle strip of
|
|
// num_slices quads. The scale is -10 .. 10 vertically, with a radius
|
|
// of 1.0.
|
|
static const int num_slices = 10;
|
|
static const int num_rings = 10;
|
|
|
|
const GeomVertexFormat *format = GeomVertexFormat::get_v3n3cp();
|
|
PT(GeomVertexData) vdata = new GeomVertexData("LOD_spindle", format, Geom::UH_static);
|
|
|
|
// Fill up the vertex table with all of the vertices.
|
|
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
|
|
GeomVertexWriter normal(vdata, InternalName::get_normal());
|
|
GeomVertexWriter color(vdata, InternalName::get_color());
|
|
|
|
int ri, si;
|
|
for (ri = 0; ri <= num_rings; ++ri) {
|
|
// r is in the range [0.0, 1.0].
|
|
float r = (float)ri / (float)num_rings;
|
|
|
|
// z is in the range [100.0, -100.0]
|
|
float z = 100.0f - r * 200.0f;
|
|
|
|
for (si = 0; si < num_slices; ++si) {
|
|
// s is in the range [0.0, 1.0).
|
|
float s = (float)si / (float)num_slices;
|
|
|
|
// t is in the range [0.0, 2pi).
|
|
float t = MathNumbers::pi_f * 2.0f * s;
|
|
|
|
float x = cosf(t);
|
|
float y = sinf(t);
|
|
vertex.add_data3f(x, y, z);
|
|
normal.add_data3f(x, y, 0.0f);
|
|
color.add_data4f(_show_color);
|
|
}
|
|
}
|
|
|
|
// Now create the triangle strips. One tristrip for each ring.
|
|
PT(GeomTristrips) strips = new GeomTristrips(Geom::UH_static);
|
|
for (ri = 0; ri < num_rings; ++ri) {
|
|
for (si = 0; si < num_slices; ++si) {
|
|
strips->add_vertex(ri * num_slices + si);
|
|
strips->add_vertex((ri + 1) * num_slices + si);
|
|
}
|
|
strips->add_vertex(ri * num_slices);
|
|
strips->add_vertex((ri + 1) * num_slices);
|
|
strips->close_primitive();
|
|
}
|
|
|
|
PT(Geom) spindle_geom = new Geom(vdata);
|
|
spindle_geom->add_primitive(strips);
|
|
|
|
PT(GeomNode) geom_node = new GeomNode("spindle");
|
|
geom_node->add_geom(spindle_geom);
|
|
|
|
CPT(RenderState) viz_state =
|
|
RenderState::make(CullFaceAttrib::make(CullFaceAttrib::M_cull_clockwise),
|
|
TextureAttrib::make_off(),
|
|
ShaderAttrib::make_off(),
|
|
RenderState::get_max_priority());
|
|
if (_show_color[3] != 1.0f) {
|
|
viz_state = viz_state->add_attrib(TransparencyAttrib::make(TransparencyAttrib::M_alpha),
|
|
RenderState::get_max_priority());
|
|
}
|
|
|
|
geom_node->set_state(viz_state);
|
|
|
|
_spindle_viz = geom_node.p();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: LODNode::Switch::compute_viz_model_state
|
|
// Access: Private
|
|
// Description: Computes a RenderState for rendering the children of
|
|
// this switch in colored wireframe mode.
|
|
////////////////////////////////////////////////////////////////////
|
|
void LODNode::Switch::
|
|
compute_viz_model_state() {
|
|
// The RenderState::make() function only takes up to four attribs at
|
|
// once. Since we need more attribs than that, we have to make up
|
|
// our state in two steps.
|
|
_viz_model_state = RenderState::make(RenderModeAttrib::make(RenderModeAttrib::M_wireframe),
|
|
TextureAttrib::make_off(),
|
|
ShaderAttrib::make_off(),
|
|
ColorAttrib::make_flat(_show_color),
|
|
RenderState::get_max_priority());
|
|
CPT(RenderState) st2 = RenderState::make(TransparencyAttrib::make(TransparencyAttrib::M_none),
|
|
RenderState::get_max_priority());
|
|
_viz_model_state = _viz_model_state->compose(st2);
|
|
}
|