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.
 
 

379 lines
14 KiB

// 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: 5.5.2020.12.18
#pragma once
#include <array>
#include <cmath>
#include <cstdint>
#include <stdexcept>
#include <thread>
#include <vector>
namespace gte
{
template <typename T>
class TetrahedraRasterizer
{
public:
// The tetrahedra are stored as indexed primitives where the indices
// are relative to the vertices[] array. The vertices of the
// tetrahedra are vertices[tetrahedra[t][i]] for 0 <= i < 4. If
// v0, v1, v2 and v3 are those vertices, the triangle faces have
// vertices {v0,v2,v1}, {v0,v1,v3}, {v0,v3,v2} and {v1,v2,v3}. The
// faces are counterclockwise ordered when viewed by an observer
// outside the tetrahedron. The canonical tetrahedron is
// {v0,v1,v2,v3} where v0 = (0,0,0), v1 = (1,0,0), v2 = (0,1,0) and
// v3 = (0,0,1).
TetrahedraRasterizer(size_t numVertices, std::array<T, 3> const* vertices,
size_t numTetrahedra, std::array<size_t, 4> const* tetrahedra)
:
mNumVertices(numVertices),
mVertices(vertices),
mNumTetrahedra(numTetrahedra),
mTetrahedra(tetrahedra),
mTetraMin(numTetrahedra),
mTetraMax(numTetrahedra),
mValid(numTetrahedra),
mGridVertices(numVertices),
mGridTetraMin(numTetrahedra),
mGridTetraMax(numTetrahedra)
{
if (numVertices == 0 || !vertices || numTetrahedra == 0 || !tetrahedra)
{
throw std::invalid_argument("Invalid argument.");
}
ComputeTetrahedraAABBs();
}
// Rasterize the tetrahedra into a 3D grid. The input region is a
// box in the coordinate system of the vertices. The box is associated
// with a 3D grid of specified bounds. A grid point is a 3-tuple
// (x,y,z) with integer coordinates satisfying 0 <= x < xBound,
// 0 <= y < yBound and 0 <= z < zBound. The point is classified based
// on whether or not it is contained by a tetrahedron, and the
// classification is stored as an integer in grid[i] where the index
// is i = x + bound[0] * (y + bound[1] * z). If the point is not
// contained by a tetrahedron, grid[i] is set to -1. If the point is
// contained by a tetrahedron, grid[i] is set to the tetrahedron
// index t where 0 <= t < numTetrahedra.
//
// To run in the main thread only, choose numThreads to be 0. For
// multithreading, choose numThreads > 0. The number of available
// hardware threads is std::thread::hardware_concurrency(). A
// reasonable choice for numThreads will not exceed the number of
// available hardware threads. You might want to keep 1 or 2 threads
// available for the operating system and other applications running
// on the machine.
void operator()(size_t numThreads, std::array<T, 3> const& regionMin,
std::array<T, 3> const& regionMax, std::array<size_t, 3> const& bound,
std::vector<int32_t>& grid)
{
if (bound[0] < 2 || bound[1] < 2 || bound[2] < 2)
{
throw std::invalid_argument("Invalid argument.");
}
// Initialize the grid values to -1. When a grid cell is contained
// in a tetrahedron, the index of that tetrahedron is stored in
// grid[i]. All such contained grid[] values are nonnegative.
grid.resize(bound[0] * bound[1] * bound[2]);
std::fill(grid.begin(), grid.end(), -1);
// Clip-cull the tetrahedra bounding boxes against the region.
ClipCullAABBs(regionMin, regionMax);
// Transform the vertices and tetrahedra bounding boxes to
// grid coordinates.
TransformToGridCoordinates(regionMin, regionMax, bound);
if (numThreads > 0)
{
MultiThreadedRasterizer(numThreads, bound, grid);
}
else
{
SingleThreadedRasterizer(bound, grid);
}
}
private:
// Compute the axis-aligned bounding boxes of the tetrahedra.
void ComputeTetrahedraAABBs()
{
for (size_t t = 0; t < mNumTetrahedra; ++t)
{
auto& tetraMin = mTetraMin[t];
auto& tetraMax = mTetraMax[t];
tetraMin = mVertices[mTetrahedra[t][0]];
tetraMax = tetraMin;
for (size_t j = 1; j < 4; ++j)
{
auto const& vertex = mVertices[mTetrahedra[t][j]];
for (size_t i = 0; i < 3; ++i)
{
if (vertex[i] < tetraMin[i])
{
tetraMin[i] = vertex[i];
}
else if (vertex[i] > tetraMax[i])
{
tetraMax[i] = vertex[i];
}
}
}
}
}
// Clip-cull the tetrahedra bounding boxes against the region.
// The mValid[t] is true whenever the mAABB[t] intersects the
// region.
void ClipCullAABBs(std::array<T, 3> const& regionMin,
std::array<T, 3> const& regionMax)
{
for (size_t t = 0; t < mNumTetrahedra; ++t)
{
auto& tetraMin = mTetraMin[t];
auto& tetraMax = mTetraMax[t];
mValid[t] = true;
for (size_t i = 0; i < 3; ++i)
{
tetraMin[i] = std::max(tetraMin[i], regionMin[i]);
tetraMax[i] = std::min(tetraMax[i], regionMax[i]);
if (tetraMin[i] > tetraMax[i])
{
mValid[t] = false;
}
}
}
}
void TransformToGridCoordinates(std::array<T, 3> const& regionMin,
std::array<T, 3> const& regionMax, std::array<size_t, 3> const& bound)
{
std::array<T, 3> multiplier{};
for (size_t i = 0; i < 3; ++i)
{
multiplier[i] = static_cast<T>(bound[i] - 1) / (regionMax[i] - regionMin[i]);
}
for (size_t v = 0; v < mNumVertices; ++v)
{
auto const& vertex = mVertices[v];
auto& gridVertex = mGridVertices[v];
for (size_t i = 0; i < 3; ++i)
{
gridVertex[i] = multiplier[i] * (vertex[i] - regionMin[i]);
}
}
for (size_t t = 0; t < mNumTetrahedra; ++t)
{
auto const& tetraMin = mTetraMin[t];
auto const& tetraMax = mTetraMax[t];
auto& gridTetraMin = mGridTetraMin[t];
auto& gridTetraMax = mGridTetraMax[t];
for (size_t i = 0; i < 3; ++i)
{
gridTetraMin[i] = static_cast<size_t>(std::ceil(
multiplier[i] * (tetraMin[i] - regionMin[i])));
gridTetraMax[i] = static_cast<size_t>(std::floor(
multiplier[i] * (tetraMax[i] - regionMin[i])));
}
}
}
void SingleThreadedRasterizer(std::array<size_t, 3> const& bound,
std::vector<int32_t>& grid)
{
for (size_t t = 0; t < mNumTetrahedra; ++t)
{
if (mValid[t])
{
Rasterize(t, bound, grid);
}
}
}
void MultiThreadedRasterizer(size_t numThreads,
std::array<size_t, 3> const& bound, std::vector<int32_t>& grid)
{
// Partition the data for multiple threads.
size_t const numTetrahedraPerThread = mNumTetrahedra / numThreads;
std::vector<size_t> nmin(numThreads), nsup(numThreads);
for (size_t k = 0; k < numThreads; ++k)
{
nmin[k] = k * numTetrahedraPerThread;
nsup[k] = (k + 1) * numTetrahedraPerThread;
}
nsup[numThreads - 1] = mNumTetrahedra;
std::vector<std::thread> process(numThreads);
for (size_t k = 0; k < numThreads; ++k)
{
process[k] = std::thread([this, k, &nmin, &nsup, &bound, &grid]()
{
for (size_t t = nmin[k]; t < nsup[k]; ++t)
{
if (mValid[t])
{
Rasterize(t, bound, grid);
}
}
});
}
for (size_t t = 0; t < numThreads; ++t)
{
process[t].join();
}
}
void Rasterize(size_t t, std::array<size_t, 3> const& bound,
std::vector<int32_t>& grid)
{
auto const& imin = mGridTetraMin[t];
auto const& imax = mGridTetraMax[t];
std::array<T, 3> gridP{};
for (size_t i2 = imin[2]; i2 <= imax[2]; ++i2)
{
gridP[2] = static_cast<T>(i2);
for (size_t i1 = imin[1]; i1 <= imax[1]; ++i1)
{
gridP[1] = static_cast<T>(i1);
size_t i0min;
for (i0min = imin[0]; i0min <= imax[0]; ++i0min)
{
gridP[0] = static_cast<T>(i0min);
if (PointInTetrahedron(gridP,
mGridVertices[mTetrahedra[t][0]],
mGridVertices[mTetrahedra[t][1]],
mGridVertices[mTetrahedra[t][2]],
mGridVertices[mTetrahedra[t][3]]))
{
break;
}
}
if (i0min > imax[0])
{
continue;
}
size_t i0max;
for (i0max = imax[0]; i0max >= i0min; --i0max)
{
gridP[0] = static_cast<T>(i0max);
if (PointInTetrahedron(gridP,
mGridVertices[mTetrahedra[t][0]],
mGridVertices[mTetrahedra[t][1]],
mGridVertices[mTetrahedra[t][2]],
mGridVertices[mTetrahedra[t][3]]))
{
break;
}
}
size_t const base = bound[0] * (i1 + bound[1] * i2);
int32_t const tetrahedronIndex = static_cast<int32_t>(t);
for (size_t i0 = i0min, j = i0 + base; i0 <= i0max; ++i0, ++j)
{
grid[j] = tetrahedronIndex;
}
}
}
}
static bool PointInTetrahedron(std::array<T, 3> const& P,
std::array<T, 3> const& V0, std::array<T, 3> const& V1,
std::array<T, 3> const& V2, std::array<T, 3> const& V3)
{
T const zero = static_cast<T>(0);
std::array<T, 3> PmV0 = Sub(P, V0);
std::array<T, 3> V1mV0 = Sub(V1, V0);
std::array<T, 3> V2mV0 = Sub(V2, V0);
if (DotCross(PmV0, V2mV0, V1mV0) > zero)
{
return false;
}
std::array<T, 3> V3mV0 = Sub(V3, V0);
if (DotCross(PmV0, V1mV0, V3mV0) > zero)
{
return false;
}
if (DotCross(PmV0, V3mV0, V2mV0) > zero)
{
return false;
}
std::array<T, 3> PmV1 = Sub(P, V1);
std::array<T, 3> V2mV1 = Sub(V2, V1);
std::array<T, 3> V3mV1 = Sub(V3, V1);
if (DotCross(PmV1, V2mV1, V3mV1) > zero)
{
return false;
}
return true;
}
inline static std::array<T, 3> Sub(std::array<T, 3> const& U,
std::array<T, 3> const& V)
{
std::array<T, 3> sub = { U[0] - V[0], U[1] - V[1], U[2] - V[2] };
return sub;
}
inline static T Dot(std::array<T, 3> const& U, std::array<T, 3> const& V)
{
T dot = U[0] * V[0] + U[1] * V[1] + U[2] * V[2];
return dot;
}
inline static std::array<T, 3> Cross(std::array<T, 3> const& U,
std::array<T, 3> const& V)
{
std::array<T, 3> cross =
{
U[1] * V[2] - U[2] * V[1],
U[2] * V[0] - U[0] * V[2],
U[0] * V[1] - U[1] * V[0]
};
return cross;
}
inline static T DotCross(std::array<T, 3> const& U, std::array<T, 3> const& V,
std::array<T, 3> const& W)
{
return Dot(U, Cross(V, W));
}
// Constructor arguments.
size_t mNumVertices;
std::array<T, 3> const* mVertices;
size_t mNumTetrahedra;
std::array<size_t, 4> const* mTetrahedra;
// Axis-aligned bounding boxes for the tetrahedra.
std::vector<std::array<T, 3>> mTetraMin;
std::vector<std::array<T, 3>> mTetraMax;
std::vector<bool> mValid;
// Vertices and axis-aligned bounding boxes in grid coordinates.
std::vector<std::array<T, 3>> mGridVertices;
std::vector<std::array<size_t, 3>> mGridTetraMin;
std::vector<std::array<size_t, 3>> mGridTetraMax;
};
}