You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

331 lines
12 KiB

3 months ago
// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2021
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2019.08.13
#pragma once
#include <Mathematics/BitHacks.h>
#include <Mathematics/ContOrientedBox3.h>
// The depth of a node in a (nonempty) tree is the distance from the node to
// the root of the tree. The height is the maximum depth. A tree with a
// single node has height 0. The set of nodes of a tree with the same depth
// is refered to as a level of a tree (corresponding to that depth). A
// complete binary tree of height H has 2^{H+1}-1 nodes. The level
// corresponding to depth D has 2^D nodes, in which case the number of
// leaf nodes (depth H) is 2^H.
namespace gte
{
template <typename Real>
class OBBTreeForPoints
{
public:
struct Node
{
Node()
:
depth(0),
minIndex(std::numeric_limits<uint32_t>::max()),
maxIndex(std::numeric_limits<uint32_t>::max()),
leftChild(std::numeric_limits<uint32_t>::max()),
rightChild(std::numeric_limits<uint32_t>::max())
{
}
OrientedBox3<Real> box;
uint32_t depth;
uint32_t minIndex, maxIndex;
uint32_t leftChild, rightChild;
};
// The 'points' array is a collection of vertices, each occupying a
// chunk of memory with 'stride' bytes. A vertex must start at the
// first byte of this chunk but does not necessarily fill it. The
// 'height' specifies the height of the tree and must be no larger
// than 31. If it is set to std::numeric_limits<uint32_t>::max(),
// then the entire tree is built and the actual height is computed
// from 'numPoints'.
OBBTreeForPoints(uint32_t numPoints, char const* points, size_t stride,
uint32_t height = std::numeric_limits<uint32_t>::max())
:
mNumPoints(numPoints),
mPoints(points),
mStride(stride),
mHeight(height),
mPartition(numPoints)
{
// The tree nodes are indexed by 32-bit unsigned integers, so
// the number of nodes can be at most 2^{32} - 1. This limits
// the height to 31.
uint32_t numNodes;
if (mHeight == std::numeric_limits<uint32_t>::max())
{
uint32_t minPowerOfTwo =
static_cast<uint32_t>(BitHacks::RoundUpToPowerOfTwo(mNumPoints));
mHeight = BitHacks::Log2OfPowerOfTwo(minPowerOfTwo);
numNodes = 2 * mNumPoints - 1;
}
else
{
// The maximum level cannot exceed 30 because we are storing
// the indices into the node array as 32-bit unsigned
// integers.
if (mHeight < 32)
{
numNodes = (uint32_t)(1ULL << (mHeight + 1)) - 1;
}
else
{
// When the precondition is not met, return a tree of
// height 0 (a single node).
mHeight = 0;
numNodes = 1;
}
}
// The tree is built recursively. A reference to a Node is
// passed to BuildTree and nodes are appended to a std::vector.
// Because the references are on the stack, we must guarantee no
// reallocations to avoid invalidating the references. TODO:
// This design can be modified to pass indices to the nodes so
// that reallocation is not a problem.
mTree.reserve(numNodes);
// Build the tree recursively. The array mPartition stores the
// indices into the 'points' array so that at a node, the points
// represented by the node are those indexed by the range
// [node.minIndex, node.maxIndex].
for (uint32_t i = 0; i < mNumPoints; ++i)
{
mPartition[i] = i;
}
mTree.push_back(Node());
BuildTree(mTree.back(), 0, mNumPoints - 1);
}
// Member access.
inline uint32_t GetNumPoints() const
{
return mNumPoints;
}
inline char const* GetPoints() const
{
return mPoints;
}
inline size_t GetStride() const
{
return mStride;
}
inline std::vector<Node> const& GetTree() const
{
return mTree;
}
inline uint32_t GetHeight() const
{
return mHeight;
}
inline std::vector<uint32_t> const& GetPartition() const
{
return mPartition;
}
private:
inline Vector3<Real> GetPosition(uint32_t index) const
{
return *reinterpret_cast<Vector3<Real> const*>(mPoints + index * mStride);
}
void BuildTree(Node& node, uint32_t i0, uint32_t i1)
{
node.minIndex = i0;
node.maxIndex = i1;
if (i0 == i1)
{
// We are at a leaf node. The left and right child indices
// were set to std::numeric_limits<uint32_t>::max() during
// construction.
// Create a degenerate box whose center is the point.
node.box.center = GetPosition(mPartition[i0]);
node.box.axis[0] = Vector3<Real>{ (Real)1, (Real)0, (Real)0 };
node.box.axis[1] = Vector3<Real>{ (Real)0, (Real)1, (Real)0 };
node.box.axis[2] = Vector3<Real>{ (Real)0, (Real)0, (Real)1 };
node.box.extent = Vector3<Real>{ (Real)0, (Real)0, (Real)0 };
}
else // i0 < i1
{
// We are at an interior node. Compute an oriented bounding
// box.
ComputeOBB(i0, i1, node.box);
if (node.depth == mHeight)
{
return;
}
// Use the box axis corresponding to largest extent for the
// splitting axis. Partition the points into two subsets, one
// for the left child and one for the right child. The subsets
// have numbers of elements that differ by at most 1, so the
// tree is balanced.
Vector3<Real> axis2 = node.box.axis[2];
uint32_t j0, j1;
SplitPoints(i0, i1, j0, j1, node.box.center, axis2);
node.leftChild = static_cast<uint32_t>(mTree.size());
node.rightChild = node.leftChild + 1;
mTree.push_back(Node());
Node& leftTree = mTree.back();
mTree.push_back(Node());
Node& rightTree = mTree.back();
leftTree.depth = node.depth + 1;
rightTree.depth = node.depth + 1;
BuildTree(leftTree, i0, j0);
BuildTree(rightTree, j1, i1);
}
}
void ComputeOBB(uint32_t i0, uint32_t i1, OrientedBox3<Real>& box)
{
// Compute the mean of the points.
Vector3<Real> zero{ (Real)0, (Real)0, (Real)0 };
box.center = zero;
for (uint32_t i = i0; i <= i1; ++i)
{
box.center += GetPosition(mPartition[i]);
}
Real invSize = ((Real)1) / (Real)(i1 - i0 + 1);
box.center *= invSize;
// Compute the covariance matrix of the points.
Real covar00 = (Real)0, covar01 = (Real)0, covar02 = (Real)0;
Real covar11 = (Real)0, covar12 = (Real)0, covar22 = (Real)0;
for (uint32_t i = i0; i <= i1; ++i)
{
Vector3<Real> diff = GetPosition(mPartition[i]) - box.center;
covar00 += diff[0] * diff[0];
covar01 += diff[0] * diff[1];
covar02 += diff[0] * diff[2];
covar11 += diff[1] * diff[1];
covar12 += diff[1] * diff[2];
covar22 += diff[2] * diff[2];
}
covar00 *= invSize;
covar01 *= invSize;
covar02 *= invSize;
covar11 *= invSize;
covar12 *= invSize;
covar22 *= invSize;
// Solve the eigensystem and use the eigenvectors for the box
// axes.
SymmetricEigensolver3x3<Real> es;
std::array<Real, 3> eval;
std::array<std::array<Real, 3>, 3> evec;
es(covar00, covar01, covar02, covar11, covar12, covar22, false, +1, eval, evec);
for (int i = 0; i < 3; ++i)
{
box.axis[i] = evec[i];
}
box.extent = eval;
// Let C be the box center and let U0, U1, and U2 be the box axes.
// Each input point is of the form X = C + y0*U0 + y1*U1 + y2*U2.
// The following code computes min(y0), max(y0), min(y1), max(y1),
// min(y2), and max(y2). The box center is then adjusted to be
// C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1
// + 0.5*(min(y2)+max(y2))*U2
Vector3<Real> pmin = zero, pmax = zero;
for (uint32_t i = i0; i <= i1; ++i)
{
Vector3<Real> diff = GetPosition(mPartition[i]) - box.center;
for (int j = 0; j < 3; ++j)
{
Real dot = Dot(diff, box.axis[j]);
if (dot < pmin[j])
{
pmin[j] = dot;
}
else if (dot > pmax[j])
{
pmax[j] = dot;
}
}
}
Real const half(0.5);
for (int j = 0; j < 3; ++j)
{
box.center += (half * (pmin[j] + pmax[j])) * box.axis[j];
box.extent[j] = half * (pmax[j] - pmin[j]);
}
}
void SplitPoints(uint32_t i0, uint32_t i1, uint32_t& j0, uint32_t& j1,
Vector3<Real> const& origin, Vector3<Real> const& direction)
{
// Project the points onto the splitting axis.
uint32_t numProjections = i1 - i0 + 1;
std::vector<ProjectionInfo> info(numProjections);
uint32_t i, k;
for (i = i0, k = 0; i <= i1; ++i, ++k)
{
Vector3<Real> diff = GetPosition(mPartition[i]) - origin;
info[k].pointIndex = mPartition[i];
info[k].projection = Dot(direction, diff);
}
// Partition the projections by the median.
uint32_t medianIndex = (numProjections - 1) / 2;
std::nth_element(info.begin(), info.begin() + medianIndex, info.end());
// Partition the points by the median.
for (k = 0, j0 = i0 - 1; k <= medianIndex; ++k)
{
mPartition[++j0] = info[k].pointIndex;
}
for (j1 = i1 + 1; k < numProjections; ++k)
{
mPartition[--j1] = info[k].pointIndex;
}
}
struct ProjectionInfo
{
ProjectionInfo()
:
pointIndex(0),
projection((Real)0)
{
}
bool operator< (ProjectionInfo const& info) const
{
return projection < info.projection;
}
uint32_t pointIndex;
Real projection;
};
uint32_t mNumPoints;
char const* mPoints;
size_t mStride;
uint32_t mHeight;
std::vector<Node> mTree;
std::vector<uint32_t> mPartition;
};
}