godot/core/math/bsp_tree.cpp

586 lines
14 KiB
C++
Raw Normal View History

2014-02-10 02:10:30 +01:00
/*************************************************************************/
/* bsp_tree.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
2014-02-10 02:10:30 +01:00
/*************************************************************************/
/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */
2014-02-10 02:10:30 +01:00
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
2014-02-10 02:10:30 +01:00
#include "bsp_tree.h"
#include "core/error_macros.h"
#include "core/print_string.h"
2014-02-10 02:10:30 +01:00
2017-11-17 03:09:00 +01:00
void BSP_Tree::from_aabb(const AABB &p_aabb) {
2014-02-10 02:10:30 +01:00
planes.clear();
for (int i = 0; i < 3; i++) {
2014-02-10 02:10:30 +01:00
Vector3 n;
n[i] = 1;
planes.push_back(Plane(n, p_aabb.position[i] + p_aabb.size[i]));
planes.push_back(Plane(-n, -p_aabb.position[i]));
2014-02-10 02:10:30 +01:00
}
nodes.clear();
for (int i = 0; i < 6; i++) {
2014-02-10 02:10:30 +01:00
Node n;
n.plane = i;
n.under = (i == 0) ? UNDER_LEAF : i - 1;
n.over = OVER_LEAF;
2014-02-10 02:10:30 +01:00
nodes.push_back(n);
}
aabb = p_aabb;
error_radius = 0;
2014-02-10 02:10:30 +01:00
}
Vector<BSP_Tree::Node> BSP_Tree::get_nodes() const {
return nodes;
}
Vector<Plane> BSP_Tree::get_planes() const {
return planes;
}
2016-03-09 00:00:52 +01:00
2017-11-17 03:09:00 +01:00
AABB BSP_Tree::get_aabb() const {
2014-02-10 02:10:30 +01:00
return aabb;
}
2016-03-09 00:00:52 +01:00
int BSP_Tree::_get_points_inside(int p_node, const Vector3 *p_points, int *p_indices, const Vector3 &p_center, const Vector3 &p_half_extents, int p_indices_count) const {
2014-02-10 02:10:30 +01:00
const Node *node = &nodes[p_node];
2014-02-10 02:10:30 +01:00
const Plane &p = planes[node->plane];
Vector3 min(
(p.normal.x > 0) ? -p_half_extents.x : p_half_extents.x,
(p.normal.y > 0) ? -p_half_extents.y : p_half_extents.y,
(p.normal.z > 0) ? -p_half_extents.z : p_half_extents.z);
Vector3 max = -min;
max += p_center;
min += p_center;
2014-02-10 02:10:30 +01:00
real_t dist_min = p.distance_to(min);
real_t dist_max = p.distance_to(max);
2014-02-10 02:10:30 +01:00
if ((dist_min * dist_max) < CMP_EPSILON) { //intersection, test point by point
2014-02-10 02:10:30 +01:00
int under_count = 0;
2014-02-10 02:10:30 +01:00
//sort points, so the are under first, over last
for (int i = 0; i < p_indices_count; i++) {
2014-02-10 02:10:30 +01:00
int index = p_indices[i];
2014-02-10 02:10:30 +01:00
if (p.is_point_over(p_points[index])) {
// kind of slow (but cache friendly), should try something else,
// but this is a corner case most of the time
for (int j = index; j < p_indices_count - 1; j++)
p_indices[j] = p_indices[j + 1];
2014-02-10 02:10:30 +01:00
p_indices[p_indices_count - 1] = index;
2014-02-10 02:10:30 +01:00
} else {
under_count++;
}
}
int total = 0;
2014-02-10 02:10:30 +01:00
if (under_count > 0) {
if (node->under == UNDER_LEAF) {
total += under_count;
2014-02-10 02:10:30 +01:00
} else {
total += _get_points_inside(node->under, p_points, p_indices, p_center, p_half_extents, under_count);
2014-02-10 02:10:30 +01:00
}
}
if (under_count != p_indices_count) {
if (node->over == OVER_LEAF) {
2014-02-10 02:10:30 +01:00
//total+=0 //if they are over an OVER_LEAF, they are outside the model
} else {
total += _get_points_inside(node->over, p_points, &p_indices[under_count], p_center, p_half_extents, p_indices_count - under_count);
2014-02-10 02:10:30 +01:00
}
}
return total;
} else if (dist_min > 0) { //all points over plane
2014-02-10 02:10:30 +01:00
if (node->over == OVER_LEAF) {
2014-02-10 02:10:30 +01:00
return 0; // all these points are not visible
}
return _get_points_inside(node->over, p_points, p_indices, p_center, p_half_extents, p_indices_count);
} else if (dist_min <= 0) { //all points behind plane
2014-02-10 02:10:30 +01:00
if (node->under == UNDER_LEAF) {
2014-02-10 02:10:30 +01:00
return p_indices_count; // all these points are visible
}
return _get_points_inside(node->under, p_points, p_indices, p_center, p_half_extents, p_indices_count);
2014-02-10 02:10:30 +01:00
}
return 0;
}
int BSP_Tree::get_points_inside(const Vector3 *p_points, int p_point_count) const {
2014-02-10 02:10:30 +01:00
if (nodes.size() == 0)
2014-02-10 02:10:30 +01:00
return 0;
#if 1
//this version is easier to debug, and and MUCH faster in real world cases
2014-02-10 02:10:30 +01:00
int pass_count = 0;
const Node *nodesptr = &nodes[0];
const Plane *planesptr = &planes[0];
int node_count = nodes.size();
2014-02-10 02:10:30 +01:00
if (node_count == 0) // no nodes!
2014-02-10 02:10:30 +01:00
return 0;
for (int i = 0; i < p_point_count; i++) {
2014-02-10 02:10:30 +01:00
const Vector3 &point = p_points[i];
2014-02-10 02:10:30 +01:00
if (!aabb.has_point(point)) {
continue;
}
int idx = node_count - 1;
2014-02-10 02:10:30 +01:00
bool pass = false;
2014-02-10 02:10:30 +01:00
while (true) {
2014-02-10 02:10:30 +01:00
if (idx == OVER_LEAF) {
pass = false;
2014-02-10 02:10:30 +01:00
break;
} else if (idx == UNDER_LEAF) {
pass = true;
2014-02-10 02:10:30 +01:00
break;
}
#ifdef DEBUG_ENABLED
Fix warnings on release builds (not DEBUG_ENABLED) Fixes the following Clang 5 warnings: ``` modules/bmp/image_loader_bmp.cpp:46:60: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare] modules/bmp/image_loader_bmp.cpp:48:61: warning: comparison of unsigned expression < 0 is always false [-Wtautological-compare] drivers/png/image_loader_png.cpp:231:20: warning: comparison of unsigned expression >= 0 is always true [-Wtautological-compare] scene/gui/graph_edit.cpp:1045:8: warning: comparison of constant 0 with expression of type 'bool' is always false [-Wtautological-constant-out-of-range-compare] core/class_db.cpp:812:13: warning: unused variable 'check' [-Wunused-variable] core/io/file_access_pack.cpp:172:11: warning: unused variable 'ver_rev' [-Wunused-variable] core/math/bsp_tree.cpp:195:13: warning: unused variable 'plane' [-Wunused-variable] core/math/bsp_tree.cpp:168:6: warning: unused variable 'plane_count' [-Wunused-variable] modules/gdscript/gdscript_function.cpp:685:10: warning: unused variable 'ok' [-Wunused-variable] modules/gdscript/gdscript_function.cpp:706:10: warning: unused variable 'ok' [-Wunused-variable] modules/gdscript/gdscript_function.cpp:755:19: warning: unused variable 'var_type' [-Wunused-variable] modules/gdscript/gdscript_function.cpp:1306:12: warning: unused variable 'err' [-Wunused-variable] modules/gdscript/gdscript_function.cpp:158:15: warning: unused function '_get_var_type' [-Wunused-function] modules/gdscript/gdscript_parser.cpp:750:20: warning: unused variable 'lv' [-Wunused-variable] modules/gdscript/gdscript_parser.cpp:59:15: warning: unused function '_find_function_name' [-Wunused-function] scene/main/node.cpp:2489:13: warning: unused function '_Node_debug_sn' [-Wunused-function] ```
2018-10-03 16:13:34 +02:00
int plane_count = planes.size();
uint16_t plane = nodesptr[idx].plane;
ERR_FAIL_INDEX_V(plane, plane_count, false);
2014-02-10 02:10:30 +01:00
#endif
idx = planesptr[nodesptr[idx].plane].is_point_over(point) ? nodes[idx].over : nodes[idx].under;
2014-02-10 02:10:30 +01:00
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V(idx < MAX_NODES && idx >= node_count, false);
2014-02-10 02:10:30 +01:00
#endif
}
if (pass)
pass_count++;
}
return pass_count;
#else
//this version scales better but it's slower for real world cases
2014-02-10 02:10:30 +01:00
int *indices = (int *)alloca(p_point_count * sizeof(int));
2014-02-10 02:10:30 +01:00
AABB bounds;
for (int i = 0; i < p_point_count; i++) {
2014-02-10 02:10:30 +01:00
indices[i] = i;
if (i == 0)
bounds.pos = p_points[i];
2014-02-10 02:10:30 +01:00
else
bounds.expand_to(p_points[i]);
}
Vector3 half_extents = bounds.size / 2.0;
return _get_points_inside(nodes.size() + 1, p_points, indices, bounds.pos + half_extents, half_extents, p_point_count);
2014-02-10 02:10:30 +01:00
#endif
}
bool BSP_Tree::point_is_inside(const Vector3 &p_point) const {
2014-02-10 02:10:30 +01:00
if (!aabb.has_point(p_point)) {
return false;
}
2016-03-09 00:00:52 +01:00
int node_count = nodes.size();
2016-03-09 00:00:52 +01:00
if (node_count == 0) // no nodes!
2014-02-10 02:10:30 +01:00
return false;
2016-03-09 00:00:52 +01:00
const Node *nodesptr = &nodes[0];
const Plane *planesptr = &planes[0];
2016-03-09 00:00:52 +01:00
int idx = node_count - 1;
2016-03-09 00:00:52 +01:00
while (true) {
2016-03-09 00:00:52 +01:00
if (idx == OVER_LEAF) {
2014-02-10 02:10:30 +01:00
return false;
}
if (idx == UNDER_LEAF) {
2014-02-10 02:10:30 +01:00
return true;
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
#ifdef DEBUG_ENABLED
2018-03-26 01:36:34 +02:00
int plane_count = planes.size();
uint16_t plane = nodesptr[idx].plane;
ERR_FAIL_INDEX_V(plane, plane_count, false);
2014-02-10 02:10:30 +01:00
#endif
2018-03-26 01:36:34 +02:00
bool over = planesptr[nodesptr[idx].plane].is_point_over(p_point);
2014-02-10 02:10:30 +01:00
idx = over ? nodes[idx].over : nodes[idx].under;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
#ifdef DEBUG_ENABLED
ERR_FAIL_COND_V(idx < MAX_NODES && idx >= node_count, false);
2014-02-10 02:10:30 +01:00
#endif
}
return false;
}
2016-03-09 00:00:52 +01:00
static int _bsp_find_best_half_plane(const Face3 *p_faces, const Vector<int> &p_indices, real_t p_tolerance) {
2014-02-10 02:10:30 +01:00
int ic = p_indices.size();
const int *indices = p_indices.ptr();
2014-02-10 02:10:30 +01:00
int best_plane = -1;
real_t best_plane_cost = 1e20;
2014-02-10 02:10:30 +01:00
// Loop to find the polygon that best divides the set.
for (int i = 0; i < ic; i++) {
2014-02-10 02:10:30 +01:00
const Face3 &f = p_faces[indices[i]];
2014-02-10 02:10:30 +01:00
Plane p = f.get_plane();
2016-03-09 00:00:52 +01:00
int num_over = 0, num_under = 0, num_spanning = 0;
2014-02-10 02:10:30 +01:00
for (int j = 0; j < ic; j++) {
2014-02-10 02:10:30 +01:00
if (i == j)
2014-02-10 02:10:30 +01:00
continue;
const Face3 &g = p_faces[indices[j]];
int over = 0, under = 0;
2014-02-10 02:10:30 +01:00
for (int k = 0; k < 3; k++) {
2014-02-10 02:10:30 +01:00
real_t d = p.distance_to(g.vertex[j]);
2014-02-10 02:10:30 +01:00
if (Math::abs(d) > p_tolerance) {
2014-02-10 02:10:30 +01:00
if (d > 0)
over++;
else
under++;
}
}
if (over && under)
num_spanning++;
else if (over)
num_over++;
else
num_under++;
}
//real_t split_cost = num_spanning / (real_t) face_count;
real_t relation = Math::abs(num_over - num_under) / (real_t)ic;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
// being honest, i never found a way to add split cost to the mix in a meaninguful way
// in this engine, also, will likely be ignored anyway
2016-03-09 00:00:52 +01:00
real_t plane_cost = /*split_cost +*/ relation;
2014-02-10 02:10:30 +01:00
//printf("plane %i, %i over, %i under, %i spanning, cost is %g\n",i,num_over,num_under,num_spanning,plane_cost);
if (plane_cost < best_plane_cost) {
2014-02-10 02:10:30 +01:00
best_plane = i;
best_plane_cost = plane_cost;
2014-02-10 02:10:30 +01:00
}
}
return best_plane;
}
2016-03-09 00:00:52 +01:00
static int _bsp_create_node(const Face3 *p_faces, const Vector<int> &p_indices, Vector<Plane> &p_planes, Vector<BSP_Tree::Node> &p_nodes, real_t p_tolerance) {
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND_V(p_nodes.size() == BSP_Tree::MAX_NODES, -1);
2014-02-10 02:10:30 +01:00
// should not reach here
ERR_FAIL_COND_V(p_indices.size() == 0, -1)
2014-02-10 02:10:30 +01:00
int ic = p_indices.size();
const int *indices = p_indices.ptr();
2014-02-10 02:10:30 +01:00
int divisor_idx = _bsp_find_best_half_plane(p_faces, p_indices, p_tolerance);
2014-02-10 02:10:30 +01:00
// returned error
ERR_FAIL_COND_V(divisor_idx < 0, -1);
2014-02-10 02:10:30 +01:00
Vector<int> faces_over;
Vector<int> faces_under;
Plane divisor_plane = p_faces[indices[divisor_idx]].get_plane();
2014-02-10 02:10:30 +01:00
for (int i = 0; i < ic; i++) {
2014-02-10 02:10:30 +01:00
if (i == divisor_idx)
2014-02-10 02:10:30 +01:00
continue;
2016-03-09 00:00:52 +01:00
const Face3 &f = p_faces[indices[i]];
2014-02-10 02:10:30 +01:00
/*
if (f.get_plane().is_almost_like(divisor_plane))
continue;
*/
2014-02-10 02:10:30 +01:00
int over_count = 0;
int under_count = 0;
2014-02-10 02:10:30 +01:00
for (int j = 0; j < 3; j++) {
2014-02-10 02:10:30 +01:00
real_t d = divisor_plane.distance_to(f.vertex[j]);
if (Math::abs(d) > p_tolerance) {
2014-02-10 02:10:30 +01:00
if (d > 0)
over_count++;
else
under_count++;
}
}
if (over_count)
faces_over.push_back(indices[i]);
2014-02-10 02:10:30 +01:00
if (under_count)
faces_under.push_back(indices[i]);
2014-02-10 02:10:30 +01:00
}
uint16_t over_idx = BSP_Tree::OVER_LEAF, under_idx = BSP_Tree::UNDER_LEAF;
2014-02-10 02:10:30 +01:00
if (faces_over.size() > 0) { //have facess above?
2016-03-09 00:00:52 +01:00
int idx = _bsp_create_node(p_faces, faces_over, p_planes, p_nodes, p_tolerance);
if (idx >= 0)
over_idx = idx;
2014-02-10 02:10:30 +01:00
}
if (faces_under.size() > 0) { //have facess above?
2014-02-10 02:10:30 +01:00
int idx = _bsp_create_node(p_faces, faces_under, p_planes, p_nodes, p_tolerance);
if (idx >= 0)
under_idx = idx;
2014-02-10 02:10:30 +01:00
}
/* Create the node */
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
// find existing divisor plane
int divisor_plane_idx = -1;
2016-03-09 00:00:52 +01:00
for (int i = 0; i < p_planes.size(); i++) {
2016-03-09 00:00:52 +01:00
if (p_planes[i].is_almost_like(divisor_plane)) {
divisor_plane_idx = i;
2014-02-10 02:10:30 +01:00
break;
}
}
if (divisor_plane_idx == -1) {
2016-03-09 00:00:52 +01:00
ERR_FAIL_COND_V(p_planes.size() == BSP_Tree::MAX_PLANES, -1);
divisor_plane_idx = p_planes.size();
p_planes.push_back(divisor_plane);
2014-02-10 02:10:30 +01:00
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
BSP_Tree::Node node;
node.plane = divisor_plane_idx;
node.under = under_idx;
node.over = over_idx;
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
p_nodes.push_back(node);
2016-03-09 00:00:52 +01:00
return p_nodes.size() - 1;
2014-02-10 02:10:30 +01:00
}
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
BSP_Tree::operator Variant() const {
Dictionary d;
d["error_radius"] = error_radius;
2014-02-10 02:10:30 +01:00
Vector<real_t> plane_values;
plane_values.resize(planes.size() * 4);
2014-02-10 02:10:30 +01:00
for (int i = 0; i < planes.size(); i++) {
2014-02-10 02:10:30 +01:00
plane_values.write[i * 4 + 0] = planes[i].normal.x;
plane_values.write[i * 4 + 1] = planes[i].normal.y;
plane_values.write[i * 4 + 2] = planes[i].normal.z;
plane_values.write[i * 4 + 3] = planes[i].d;
2014-02-10 02:10:30 +01:00
}
d["planes"] = plane_values;
2014-02-10 02:10:30 +01:00
PoolVector<int> dst_nodes;
dst_nodes.resize(nodes.size() * 3);
2014-02-10 02:10:30 +01:00
for (int i = 0; i < nodes.size(); i++) {
2014-02-10 02:10:30 +01:00
dst_nodes.set(i * 3 + 0, nodes[i].over);
dst_nodes.set(i * 3 + 1, nodes[i].under);
dst_nodes.set(i * 3 + 2, nodes[i].plane);
2014-02-10 02:10:30 +01:00
}
d["nodes"] = dst_nodes;
2014-02-10 02:10:30 +01:00
d["aabb"] = aabb;
return Variant(d);
}
BSP_Tree::BSP_Tree() {
}
BSP_Tree::BSP_Tree(const Variant &p_variant) {
2014-02-10 02:10:30 +01:00
Dictionary d = p_variant;
2014-02-10 02:10:30 +01:00
ERR_FAIL_COND(!d.has("nodes"));
ERR_FAIL_COND(!d.has("planes"));
ERR_FAIL_COND(!d.has("aabb"));
ERR_FAIL_COND(!d.has("error_radius"));
PoolVector<int> src_nodes = d["nodes"];
ERR_FAIL_COND(src_nodes.size() % 3);
2014-02-10 02:10:30 +01:00
if (d["planes"].get_type() == Variant::POOL_REAL_ARRAY) {
2014-02-10 02:10:30 +01:00
PoolVector<real_t> src_planes = d["planes"];
int plane_count = src_planes.size();
ERR_FAIL_COND(plane_count % 4);
planes.resize(plane_count / 4);
2014-02-10 02:10:30 +01:00
if (plane_count) {
PoolVector<real_t>::Read r = src_planes.read();
for (int i = 0; i < plane_count / 4; i++) {
2014-02-10 02:10:30 +01:00
planes.write[i].normal.x = r[i * 4 + 0];
planes.write[i].normal.y = r[i * 4 + 1];
planes.write[i].normal.z = r[i * 4 + 2];
planes.write[i].d = r[i * 4 + 3];
2014-02-10 02:10:30 +01:00
}
}
} else {
planes = d["planes"];
}
error_radius = d["error"];
aabb = d["aabb"];
//int node_count = src_nodes.size();
nodes.resize(src_nodes.size() / 3);
2014-02-10 02:10:30 +01:00
PoolVector<int>::Read r = src_nodes.read();
2014-02-10 02:10:30 +01:00
for (int i = 0; i < nodes.size(); i++) {
2014-02-10 02:10:30 +01:00
nodes.write[i].over = r[i * 3 + 0];
nodes.write[i].under = r[i * 3 + 1];
nodes.write[i].plane = r[i * 3 + 2];
2014-02-10 02:10:30 +01:00
}
}
BSP_Tree::BSP_Tree(const PoolVector<Face3> &p_faces, real_t p_error_radius) {
2014-02-10 02:10:30 +01:00
// compute aabb
2016-03-09 00:00:52 +01:00
int face_count = p_faces.size();
PoolVector<Face3>::Read faces_r = p_faces.read();
2014-02-10 02:10:30 +01:00
const Face3 *facesptr = faces_r.ptr();
bool first = true;
2014-02-10 02:10:30 +01:00
Vector<int> indices;
for (int i = 0; i < face_count; i++) {
2016-03-09 00:00:52 +01:00
const Face3 &f = facesptr[i];
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
if (f.is_degenerate())
continue;
2016-03-09 00:00:52 +01:00
for (int j = 0; j < 3; j++) {
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
if (first) {
2016-03-09 00:00:52 +01:00
aabb.position = f.vertex[0];
first = false;
2014-02-10 02:10:30 +01:00
} else {
2016-03-09 00:00:52 +01:00
2014-02-10 02:10:30 +01:00
aabb.expand_to(f.vertex[j]);
}
}
indices.push_back(i);
}
ERR_FAIL_COND(aabb.has_no_area());
2014-02-10 02:10:30 +01:00
int top = _bsp_create_node(faces_r.ptr(), indices, planes, nodes, aabb.get_longest_axis_size() * 0.0001);
2014-02-10 02:10:30 +01:00
if (top < 0) {
2014-02-10 02:10:30 +01:00
nodes.clear();
planes.clear();
ERR_FAIL_COND(top < 0);
2014-02-10 02:10:30 +01:00
}
error_radius = p_error_radius;
2014-02-10 02:10:30 +01:00
}
BSP_Tree::BSP_Tree(const Vector<Node> &p_nodes, const Vector<Plane> &p_planes, const AABB &p_aabb, real_t p_error_radius) :
nodes(p_nodes),
planes(p_planes),
aabb(p_aabb),
error_radius(p_error_radius) {
2014-02-10 02:10:30 +01:00
}
BSP_Tree::~BSP_Tree() {
}