open_toontown_panda3d/panda/src/pgraphnodes/lodNode.cxx

1010 lines
31 KiB
C++

/**
* 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."
*
* @file lodNode.cxx
* @author drose
* @date 2002-03-06
*/
#include "lodNode.h"
#include "fadeLodNode.h"
#include "cullTraverserData.h"
#include "cullTraverser.h"
#include "config_pgraphnodes.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"
#include "clipPlaneAttrib.h"
TypeHandle LODNode::_type_handle;
/**
* Creates a new LODNode of the type specified by the default-lod-type config
* variable.
*/
PT(LODNode) LODNode::
make_default_lod(const string &name) {
switch (default_lod_type.get_value()) {
case LNT_pop:
return new LODNode(name);
case LNT_fade:
return new FadeLODNode(name);
default:
pgraph_cat.error()
<< "Invalid LODNodeType value: " << (int)default_lod_type << "\n";
return new LODNode(name);
}
}
/**
* 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);
}
/**
* 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;
}
/**
* 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;
}
/**
* 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 LMatrix4 &mat) {
CDWriter cdata(_cycler);
cdata->_center = cdata->_center * mat;
// We'll take just the length of the y axis as the matrix's scale.
LVector3 y;
mat.get_row3(y, 1);
PN_stdfloat factor = y.length();
SwitchVector::iterator si;
for (si = cdata->_switch_vector.begin();
si != cdata->_switch_vector.end();
++si) {
(*si).rescale(factor);
}
}
/**
* 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);
LPoint3 center = cdata->_center * rel_transform->get_mat();
PN_stdfloat 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 * cdata->_lod_scale
* trav->get_scene()->get_camera_node()->get_lod_scale());
}
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;
}
/**
*
*/
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;
}
}
}
/**
* 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;
}
/**
* 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();
}
/**
* 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 LColor &color) {
CDWriter cdata(_cycler);
do_show_switch(cdata, index, color);
mark_internal_bounds_stale();
}
/**
* 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();
}
/**
* 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();
}
/**
* 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();
}
/**
* 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) {
PN_stdfloat 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;
}
/**
* 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);
LPoint3 center = cdata->_center * rel_transform->get_mat();
PN_stdfloat dist2 = center.dot(center);
for (int index = 0; index < (int)cdata->_switch_vector.size(); ++index) {
if (cdata->_switch_vector[index].in_range_2(dist2 * cdata->_lod_scale
* trav->get_scene()->get_camera_node()->get_lod_scale())) {
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
<< data.get_node_path() << " at distance " << sqrt(dist2)
<< ", selected child " << index << "\n";
}
return index;
}
}
if (pgraph_cat.is_debug()) {
pgraph_cat.debug()
<< data.get_node_path() << " at distance " << sqrt(dist2)
<< ", no children in range.\n";
}
return -1;
}
/**
* 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);
LPoint3 center = cdata->_center * rel_transform->get_mat();
PN_stdfloat 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.
LMatrix4 mat;
look_at(mat, -center, LVector3(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(viz_transform);
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(viz_transform);
trav->traverse(next_data);
}
}
// Now return false indicating that we have already taken care of the
// traversal from here.
return false;
}
/**
* Returns a newly-allocated BoundingVolume that represents the internal
* contents of the node. Should be overridden by PandaNode classes that
* contain something internally.
*/
void LODNode::
compute_internal_bounds(CPT(BoundingVolume) &internal_bounds,
int &internal_vertices,
int pipeline_stage,
Thread *current_thread) const {
// First, get ourselves a fresh, empty bounding volume.
PT(BoundingVolume) bound = new BoundingSphere;
// 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);
}
internal_bounds = bound;
internal_vertices = 0;
}
/**
* 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;
}
/**
* The private implementation of show_switch().
*/
void LODNode::
do_show_switch(LODNode::CData *cdata, int index, const LColor &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);
}
/**
* 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();
}
/**
* 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,
PN_stdfloat &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.
LPoint3 min_point(0.0f, 0.0f, 0.0f);
LPoint3 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.
LPoint3 box_center = (min_point + max_point) / 2.0f;
PN_stdfloat 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;
}
/**
* 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) {
PN_stdfloat 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.get_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;
}
}
/**
* Returns a default color appropriate for showing the indicated level.
*/
const LColor &LODNode::
get_default_show_color(int index) {
static LColor default_colors[] = {
LColor(1.0f, 0.0f, 0.0f, 0.7f),
LColor(0.0f, 1.0f, 0.0f, 0.7f),
LColor(0.0f, 0.0f, 1.0f, 0.7f),
LColor(0.0f, 1.0f, 1.0f, 0.7f),
LColor(1.0f, 0.0f, 1.0f, 0.7f),
LColor(1.0f, 1.0f, 0.0f, 0.7f),
};
static const int num_default_colors = sizeof(default_colors) / sizeof(LColor);
return default_colors[index % num_default_colors];
}
/**
* 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);
}
/**
* 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);
}
/**
* 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 &params) {
LODNode *node = new LODNode("");
DatagramIterator scan;
BamReader *manager;
parse_params(params, scan, manager);
node->fillin(scan, manager);
return node;
}
/**
* 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);
}
/**
*
*/
CycleData *LODNode::CData::
make_copy() const {
return new CData(*this);
}
/**
* 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;
}
}
}
/**
* 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);
}
}
/**
* 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);
}
_lod_scale = 1;
}
/**
* 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 PN_stdfloat 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].
PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
// d is in the range [_out, _in].
PN_stdfloat d = r * (_in - _out) + _out;
for (si = 0; si < num_slices; ++si) {
// s is in the range [0.0, 1.0).
PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
// t is in the range [0.0, 2pi).
PN_stdfloat t = MathNumbers::pi * 2.0f * s;
PN_stdfloat x = ccos(t);
PN_stdfloat y = csin(t);
vertex.add_data3(x * d, y * d, 0.0f);
normal.add_data3(0.0f, 0.0f, 1.0f);
color.add_data4(_show_color);
}
}
// Next, the vertices for the inner and outer edges.
for (ri = 0; ri <= 1; ++ri) {
PN_stdfloat r = (PN_stdfloat)ri;
PN_stdfloat d = r * (_in - _out) + _out;
for (si = 0; si < num_slices; ++si) {
PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
PN_stdfloat t = MathNumbers::pi * 2.0f * s;
PN_stdfloat x = ccos(t);
PN_stdfloat y = csin(t);
vertex.add_data3(x * d, y * d, 0.5f * edge_ratio * d);
normal.add_data3(x, y, 0.0f);
color.add_data4(_show_color);
}
for (si = 0; si < num_slices; ++si) {
PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
PN_stdfloat t = MathNumbers::pi * 2.0f * s;
PN_stdfloat x = ccos(t);
PN_stdfloat y = csin(t);
vertex.add_data3(x * d, y * d, -0.5f * edge_ratio * d);
normal.add_data3(x, y, 0.0f);
color.add_data4(_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();
}
/**
* 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].
PN_stdfloat r = (PN_stdfloat)ri / (PN_stdfloat)num_rings;
// z is in the range [100.0, -100.0]
PN_stdfloat z = 100.0f - r * 200.0f;
for (si = 0; si < num_slices; ++si) {
// s is in the range [0.0, 1.0).
PN_stdfloat s = (PN_stdfloat)si / (PN_stdfloat)num_slices;
// t is in the range [0.0, 2pi).
PN_stdfloat t = MathNumbers::pi * 2.0f * s;
PN_stdfloat x = ccos(t);
PN_stdfloat y = csin(t);
vertex.add_data3(x, y, z);
normal.add_data3(x, y, 0.0f);
color.add_data4(_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();
}
/**
* 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);
}