nori/include/nori/bbox.h

400 lines
12 KiB
C++

/*
This file is part of Nori, a simple educational ray tracer
Copyright (c) 2015 by Wenzel Jakob
Nori is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License Version 3
as published by the Free Software Foundation.
Nori is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <nori/ray.h>
NORI_NAMESPACE_BEGIN
/**
* \brief Generic n-dimensional bounding box data structure
*
* Maintains a minimum and maximum position along each dimension and provides
* various convenience functions for querying and modifying them.
*
* This class is parameterized by the underlying point data structure,
* which permits the use of different scalar types and dimensionalities, e.g.
* \code
* TBoundingBox<Vector3i> integerBBox(Point3i(0, 1, 3), Point3i(4, 5, 6));
* TBoundingBox<Vector2d> doubleBBox(Point2d(0.0, 1.0), Point2d(4.0, 5.0));
* \endcode
*
* \tparam T The underlying point data type (e.g. \c Point2d)
* \ingroup libcore
*/
template <typename _PointType> struct TBoundingBox {
enum {
Dimension = _PointType::Dimension
};
typedef _PointType PointType;
typedef typename PointType::Scalar Scalar;
typedef typename PointType::VectorType VectorType;
/**
* \brief Create a new invalid bounding box
*
* Initializes the components of the minimum
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
* respectively.
*/
TBoundingBox() {
reset();
}
/// Create a collapsed bounding box from a single point
TBoundingBox(const PointType &p)
: min(p), max(p) { }
/// Create a bounding box from two positions
TBoundingBox(const PointType &min, const PointType &max)
: min(min), max(max) {
}
/// Test for equality against another bounding box
bool operator==(const TBoundingBox &bbox) const {
return min == bbox.min && max == bbox.max;
}
/// Test for inequality against another bounding box
bool operator!=(const TBoundingBox &bbox) const {
return min != bbox.min || max != bbox.max;
}
/// Calculate the n-dimensional volume of the bounding box
Scalar getVolume() const {
return (max - min).prod();
}
/// Calculate the n-1 dimensional volume of the boundary
float getSurfaceArea() const {
VectorType d = max - min;
float result = 0.0f;
for (int i=0; i<Dimension; ++i) {
float term = 1.0f;
for (int j=0; j<Dimension; ++j) {
if (i == j)
continue;
term *= d[j];
}
result += term;
}
return 2.0f * result;
}
/// Return the center point
PointType getCenter() const {
return (max + min) * (Scalar) 0.5f;
}
/**
* \brief Check whether a point lies \a on or \a inside the bounding box
*
* \param p The point to be tested
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*/
bool contains(const PointType &p, bool strict = false) const {
if (strict) {
return (p.array() > min.array()).all()
&& (p.array() < max.array()).all();
} else {
return (p.array() >= min.array()).all()
&& (p.array() <= max.array()).all();
}
}
/**
* \brief Check whether a specified bounding box lies \a on or \a within
* the current bounding box
*
* Note that by definition, an 'invalid' bounding box (where min=\f$\infty\f$
* and max=\f$-\infty\f$) does not cover any space. Hence, this method will always
* return \a true when given such an argument.
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*/
bool contains(const TBoundingBox &bbox, bool strict = false) const {
if (strict) {
return (bbox.min.array() > min.array()).all()
&& (bbox.max.array() < max.array()).all();
} else {
return (bbox.min.array() >= min.array()).all()
&& (bbox.max.array() <= max.array()).all();
}
}
/**
* \brief Check two axis-aligned bounding boxes for possible overlap.
*
* \param strict Set this parameter to \c true if the bounding
* box boundary should be excluded in the test
*
* \return \c true If overlap was detected.
*/
bool overlaps(const TBoundingBox &bbox, bool strict = false) const {
if (strict) {
return (bbox.min.array() < max.array()).all()
&& (bbox.max.array() > min.array()).all();
} else {
return (bbox.min.array() <= max.array()).all()
&& (bbox.max.array() >= min.array()).all();
}
}
/**
* \brief Calculate the smallest squared distance between
* the axis-aligned bounding box and the point \c p.
*/
Scalar squaredDistanceTo(const PointType &p) const {
Scalar result = 0;
for (int i=0; i<Dimension; ++i) {
Scalar value = 0;
if (p[i] < min[i])
value = min[i] - p[i];
else if (p[i] > max[i])
value = p[i] - max[i];
result += value*value;
}
return result;
}
/**
* \brief Calculate the smallest distance between
* the axis-aligned bounding box and the point \c p.
*/
Scalar distanceTo(const PointType &p) const {
return std::sqrt(squaredDistanceTo(p));
}
/**
* \brief Calculate the smallest square distance between
* the axis-aligned bounding box and \c bbox.
*/
Scalar squaredDistanceTo(const TBoundingBox &bbox) const {
Scalar result = 0;
for (int i=0; i<Dimension; ++i) {
Scalar value = 0;
if (bbox.max[i] < min[i])
value = min[i] - bbox.max[i];
else if (bbox.min[i] > max[i])
value = bbox.min[i] - max[i];
result += value*value;
}
return result;
}
/**
* \brief Calculate the smallest distance between
* the axis-aligned bounding box and \c bbox.
*/
Scalar distanceTo(const TBoundingBox &bbox) const {
return std::sqrt(squaredDistanceTo(bbox));
}
/**
* \brief Check whether this is a valid bounding box
*
* A bounding box \c bbox is valid when
* \code
* bbox.min[dim] <= bbox.max[dim]
* \endcode
* holds along each dimension \c dim.
*/
bool isValid() const {
return (max.array() >= min.array()).all();
}
/// Check whether this bounding box has collapsed to a single point
bool isPoint() const {
return (max.array() == min.array()).all();
}
/// Check whether this bounding box has any associated volume
bool hasVolume() const {
return (max.array() > min.array()).all();
}
/// Return the dimension index with the largest associated side length
int getMajorAxis() const {
VectorType d = max - min;
int largest = 0;
for (int i=1; i<Dimension; ++i)
if (d[i] > d[largest])
largest = i;
return largest;
}
/// Return the dimension index with the shortest associated side length
int getMinorAxis() const {
VectorType d = max - min;
int shortest = 0;
for (int i=1; i<Dimension; ++i)
if (d[i] < d[shortest])
shortest = i;
return shortest;
}
/**
* \brief Calculate the bounding box extents
* \return max-min
*/
VectorType getExtents() const {
return max - min;
}
/// Clip to another bounding box
void clip(const TBoundingBox &bbox) {
min = min.cwiseMax(bbox.min);
max = max.cwiseMin(bbox.max);
}
/**
* \brief Mark the bounding box as invalid.
*
* This operation sets the components of the minimum
* and maximum position to \f$\infty\f$ and \f$-\infty\f$,
* respectively.
*/
void reset() {
min.setConstant( std::numeric_limits<Scalar>::infinity());
max.setConstant(-std::numeric_limits<Scalar>::infinity());
}
/// Expand the bounding box to contain another point
void expandBy(const PointType &p) {
min = min.cwiseMin(p);
max = max.cwiseMax(p);
}
/// Expand the bounding box to contain another bounding box
void expandBy(const TBoundingBox &bbox) {
min = min.cwiseMin(bbox.min);
max = max.cwiseMax(bbox.max);
}
/// Merge two bounding boxes
static TBoundingBox merge(const TBoundingBox &bbox1, const TBoundingBox &bbox2) {
return TBoundingBox(
bbox1.min.cwiseMin(bbox2.min),
bbox1.max.cwiseMax(bbox2.max)
);
}
/// Return the index of the largest axis
int getLargestAxis() const {
VectorType extents = max-min;
if (extents[0] >= extents[1] && extents[0] >= extents[2])
return 0;
else if (extents[1] >= extents[0] && extents[1] >= extents[2])
return 1;
else
return 2;
}
/// Return the position of a bounding box corner
PointType getCorner(int index) const {
PointType result;
for (int i=0; i<Dimension; ++i)
result[i] = (index & (1 << i)) ? max[i] : min[i];
return result;
}
/// Return a string representation of the bounding box
std::string toString() const {
if (!isValid())
return "BoundingBox[invalid]";
else
return tfm::format("BoundingBox[min=%s, max=%s]", min.toString(), max.toString());
}
/// Check if a ray intersects a bounding box
bool rayIntersect(const Ray3f &ray) const {
float nearT = -std::numeric_limits<float>::infinity();
float farT = std::numeric_limits<float>::infinity();
for (int i=0; i<3; i++) {
float origin = ray.o[i];
float minVal = min[i], maxVal = max[i];
if (ray.d[i] == 0) {
if (origin < minVal || origin > maxVal)
return false;
} else {
float t1 = (minVal - origin) * ray.dRcp[i];
float t2 = (maxVal - origin) * ray.dRcp[i];
if (t1 > t2)
std::swap(t1, t2);
nearT = std::max(t1, nearT);
farT = std::min(t2, farT);
if (!(nearT <= farT))
return false;
}
}
return ray.mint <= farT && nearT <= ray.maxt;
}
/// Return the overlapping region of the bounding box and an unbounded ray
bool rayIntersect(const Ray3f &ray, float &nearT, float &farT) const {
nearT = -std::numeric_limits<float>::infinity();
farT = std::numeric_limits<float>::infinity();
for (int i=0; i<3; i++) {
float origin = ray.o[i];
float minVal = min[i], maxVal = max[i];
if (ray.d[i] == 0) {
if (origin < minVal || origin > maxVal)
return false;
} else {
float t1 = (minVal - origin) * ray.dRcp[i];
float t2 = (maxVal - origin) * ray.dRcp[i];
if (t1 > t2)
std::swap(t1, t2);
nearT = std::max(t1, nearT);
farT = std::min(t2, farT);
if (!(nearT <= farT))
return false;
}
}
return true;
}
PointType min; ///< Component-wise minimum
PointType max; ///< Component-wise maximum
};
NORI_NAMESPACE_END