// 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 #include #include #include #include #include namespace gte { template 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 const* vertices, size_t numTetrahedra, std::array 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 const& regionMin, std::array const& regionMax, std::array const& bound, std::vector& 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 const& regionMin, std::array 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 const& regionMin, std::array const& regionMax, std::array const& bound) { std::array multiplier{}; for (size_t i = 0; i < 3; ++i) { multiplier[i] = static_cast(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(std::ceil( multiplier[i] * (tetraMin[i] - regionMin[i]))); gridTetraMax[i] = static_cast(std::floor( multiplier[i] * (tetraMax[i] - regionMin[i]))); } } } void SingleThreadedRasterizer(std::array const& bound, std::vector& grid) { for (size_t t = 0; t < mNumTetrahedra; ++t) { if (mValid[t]) { Rasterize(t, bound, grid); } } } void MultiThreadedRasterizer(size_t numThreads, std::array const& bound, std::vector& grid) { // Partition the data for multiple threads. size_t const numTetrahedraPerThread = mNumTetrahedra / numThreads; std::vector 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 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 const& bound, std::vector& grid) { auto const& imin = mGridTetraMin[t]; auto const& imax = mGridTetraMax[t]; std::array gridP{}; for (size_t i2 = imin[2]; i2 <= imax[2]; ++i2) { gridP[2] = static_cast(i2); for (size_t i1 = imin[1]; i1 <= imax[1]; ++i1) { gridP[1] = static_cast(i1); size_t i0min; for (i0min = imin[0]; i0min <= imax[0]; ++i0min) { gridP[0] = static_cast(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(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(t); for (size_t i0 = i0min, j = i0 + base; i0 <= i0max; ++i0, ++j) { grid[j] = tetrahedronIndex; } } } } static bool PointInTetrahedron(std::array const& P, std::array const& V0, std::array const& V1, std::array const& V2, std::array const& V3) { T const zero = static_cast(0); std::array PmV0 = Sub(P, V0); std::array V1mV0 = Sub(V1, V0); std::array V2mV0 = Sub(V2, V0); if (DotCross(PmV0, V2mV0, V1mV0) > zero) { return false; } std::array V3mV0 = Sub(V3, V0); if (DotCross(PmV0, V1mV0, V3mV0) > zero) { return false; } if (DotCross(PmV0, V3mV0, V2mV0) > zero) { return false; } std::array PmV1 = Sub(P, V1); std::array V2mV1 = Sub(V2, V1); std::array V3mV1 = Sub(V3, V1); if (DotCross(PmV1, V2mV1, V3mV1) > zero) { return false; } return true; } inline static std::array Sub(std::array const& U, std::array const& V) { std::array sub = { U[0] - V[0], U[1] - V[1], U[2] - V[2] }; return sub; } inline static T Dot(std::array const& U, std::array const& V) { T dot = U[0] * V[0] + U[1] * V[1] + U[2] * V[2]; return dot; } inline static std::array Cross(std::array const& U, std::array const& V) { std::array 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 const& U, std::array const& V, std::array const& W) { return Dot(U, Cross(V, W)); } // Constructor arguments. size_t mNumVertices; std::array const* mVertices; size_t mNumTetrahedra; std::array const* mTetrahedra; // Axis-aligned bounding boxes for the tetrahedra. std::vector> mTetraMin; std::vector> mTetraMax; std::vector mValid; // Vertices and axis-aligned bounding boxes in grid coordinates. std::vector> mGridVertices; std::vector> mGridTetraMin; std::vector> mGridTetraMax; }; }