panda3d/panda/src/mathutil/boundingSphere.cxx

455 lines
11 KiB
C++

// Filename: boundingSphere.cxx
// Created by: drose (01Oct99)
//
////////////////////////////////////////////////////////////////////
#include "boundingSphere.h"
#include "boundingHexahedron.h"
#include "boundingLine.h"
#include "config_mathutil.h"
#include <math.h>
#include <algorithm>
TypeHandle BoundingSphere::_type_handle;
BoundingVolume *BoundingSphere::
make_copy() const {
return new BoundingSphere(*this);
}
LPoint3f BoundingSphere::
get_min() const {
nassertr(!is_empty(), LPoint3f(0.0, 0.0, 0.0));
nassertr(!is_infinite(), LPoint3f(0.0, 0.0, 0.0));
return LPoint3f(_center[0] - _radius,
_center[1] - _radius,
_center[2] - _radius);
}
LPoint3f BoundingSphere::
get_max() const {
nassertr(!is_empty(), LPoint3f(0.0, 0.0, 0.0));
nassertr(!is_infinite(), LPoint3f(0.0, 0.0, 0.0));
return LPoint3f(_center[0] + _radius,
_center[1] + _radius,
_center[2] + _radius);
}
LPoint3f BoundingSphere::
get_approx_center() const {
nassertr(!is_empty(), LPoint3f(0.0, 0.0, 0.0));
nassertr(!is_infinite(), LPoint3f(0.0, 0.0, 0.0));
return get_center();
}
void BoundingSphere::
xform(const LMatrix4f &mat) {
nassertv(!mat.is_nan());
if (!is_empty() && !is_infinite()) {
// First, determine the longest axis of the matrix, in case it
// contains a non-proportionate scale.
LVector3f x = mat.get_row3(0);
LVector3f y = mat.get_row3(1);
LVector3f z = mat.get_row3(2);
float xd = dot(x, x);
float yd = dot(y, y);
float zd = dot(z, z);
float scale;
if (xd < yd) {
if (yd < zd) {
scale = sqrtf(zd);
} else {
scale = sqrtf(yd);
}
} else {
if (xd < zd) {
scale = sqrtf(zd);
} else {
scale = sqrtf(xd);
}
}
// Transform the center
_center = _center * mat;
// And the radius.
_radius *= scale;
}
}
void BoundingSphere::
output(ostream &out) const {
if (is_empty()) {
out << "bsphere, empty";
} else if (is_infinite()) {
out << "bsphere, infinite";
} else {
out << "bsphere, c (" << _center << "), r " << _radius;
}
}
bool BoundingSphere::
extend_other(BoundingVolume *other) const {
return other->extend_by_sphere(this);
}
bool BoundingSphere::
around_other(BoundingVolume *other,
const BoundingVolume **first,
const BoundingVolume **last) const {
return other->around_spheres(first, last);
}
int BoundingSphere::
contains_other(const BoundingVolume *other) const {
return other->contains_sphere(this);
}
bool BoundingSphere::
extend_by_point(const LPoint3f &point) {
nassertr(!point.is_nan(), false);
if (is_empty()) {
_center = point;
_radius = 0.0;
_flags = 0;
} else if (!is_infinite()) {
LVector3f v = point - _center;
float dist2 = dot(v, v);
if (dist2 > _radius * _radius) {
_radius = sqrtf(dist2);
}
}
return true;
}
bool BoundingSphere::
extend_by_sphere(const BoundingSphere *sphere) {
nassertr(!sphere->is_empty() && !sphere->is_infinite(), false);
nassertr(!is_infinite(), false);
if (is_empty()) {
_center = sphere->_center;
_radius = sphere->_radius;
_flags = 0;
} else {
float dist = length(sphere->_center - _center);
_radius = max(_radius, dist + sphere->_radius);
}
return true;
}
bool BoundingSphere::
extend_by_hexahedron(const BoundingHexahedron *hexahedron) {
return extend_by_finite(hexahedron);
}
bool BoundingSphere::
extend_by_finite(const FiniteBoundingVolume *volume) {
nassertr(!volume->is_empty(), false);
LVector3f min1 = volume->get_min();
LVector3f max1 = volume->get_max();
if (is_empty()) {
_center = (min1 + max1) * 0.5;
_radius = length(LVector3f(max1 - _center));
_flags = 0;
} else {
LVector3f v = max1 - _center;
float dist2 = dot(v, v);
if (dist2 > _radius * _radius) {
_radius = sqrtf(dist2);
}
}
return true;
}
bool BoundingSphere::
around_points(const LPoint3f *first, const LPoint3f *last) {
nassertr(first != last, false);
// First, get the minmax of all the points to construct a bounding
// box.
const LPoint3f *p = first;
#ifndef NDEBUG
// Skip any NaN points.
int skipped_nan = 0;
while (p != last && (*p).is_nan()) {
++p;
++skipped_nan;
}
if (p == last) {
mathutil_cat.warning()
<< "BoundingSphere around NaN\n";
return false;
}
#endif
LPoint3f min_box = *p;
LPoint3f max_box = *p;
++p;
#ifndef NDEBUG
// Skip more NaN points.
while (p != last && (*p).is_nan()) {
++p;
++skipped_nan;
}
#endif
if (p == last) {
// Only one point; we have a radius of zero. This is not the same
// thing as an empty sphere, because our volume contains one
// point; an empty sphere contains no points.
_radius = 0.0;
} else {
// More than one point; we have a nonzero radius.
while (p != last) {
#ifndef NDEBUG
// Skip more NaN points.
if ((*p).is_nan()) {
++skipped_nan;
} else
#endif
{
min_box.set(min(min_box[0], (*p)[0]),
min(min_box[1], (*p)[1]),
min(min_box[2], (*p)[2]));
max_box.set(max(max_box[0], (*p)[0]),
max(max_box[1], (*p)[1]),
max(max_box[2], (*p)[2]));
}
++p;
}
// Now take the center of the bounding box as the center of the sphere.
_center = (min_box + max_box) / 2.0;
// Now walk back through to get the max distance from center.
float max_dist2 = 0.0;
for (p = first; p != last; ++p) {
LVector3f v = (*p) - _center;
float dist2 = dot(v, v);
max_dist2 = max(max_dist2, dist2);
}
_radius = sqrtf(max_dist2);
}
#ifndef NDEBUG
if (skipped_nan != 0) {
mathutil_cat.warning()
<< "BoundingSphere ignored " << skipped_nan << " NaN points of "
<< (last - first) << " total.\n";
}
#endif
_flags = 0;
return true;
}
bool BoundingSphere::
around_spheres(const BoundingVolume **first,
const BoundingVolume **last) {
return around_finite(first, last);
}
bool BoundingSphere::
around_hexahedrons(const BoundingVolume **first,
const BoundingVolume **last) {
return around_finite(first, last);
}
bool BoundingSphere::
around_finite(const BoundingVolume **first,
const BoundingVolume **last) {
nassertr(first != last, false);
// We're given a set of bounding volumes, at least the first one of
// which is guaranteed to be finite and nonempty. Some others may
// not be.
// First, get the minmax of all the points to construct a bounding
// box.
const BoundingVolume **p = first;
nassertr(!(*p)->is_empty() && !(*p)->is_infinite(), false);
nassertr((*p)->is_of_type(FiniteBoundingVolume::get_class_type()), false);
const FiniteBoundingVolume *vol = DCAST(FiniteBoundingVolume, *p);
LPoint3f min_box = vol->get_min();
LPoint3f max_box = vol->get_max();
bool any_unknown = false;
for (++p; p != last; ++p) {
nassertr(!(*p)->is_infinite(), false);
if (!(*p)->is_empty() &&
(*p)->is_of_type(FiniteBoundingVolume::get_class_type())) {
const FiniteBoundingVolume *vol = DCAST(FiniteBoundingVolume, *p);
LPoint3f min1 = vol->get_min();
LPoint3f max1 = vol->get_max();
min_box.set(min(min_box[0], min1[0]),
min(min_box[1], min1[1]),
min(min_box[2], min1[2]));
max_box.set(max(max_box[0], max1[0]),
max(max_box[1], max1[1]),
max(max_box[2], max1[2]));
if (!(*p)->is_of_type(BoundingSphere::get_class_type())) {
any_unknown = true;
}
}
}
// Now take the center of the bounding box as the center of the sphere.
_center = (min_box + max_box) * 0.5;
if (any_unknown) {
// If we have any volumes in the list that we don't know what to
// do with, we'll have to make the bounding sphere large enough to
// enclose the bounding box. Less than ideal, but too bad.
_radius = length(max_box - _center);
} else {
// Otherwise, we do understand all the volumes in the list; make
// the sphere as tight as we can.
_radius = 0.0;
for (p = first; p != last; ++p) {
if (!(*p)->is_empty()) {
if ((*p)->is_of_type(BoundingSphere::get_class_type())) {
const BoundingSphere *sphere = DCAST(BoundingSphere, *p);
float dist = length(sphere->_center - _center);
_radius = max(_radius, dist + sphere->_radius);
} else {
// Shouldn't get here, unless we missed a type from above.
mathutil_cat.error()
<< "Unexpected type in BoundingSphere::around_finite()\n";
nassertr(false, false);
}
}
}
}
_flags = 0;
return true;
}
int BoundingSphere::
contains_point(const LPoint3f &point) const {
nassertr(!point.is_nan(), IF_no_intersection);
if (is_empty()) {
return IF_no_intersection;
} else if (is_infinite()) {
return IF_possible | IF_some | IF_all;
} else {
LVector3f v = point - _center;
float dist2 = dot(v, v);
return (dist2 <= _radius * _radius) ?
IF_possible | IF_some | IF_all : IF_no_intersection;
}
}
int BoundingSphere::
contains_lineseg(const LPoint3f &a, const LPoint3f &b) const {
nassertr(!a.is_nan() && !b.is_nan(), IF_no_intersection);
if (a == b) {
return contains_point(a);
}
if (is_empty()) {
return IF_no_intersection;
} else if (is_infinite()) {
return IF_possible | IF_some | IF_all;
} else {
LPoint3f from = a;
LVector3f delta = b - a;
float t1, t2;
// Solve the equation for the intersection of a line with a sphere
// using the quadratic equation.
float A = dot(delta, delta);
nassertr(A != 0.0, 0); // Trivial line segment.
LVector3f fc = from - _center;
float B = 2.0 * dot(delta, fc);
float C = dot(fc, fc) - _radius * _radius;
float radical = B*B - 4.0*A*C;
if (IS_NEARLY_ZERO(radical)) {
// Tangent.
t1 = t2 = -B / (2.0*A);
return (t1 >= 0.0 && t1 <= 1.0) ?
IF_possible | IF_some : IF_no_intersection;
}
if (radical < 0.0) {
// No real roots: no intersection with the line.
return IF_no_intersection;
}
float reciprocal_2A = 1.0f/(2.0*A);
float sqrt_radical = sqrtf(radical);
t1 = ( -B - sqrt_radical ) * reciprocal_2A;
t2 = ( -B + sqrt_radical ) * reciprocal_2A;
if (t1 >= 0.0 && t2 <= 1.0) {
return IF_possible | IF_some | IF_all;
} else if (t1 <= 1.0 && t2 >= 0.0) {
return IF_possible | IF_some;
} else {
return IF_no_intersection;
}
}
}
int BoundingSphere::
contains_sphere(const BoundingSphere *sphere) const {
nassertr(!is_empty() && !is_infinite(), 0);
nassertr(!sphere->is_empty() && !sphere->is_infinite(), 0);
LVector3f v = sphere->_center - _center;
float dist2 = dot(v, v);
if (_radius >= sphere->_radius &&
dist2 <= (_radius - sphere->_radius) * (_radius - sphere->_radius)) {
// The other sphere is completely within this sphere.
return IF_possible | IF_some | IF_all;
} else if (dist2 > (_radius + sphere->_radius) * (_radius + sphere->_radius)) {
// The other sphere is completely outside this sphere.
return IF_no_intersection;
} else {
// The other sphere is partially within this sphere.
return IF_possible | IF_some;
}
}
int BoundingSphere::
contains_hexahedron(const BoundingHexahedron *hexahedron) const {
return hexahedron->contains_sphere(this) & ~IF_all;
}
int BoundingSphere::
contains_line(const BoundingLine *line) const {
return line->contains_sphere(this) & ~IF_all;
}