open_toontown_panda3d/panda/src/cull/cullBinOcclusionTest.cxx

1058 lines
38 KiB
C++

// Filename: cullBinOcclusionTest.cxx
// Created by: drose (24Mar06)
//
////////////////////////////////////////////////////////////////////
//
// 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 "cullBinOcclusionTest.h"
#include "graphicsStateGuardianBase.h"
#include "geometricBoundingVolume.h"
#include "geomLines.h"
#include "geomTriangles.h"
#include "geomVertexWriter.h"
#include "depthWriteAttrib.h"
#include "depthTestAttrib.h"
#include "colorWriteAttrib.h"
#include "cullableObject.h"
#include "cullHandler.h"
#include "pStatTimer.h"
#include "config_cull.h"
#include "thread.h"
#include <algorithm>
PStatCollector CullBinOcclusionTest::_wait_occlusion_pcollector("Draw:Wait occlusion");
PStatCollector CullBinOcclusionTest::_occlusion_previous_pcollector("Occlusion test:Previously visible");
PStatCollector CullBinOcclusionTest::_occlusion_passed_pcollector("Occlusion test:Visible");
PStatCollector CullBinOcclusionTest::_occlusion_failed_pcollector("Occlusion test:Occluded");
const LPoint3f CullBinOcclusionTest::_corner_points[8] = {
LPoint3f(-1.0f, -1.0f, -1.0f), // 0
LPoint3f(1.0f, -1.0f, -1.0f), // OC_x
LPoint3f(-1.0f, 1.0f, -1.0f), // OC_y
LPoint3f(1.0f, 1.0f, -1.0f), // OC_x | OC_y
LPoint3f(-1.0f, -1.0f, 1.0f), // OC_z
LPoint3f(1.0f, -1.0f, 1.0f), // OC_x | OC_z
LPoint3f(-1.0f, 1.0f, 1.0f), // OC_y | OC_z
LPoint3f(1.0f, 1.0f, 1.0f), // OC_x | OC_y | OC_z
};
PT(Geom) CullBinOcclusionTest::_octree_solid_test;
PT(Geom) CullBinOcclusionTest::_octree_wireframe_viz;
CPT(RenderState) CullBinOcclusionTest::_octree_solid_test_state;
TypeHandle CullBinOcclusionTest::_type_handle;
// This class is used to sort the corner index numbers into order from
// closest to the camera to furthest from the camera.
class SortCornersFrontToBack {
public:
inline bool operator () (int a, int b) {
return _distances[a] < _distances[b];
}
float _distances[8];
};
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::Destructor
// Access: Public, Virtual
// Description:
////////////////////////////////////////////////////////////////////
CullBinOcclusionTest::
~CullBinOcclusionTest() {
ObjectPointers::iterator pi;
for (pi = _object_pointers.begin(); pi != _object_pointers.end(); ++pi) {
delete (*pi);
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::make_bin
// Access: Public, Static
// Description: Factory constructor for passing to the CullBinManager.
////////////////////////////////////////////////////////////////////
CullBin *CullBinOcclusionTest::
make_bin(const string &name, GraphicsStateGuardianBase *gsg,
const PStatCollector &draw_region_pcollector) {
return new CullBinOcclusionTest(name, gsg, draw_region_pcollector);
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::make_next
// Access: Public, Virtual
// Description: Returns a newly-allocated CullBin object that
// contains a copy of just the subset of the data from
// this CullBin object that is worth keeping around
// for next frame.
//
// If a particular CullBin object has no data worth
// preserving till next frame, it is acceptable to
// return NULL (which is the default behavior of this
// method).
////////////////////////////////////////////////////////////////////
PT(CullBin) CullBinOcclusionTest::
make_next() const {
// We use the copy constructor, which creates an empty CullBin
// object, but also copies the _prev_draw pointer into it, so that
// there will be inter-frame continuity.
return new CullBinOcclusionTest(*this);
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::add_object
// Access: Public, Virtual
// Description: Adds a geom, along with its associated state, to
// the bin for rendering.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::
add_object(CullableObject *object, Thread *current_thread) {
// Determine the world-space bounding sphere for the object.
CPT(BoundingVolume) volume = object->_geom->get_bounds();
if (volume->is_empty()) {
delete object;
return;
}
++_num_objects;
PT(BoundingSphere) sphere;
if (volume->is_exact_type(BoundingSphere::get_class_type())) {
sphere = DCAST(BoundingSphere, volume->make_copy());
} else {
const GeometricBoundingVolume *gbv = DCAST(GeometricBoundingVolume, volume);
PT(BoundingSphere) sphere = new BoundingSphere;
sphere->around(&gbv, &gbv + 1);
}
object->_already_drawn = false;
sphere->xform(object->_net_transform->get_mat());
_root.initial_assign(ObjectData(object, sphere));
_object_pointers.push_back(object);
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::finish_cull
// Access: Public
// Description: Called after all the geoms have been added, this
// indicates that the cull process is finished for this
// frame and gives the bins a chance to do any
// post-processing (like sorting) before moving on to
// draw.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::
finish_cull(SceneSetup *scene_setup, Thread *current_thread) {
PStatTimer timer(_cull_this_pcollector, current_thread);
// Now we have a loose list of objects that are to be rendered.
// We'd rather have them in an octree, which has much better
// grouping properties for the purpose of this algorithm.
// For now, we'll just build an octree here at runtime, a new one
// fresh for each frame. Maybe it won't be *too* bad. But later,
// we can optimize this to take advantage of temporal coherence by
// starting from the previous frame's octree.
_root.make_initial_bounds();
_root.group_objects();
// Figure out the best front-to-back order of the corners of each
// octree node, based on the current viewing orientation.
CPT(TransformState) world_transform = scene_setup->get_world_transform();
const LMatrix4f &world_mat = world_transform->get_mat();
// A temporary object to record distances, and manage the sorting.
SortCornersFrontToBack sorter;
for (int i = 0; i < 8; ++i) {
_corners_front_to_back[i] = i;
LPoint3f p = _corner_points[i] * world_mat;
sorter._distances[i] = _gsg->compute_distance_to(p);
}
// Now sort, using the STL sort function.
::sort(&_corners_front_to_back[0], &_corners_front_to_back[8], sorter);
// Finally, use that information to compute the distance of each
// octree node fom the camera plane.
_root.compute_distance(world_mat, *this);
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::draw
// Access: Public, Virtual
// Description: Draws all the geoms in the bin, in the appropriate
// order.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::
draw(Thread *current_thread) {
PStatTimer timer(_draw_this_pcollector, current_thread);
// We'll want to know the near plane distance.
_near_distance = _gsg->get_scene()->get_lens()->get_near();
// First, draw any objects that were visible last frame.
int num_drawn_previous;
{
MutexHolder holder(_prev_draw->_visible_lock);
num_drawn_previous = _root.draw_previous(*this, current_thread);
}
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Drew " << num_drawn_previous << " objects.\n";
}
// Now draw the objects that may or may not remain.
int num_drawn;
num_drawn = _root.draw(*this, current_thread);
if (show_octree) {
_root.draw_wireframe(*this, current_thread);
}
while (!_pending_nodes.empty()) {
PendingNode &pending = _pending_nodes.front();
int num_fragments;
if (!pending._query->is_answer_ready()) {
// The answer isn't ready yet. We have to wait.
PStatTimer timer(_wait_occlusion_pcollector);
num_fragments = pending._query->get_num_fragments();
} else {
// The answer is ready right now. There will be no waiting.
num_fragments = pending._query->get_num_fragments();
}
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "OctreeNode " << *pending._octree_node
<< " shows " << num_fragments << " fragments\n";
}
if (num_fragments != 0) {
// The octree cell is at least partially visible. Draw it, and
// continue recursion.
num_drawn += pending._octree_node->draw(*this, current_thread);
if (show_octree) {
pending._octree_node->draw_wireframe(*this, current_thread);
}
}
_pending_nodes.pop_front();
}
_occlusion_previous_pcollector.add_level_now(num_drawn_previous);
_occlusion_passed_pcollector.add_level_now(num_drawn);
_occlusion_failed_pcollector.add_level_now(_num_objects - (num_drawn_previous + num_drawn));
// Now, store a list of the objects within OctreeNodes that passed
// the occlusion test this frame, so we can ensure that they are
// drawn first next frame.
VisibleGeoms visible_geoms;
_root.record_visible_geoms(visible_geoms);
visible_geoms.sort();
{
MutexHolder holder(_prev_draw->_visible_lock);
_prev_draw->_visible_geoms.swap(visible_geoms);
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullBinOcclusionTest::OctreeNode::
OctreeNode() {
for (int i = 0; i < 8; ++i) {
_corners[i] = (OctreeNode *)NULL;
}
_is_visible = false;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::Constructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullBinOcclusionTest::OctreeNode::
OctreeNode(float mid_x, float mid_y, float mid_z, float half_side) :
_mid(mid_x, mid_y, mid_z),
_half_side(half_side)
{
for (int i = 0; i < 8; ++i) {
_corners[i] = (OctreeNode *)NULL;
}
// OctreeNodes are considered occluded until they pass the occlusion
// test.
_is_visible = false;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::Destructor
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
CullBinOcclusionTest::OctreeNode::
~OctreeNode() {
for (int i = 0; i < 8; ++i) {
if (_corners[i] != (OctreeNode *)NULL) {
delete _corners[i];
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::make_initial_bounds
// Access: Public
// Description: Determines the minmax bounding volume of the root
// OctreeNode, based on the objects it contains.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
make_initial_bounds() {
if (_objects.empty()) {
return;
}
LPoint3f scene_min = _objects[0]._bounds->get_center();
LPoint3f scene_max = _objects[0]._bounds->get_center();
Objects::const_iterator oi;
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
LPoint3f object_min = (*oi)._bounds->get_min();
LPoint3f object_max = (*oi)._bounds->get_max();
scene_min[0] = min(scene_min[0], object_min[0]);
scene_min[1] = min(scene_min[1], object_min[1]);
scene_min[2] = min(scene_min[2], object_min[2]);
scene_max[0] = max(scene_max[0], object_max[0]);
scene_max[1] = max(scene_max[1], object_max[1]);
scene_max[2] = max(scene_max[2], object_max[2]);
}
float side = max(max(scene_max[0] - scene_min[0],
scene_max[1] - scene_min[1]),
scene_max[2] - scene_min[2]);
_mid.set((scene_min[0] + scene_max[0]) * 0.5f,
(scene_min[1] + scene_max[1]) * 0.5f,
(scene_min[2] + scene_max[2]) * 0.5f);
_half_side = side * 0.5f;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::group_objects
// Access: Public
// Description: Recursively groups the objects assigned to this node
// into smaller octree nodes, as appropriate.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
group_objects() {
if ((int)_objects.size() <= max_objects_per_octree_node) {
// No need to do any more subdividing.
return;
}
// Assign all objects to one or more corners.
Objects old_objects;
old_objects.swap(_objects);
Objects::const_iterator oi;
for (oi = old_objects.begin(); oi != old_objects.end(); ++oi) {
const ObjectData &object_data = (*oi);
const LPoint3f &c = object_data._bounds->get_center();
float r = object_data._bounds->get_radius();
if (c[0] + r <= _mid[0]) {
// -X
if (c[1] + r <= _mid[1]) {
// -X, -Y
if (c[2] + r <= _mid[2]) {
// -X, -Y, -Z
assign_to_corner(0, object_data);
} else if (c[2] - r >= _mid[2]) {
// -X, -Y, +Z
assign_to_corner(OC_z, object_data);
} else {
// -X, -Y, 0
reassign(object_data);
}
} else if (c[1] - r >= _mid[1]) {
// -X, +Y
if (c[2] + r <= _mid[2]) {
// -X, +Y, -Z
assign_to_corner(OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// -X, +Y, +Z
assign_to_corner(OC_y | OC_z, object_data);
} else {
// -X, +Y, 0
reassign(object_data);
}
} else {
// -X, 0
reassign(object_data);
}
} else if (c[0] - r >= _mid[0]) {
// +X
if (c[1] + r <= _mid[1]) {
// +X, -Y
if (c[2] + r <= _mid[2]) {
// +X, -Y, -Z
assign_to_corner(OC_x, object_data);
} else if (c[2] - r >= _mid[2]) {
// +X, -Y, +Z
assign_to_corner(OC_x | OC_z, object_data);
} else {
// +X, -Y, 0
reassign(object_data);
}
} else if (c[1] - r >= _mid[1]) {
// +X, +Y
if (c[2] + r <= _mid[2]) {
// +X, +Y, -Z
assign_to_corner(OC_x | OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// +X, +Y, +Z
assign_to_corner(OC_x | OC_y | OC_z, object_data);
} else {
// +X, +Y, 0
reassign(object_data);
}
} else {
// +X, 0
reassign(object_data);
}
} else {
// 0
reassign(object_data);
}
}
for (int i = 0; i < 8; ++i) {
if (_corners[i] != (OctreeNode *)NULL) {
_corners[i]->group_objects();
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::compute_distance
// Access: Public
// Description: Recursively computes the _distance member of each
// octree node, as the linear distance from the camera
// plane to the nearest corner of the octree node.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
compute_distance(const LMatrix4f &world_mat,
CullBinOcclusionTest &bin) {
int index = bin._corners_front_to_back[0];
LPoint3f p = get_corner_point(index) * world_mat;
_distance = bin._gsg->compute_distance_to(p);
for (int i = 0; i < 8; ++i) {
if (_corners[i] != (OctreeNode *)NULL) {
_corners[i]->compute_distance(world_mat, bin);
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::occlusion_test
// Access: Public
// Description: Tests the octree node for visibility by rendering the
// octree cube, invisibly, with a query. Returns the
// occlusion query object representing this test.
////////////////////////////////////////////////////////////////////
PT(OcclusionQueryContext) CullBinOcclusionTest::OctreeNode::
occlusion_test(CullBinOcclusionTest &bin, Thread *current_thread) {
// Draw the bounding volume for visualization. This is
// complicated because we're doing this at such a low level, here
// in the middle of the draw task--we've already completed the
// cull traversal, so we can't just create a CullableObject or do
// anything else that requires a pointer to a CullTraverser.
// Instead, we have to do the relevant code by hand.
CPT(TransformState) net_transform = TransformState::make_pos_hpr_scale
(_mid, LVecBase3f(0.0f, 0.0f, 0.0f),
LVecBase3f(_half_side, _half_side, _half_side));
CPT(TransformState) world_transform = bin._gsg->get_scene()->get_world_transform();
CPT(TransformState) modelview_transform = world_transform->compose(net_transform);
CPT(TransformState) internal_transform = bin._gsg->get_cs_transform()->compose(modelview_transform);
CPT(RenderState) state = get_octree_solid_test_state();
PT(GeomMunger) munger = bin._gsg->get_geom_munger(state, current_thread);
CPT(Geom) viz = get_octree_solid_test();
CPT(GeomVertexData) munged_data = viz->get_vertex_data();
munger->munge_geom(viz, munged_data, current_thread);
bin._gsg->set_state_and_transform(state, internal_transform);
PStatTimer timer(bin._draw_occlusion_pcollector);
bin._gsg->begin_occlusion_query();
viz->draw(bin._gsg, munger, munged_data, current_thread);
return bin._gsg->end_occlusion_query();
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::draw_previous
// Access: Public
// Description: Recursively draws only those objects which are known
// to have been drawn last frame. Returns the number of
// objects drawn.
////////////////////////////////////////////////////////////////////
int CullBinOcclusionTest::OctreeNode::
draw_previous(CullBinOcclusionTest &bin, Thread *current_thread) {
int num_drawn = 0;
if (!_objects.empty()) {
Objects::const_iterator oi;
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
CullableObject *object = (*oi)._object;
if (!object->_already_drawn) {
VisibleGeom vg(object->_geom, object->_net_transform);
if (bin._prev_draw->_visible_geoms.find(vg) != bin._prev_draw->_visible_geoms.end()) {
// This object is visible.
CullHandler::draw(object, bin._gsg, current_thread);
object->_already_drawn = true;
++num_drawn;
}
}
}
}
for (int i = 0; i < 8; ++i) {
// Render the child octree nodes in order from nearest to
// farthest.
int index = bin._corners_front_to_back[i];
if (_corners[index] != (OctreeNode *)NULL) {
num_drawn += _corners[index]->draw_previous(bin, current_thread);
}
}
return num_drawn;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::draw
// Access: Public
// Description: Draws all of the objects in this node, and
// recursively performs occlusion tests on all of the
// nested nodes. Returns the number of objects drawn.
////////////////////////////////////////////////////////////////////
int CullBinOcclusionTest::OctreeNode::
draw(CullBinOcclusionTest &bin, Thread *current_thread) {
// If the node is being drawn, it must have passed the occlusion
// test. Flag it as such.
_is_visible = true;
if (cull_cat.is_spam()) {
cull_cat.spam()
<< "Drawing OctreeNode " << this << "\n";
}
int num_drawn = 0;
if (!_objects.empty()) {
// Now draw the objects within the octree node.
Objects::const_iterator oi;
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
CullableObject *object = (*oi)._object;
if (!object->_already_drawn) {
CullHandler::draw(object, bin._gsg, current_thread);
object->_already_drawn = true;
++num_drawn;
}
}
}
// Now recurse on each child node.
for (int i = 0; i < 8; ++i) {
// Make sure we render the child octree nodes in order from
// nearest to farthest.
int index = bin._corners_front_to_back[i];
if (_corners[index] != (OctreeNode *)NULL) {
if (_corners[index]->_distance < bin._near_distance) {
// If a corner of the cube pokes through the near plane, go
// ahead and render the whole cube without performing an
// occlusion test. The occlusion test would be invalid (since
// some or all of the cube would be clipped), but it's not
// likely that anything will be occluding something so close
// to the camera anyway.
_corners[index]->draw(bin, current_thread);
} else {
// Otherwise, if the entire cube is in front of the near
// plane, perform an occlusion test by rendering out the
// (invisible) octree cube and then see if any pixels make it
// through the depth test.
PendingNode pending;
pending._octree_node = _corners[index];
pending._query = _corners[index]->occlusion_test(bin, current_thread);
// We push it onto the list of nodes that are awaiting
// feedback from the graphics pipe. This way we can go work
// on another octree node while we're waiting for this one.
bin._pending_nodes.push_back(pending);
}
}
}
return num_drawn;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::draw_wireframe
// Access: Public
// Description: Draws a wireframe representation of the octree cube,
// for debugging and visualization purposes.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
draw_wireframe(CullBinOcclusionTest &bin, Thread *current_thread) {
// As above, this is complicated because we're doing this at such a
// low level.
CPT(TransformState) net_transform = TransformState::make_pos_hpr_scale
(_mid, LVecBase3f(0.0f, 0.0f, 0.0f),
LVecBase3f(_half_side, _half_side, _half_side));
CPT(TransformState) world_transform = bin._gsg->get_scene()->get_world_transform();
CPT(TransformState) modelview_transform = world_transform->compose(net_transform);
CPT(TransformState) internal_transform = bin._gsg->get_cs_transform()->compose(modelview_transform);
CPT(RenderState) state = RenderState::make_empty();
PT(GeomMunger) munger = bin._gsg->get_geom_munger(state, current_thread);
CPT(Geom) viz = get_octree_wireframe_viz();
CPT(GeomVertexData) munged_data = viz->get_vertex_data();
munger->munge_geom(viz, munged_data, current_thread);
bin._gsg->set_state_and_transform(state, internal_transform);
viz->draw(bin._gsg, munger, munged_data, current_thread);
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::record_visible_geoms
// Access: Public
// Description: Records any Geoms associated with OctreeNodes that
// passed the occlusion test for next frame, to improve
// temporal coherence.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
record_visible_geoms(CullBinOcclusionTest::VisibleGeoms &visible_geoms) {
if (_is_visible) {
Objects::const_iterator oi;
for (oi = _objects.begin(); oi != _objects.end(); ++oi) {
CullableObject *object = (*oi)._object;
nassertv(object->_already_drawn);
VisibleGeom vg(object->_geom, object->_net_transform);
visible_geoms.push_back(vg);
}
for (int i = 0; i < 8; ++i) {
if (_corners[i] != (OctreeNode *)NULL) {
_corners[i]->record_visible_geoms(visible_geoms);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::output
// Access: Public
// Description:
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
output(ostream &out) const {
out << "OctreeNode " << _mid << ", " << _half_side << ", dist "
<< _distance;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::get_octree_solid_test
// Access: Private, Static
// Description: Returns a Geom that may be used to render the solid
// faces of octree cube, presumably invisibly. This
// returns a cube over the range (-1, -1, -1) - (1, 1,
// 1).
////////////////////////////////////////////////////////////////////
CPT(Geom) CullBinOcclusionTest::
get_octree_solid_test() {
if (_octree_solid_test == (Geom *)NULL) {
CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3();
PT(GeomVertexData) vdata =
new GeomVertexData("octree", format, Geom::UH_static);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
vertex.add_data3f(-1.0f, -1.0f, -1.0f);
vertex.add_data3f(1.0f, -1.0f, -1.0f);
vertex.add_data3f(-1.0f, -1.0f, 1.0f);
vertex.add_data3f(1.0f, -1.0f, 1.0f);
vertex.add_data3f(-1.0f, 1.0f, -1.0f);
vertex.add_data3f(1.0f, 1.0f, -1.0f);
vertex.add_data3f(-1.0f, 1.0f, 1.0f);
vertex.add_data3f(1.0f, 1.0f, 1.0f);
PT(GeomTriangles) tris = new GeomTriangles(Geom::UH_static);
tris->add_vertices(2, 0, 3); tris->close_primitive();
tris->add_vertices(0, 1, 3); tris->close_primitive();
tris->add_vertices(3, 1, 7); tris->close_primitive();
tris->add_vertices(1, 5, 7); tris->close_primitive();
tris->add_vertices(7, 5, 6); tris->close_primitive();
tris->add_vertices(5, 4, 6); tris->close_primitive();
tris->add_vertices(6, 4, 2); tris->close_primitive();
tris->add_vertices(4, 0, 2); tris->close_primitive();
tris->add_vertices(6, 2, 7); tris->close_primitive();
tris->add_vertices(2, 3, 7); tris->close_primitive();
tris->add_vertices(0, 4, 1); tris->close_primitive();
tris->add_vertices(4, 5, 1); tris->close_primitive();
_octree_solid_test = new Geom(vdata);
_octree_solid_test->add_primitive(tris);
}
return _octree_solid_test;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::get_octree_wireframe_viz
// Access: Private, Static
// Description: Returns a Geom that may be used to render an
// OctreeNode in wireframe. This actually draws a
// wireframe cube in the range (-1, -1, -1) - (1, 1, 1).
////////////////////////////////////////////////////////////////////
CPT(Geom) CullBinOcclusionTest::
get_octree_wireframe_viz() {
if (_octree_wireframe_viz == (Geom *)NULL) {
CPT(GeomVertexFormat) format = GeomVertexFormat::get_v3cp();
PT(GeomVertexData) vdata =
new GeomVertexData("octree", format, Geom::UH_static);
GeomVertexWriter vertex(vdata, InternalName::get_vertex());
vertex.add_data3f(-1.0f, -1.0f, -1.0f);
vertex.add_data3f(1.0f, -1.0f, -1.0f);
vertex.add_data3f(-1.0f, -1.0f, 1.0f);
vertex.add_data3f(1.0f, -1.0f, 1.0f);
vertex.add_data3f(-1.0f, 1.0f, -1.0f);
vertex.add_data3f(1.0f, 1.0f, -1.0f);
vertex.add_data3f(-1.0f, 1.0f, 1.0f);
vertex.add_data3f(1.0f, 1.0f, 1.0f);
CPT(GeomVertexData) cvdata = vdata->set_color(Colorf(1.0f, 0.5f, 0.0f, 1.0f));
PT(GeomLines) lines = new GeomLines(Geom::UH_static);
lines->add_vertices(0, 1); lines->close_primitive();
lines->add_vertices(1, 3); lines->close_primitive();
lines->add_vertices(3, 2); lines->close_primitive();
lines->add_vertices(2, 0); lines->close_primitive();
lines->add_vertices(0, 4); lines->close_primitive();
lines->add_vertices(4, 6); lines->close_primitive();
lines->add_vertices(6, 7); lines->close_primitive();
lines->add_vertices(7, 5); lines->close_primitive();
lines->add_vertices(5, 4); lines->close_primitive();
lines->add_vertices(1, 5); lines->close_primitive();
lines->add_vertices(3, 7); lines->close_primitive();
lines->add_vertices(2, 6); lines->close_primitive();
_octree_wireframe_viz = new Geom(cvdata);
_octree_wireframe_viz->add_primitive(lines);
}
return _octree_wireframe_viz;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::get_octree_solid_test_state
// Access: Private, Static
// Description: Returns the RenderState appropriate to rendering the
// octree test invisibly.
////////////////////////////////////////////////////////////////////
CPT(RenderState) CullBinOcclusionTest::
get_octree_solid_test_state() {
if (_octree_solid_test_state == (RenderState *)NULL) {
_octree_solid_test_state = RenderState::make
(DepthWriteAttrib::make(DepthWriteAttrib::M_off),
DepthTestAttrib::make(DepthTestAttrib::M_less),
ColorWriteAttrib::make(ColorWriteAttrib::C_off));
}
return _octree_solid_test_state;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::multi_assign
// Access: Public
// Description: The object intersects a center plane, but is too
// small to justify keeping within this big node.
// Duplicate it into the sub-nodes.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
multi_assign(const CullBinOcclusionTest::ObjectData &object_data) {
const LPoint3f &c = object_data._bounds->get_center();
float r = object_data._bounds->get_radius();
if (c[0] + r <= _mid[0]) {
// -X
if (c[1] + r <= _mid[1]) {
// -X, -Y
if (c[2] + r <= _mid[2]) {
// -X, -Y, -Z
nassertv(false);
} else if (c[2] - r >= _mid[2]) {
// -X, -Y, +Z
nassertv(false);
} else {
// -X, -Y, 0
assign_to_corner(0, object_data);
assign_to_corner(OC_z, object_data);
}
} else if (c[1] - r >= _mid[1]) {
// -X, +Y
if (c[2] + r <= _mid[2]) {
// -X, +Y, -Z
nassertv(false);
} else if (c[2] - r >= _mid[2]) {
// -X, +Y, +Z
nassertv(false);
} else {
// -X, +Y, 0
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_y | OC_z, object_data);
}
} else {
// -X, 0
if (c[2] + r <= _mid[2]) {
// -X, 0, -Z
assign_to_corner(0, object_data);
assign_to_corner(OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// -X, 0, +Z
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_y | OC_z, object_data);
} else {
// -X, 0, 0
assign_to_corner(0, object_data);
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_y | OC_z, object_data);
}
}
} else if (c[0] - r >= _mid[0]) {
// +X
if (c[1] + r <= _mid[1]) {
// +X, -Y
if (c[2] + r <= _mid[2]) {
// +X, -Y, -Z
nassertv(false);
} else if (c[2] - r >= _mid[2]) {
// +X, -Y, +Z
nassertv(false);
} else {
// +X, -Y, 0
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_z, object_data);
}
} else if (c[1] - r >= _mid[1]) {
// +X, +Y
if (c[2] + r <= _mid[2]) {
// +X, +Y, -Z
nassertv(false);
} else if (c[2] - r >= _mid[2]) {
// +X, +Y, +Z
nassertv(false);
} else {
// +X, +Y, 0
assign_to_corner(OC_x | OC_y, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
}
} else {
// +X, 0
if (c[2] + r <= _mid[2]) {
// +X, 0, -Z
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// +X, 0, +Z
assign_to_corner(OC_x | OC_z, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
} else {
// +X, 0, 0
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_z, object_data);
assign_to_corner(OC_x | OC_y, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
}
}
} else {
// 0
if (c[1] + r <= _mid[1]) {
// 0, -Y
if (c[2] + r <= _mid[2]) {
// 0, -Y, -Z
assign_to_corner(0, object_data);
assign_to_corner(OC_x, object_data);
} else if (c[2] - r >= _mid[2]) {
// 0, -Y, +Z
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_x | OC_z, object_data);
} else {
// 0, -Y, 0
assign_to_corner(0, object_data);
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_z, object_data);
}
} else if (c[1] - r >= _mid[1]) {
// 0, +Y
if (c[2] + r <= _mid[2]) {
// 0, +Y, -Z
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_x | OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// 0, +Y, +Z
assign_to_corner(OC_y | OC_z, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
} else {
// 0, +Y, 0
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_y | OC_z, object_data);
assign_to_corner(OC_x | OC_y, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
}
} else {
// 0, 0
if (c[2] + r <= _mid[2]) {
// 0, 0, -Z
assign_to_corner(0, object_data);
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_y, object_data);
} else if (c[2] - r >= _mid[2]) {
// 0, 0, +Z
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_y | OC_z, object_data);
assign_to_corner(OC_x | OC_z, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
} else {
// 0, 0, 0
assign_to_corner(0, object_data);
assign_to_corner(OC_z, object_data);
assign_to_corner(OC_y, object_data);
assign_to_corner(OC_y | OC_z, object_data);
assign_to_corner(OC_x, object_data);
assign_to_corner(OC_x | OC_z, object_data);
assign_to_corner(OC_x | OC_y, object_data);
assign_to_corner(OC_x | OC_y | OC_z, object_data);
}
}
}
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::make_corner
// Access: Private
// Description: Makes a new octree node for the indicated corner.
////////////////////////////////////////////////////////////////////
void CullBinOcclusionTest::OctreeNode::
make_corner(int index) {
nassertv(_corners[index] == NULL);
OctreeNode *node = NULL;
double q = _half_side * 0.5f;
switch (index) {
case 0:
// -X, -Y, -Z
node = new OctreeNode(_mid[0] - q, _mid[1] - q, _mid[2] - q, q);
break;
case OC_x:
// +X, -Y, -Z
node = new OctreeNode(_mid[0] + q, _mid[1] - q, _mid[2] - q, q);
break;
case OC_y:
// -X, +Y, -Z
node = new OctreeNode(_mid[0] - q, _mid[1] + q, _mid[2] - q, q);
break;
case OC_x | OC_y:
// +X, +Y, -Z
node = new OctreeNode(_mid[0] + q, _mid[1] + q, _mid[2] - q, q);
break;
case OC_z:
// -X, -Y, +Z
node = new OctreeNode(_mid[0] - q, _mid[1] - q, _mid[2] + q, q);
break;
case OC_x | OC_z:
// +X, -Y, +Z
node = new OctreeNode(_mid[0] + q, _mid[1] - q, _mid[2] + q, q);
break;
case OC_y | OC_z:
// -X, +Y, +Z
node = new OctreeNode(_mid[0] - q, _mid[1] + q, _mid[2] + q, q);
break;
case OC_x | OC_y | OC_z:
// +X, +Y, +Z
node = new OctreeNode(_mid[0] + q, _mid[1] + q, _mid[2] + q, q);
break;
}
nassertv(node != (OctreeNode *)NULL);
_corners[index] = node;
}
////////////////////////////////////////////////////////////////////
// Function: CullBinOcclusionTest::OctreeNode::get_corner_point
// Access: Private
// Description: Returns the 3-d point, in world space, of the
// indicated corner.
////////////////////////////////////////////////////////////////////
LPoint3f CullBinOcclusionTest::OctreeNode::
get_corner_point(int index) {
switch (index) {
case 0:
// -X, -Y, -Z
return LPoint3f(_mid[0] - _half_side, _mid[1] - _half_side, _mid[2] - _half_side);
case OC_x:
// +X, -Y, -Z
return LPoint3f(_mid[0] + _half_side, _mid[1] - _half_side, _mid[2] - _half_side);
case OC_y:
// -X, +Y, -Z
return LPoint3f(_mid[0] - _half_side, _mid[1] + _half_side, _mid[2] - _half_side);
case OC_x | OC_y:
// +X, +Y, -Z
return LPoint3f(_mid[0] + _half_side, _mid[1] + _half_side, _mid[2] - _half_side);
case OC_z:
// -X, -Y, +Z
return LPoint3f(_mid[0] - _half_side, _mid[1] - _half_side, _mid[2] + _half_side);
case OC_x | OC_z:
// +X, -Y, +Z
return LPoint3f(_mid[0] + _half_side, _mid[1] - _half_side, _mid[2] + _half_side);
case OC_y | OC_z:
// -X, +Y, +Z
return LPoint3f(_mid[0] - _half_side, _mid[1] + _half_side, _mid[2] + _half_side);
case OC_x | OC_y | OC_z:
// +X, +Y, +Z
return LPoint3f(_mid[0] + _half_side, _mid[1] + _half_side, _mid[2] + _half_side);
}
nassertr(false, LPoint3f::zero());
return LPoint3f::zero();
}