Loading...
Searching...
No Matches
MeshToVolume.h
Go to the documentation of this file.
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: MPL-2.0
/// @file MeshToVolume.h
///
/// @brief Convert polygonal meshes that consist of quads and/or triangles
/// into signed or unsigned distance field volumes.
///
/// @note The signed distance field conversion requires a closed surface
/// but not necessarily a manifold surface. Supports surfaces with
/// self intersections and degenerate faces and is independent of
/// mesh surface normals / polygon orientation.
///
/// @author Mihai Alden
#ifndef OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED
#define OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED
#include <openvdb/Platform.h> // for OPENVDB_HAS_CXX11
#include <openvdb/Types.h>
#include <openvdb/math/FiniteDifference.h> // for GodunovsNormSqrd
#include <openvdb/math/Proximity.h> // for closestPointOnTriangleToPoint
#include <openvdb/util/NullInterrupter.h>
#include <openvdb/util/Util.h>
#include <openvdb/thread/Threading.h>
#include <openvdb/openvdb.h>
#include "ChangeBackground.h"
#include "Prune.h" // for pruneInactive and pruneLevelSet
#include "SignedFloodFill.h" // for signedFloodFillWithValues
#include <tbb/blocked_range.h>
#include <tbb/enumerable_thread_specific.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <tbb/partitioner.h>
#include <tbb/task_group.h>
#include <tbb/task_arena.h>
#include <algorithm> // for std::sort()
#include <cmath> // for std::isfinite(), std::isnan()
#include <deque>
#include <limits>
#include <memory>
#include <sstream>
#include <type_traits>
#include <vector>
namespace openvdb {
OPENVDB_USE_VERSION_NAMESPACE
namespace OPENVDB_VERSION_NAME {
namespace tools {
////////////////////////////////////////
/// @brief Mesh to volume conversion flags
enum MeshToVolumeFlags {
/// Switch from the default signed distance field conversion that classifies
/// regions as either inside or outside the mesh boundary to a unsigned distance
/// field conversion that only computes distance values. This conversion type
/// does not require a closed watertight mesh.
UNSIGNED_DISTANCE_FIELD = 0x1,
/// Disable the cleanup step that removes voxels created by self intersecting
/// portions of the mesh.
DISABLE_INTERSECTING_VOXEL_REMOVAL = 0x2,
/// Disable the distance renormalization step that smooths out bumps caused
/// by self intersecting or overlapping portions of the mesh
DISABLE_RENORMALIZATION = 0x4,
/// Disable the cleanup step that removes active voxels that exceed the
/// narrow band limits. (Only relevant for small limits)
DISABLE_NARROW_BAND_TRIMMING = 0x8
};
/// @brief Different staregies how to determine sign of an SDF when using
/// interior test.
enum InteriorTestStrategy {
/// Evaluates interior test at every voxel. This is usefull when we rebuild already
/// existing SDF where evaluating previous grid is cheap
EVAL_EVERY_VOXEL = 0,
/// Evaluates interior test at least once per tile and flood fills within the tile.
EVAL_EVERY_TILE = 1,
};
/// @brief Convert polygonal meshes that consist of quads and/or triangles into
/// signed or unsigned distance field volumes.
///
/// @note Requires a closed surface but not necessarily a manifold surface.
/// Supports surfaces with self intersections and degenerate faces
/// and is independent of mesh surface normals.
///
/// @interface MeshDataAdapter
/// Expected interface for the MeshDataAdapter class
/// @code
/// struct MeshDataAdapter {
/// size_t polygonCount() const; // Total number of polygons
/// size_t pointCount() const; // Total number of points
/// size_t vertexCount(size_t n) const; // Vertex count for polygon n
///
/// // Return position pos in local grid index space for polygon n and vertex v
/// void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
/// };
/// @endcode
///
/// @param mesh mesh data access class that conforms to the MeshDataAdapter
/// interface
/// @param transform world-to-index-space transform
/// @param exteriorBandWidth exterior narrow band width in voxel units
/// @param interiorBandWidth interior narrow band width in voxel units
/// (set to std::numeric_limits<float>::max() to fill object
/// interior with distance values)
/// @param flags optional conversion flags defined in @c MeshToVolumeFlags
/// @param polygonIndexGrid optional grid output that will contain the closest-polygon
/// index for each voxel in the narrow band region
/// @param interiorTest function `Coord -> Bool` that evaluates to true inside of the
/// mesh and false outside, for more see evaluateInteriortest
/// @param interiorTestStrat determines how the interiorTest is used, see InteriorTestStrategy
template <typename GridType, typename MeshDataAdapter, typename InteriorTest = std::nullptr_t>
typename GridType::Ptr
meshToVolume(
const MeshDataAdapter& mesh,
const math::Transform& transform,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f,
int flags = 0,
typename GridType::template ValueConverter<Int32>::Type * polygonIndexGrid = nullptr,
InteriorTest interiorTest = nullptr,
InteriorTestStrategy interiorTestStrat = EVAL_EVERY_VOXEL);
/// @brief Convert polygonal meshes that consist of quads and/or triangles into
/// signed or unsigned distance field volumes.
///
/// @param interrupter a callback to interrupt the conversion process that conforms
/// to the util::NullInterrupter interface
/// @param mesh mesh data access class that conforms to the MeshDataAdapter
/// interface
/// @param transform world-to-index-space transform
/// @param exteriorBandWidth exterior narrow band width in voxel units
/// @param interiorBandWidth interior narrow band width in voxel units (set this value to
/// std::numeric_limits<float>::max() to fill interior regions
/// with distance values)
/// @param flags optional conversion flags defined in @c MeshToVolumeFlags
/// @param polygonIndexGrid optional grid output that will contain the closest-polygon
/// index for each voxel in the active narrow band region
/// @param interiorTest function `Coord -> Bool` that evaluates to true inside of the
/// mesh and false outside, for more see evaluatInteriorTest
/// @param interiorTestStrat determines how the interiorTest is used, see InteriorTestStrategy
template <typename GridType, typename MeshDataAdapter, typename Interrupter, typename InteriorTest = std::nullptr_t>
typename GridType::Ptr
meshToVolume(
Interrupter& interrupter,
const MeshDataAdapter& mesh,
const math::Transform& transform,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f,
int flags = 0,
typename GridType::template ValueConverter<Int32>::Type * polygonIndexGrid = nullptr,
InteriorTest interiorTest = nullptr,
InteriorTestStrategy interiorTestStrategy = EVAL_EVERY_VOXEL);
////////////////////////////////////////
/// @brief Contiguous quad and triangle data adapter class
///
/// @details PointType and PolygonType must provide element access
/// through the square brackets operator.
/// @details Points are assumed to be in local grid index space.
/// @details The PolygonType tuple can have either three or four components
/// this property must be specified in a static member variable
/// named @c size, similar to the math::Tuple class.
/// @details A four component tuple can represent a quads or a triangle
/// if the fourth component set to @c util::INVALID_INDEX
template<typename PointType, typename PolygonType>
struct QuadAndTriangleDataAdapter {
QuadAndTriangleDataAdapter(const std::vector<PointType>& points,
const std::vector<PolygonType>& polygons)
: mPointArray(points.empty() ? nullptr : &points[0])
, mPointArraySize(points.size())
, mPolygonArray(polygons.empty() ? nullptr : &polygons[0])
, mPolygonArraySize(polygons.size())
{
}
QuadAndTriangleDataAdapter(const PointType * pointArray, size_t pointArraySize,
const PolygonType* polygonArray, size_t polygonArraySize)
: mPointArray(pointArray)
, mPointArraySize(pointArraySize)
, mPolygonArray(polygonArray)
, mPolygonArraySize(polygonArraySize)
{
}
size_t polygonCount() const { return mPolygonArraySize; }
size_t pointCount() const { return mPointArraySize; }
/// @brief Vertex count for polygon @a n
size_t vertexCount(size_t n) const {
return (PolygonType::size == 3 || mPolygonArray[n][3] == util::INVALID_IDX) ? 3 : 4;
}
/// @brief Returns position @a pos in local grid index space
/// for polygon @a n and vertex @a v
void getIndexSpacePoint(size_t n, size_t v, Vec3d& pos) const {
const PointType& p = mPointArray[mPolygonArray[n][int(v)]];
pos[0] = double(p[0]);
pos[1] = double(p[1]);
pos[2] = double(p[2]);
}
private:
PointType const * const mPointArray;
size_t const mPointArraySize;
PolygonType const * const mPolygonArray;
size_t const mPolygonArraySize;
}; // struct QuadAndTriangleDataAdapter
////////////////////////////////////////
// Convenience functions for the mesh to volume converter that wrap stl containers.
//
// Note the meshToVolume() method declared above is more flexible and better suited
// for arbitrary data structures.
/// @brief Convert a triangle mesh to a level set volume.
///
/// @return a grid of type @c GridType containing a narrow-band level set
/// representation of the input mesh.
///
/// @throw TypeError if @c GridType is not scalar or not floating-point
///
/// @note Requires a closed surface but not necessarily a manifold surface.
/// Supports surfaces with self intersections and degenerate faces
/// and is independent of mesh surface normals.
///
/// @param xform transform for the output grid
/// @param points list of world space point positions
/// @param triangles triangle index list
/// @param halfWidth half the width of the narrow band, in voxel units
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// Adds support for a @a interrupter callback used to cancel the conversion.
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// @brief Convert a quad mesh to a level set volume.
///
/// @return a grid of type @c GridType containing a narrow-band level set
/// representation of the input mesh.
///
/// @throw TypeError if @c GridType is not scalar or not floating-point
///
/// @note Requires a closed surface but not necessarily a manifold surface.
/// Supports surfaces with self intersections and degenerate faces
/// and is independent of mesh surface normals.
///
/// @param xform transform for the output grid
/// @param points list of world space point positions
/// @param quads quad index list
/// @param halfWidth half the width of the narrow band, in voxel units
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec4I>& quads,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// Adds support for a @a interrupter callback used to cancel the conversion.
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec4I>& quads,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// @brief Convert a triangle and quad mesh to a level set volume.
///
/// @return a grid of type @c GridType containing a narrow-band level set
/// representation of the input mesh.
///
/// @throw TypeError if @c GridType is not scalar or not floating-point
///
/// @note Requires a closed surface but not necessarily a manifold surface.
/// Supports surfaces with self intersections and degenerate faces
/// and is independent of mesh surface normals.
///
/// @param xform transform for the output grid
/// @param points list of world space point positions
/// @param triangles triangle index list
/// @param quads quad index list
/// @param halfWidth half the width of the narrow band, in voxel units
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// Adds support for a @a interrupter callback used to cancel the conversion.
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float halfWidth = float(LEVEL_SET_HALF_WIDTH));
/// @brief Convert a triangle and quad mesh to a signed distance field
/// with an asymmetrical narrow band.
///
/// @return a grid of type @c GridType containing a narrow-band signed
/// distance field representation of the input mesh.
///
/// @throw TypeError if @c GridType is not scalar or not floating-point
///
/// @note Requires a closed surface but not necessarily a manifold surface.
/// Supports surfaces with self intersections and degenerate faces
/// and is independent of mesh surface normals.
///
/// @param xform transform for the output grid
/// @param points list of world space point positions
/// @param triangles triangle index list
/// @param quads quad index list
/// @param exBandWidth the exterior narrow-band width in voxel units
/// @param inBandWidth the interior narrow-band width in voxel units
template<typename GridType>
typename GridType::Ptr
meshToSignedDistanceField(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float exBandWidth,
float inBandWidth);
/// Adds support for a @a interrupter callback used to cancel the conversion.
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToSignedDistanceField(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float exBandWidth,
float inBandWidth);
/// @brief Convert a triangle and quad mesh to an unsigned distance field.
///
/// @return a grid of type @c GridType containing a narrow-band unsigned
/// distance field representation of the input mesh.
///
/// @throw TypeError if @c GridType is not scalar or not floating-point
///
/// @note Does not requires a closed surface.
///
/// @param xform transform for the output grid
/// @param points list of world space point positions
/// @param triangles triangle index list
/// @param quads quad index list
/// @param bandWidth the width of the narrow band, in voxel units
template<typename GridType>
typename GridType::Ptr
meshToUnsignedDistanceField(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float bandWidth);
/// Adds support for a @a interrupter callback used to cancel the conversion.
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToUnsignedDistanceField(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float bandWidth);
////////////////////////////////////////
/// @brief Return a grid of type @c GridType containing a narrow-band level set
/// representation of a box.
///
/// @param bbox a bounding box in world units
/// @param xform world-to-index-space transform
/// @param halfWidth half the width of the narrow band, in voxel units
template<typename GridType, typename VecType>
typename GridType::Ptr
createLevelSetBox(const math::BBox<VecType>& bbox,
const openvdb::math::Transform& xform,
typename VecType::ValueType halfWidth = LEVEL_SET_HALF_WIDTH);
////////////////////////////////////////
/// @brief Traces the exterior voxel boundary of closed objects in the input
/// volume @a tree. Exterior voxels are marked with a negative sign,
/// voxels with a value below @c 0.75 are left unchanged and act as
/// the boundary layer.
///
/// @note Does not propagate sign information into tile regions.
template <typename FloatTreeT>
void
traceExteriorBoundaries(FloatTreeT& tree);
////////////////////////////////////////
/// @brief Extracts and stores voxel edge intersection data from a mesh.
class MeshToVoxelEdgeData
{
public:
//////////
///@brief Internal edge data type.
struct EdgeData {
EdgeData(float dist = 1.0)
: mXDist(dist), mYDist(dist), mZDist(dist)
, mXPrim(util::INVALID_IDX)
, mYPrim(util::INVALID_IDX)
, mZPrim(util::INVALID_IDX)
{
}
//@{
/// Required by several of the tree nodes
/// @note These methods don't perform meaningful operations.
bool operator< (const EdgeData&) const { return false; }
bool operator> (const EdgeData&) const { return false; }
template<class T> EdgeData operator+(const T&) const { return *this; }
template<class T> EdgeData operator-(const T&) const { return *this; }
EdgeData operator-() const { return *this; }
//@}
bool operator==(const EdgeData& rhs) const
{
return mXPrim == rhs.mXPrim && mYPrim == rhs.mYPrim && mZPrim == rhs.mZPrim;
}
float mXDist, mYDist, mZDist;
Index32 mXPrim, mYPrim, mZPrim;
};
using TreeType = tree::Tree4<EdgeData, 5, 4, 3>::Type;
using Accessor = tree::ValueAccessor<TreeType>;
//////////
MeshToVoxelEdgeData();
/// @brief Threaded method to extract voxel edge data, the closest
/// intersection point and corresponding primitive index,
/// from the given mesh.
///
/// @param pointList List of points in grid index space, preferably unique
/// and shared by different polygons.
/// @param polygonList List of triangles and/or quads.
void convert(const std::vector<Vec3s>& pointList, const std::vector<Vec4I>& polygonList);
/// @brief Returns intersection points with corresponding primitive
/// indices for the given @c ijk voxel.
void getEdgeData(Accessor& acc, const Coord& ijk,
std::vector<Vec3d>& points, std::vector<Index32>& primitives);
/// @return An accessor of @c MeshToVoxelEdgeData::Accessor type that
/// provides random read access to the internal tree.
Accessor getAccessor() { return Accessor(mTree); }
private:
void operator=(const MeshToVoxelEdgeData&) {}
TreeType mTree;
class GenEdgeData;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Internal utility objects and implementation details
/// @cond OPENVDB_DOCS_INTERNAL
namespace mesh_to_volume_internal {
template<typename PointType>
struct TransformPoints {
TransformPoints(const PointType* pointsIn, PointType* pointsOut,
const math::Transform& xform)
: mPointsIn(pointsIn), mPointsOut(pointsOut), mXform(&xform)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
Vec3d pos;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
const PointType& wsP = mPointsIn[n];
pos[0] = double(wsP[0]);
pos[1] = double(wsP[1]);
pos[2] = double(wsP[2]);
pos = mXform->worldToIndex(pos);
PointType& isP = mPointsOut[n];
isP[0] = typename PointType::value_type(pos[0]);
isP[1] = typename PointType::value_type(pos[1]);
isP[2] = typename PointType::value_type(pos[2]);
}
}
PointType const * const mPointsIn;
PointType * const mPointsOut;
math::Transform const * const mXform;
}; // TransformPoints
template<typename ValueType>
struct Tolerance
{
static ValueType epsilon() { return ValueType(1e-7); }
static ValueType minNarrowBandWidth() { return ValueType(1.0 + 1e-6); }
};
////////////////////////////////////////
template<typename TreeType>
class CombineLeafNodes
{
public:
using Int32TreeType = typename TreeType::template ValueConverter<Int32>::Type;
using LeafNodeType = typename TreeType::LeafNodeType;
using Int32LeafNodeType = typename Int32TreeType::LeafNodeType;
CombineLeafNodes(TreeType& lhsDistTree, Int32TreeType& lhsIdxTree,
LeafNodeType ** rhsDistNodes, Int32LeafNodeType ** rhsIdxNodes)
: mDistTree(&lhsDistTree)
, mIdxTree(&lhsIdxTree)
, mRhsDistNodes(rhsDistNodes)
, mRhsIdxNodes(rhsIdxNodes)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
tree::ValueAccessor<TreeType> distAcc(*mDistTree);
tree::ValueAccessor<Int32TreeType> idxAcc(*mIdxTree);
using DistValueType = typename LeafNodeType::ValueType;
using IndexValueType = typename Int32LeafNodeType::ValueType;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
const Coord& origin = mRhsDistNodes[n]->origin();
LeafNodeType* lhsDistNode = distAcc.probeLeaf(origin);
Int32LeafNodeType* lhsIdxNode = idxAcc.probeLeaf(origin);
DistValueType* lhsDistData = lhsDistNode->buffer().data();
IndexValueType* lhsIdxData = lhsIdxNode->buffer().data();
const DistValueType* rhsDistData = mRhsDistNodes[n]->buffer().data();
const IndexValueType* rhsIdxData = mRhsIdxNodes[n]->buffer().data();
for (Index32 offset = 0; offset < LeafNodeType::SIZE; ++offset) {
if (rhsIdxData[offset] != Int32(util::INVALID_IDX)) {
const DistValueType& lhsValue = lhsDistData[offset];
const DistValueType& rhsValue = rhsDistData[offset];
if (rhsValue < lhsValue) {
lhsDistNode->setValueOn(offset, rhsValue);
lhsIdxNode->setValueOn(offset, rhsIdxData[offset]);
} else if (math::isExactlyEqual(rhsValue, lhsValue)) {
lhsIdxNode->setValueOn(offset,
std::min(lhsIdxData[offset], rhsIdxData[offset]));
}
}
}
delete mRhsDistNodes[n];
delete mRhsIdxNodes[n];
}
}
private:
TreeType * const mDistTree;
Int32TreeType * const mIdxTree;
LeafNodeType ** const mRhsDistNodes;
Int32LeafNodeType ** const mRhsIdxNodes;
}; // class CombineLeafNodes
////////////////////////////////////////
template<typename TreeType>
struct StashOriginAndStoreOffset
{
using LeafNodeType = typename TreeType::LeafNodeType;
StashOriginAndStoreOffset(std::vector<LeafNodeType*>& nodes, Coord* coordinates)
: mNodes(nodes.empty() ? nullptr : &nodes[0]), mCoordinates(coordinates)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
Coord& origin = const_cast<Coord&>(mNodes[n]->origin());
mCoordinates[n] = origin;
origin[0] = static_cast<int>(n);
}
}
LeafNodeType ** const mNodes;
Coord * const mCoordinates;
};
template<typename TreeType>
struct RestoreOrigin
{
using LeafNodeType = typename TreeType::LeafNodeType;
RestoreOrigin(std::vector<LeafNodeType*>& nodes, const Coord* coordinates)
: mNodes(nodes.empty() ? nullptr : &nodes[0]), mCoordinates(coordinates)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
Coord& origin = const_cast<Coord&>(mNodes[n]->origin());
origin[0] = mCoordinates[n][0];
}
}
LeafNodeType ** const mNodes;
Coord const * const mCoordinates;
};
template<typename TreeType>
class ComputeNodeConnectivity
{
public:
using LeafNodeType = typename TreeType::LeafNodeType;
ComputeNodeConnectivity(const TreeType& tree, const Coord* coordinates,
size_t* offsets, size_t numNodes, const CoordBBox& bbox)
: mTree(&tree)
, mCoordinates(coordinates)
, mOffsets(offsets)
, mNumNodes(numNodes)
, mBBox(bbox)
{
}
ComputeNodeConnectivity(const ComputeNodeConnectivity&) = default;
// Disallow assignment
ComputeNodeConnectivity& operator=(const ComputeNodeConnectivity&) = delete;
void operator()(const tbb::blocked_range<size_t>& range) const {
size_t* offsetsNextX = mOffsets;
size_t* offsetsPrevX = mOffsets + mNumNodes;
size_t* offsetsNextY = mOffsets + mNumNodes * 2;
size_t* offsetsPrevY = mOffsets + mNumNodes * 3;
size_t* offsetsNextZ = mOffsets + mNumNodes * 4;
size_t* offsetsPrevZ = mOffsets + mNumNodes * 5;
tree::ValueAccessor<const TreeType> acc(*mTree);
Coord ijk;
const Int32 DIM = static_cast< Int32 >(LeafNodeType::DIM);
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
const Coord& origin = mCoordinates[n];
offsetsNextX[n] = findNeighbourNode(acc, origin, Coord(DIM, 0, 0));
offsetsPrevX[n] = findNeighbourNode(acc, origin, Coord(-DIM, 0, 0));
offsetsNextY[n] = findNeighbourNode(acc, origin, Coord(0, DIM, 0));
offsetsPrevY[n] = findNeighbourNode(acc, origin, Coord(0, -DIM, 0));
offsetsNextZ[n] = findNeighbourNode(acc, origin, Coord(0, 0, DIM));
offsetsPrevZ[n] = findNeighbourNode(acc, origin, Coord(0, 0, -DIM));
}
}
size_t findNeighbourNode(tree::ValueAccessor<const TreeType>& acc,
const Coord& start, const Coord& step) const
{
Coord ijk = start + step;
CoordBBox bbox(mBBox);
while (bbox.isInside(ijk)) {
const LeafNodeType* node = acc.probeConstLeaf(ijk);
if (node) return static_cast<size_t>(node->origin()[0]);
ijk += step;
}
return std::numeric_limits<size_t>::max();
}
private:
TreeType const * const mTree;
Coord const * const mCoordinates;
size_t * const mOffsets;
const size_t mNumNodes;
const CoordBBox mBBox;
}; // class ComputeNodeConnectivity
template<typename TreeType>
struct LeafNodeConnectivityTable
{
enum { INVALID_OFFSET = std::numeric_limits<size_t>::max() };
using LeafNodeType = typename TreeType::LeafNodeType;
LeafNodeConnectivityTable(TreeType& tree)
{
mLeafNodes.reserve(tree.leafCount());
tree.getNodes(mLeafNodes);
if (mLeafNodes.empty()) return;
CoordBBox bbox;
tree.evalLeafBoundingBox(bbox);
const tbb::blocked_range<size_t> range(0, mLeafNodes.size());
// stash the leafnode origin coordinate and temporarily store the
// linear offset in the origin.x variable.
std::unique_ptr<Coord[]> coordinates{new Coord[mLeafNodes.size()]};
tbb::parallel_for(range,
StashOriginAndStoreOffset<TreeType>(mLeafNodes, coordinates.get()));
// build the leafnode offset table
mOffsets.reset(new size_t[mLeafNodes.size() * 6]);
tbb::parallel_for(range, ComputeNodeConnectivity<TreeType>(
tree, coordinates.get(), mOffsets.get(), mLeafNodes.size(), bbox));
// restore the leafnode origin coordinate
tbb::parallel_for(range, RestoreOrigin<TreeType>(mLeafNodes, coordinates.get()));
}
size_t size() const { return mLeafNodes.size(); }
std::vector<LeafNodeType*>& nodes() { return mLeafNodes; }
const std::vector<LeafNodeType*>& nodes() const { return mLeafNodes; }
const size_t* offsetsNextX() const { return mOffsets.get(); }
const size_t* offsetsPrevX() const { return mOffsets.get() + mLeafNodes.size(); }
const size_t* offsetsNextY() const { return mOffsets.get() + mLeafNodes.size() * 2; }
const size_t* offsetsPrevY() const { return mOffsets.get() + mLeafNodes.size() * 3; }
const size_t* offsetsNextZ() const { return mOffsets.get() + mLeafNodes.size() * 4; }
const size_t* offsetsPrevZ() const { return mOffsets.get() + mLeafNodes.size() * 5; }
private:
std::vector<LeafNodeType*> mLeafNodes;
std::unique_ptr<size_t[]> mOffsets;
}; // struct LeafNodeConnectivityTable
template<typename TreeType>
class SweepExteriorSign
{
public:
enum Axis { X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2 };
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
using ConnectivityTable = LeafNodeConnectivityTable<TreeType>;
SweepExteriorSign(Axis axis, const std::vector<size_t>& startNodeIndices,
ConnectivityTable& connectivity)
: mStartNodeIndices(startNodeIndices.empty() ? nullptr : &startNodeIndices[0])
, mConnectivity(&connectivity)
, mAxis(axis)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
constexpr Int32 DIM = static_cast< Int32 >(LeafNodeType::DIM);
std::vector<LeafNodeType*>& nodes = mConnectivity->nodes();
// Z Axis
size_t idxA = 0, idxB = 1;
Int32 step = 1;
const size_t* nextOffsets = mConnectivity->offsetsNextZ();
const size_t* prevOffsets = mConnectivity->offsetsPrevZ();
if (mAxis == Y_AXIS) {
idxA = 0;
idxB = 2;
step = DIM;
nextOffsets = mConnectivity->offsetsNextY();
prevOffsets = mConnectivity->offsetsPrevY();
} else if (mAxis == X_AXIS) {
idxA = 1;
idxB = 2;
step = DIM*DIM;
nextOffsets = mConnectivity->offsetsNextX();
prevOffsets = mConnectivity->offsetsPrevX();
}
Coord ijk(0, 0, 0);
Int32& a = ijk[idxA];
Int32& b = ijk[idxB];
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
size_t startOffset = mStartNodeIndices[n];
size_t lastOffset = startOffset;
Int32 pos(0);
for (a = 0; a < DIM; ++a) {
for (b = 0; b < DIM; ++b) {
pos = static_cast< Int32 >(LeafNodeType::coordToOffset(ijk));
size_t offset = startOffset;
// sweep in +axis direction until a boundary voxel is hit.
while ( offset != ConnectivityTable::INVALID_OFFSET &&
traceVoxelLine(*nodes[offset], pos, step) ) {
lastOffset = offset;
offset = nextOffsets[offset];
}
// find last leafnode in +axis direction
offset = lastOffset;
while (offset != ConnectivityTable::INVALID_OFFSET) {
lastOffset = offset;
offset = nextOffsets[offset];
}
// sweep in -axis direction until a boundary voxel is hit.
offset = lastOffset;
pos += step * (DIM - 1);
while ( offset != ConnectivityTable::INVALID_OFFSET &&
traceVoxelLine(*nodes[offset], pos, -step)) {
offset = prevOffsets[offset];
}
}
}
}
}
bool traceVoxelLine(LeafNodeType& node, Int32 pos, const Int32 step) const {
ValueType* data = node.buffer().data();
bool isOutside = true;
for (Index i = 0; i < LeafNodeType::DIM; ++i) {
assert(pos >= 0);
ValueType& dist = data[pos];
if (dist < ValueType(0.0)) {
isOutside = true;
} else {
// Boundary voxel check. (Voxel that intersects the surface)
if (!(dist > ValueType(0.75))) isOutside = false;
if (isOutside) dist = ValueType(-dist);
}
pos += step;
}
return isOutside;
}
private:
size_t const * const mStartNodeIndices;
ConnectivityTable * const mConnectivity;
const Axis mAxis;
}; // class SweepExteriorSign
template<typename LeafNodeType>
inline void
seedFill(LeafNodeType& node)
{
using ValueType = typename LeafNodeType::ValueType;
using Queue = std::deque<Index>;
ValueType* data = node.buffer().data();
// find seed points
Queue seedPoints;
for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) {
if (data[pos] < 0.0) seedPoints.push_back(pos);
}
if (seedPoints.empty()) return;
// clear sign information
for (Queue::iterator it = seedPoints.begin(); it != seedPoints.end(); ++it) {
ValueType& dist = data[*it];
dist = -dist;
}
// flood fill
Coord ijk(0, 0, 0);
Index pos(0), nextPos(0);
while (!seedPoints.empty()) {
pos = seedPoints.back();
seedPoints.pop_back();
ValueType& dist = data[pos];
if (!(dist < ValueType(0.0))) {
dist = -dist; // flip sign
ijk = LeafNodeType::offsetToLocalCoord(pos);
if (ijk[0] != 0) { // i - 1, j, k
nextPos = pos - LeafNodeType::DIM * LeafNodeType::DIM;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
if (ijk[0] != (LeafNodeType::DIM - 1)) { // i + 1, j, k
nextPos = pos + LeafNodeType::DIM * LeafNodeType::DIM;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
if (ijk[1] != 0) { // i, j - 1, k
nextPos = pos - LeafNodeType::DIM;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
if (ijk[1] != (LeafNodeType::DIM - 1)) { // i, j + 1, k
nextPos = pos + LeafNodeType::DIM;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
if (ijk[2] != 0) { // i, j, k - 1
nextPos = pos - 1;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
if (ijk[2] != (LeafNodeType::DIM - 1)) { // i, j, k + 1
nextPos = pos + 1;
if (data[nextPos] > ValueType(0.75)) seedPoints.push_back(nextPos);
}
}
}
} // seedFill()
template<typename LeafNodeType>
inline bool
scanFill(LeafNodeType& node)
{
bool updatedNode = false;
using ValueType = typename LeafNodeType::ValueType;
ValueType* data = node.buffer().data();
Coord ijk(0, 0, 0);
bool updatedSign = true;
while (updatedSign) {
updatedSign = false;
for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) {
ValueType& dist = data[pos];
if (!(dist < ValueType(0.0)) && dist > ValueType(0.75)) {
ijk = LeafNodeType::offsetToLocalCoord(pos);
// i, j, k - 1
if (ijk[2] != 0 && data[pos - 1] < ValueType(0.0)) {
updatedSign = true;
dist = ValueType(-dist);
// i, j, k + 1
} else if (ijk[2] != (LeafNodeType::DIM - 1) && data[pos + 1] < ValueType(0.0)) {
updatedSign = true;
dist = ValueType(-dist);
// i, j - 1, k
} else if (ijk[1] != 0 && data[pos - LeafNodeType::DIM] < ValueType(0.0)) {
updatedSign = true;
dist = ValueType(-dist);
// i, j + 1, k
} else if (ijk[1] != (LeafNodeType::DIM - 1)
&& data[pos + LeafNodeType::DIM] < ValueType(0.0))
{
updatedSign = true;
dist = ValueType(-dist);
// i - 1, j, k
} else if (ijk[0] != 0
&& data[pos - LeafNodeType::DIM * LeafNodeType::DIM] < ValueType(0.0))
{
updatedSign = true;
dist = ValueType(-dist);
// i + 1, j, k
} else if (ijk[0] != (LeafNodeType::DIM - 1)
&& data[pos + LeafNodeType::DIM * LeafNodeType::DIM] < ValueType(0.0))
{
updatedSign = true;
dist = ValueType(-dist);
}
}
} // end value loop
updatedNode |= updatedSign;
} // end update loop
return updatedNode;
} // scanFill()
template<typename TreeType>
class SeedFillExteriorSign
{
public:
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
SeedFillExteriorSign(std::vector<LeafNodeType*>& nodes, const bool* changedNodeMask)
: mNodes(nodes.empty() ? nullptr : &nodes[0])
, mChangedNodeMask(changedNodeMask)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
if (mChangedNodeMask[n]) {
//seedFill(*mNodes[n]);
// Do not update the flag in mChangedNodeMask even if scanFill
// returns false. mChangedNodeMask is queried by neighboring
// accesses in ::SeedPoints which needs to know that this
// node has values propagated on a previous iteration.
scanFill(*mNodes[n]);
}
}
}
LeafNodeType ** const mNodes;
const bool * const mChangedNodeMask;
};
template<typename ValueType>
struct FillArray
{
FillArray(ValueType* array, const ValueType v) : mArray(array), mValue(v) { }
void operator()(const tbb::blocked_range<size_t>& range) const {
const ValueType v = mValue;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
mArray[n] = v;
}
}
ValueType * const mArray;
const ValueType mValue;
};
template<typename ValueType>
inline void
fillArray(ValueType* array, const ValueType val, const size_t length)
{
const auto grainSize = std::max<size_t>(
length / tbb::this_task_arena::max_concurrency(), 1024);
const tbb::blocked_range<size_t> range(0, length, grainSize);
tbb::parallel_for(range, FillArray<ValueType>(array, val), tbb::simple_partitioner());
}
template<typename TreeType>
class SyncVoxelMask
{
public:
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
SyncVoxelMask(std::vector<LeafNodeType*>& nodes,
const bool* changedNodeMask, bool* changedVoxelMask)
: mNodes(nodes.empty() ? nullptr : &nodes[0])
, mChangedNodeMask(changedNodeMask)
, mChangedVoxelMask(changedVoxelMask)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
if (mChangedNodeMask[n]) {
bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE];
ValueType* data = mNodes[n]->buffer().data();
for (Index pos = 0; pos < LeafNodeType::SIZE; ++pos) {
if (mask[pos]) {
data[pos] = ValueType(-data[pos]);
mask[pos] = false;
}
}
}
}
}
LeafNodeType ** const mNodes;
bool const * const mChangedNodeMask;
bool * const mChangedVoxelMask;
};
template<typename TreeType>
class SeedPoints
{
public:
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
using ConnectivityTable = LeafNodeConnectivityTable<TreeType>;
SeedPoints(ConnectivityTable& connectivity,
bool* changedNodeMask, bool* nodeMask, bool* changedVoxelMask)
: mConnectivity(&connectivity)
, mChangedNodeMask(changedNodeMask)
, mNodeMask(nodeMask)
, mChangedVoxelMask(changedVoxelMask)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
bool changedValue = false;
changedValue |= processZ(n, /*firstFace=*/true);
changedValue |= processZ(n, /*firstFace=*/false);
changedValue |= processY(n, /*firstFace=*/true);
changedValue |= processY(n, /*firstFace=*/false);
changedValue |= processX(n, /*firstFace=*/true);
changedValue |= processX(n, /*firstFace=*/false);
mNodeMask[n] = changedValue;
}
}
bool processZ(const size_t n, bool firstFace) const
{
const size_t offset =
firstFace ? mConnectivity->offsetsPrevZ()[n] : mConnectivity->offsetsNextZ()[n];
if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) {
bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE];
const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data();
const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data();
const Index lastOffset = LeafNodeType::DIM - 1;
const Index lhsOffset =
firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0;
Index tmpPos(0), pos(0);
bool changedValue = false;
for (Index x = 0; x < LeafNodeType::DIM; ++x) {
tmpPos = x << (2 * LeafNodeType::LOG2DIM);
for (Index y = 0; y < LeafNodeType::DIM; ++y) {
pos = tmpPos + (y << LeafNodeType::LOG2DIM);
if (lhsData[pos + lhsOffset] > ValueType(0.75)) {
if (rhsData[pos + rhsOffset] < ValueType(0.0)) {
changedValue = true;
mask[pos + lhsOffset] = true;
}
}
}
}
return changedValue;
}
return false;
}
bool processY(const size_t n, bool firstFace) const
{
const size_t offset =
firstFace ? mConnectivity->offsetsPrevY()[n] : mConnectivity->offsetsNextY()[n];
if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) {
bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE];
const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data();
const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data();
const Index lastOffset = LeafNodeType::DIM * (LeafNodeType::DIM - 1);
const Index lhsOffset =
firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0;
Index tmpPos(0), pos(0);
bool changedValue = false;
for (Index x = 0; x < LeafNodeType::DIM; ++x) {
tmpPos = x << (2 * LeafNodeType::LOG2DIM);
for (Index z = 0; z < LeafNodeType::DIM; ++z) {
pos = tmpPos + z;
if (lhsData[pos + lhsOffset] > ValueType(0.75)) {
if (rhsData[pos + rhsOffset] < ValueType(0.0)) {
changedValue = true;
mask[pos + lhsOffset] = true;
}
}
}
}
return changedValue;
}
return false;
}
bool processX(const size_t n, bool firstFace) const
{
const size_t offset =
firstFace ? mConnectivity->offsetsPrevX()[n] : mConnectivity->offsetsNextX()[n];
if (offset != ConnectivityTable::INVALID_OFFSET && mChangedNodeMask[offset]) {
bool* mask = &mChangedVoxelMask[n * LeafNodeType::SIZE];
const ValueType* lhsData = mConnectivity->nodes()[n]->buffer().data();
const ValueType* rhsData = mConnectivity->nodes()[offset]->buffer().data();
const Index lastOffset = LeafNodeType::DIM * LeafNodeType::DIM * (LeafNodeType::DIM-1);
const Index lhsOffset =
firstFace ? 0 : lastOffset, rhsOffset = firstFace ? lastOffset : 0;
Index tmpPos(0), pos(0);
bool changedValue = false;
for (Index y = 0; y < LeafNodeType::DIM; ++y) {
tmpPos = y << LeafNodeType::LOG2DIM;
for (Index z = 0; z < LeafNodeType::DIM; ++z) {
pos = tmpPos + z;
if (lhsData[pos + lhsOffset] > ValueType(0.75)) {
if (rhsData[pos + rhsOffset] < ValueType(0.0)) {
changedValue = true;
mask[pos + lhsOffset] = true;
}
}
}
}
return changedValue;
}
return false;
}
ConnectivityTable * const mConnectivity;
bool * const mChangedNodeMask;
bool * const mNodeMask;
bool * const mChangedVoxelMask;
};
////////////////////////////////////////
template<typename TreeType, typename MeshDataAdapter>
struct ComputeIntersectingVoxelSign
{
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
using Int32TreeType = typename TreeType::template ValueConverter<Int32>::Type;
using Int32LeafNodeType = typename Int32TreeType::LeafNodeType;
using PointArray = std::unique_ptr<Vec3d[]>;
using MaskArray = std::unique_ptr<bool[]>;
using LocalData = std::pair<PointArray, MaskArray>;
using LocalDataTable = tbb::enumerable_thread_specific<LocalData>;
ComputeIntersectingVoxelSign(
std::vector<LeafNodeType*>& distNodes,
const TreeType& distTree,
const Int32TreeType& indexTree,
const MeshDataAdapter& mesh)
: mDistNodes(distNodes.empty() ? nullptr : &distNodes[0])
, mDistTree(&distTree)
, mIndexTree(&indexTree)
, mMesh(&mesh)
, mLocalDataTable(new LocalDataTable())
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
tree::ValueAccessor<const TreeType> distAcc(*mDistTree);
tree::ValueAccessor<const Int32TreeType> idxAcc(*mIndexTree);
ValueType nval;
CoordBBox bbox;
Index xPos(0), yPos(0);
Coord ijk, nijk, nodeMin, nodeMax;
Vec3d cp, xyz, nxyz, dir1, dir2;
LocalData& localData = mLocalDataTable->local();
PointArray& points = localData.first;
if (!points) points.reset(new Vec3d[LeafNodeType::SIZE * 2]);
MaskArray& mask = localData.second;
if (!mask) mask.reset(new bool[LeafNodeType::SIZE]);
typename LeafNodeType::ValueOnCIter it;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
LeafNodeType& node = *mDistNodes[n];
ValueType* data = node.buffer().data();
const Int32LeafNodeType* idxNode = idxAcc.probeConstLeaf(node.origin());
const Int32* idxData = idxNode->buffer().data();
nodeMin = node.origin();
nodeMax = nodeMin.offsetBy(LeafNodeType::DIM - 1);
// reset computed voxel mask.
memset(mask.get(), 0, sizeof(bool) * LeafNodeType::SIZE);
for (it = node.cbeginValueOn(); it; ++it) {
Index pos = it.pos();
ValueType& dist = data[pos];
if (dist < 0.0 || dist > 0.75) continue;
ijk = node.offsetToGlobalCoord(pos);
xyz[0] = double(ijk[0]);
xyz[1] = double(ijk[1]);
xyz[2] = double(ijk[2]);
bbox.min() = Coord::maxComponent(ijk.offsetBy(-1), nodeMin);
bbox.max() = Coord::minComponent(ijk.offsetBy(1), nodeMax);
bool flipSign = false;
for (nijk[0] = bbox.min()[0]; nijk[0] <= bbox.max()[0] && !flipSign; ++nijk[0]) {
xPos = (nijk[0] & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM);
for (nijk[1]=bbox.min()[1]; nijk[1] <= bbox.max()[1] && !flipSign; ++nijk[1]) {
yPos = xPos + ((nijk[1] & (LeafNodeType::DIM-1u)) << LeafNodeType::LOG2DIM);
for (nijk[2] = bbox.min()[2]; nijk[2] <= bbox.max()[2]; ++nijk[2]) {
pos = yPos + (nijk[2] & (LeafNodeType::DIM - 1u));
const Int32& polyIdx = idxData[pos];
if (polyIdx == Int32(util::INVALID_IDX) || !(data[pos] < -0.75))
continue;
const Index pointIndex = pos * 2;
if (!mask[pos]) {
mask[pos] = true;
nxyz[0] = double(nijk[0]);
nxyz[1] = double(nijk[1]);
nxyz[2] = double(nijk[2]);
Vec3d& point = points[pointIndex];
point = closestPoint(nxyz, polyIdx);
Vec3d& direction = points[pointIndex + 1];
direction = nxyz - point;
direction.normalize();
}
dir1 = xyz - points[pointIndex];
dir1.normalize();
if (points[pointIndex + 1].dot(dir1) > 0.0) {
flipSign = true;
break;
}
}
}
}
if (flipSign) {
dist = -dist;
} else {
for (Int32 m = 0; m < 26; ++m) {
nijk = ijk + util::COORD_OFFSETS[m];
if (!bbox.isInside(nijk) && distAcc.probeValue(nijk, nval) && nval<-0.75) {
nxyz[0] = double(nijk[0]);
nxyz[1] = double(nijk[1]);
nxyz[2] = double(nijk[2]);
cp = closestPoint(nxyz, idxAcc.getValue(nijk));
dir1 = xyz - cp;
dir1.normalize();
dir2 = nxyz - cp;
dir2.normalize();
if (dir2.dot(dir1) > 0.0) {
dist = -dist;
break;
}
}
}
}
} // active voxel loop
} // leaf node loop
}
private:
Vec3d closestPoint(const Vec3d& center, Int32 polyIdx) const
{
Vec3d a, b, c, cp, uvw;
const size_t polygon = size_t(polyIdx);
mMesh->getIndexSpacePoint(polygon, 0, a);
mMesh->getIndexSpacePoint(polygon, 1, b);
mMesh->getIndexSpacePoint(polygon, 2, c);
cp = closestPointOnTriangleToPoint(a, c, b, center, uvw);
if (4 == mMesh->vertexCount(polygon)) {
mMesh->getIndexSpacePoint(polygon, 3, b);
c = closestPointOnTriangleToPoint(a, b, c, center, uvw);
if ((center - c).lengthSqr() < (center - cp).lengthSqr()) {
cp = c;
}
}
return cp;
}
LeafNodeType ** const mDistNodes;
TreeType const * const mDistTree;
Int32TreeType const * const mIndexTree;
MeshDataAdapter const * const mMesh;
SharedPtr<LocalDataTable> mLocalDataTable;
}; // ComputeIntersectingVoxelSign
////////////////////////////////////////
template<typename LeafNodeType>
inline void
maskNodeInternalNeighbours(const Index pos, bool (&mask)[26])
{
using NodeT = LeafNodeType;
const Coord ijk = NodeT::offsetToLocalCoord(pos);
// Face adjacent neighbours
// i+1, j, k
mask[0] = ijk[0] != (NodeT::DIM - 1);
// i-1, j, k
mask[1] = ijk[0] != 0;
// i, j+1, k
mask[2] = ijk[1] != (NodeT::DIM - 1);
// i, j-1, k
mask[3] = ijk[1] != 0;
// i, j, k+1
mask[4] = ijk[2] != (NodeT::DIM - 1);
// i, j, k-1
mask[5] = ijk[2] != 0;
// Edge adjacent neighbour
// i+1, j, k-1
mask[6] = mask[0] && mask[5];
// i-1, j, k-1
mask[7] = mask[1] && mask[5];
// i+1, j, k+1
mask[8] = mask[0] && mask[4];
// i-1, j, k+1
mask[9] = mask[1] && mask[4];
// i+1, j+1, k
mask[10] = mask[0] && mask[2];
// i-1, j+1, k
mask[11] = mask[1] && mask[2];
// i+1, j-1, k
mask[12] = mask[0] && mask[3];
// i-1, j-1, k
mask[13] = mask[1] && mask[3];
// i, j-1, k+1
mask[14] = mask[3] && mask[4];
// i, j-1, k-1
mask[15] = mask[3] && mask[5];
// i, j+1, k+1
mask[16] = mask[2] && mask[4];
// i, j+1, k-1
mask[17] = mask[2] && mask[5];
// Corner adjacent neighbours
// i-1, j-1, k-1
mask[18] = mask[1] && mask[3] && mask[5];
// i-1, j-1, k+1
mask[19] = mask[1] && mask[3] && mask[4];
// i+1, j-1, k+1
mask[20] = mask[0] && mask[3] && mask[4];
// i+1, j-1, k-1
mask[21] = mask[0] && mask[3] && mask[5];
// i-1, j+1, k-1
mask[22] = mask[1] && mask[2] && mask[5];
// i-1, j+1, k+1
mask[23] = mask[1] && mask[2] && mask[4];
// i+1, j+1, k+1
mask[24] = mask[0] && mask[2] && mask[4];
// i+1, j+1, k-1
mask[25] = mask[0] && mask[2] && mask[5];
}
template<typename Compare, typename LeafNodeType>
inline bool
checkNeighbours(const Index pos, const typename LeafNodeType::ValueType * data, bool (&mask)[26])
{
using NodeT = LeafNodeType;
// i, j, k - 1
if (mask[5] && Compare::check(data[pos - 1])) return true;
// i, j, k + 1
if (mask[4] && Compare::check(data[pos + 1])) return true;
// i, j - 1, k
if (mask[3] && Compare::check(data[pos - NodeT::DIM])) return true;
// i, j + 1, k
if (mask[2] && Compare::check(data[pos + NodeT::DIM])) return true;
// i - 1, j, k
if (mask[1] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM])) return true;
// i + 1, j, k
if (mask[0] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM])) return true;
// i+1, j, k-1
if (mask[6] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM])) return true;
// i-1, j, k-1
if (mask[7] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - 1])) return true;
// i+1, j, k+1
if (mask[8] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + 1])) return true;
// i-1, j, k+1
if (mask[9] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + 1])) return true;
// i+1, j+1, k
if (mask[10] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM])) return true;
// i-1, j+1, k
if (mask[11] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM])) return true;
// i+1, j-1, k
if (mask[12] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM])) return true;
// i-1, j-1, k
if (mask[13] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM])) return true;
// i, j-1, k+1
if (mask[14] && Compare::check(data[pos - NodeT::DIM + 1])) return true;
// i, j-1, k-1
if (mask[15] && Compare::check(data[pos - NodeT::DIM - 1])) return true;
// i, j+1, k+1
if (mask[16] && Compare::check(data[pos + NodeT::DIM + 1])) return true;
// i, j+1, k-1
if (mask[17] && Compare::check(data[pos + NodeT::DIM - 1])) return true;
// i-1, j-1, k-1
if (mask[18] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM - 1])) return true;
// i-1, j-1, k+1
if (mask[19] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM - NodeT::DIM + 1])) return true;
// i+1, j-1, k+1
if (mask[20] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM + 1])) return true;
// i+1, j-1, k-1
if (mask[21] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM - NodeT::DIM - 1])) return true;
// i-1, j+1, k-1
if (mask[22] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM - 1])) return true;
// i-1, j+1, k+1
if (mask[23] && Compare::check(data[pos - NodeT::DIM * NodeT::DIM + NodeT::DIM + 1])) return true;
// i+1, j+1, k+1
if (mask[24] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM + 1])) return true;
// i+1, j+1, k-1
if (mask[25] && Compare::check(data[pos + NodeT::DIM * NodeT::DIM + NodeT::DIM - 1])) return true;
return false;
}
template<typename Compare, typename AccessorType>
inline bool
checkNeighbours(const Coord& ijk, AccessorType& acc, bool (&mask)[26])
{
for (Int32 m = 0; m < 26; ++m) {
if (!mask[m] && Compare::check(acc.getValue(ijk + util::COORD_OFFSETS[m]))) {
return true;
}
}
return false;
}
template<typename TreeType>
struct ValidateIntersectingVoxels
{
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
struct IsNegative { static bool check(const ValueType v) { return v < ValueType(0.0); } };
ValidateIntersectingVoxels(TreeType& tree, std::vector<LeafNodeType*>& nodes)
: mTree(&tree)
, mNodes(nodes.empty() ? nullptr : &nodes[0])
{
}
void operator()(const tbb::blocked_range<size_t>& range) const
{
tree::ValueAccessor<const TreeType> acc(*mTree);
bool neighbourMask[26];
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
LeafNodeType& node = *mNodes[n];
ValueType* data = node.buffer().data();
typename LeafNodeType::ValueOnCIter it;
for (it = node.cbeginValueOn(); it; ++it) {
const Index pos = it.pos();
ValueType& dist = data[pos];
if (dist < 0.0 || dist > 0.75) continue;
// Mask node internal neighbours
maskNodeInternalNeighbours<LeafNodeType>(pos, neighbourMask);
const bool hasNegativeNeighbour =
checkNeighbours<IsNegative, LeafNodeType>(pos, data, neighbourMask) ||
checkNeighbours<IsNegative>(node.offsetToGlobalCoord(pos), acc, neighbourMask);
if (!hasNegativeNeighbour) {
// push over boundary voxel distance
dist = ValueType(0.75) + Tolerance<ValueType>::epsilon();
}
}
}
}
TreeType * const mTree;
LeafNodeType ** const mNodes;
}; // ValidateIntersectingVoxels
template<typename TreeType>
struct RemoveSelfIntersectingSurface
{
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
using Int32TreeType = typename TreeType::template ValueConverter<Int32>::Type;
struct Comp { static bool check(const ValueType v) { return !(v > ValueType(0.75)); } };
RemoveSelfIntersectingSurface(std::vector<LeafNodeType*>& nodes,
TreeType& distTree, Int32TreeType& indexTree)
: mNodes(nodes.empty() ? nullptr : &nodes[0])
, mDistTree(&distTree)
, mIndexTree(&indexTree)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const
{
tree::ValueAccessor<const TreeType> distAcc(*mDistTree);
tree::ValueAccessor<Int32TreeType> idxAcc(*mIndexTree);
bool neighbourMask[26];
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
LeafNodeType& distNode = *mNodes[n];
ValueType* data = distNode.buffer().data();
typename Int32TreeType::LeafNodeType* idxNode =
idxAcc.probeLeaf(distNode.origin());
typename LeafNodeType::ValueOnCIter it;
for (it = distNode.cbeginValueOn(); it; ++it) {
const Index pos = it.pos();
if (!(data[pos] > 0.75)) continue;
// Mask node internal neighbours
maskNodeInternalNeighbours<LeafNodeType>(pos, neighbourMask);
const bool hasBoundaryNeighbour =
checkNeighbours<Comp, LeafNodeType>(pos, data, neighbourMask) ||
checkNeighbours<Comp>(distNode.offsetToGlobalCoord(pos),distAcc,neighbourMask);
if (!hasBoundaryNeighbour) {
distNode.setValueOff(pos);
idxNode->setValueOff(pos);
}
}
}
}
LeafNodeType * * const mNodes;
TreeType * const mDistTree;
Int32TreeType * const mIndexTree;
}; // RemoveSelfIntersectingSurface
////////////////////////////////////////
template<typename NodeType>
struct ReleaseChildNodes
{
ReleaseChildNodes(NodeType ** nodes) : mNodes(nodes) {}
void operator()(const tbb::blocked_range<size_t>& range) const {
using NodeMaskType = typename NodeType::NodeMaskType;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
const_cast<NodeMaskType&>(mNodes[n]->getChildMask()).setOff();
}
}
NodeType ** const mNodes;
};
template<typename TreeType>
inline void
releaseLeafNodes(TreeType& tree)
{
using RootNodeType = typename TreeType::RootNodeType;
using NodeChainType = typename RootNodeType::NodeChainType;
using InternalNodeType = typename NodeChainType::template Get<1>;
std::vector<InternalNodeType*> nodes;
tree.getNodes(nodes);
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
ReleaseChildNodes<InternalNodeType>(nodes.empty() ? nullptr : &nodes[0]));
}
template<typename TreeType>
struct StealUniqueLeafNodes
{
using LeafNodeType = typename TreeType::LeafNodeType;
StealUniqueLeafNodes(TreeType& lhsTree, TreeType& rhsTree,
std::vector<LeafNodeType*>& overlappingNodes)
: mLhsTree(&lhsTree)
, mRhsTree(&rhsTree)
, mNodes(&overlappingNodes)
{
}
void operator()() const {
std::vector<LeafNodeType*> rhsLeafNodes;
rhsLeafNodes.reserve(mRhsTree->leafCount());
//mRhsTree->getNodes(rhsLeafNodes);
//releaseLeafNodes(*mRhsTree);
mRhsTree->stealNodes(rhsLeafNodes);
tree::ValueAccessor<TreeType> acc(*mLhsTree);
for (size_t n = 0, N = rhsLeafNodes.size(); n < N; ++n) {
if (!acc.probeLeaf(rhsLeafNodes[n]->origin())) {
acc.addLeaf(rhsLeafNodes[n]);
} else {
mNodes->push_back(rhsLeafNodes[n]);
}
}
}
private:
TreeType * const mLhsTree;
TreeType * const mRhsTree;
std::vector<LeafNodeType*> * const mNodes;
};
template<typename DistTreeType, typename IndexTreeType>
inline void
combineData(DistTreeType& lhsDist, IndexTreeType& lhsIdx,
DistTreeType& rhsDist, IndexTreeType& rhsIdx)
{
using DistLeafNodeType = typename DistTreeType::LeafNodeType;
using IndexLeafNodeType = typename IndexTreeType::LeafNodeType;
std::vector<DistLeafNodeType*> overlappingDistNodes;
std::vector<IndexLeafNodeType*> overlappingIdxNodes;
// Steal unique leafnodes
tbb::task_group tasks;
tasks.run(StealUniqueLeafNodes<DistTreeType>(lhsDist, rhsDist, overlappingDistNodes));
tasks.run(StealUniqueLeafNodes<IndexTreeType>(lhsIdx, rhsIdx, overlappingIdxNodes));
tasks.wait();
// Combine overlapping leaf nodes
if (!overlappingDistNodes.empty() && !overlappingIdxNodes.empty()) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, overlappingDistNodes.size()),
CombineLeafNodes<DistTreeType>(lhsDist, lhsIdx,
&overlappingDistNodes[0], &overlappingIdxNodes[0]));
}
}
/// @brief TBB body object to voxelize a mesh of triangles and/or quads into a collection
/// of VDB grids, namely a squared distance grid, a closest primitive grid and an
/// intersecting voxels grid (masks the mesh intersecting voxels)
/// @note Only the leaf nodes that intersect the mesh are allocated, and only voxels in
/// a narrow band (of two to three voxels in proximity to the mesh's surface) are activated.
/// They are populated with distance values and primitive indices.
template<typename TreeType>
struct VoxelizationData {
using Ptr = std::unique_ptr<VoxelizationData>;
using ValueType = typename TreeType::ValueType;
using Int32TreeType = typename TreeType::template ValueConverter<Int32>::Type;
using UCharTreeType = typename TreeType::template ValueConverter<unsigned char>::Type;
using FloatTreeAcc = tree::ValueAccessor<TreeType>;
using Int32TreeAcc = tree::ValueAccessor<Int32TreeType>;
using UCharTreeAcc = tree::ValueAccessor<UCharTreeType>;
VoxelizationData()
: distTree(std::numeric_limits<ValueType>::max())
, distAcc(distTree)
, indexTree(Int32(util::INVALID_IDX))
, indexAcc(indexTree)
, primIdTree(MaxPrimId)
, primIdAcc(primIdTree)
, mPrimCount(0)
{
}
TreeType distTree;
FloatTreeAcc distAcc;
Int32TreeType indexTree;
Int32TreeAcc indexAcc;
UCharTreeType primIdTree;
UCharTreeAcc primIdAcc;
unsigned char getNewPrimId() {
/// @warning Don't use parallel methods here!
/// The primIdTree is used as a "scratch" pad to mark visits for a given polygon
/// into voxels which it may contribute to. The tree is kept as lightweight as
/// possible and is reset when a maximum count or size is reached. A previous
/// bug here occurred due to the calling of tree methods with multi-threaded
/// implementations, resulting in nested parallelization and re-use of the TLS
/// from the initial task. This consequently resulted in non deterministic values
/// of mPrimCount on the return of the initial task, and could potentially end up
/// with a mPrimCount equal to that of the MaxPrimId. This is used as the background
/// value of the scratch tree.
/// @see jira.aswf.io/browse/OVDB-117, PR #564
/// @todo Consider profiling this operator with tree.clear() and Investigate the
/// chosen value of MaxPrimId
if (mPrimCount == MaxPrimId || primIdTree.leafCount() > 1000) {
mPrimCount = 0;
primIdTree.root().clear();
primIdTree.clearAllAccessors();
assert(mPrimCount == 0);
}
return mPrimCount++;
}
private:
enum { MaxPrimId = 100 };
unsigned char mPrimCount;
};
template<typename TreeType, typename MeshDataAdapter, typename Interrupter = util::NullInterrupter>
class VoxelizePolygons
{
public:
using VoxelizationDataType = VoxelizationData<TreeType>;
using DataTable = tbb::enumerable_thread_specific<typename VoxelizationDataType::Ptr>;
VoxelizePolygons(DataTable& dataTable,
const MeshDataAdapter& mesh,
Interrupter* interrupter = nullptr)
: mDataTable(&dataTable)
, mMesh(&mesh)
, mInterrupter(interrupter)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
typename VoxelizationDataType::Ptr& dataPtr = mDataTable->local();
if (!dataPtr) dataPtr.reset(new VoxelizationDataType());
Triangle prim;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
if (this->wasInterrupted()) {
thread::cancelGroupExecution();
break;
}
const size_t numVerts = mMesh->vertexCount(n);
// rasterize triangles and quads.
if (numVerts == 3 || numVerts == 4) {
prim.index = Int32(n);
mMesh->getIndexSpacePoint(n, 0, prim.a);
mMesh->getIndexSpacePoint(n, 1, prim.b);
mMesh->getIndexSpacePoint(n, 2, prim.c);
evalTriangle(prim, *dataPtr);
if (numVerts == 4) {
mMesh->getIndexSpacePoint(n, 3, prim.b);
evalTriangle(prim, *dataPtr);
}
}
}
}
private:
bool wasInterrupted() const { return mInterrupter && mInterrupter->wasInterrupted(); }
struct Triangle { Vec3d a, b, c; Int32 index; };
struct SubTask
{
enum { POLYGON_LIMIT = 1000 };
SubTask(const Triangle& prim, DataTable& dataTable,
int subdivisionCount, size_t polygonCount,
Interrupter* interrupter = nullptr)
: mLocalDataTable(&dataTable)
, mPrim(prim)
, mSubdivisionCount(subdivisionCount)
, mPolygonCount(polygonCount)
, mInterrupter(interrupter)
{
}
void operator()() const
{
if (mSubdivisionCount <= 0 || mPolygonCount >= POLYGON_LIMIT) {
typename VoxelizationDataType::Ptr& dataPtr = mLocalDataTable->local();
if (!dataPtr) dataPtr.reset(new VoxelizationDataType());
voxelizeTriangle(mPrim, *dataPtr, mInterrupter);
} else if (!(mInterrupter && mInterrupter->wasInterrupted())) {
spawnTasks(mPrim, *mLocalDataTable, mSubdivisionCount, mPolygonCount, mInterrupter);
}
}
DataTable * const mLocalDataTable;
Triangle const mPrim;
int const mSubdivisionCount;
size_t const mPolygonCount;
Interrupter * const mInterrupter;
}; // struct SubTask
inline static int evalSubdivisionCount(const Triangle& prim)
{
const double ax = prim.a[0], bx = prim.b[0], cx = prim.c[0];
const double dx = std::max(ax, std::max(bx, cx)) - std::min(ax, std::min(bx, cx));
const double ay = prim.a[1], by = prim.b[1], cy = prim.c[1];
const double dy = std::max(ay, std::max(by, cy)) - std::min(ay, std::min(by, cy));
const double az = prim.a[2], bz = prim.b[2], cz = prim.c[2];
const double dz = std::max(az, std::max(bz, cz)) - std::min(az, std::min(bz, cz));
return int(std::max(dx, std::max(dy, dz)) / double(TreeType::LeafNodeType::DIM * 2));
}
void evalTriangle(const Triangle& prim, VoxelizationDataType& data) const
{
const size_t polygonCount = mMesh->polygonCount();
const int subdivisionCount =
polygonCount < SubTask::POLYGON_LIMIT ? evalSubdivisionCount(prim) : 0;
if (subdivisionCount <= 0) {
voxelizeTriangle(prim, data, mInterrupter);
} else {
spawnTasks(prim, *mDataTable, subdivisionCount, polygonCount, mInterrupter);
}
}
static void spawnTasks(
const Triangle& mainPrim,
DataTable& dataTable,
int subdivisionCount,
size_t polygonCount,
Interrupter* const interrupter)
{
subdivisionCount -= 1;
polygonCount *= 4;
tbb::task_group tasks;
const Vec3d ac = (mainPrim.a + mainPrim.c) * 0.5;
const Vec3d bc = (mainPrim.b + mainPrim.c) * 0.5;
const Vec3d ab = (mainPrim.a + mainPrim.b) * 0.5;
Triangle prim;
prim.index = mainPrim.index;
prim.a = mainPrim.a;
prim.b = ab;
prim.c = ac;
tasks.run(SubTask(prim, dataTable, subdivisionCount, polygonCount, interrupter));
prim.a = ab;
prim.b = bc;
prim.c = ac;
tasks.run(SubTask(prim, dataTable, subdivisionCount, polygonCount, interrupter));
prim.a = ab;
prim.b = mainPrim.b;
prim.c = bc;
tasks.run(SubTask(prim, dataTable, subdivisionCount, polygonCount, interrupter));
prim.a = ac;
prim.b = bc;
prim.c = mainPrim.c;
tasks.run(SubTask(prim, dataTable, subdivisionCount, polygonCount, interrupter));
tasks.wait();
}
static void voxelizeTriangle(const Triangle& prim, VoxelizationDataType& data, Interrupter* const interrupter)
{
std::deque<Coord> coordList;
Coord ijk, nijk;
ijk = Coord::floor(prim.a);
coordList.push_back(ijk);
// The first point may not be quite in bounds, and rely
// on one of the neighbours to have the first valid seed,
// so we cannot early-exit here.
updateDistance(ijk, prim, data);
unsigned char primId = data.getNewPrimId();
data.primIdAcc.setValueOnly(ijk, primId);
while (!coordList.empty()) {
if (interrupter && interrupter->wasInterrupted()) {
thread::cancelGroupExecution();
break;
}
for (Int32 pass = 0; pass < 1048576 && !coordList.empty(); ++pass) {
ijk = coordList.back();
coordList.pop_back();
for (Int32 i = 0; i < 26; ++i) {
nijk = ijk + util::COORD_OFFSETS[i];
if (primId != data.primIdAcc.getValue(nijk)) {
data.primIdAcc.setValueOnly(nijk, primId);
if(updateDistance(nijk, prim, data)) coordList.push_back(nijk);
}
}
}
}
}
static bool updateDistance(const Coord& ijk, const Triangle& prim, VoxelizationDataType& data)
{
Vec3d uvw, voxelCenter(ijk[0], ijk[1], ijk[2]);
using ValueType = typename TreeType::ValueType;
const ValueType dist = ValueType((voxelCenter -
closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, voxelCenter, uvw)).lengthSqr());
// Either the points may be NAN, or they could be far enough from
// the origin that computing distance fails.
if (std::isnan(dist))
return false;
const ValueType oldDist = data.distAcc.getValue(ijk);
if (dist < oldDist) {
data.distAcc.setValue(ijk, dist);
data.indexAcc.setValue(ijk, prim.index);
} else if (math::isExactlyEqual(dist, oldDist)) {
// makes reduction deterministic when different polygons
// produce the same distance value.
data.indexAcc.setValueOnly(ijk, std::min(prim.index, data.indexAcc.getValue(ijk)));
}
return !(dist > 0.75); // true if the primitive intersects the voxel.
}
DataTable * const mDataTable;
MeshDataAdapter const * const mMesh;
Interrupter * const mInterrupter;
}; // VoxelizePolygons
////////////////////////////////////////
template<typename TreeType>
struct DiffLeafNodeMask
{
using AccessorType = typename tree::ValueAccessor<TreeType>;
using LeafNodeType = typename TreeType::LeafNodeType;
using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
DiffLeafNodeMask(const TreeType& rhsTree,
std::vector<BoolLeafNodeType*>& lhsNodes)
: mRhsTree(&rhsTree), mLhsNodes(lhsNodes.empty() ? nullptr : &lhsNodes[0])
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
tree::ValueAccessor<const TreeType> acc(*mRhsTree);
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
BoolLeafNodeType* lhsNode = mLhsNodes[n];
const LeafNodeType* rhsNode = acc.probeConstLeaf(lhsNode->origin());
if (rhsNode) lhsNode->topologyDifference(*rhsNode, false);
}
}
private:
TreeType const * const mRhsTree;
BoolLeafNodeType ** const mLhsNodes;
};
template<typename LeafNodeTypeA, typename LeafNodeTypeB>
struct UnionValueMasks
{
UnionValueMasks(std::vector<LeafNodeTypeA*>& nodesA, std::vector<LeafNodeTypeB*>& nodesB)
: mNodesA(nodesA.empty() ? nullptr : &nodesA[0])
, mNodesB(nodesB.empty() ? nullptr : &nodesB[0])
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
mNodesA[n]->topologyUnion(*mNodesB[n]);
}
}
private:
LeafNodeTypeA ** const mNodesA;
LeafNodeTypeB ** const mNodesB;
};
template<typename TreeType>
struct ConstructVoxelMask
{
using LeafNodeType = typename TreeType::LeafNodeType;
using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
ConstructVoxelMask(BoolTreeType& maskTree, const TreeType& tree,
std::vector<LeafNodeType*>& nodes)
: mTree(&tree)
, mNodes(nodes.empty() ? nullptr : &nodes[0])
, mLocalMaskTree(false)
, mMaskTree(&maskTree)
{
}
ConstructVoxelMask(ConstructVoxelMask& rhs, tbb::split)
: mTree(rhs.mTree)
, mNodes(rhs.mNodes)
, mLocalMaskTree(false)
, mMaskTree(&mLocalMaskTree)
{
}
void operator()(const tbb::blocked_range<size_t>& range)
{
using Iterator = typename LeafNodeType::ValueOnCIter;
tree::ValueAccessor<const TreeType> acc(*mTree);
tree::ValueAccessor<BoolTreeType> maskAcc(*mMaskTree);
Coord ijk, nijk, localCorod;
Index pos, npos;
for (size_t n = range.begin(); n != range.end(); ++n) {
LeafNodeType& node = *mNodes[n];
CoordBBox bbox = node.getNodeBoundingBox();
bbox.expand(-1);
BoolLeafNodeType& maskNode = *maskAcc.touchLeaf(node.origin());
for (Iterator it = node.cbeginValueOn(); it; ++it) {
ijk = it.getCoord();
pos = it.pos();
localCorod = LeafNodeType::offsetToLocalCoord(pos);
if (localCorod[2] < int(LeafNodeType::DIM - 1)) {
npos = pos + 1;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(0, 0, 1);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
if (localCorod[2] > 0) {
npos = pos - 1;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(0, 0, -1);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
if (localCorod[1] < int(LeafNodeType::DIM - 1)) {
npos = pos + LeafNodeType::DIM;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(0, 1, 0);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
if (localCorod[1] > 0) {
npos = pos - LeafNodeType::DIM;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(0, -1, 0);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
if (localCorod[0] < int(LeafNodeType::DIM - 1)) {
npos = pos + LeafNodeType::DIM * LeafNodeType::DIM;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(1, 0, 0);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
if (localCorod[0] > 0) {
npos = pos - LeafNodeType::DIM * LeafNodeType::DIM;
if (!node.isValueOn(npos)) maskNode.setValueOn(npos);
} else {
nijk = ijk.offsetBy(-1, 0, 0);
if (!acc.isValueOn(nijk)) maskAcc.setValueOn(nijk);
}
}
}
}
void join(ConstructVoxelMask& rhs) { mMaskTree->merge(*rhs.mMaskTree); }
private:
TreeType const * const mTree;
LeafNodeType ** const mNodes;
BoolTreeType mLocalMaskTree;
BoolTreeType * const mMaskTree;
};
/// @note The interior and exterior widths should be in world space units and squared.
template<typename TreeType, typename MeshDataAdapter>
struct ExpandNarrowband
{
using ValueType = typename TreeType::ValueType;
using LeafNodeType = typename TreeType::LeafNodeType;
using NodeMaskType = typename LeafNodeType::NodeMaskType;
using Int32TreeType = typename TreeType::template ValueConverter<Int32>::Type;
using Int32LeafNodeType = typename Int32TreeType::LeafNodeType;
using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
using BoolLeafNodeType = typename BoolTreeType::LeafNodeType;
struct Fragment
{
Int32 idx, x, y, z;
ValueType dist;
Fragment() : idx(0), x(0), y(0), z(0), dist(0.0) {}
Fragment(Int32 idx_, Int32 x_, Int32 y_, Int32 z_, ValueType dist_)
: idx(idx_), x(x_), y(y_), z(z_), dist(dist_)
{
}
bool operator<(const Fragment& rhs) const { return idx < rhs.idx; }
}; // struct Fragment
////////////////////
ExpandNarrowband(
std::vector<BoolLeafNodeType*>& maskNodes,
BoolTreeType& maskTree,
TreeType& distTree,
Int32TreeType& indexTree,
const MeshDataAdapter& mesh,
ValueType exteriorBandWidth,
ValueType interiorBandWidth,
ValueType voxelSize)
: mMaskNodes(maskNodes.empty() ? nullptr : &maskNodes[0])
, mMaskTree(&maskTree)
, mDistTree(&distTree)
, mIndexTree(&indexTree)
, mMesh(&mesh)
, mNewMaskTree(false)
, mDistNodes()
, mUpdatedDistNodes()
, mIndexNodes()
, mUpdatedIndexNodes()
, mExteriorBandWidth(exteriorBandWidth)
, mInteriorBandWidth(interiorBandWidth)
, mVoxelSize(voxelSize)
{
}
ExpandNarrowband(const ExpandNarrowband& rhs, tbb::split)
: mMaskNodes(rhs.mMaskNodes)
, mMaskTree(rhs.mMaskTree)
, mDistTree(rhs.mDistTree)
, mIndexTree(rhs.mIndexTree)
, mMesh(rhs.mMesh)
, mNewMaskTree(false)
, mDistNodes()
, mUpdatedDistNodes()
, mIndexNodes()
, mUpdatedIndexNodes()
, mExteriorBandWidth(rhs.mExteriorBandWidth)
, mInteriorBandWidth(rhs.mInteriorBandWidth)
, mVoxelSize(rhs.mVoxelSize)
{
}
void join(ExpandNarrowband& rhs)
{
mDistNodes.insert(mDistNodes.end(), rhs.mDistNodes.begin(), rhs.mDistNodes.end());
mIndexNodes.insert(mIndexNodes.end(), rhs.mIndexNodes.begin(), rhs.mIndexNodes.end());
mUpdatedDistNodes.insert(mUpdatedDistNodes.end(),
rhs.mUpdatedDistNodes.begin(), rhs.mUpdatedDistNodes.end());
mUpdatedIndexNodes.insert(mUpdatedIndexNodes.end(),
rhs.mUpdatedIndexNodes.begin(), rhs.mUpdatedIndexNodes.end());
mNewMaskTree.merge(rhs.mNewMaskTree);
}
void operator()(const tbb::blocked_range<size_t>& range)
{
tree::ValueAccessor<BoolTreeType> newMaskAcc(mNewMaskTree);
tree::ValueAccessor<TreeType> distAcc(*mDistTree);
tree::ValueAccessor<Int32TreeType> indexAcc(*mIndexTree);
std::vector<Fragment> fragments;
fragments.reserve(256);
std::unique_ptr<LeafNodeType> newDistNodePt;
std::unique_ptr<Int32LeafNodeType> newIndexNodePt;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
BoolLeafNodeType& maskNode = *mMaskNodes[n];
if (maskNode.isEmpty()) continue;
// Setup local caches
const Coord& origin = maskNode.origin();
LeafNodeType * distNodePt = distAcc.probeLeaf(origin);
Int32LeafNodeType * indexNodePt = indexAcc.probeLeaf(origin);
assert(!distNodePt == !indexNodePt);
bool usingNewNodes = false;
if (!distNodePt && !indexNodePt) {
const ValueType backgroundDist = distAcc.getValue(origin);
if (!newDistNodePt.get() && !newIndexNodePt.get()) {
newDistNodePt.reset(new LeafNodeType(origin, backgroundDist));
newIndexNodePt.reset(new Int32LeafNodeType(origin, indexAcc.getValue(origin)));
} else {
if ((backgroundDist < ValueType(0.0)) !=
(newDistNodePt->getValue(0) < ValueType(0.0))) {
newDistNodePt->buffer().fill(backgroundDist);
}
newDistNodePt->setOrigin(origin);
newIndexNodePt->setOrigin(origin);
}
distNodePt = newDistNodePt.get();
indexNodePt = newIndexNodePt.get();
usingNewNodes = true;
}
// Gather neighbour information
CoordBBox bbox(Coord::max(), Coord::min());
for (typename BoolLeafNodeType::ValueOnIter it = maskNode.beginValueOn(); it; ++it) {
bbox.expand(it.getCoord());
}
bbox.expand(1);
gatherFragments(fragments, bbox, distAcc, indexAcc);
// Compute first voxel layer
bbox = maskNode.getNodeBoundingBox();
NodeMaskType mask;
bool updatedLeafNodes = false;
for (typename BoolLeafNodeType::ValueOnIter it = maskNode.beginValueOn(); it; ++it) {
const Coord ijk = it.getCoord();
if (updateVoxel(ijk, 5, fragments, *distNodePt, *indexNodePt, &updatedLeafNodes)) {
for (Int32 i = 0; i < 6; ++i) {
const Coord nijk = ijk + util::COORD_OFFSETS[i];
if (bbox.isInside(nijk)) {
mask.setOn(BoolLeafNodeType::coordToOffset(nijk));
} else {
newMaskAcc.setValueOn(nijk);
}
}
for (Int32 i = 6; i < 26; ++i) {
const Coord nijk = ijk + util::COORD_OFFSETS[i];
if (bbox.isInside(nijk)) {
mask.setOn(BoolLeafNodeType::coordToOffset(nijk));
}
}
}
}
if (updatedLeafNodes) {
// Compute second voxel layer
mask -= indexNodePt->getValueMask();
for (typename NodeMaskType::OnIterator it = mask.beginOn(); it; ++it) {
const Index pos = it.pos();
const Coord ijk = maskNode.origin() + LeafNodeType::offsetToLocalCoord(pos);
if (updateVoxel(ijk, 6, fragments, *distNodePt, *indexNodePt)) {
for (Int32 i = 0; i < 6; ++i) {
newMaskAcc.setValueOn(ijk + util::COORD_OFFSETS[i]);
}
}
}
// Export new distance values
if (usingNewNodes) {
newDistNodePt->topologyUnion(*newIndexNodePt);
mDistNodes.push_back(newDistNodePt.release());
mIndexNodes.push_back(newIndexNodePt.release());
} else {
mUpdatedDistNodes.push_back(distNodePt);
mUpdatedIndexNodes.push_back(indexNodePt);
}
}
} // end leafnode loop
}
//////////
BoolTreeType& newMaskTree() { return mNewMaskTree; }
std::vector<LeafNodeType*>& newDistNodes() { return mDistNodes; }
std::vector<LeafNodeType*>& updatedDistNodes() { return mUpdatedDistNodes; }
std::vector<Int32LeafNodeType*>& newIndexNodes() { return mIndexNodes; }
std::vector<Int32LeafNodeType*>& updatedIndexNodes() { return mUpdatedIndexNodes; }
private:
/// @note The output fragment list is ordered by the primitive index
void
gatherFragments(std::vector<Fragment>& fragments, const CoordBBox& bbox,
tree::ValueAccessor<TreeType>& distAcc, tree::ValueAccessor<Int32TreeType>& indexAcc)
{
fragments.clear();
const Coord nodeMin = bbox.min() & ~(LeafNodeType::DIM - 1);
const Coord nodeMax = bbox.max() & ~(LeafNodeType::DIM - 1);
CoordBBox region;
Coord ijk;
for (ijk[0] = nodeMin[0]; ijk[0] <= nodeMax[0]; ijk[0] += LeafNodeType::DIM) {
for (ijk[1] = nodeMin[1]; ijk[1] <= nodeMax[1]; ijk[1] += LeafNodeType::DIM) {
for (ijk[2] = nodeMin[2]; ijk[2] <= nodeMax[2]; ijk[2] += LeafNodeType::DIM) {
if (LeafNodeType* distleaf = distAcc.probeLeaf(ijk)) {
region.min() = Coord::maxComponent(bbox.min(), ijk);
region.max() = Coord::minComponent(bbox.max(),
ijk.offsetBy(LeafNodeType::DIM - 1));
gatherFragments(fragments, region, *distleaf, *indexAcc.probeLeaf(ijk));
}
}
}
}
std::sort(fragments.begin(), fragments.end());
}
void
gatherFragments(std::vector<Fragment>& fragments, const CoordBBox& bbox,
const LeafNodeType& distLeaf, const Int32LeafNodeType& idxLeaf) const
{
const typename LeafNodeType::NodeMaskType& mask = distLeaf.getValueMask();
const ValueType* distData = distLeaf.buffer().data();
const Int32* idxData = idxLeaf.buffer().data();
for (int x = bbox.min()[0]; x <= bbox.max()[0]; ++x) {
const Index xPos = (x & (LeafNodeType::DIM - 1u)) << (2 * LeafNodeType::LOG2DIM);
for (int y = bbox.min()[1]; y <= bbox.max()[1]; ++y) {
const Index yPos = xPos + ((y & (LeafNodeType::DIM - 1u)) << LeafNodeType::LOG2DIM);
for (int z = bbox.min()[2]; z <= bbox.max()[2]; ++z) {
const Index pos = yPos + (z & (LeafNodeType::DIM - 1u));
if (mask.isOn(pos)) {
fragments.push_back(Fragment(idxData[pos],x,y,z, std::abs(distData[pos])));
}
}
}
}
}
/// @note This method expects the fragment list to be ordered by the primitive index
/// to avoid redundant distance computations.
ValueType
computeDistance(const Coord& ijk, const Int32 manhattanLimit,
const std::vector<Fragment>& fragments, Int32& closestPrimIdx) const
{
Vec3d a, b, c, uvw, voxelCenter(ijk[0], ijk[1], ijk[2]);
double primDist, tmpDist, dist = std::numeric_limits<double>::max();
Int32 lastIdx = Int32(util::INVALID_IDX);
for (size_t n = 0, N = fragments.size(); n < N; ++n) {
const Fragment& fragment = fragments[n];
if (lastIdx == fragment.idx) continue;
const Int32 dx = std::abs(fragment.x - ijk[0]);
const Int32 dy = std::abs(fragment.y - ijk[1]);
const Int32 dz = std::abs(fragment.z - ijk[2]);
const Int32 manhattan = dx + dy + dz;
if (manhattan > manhattanLimit) continue;
lastIdx = fragment.idx;
const size_t polygon = size_t(lastIdx);
mMesh->getIndexSpacePoint(polygon, 0, a);
mMesh->getIndexSpacePoint(polygon, 1, b);
mMesh->getIndexSpacePoint(polygon, 2, c);
primDist = (voxelCenter -
closestPointOnTriangleToPoint(a, c, b, voxelCenter, uvw)).lengthSqr();
// Split quad into a second triangle
if (4 == mMesh->vertexCount(polygon)) {
mMesh->getIndexSpacePoint(polygon, 3, b);
tmpDist = (voxelCenter - closestPointOnTriangleToPoint(
a, b, c, voxelCenter, uvw)).lengthSqr();
if (tmpDist < primDist) primDist = tmpDist;
}
if (primDist < dist) {
dist = primDist;
closestPrimIdx = lastIdx;
}
}
return ValueType(std::sqrt(dist)) * mVoxelSize;
}
/// @note Returns true if the current voxel was updated and neighbouring
/// voxels need to be evaluated.
bool
updateVoxel(const Coord& ijk, const Int32 manhattanLimit,
const std::vector<Fragment>& fragments,
LeafNodeType& distLeaf, Int32LeafNodeType& idxLeaf, bool* updatedLeafNodes = nullptr)
{
Int32 closestPrimIdx = 0;
const ValueType distance = computeDistance(ijk, manhattanLimit, fragments, closestPrimIdx);
const Index pos = LeafNodeType::coordToOffset(ijk);
const bool inside = distLeaf.getValue(pos) < ValueType(0.0);
bool activateNeighbourVoxels = false;
if (!inside && distance < mExteriorBandWidth) {
if (updatedLeafNodes) *updatedLeafNodes = true;
activateNeighbourVoxels = (distance + mVoxelSize) < mExteriorBandWidth;
distLeaf.setValueOnly(pos, distance);
idxLeaf.setValueOn(pos, closestPrimIdx);
} else if (inside && distance < mInteriorBandWidth) {
if (updatedLeafNodes) *updatedLeafNodes = true;
activateNeighbourVoxels = (distance + mVoxelSize) < mInteriorBandWidth;
distLeaf.setValueOnly(pos, -distance);
idxLeaf.setValueOn(pos, closestPrimIdx);
}
return activateNeighbourVoxels;
}
//////////
BoolLeafNodeType ** const mMaskNodes;
BoolTreeType * const mMaskTree;
TreeType * const mDistTree;
Int32TreeType * const mIndexTree;
MeshDataAdapter const * const mMesh;
BoolTreeType mNewMaskTree;
std::vector<LeafNodeType*> mDistNodes, mUpdatedDistNodes;
std::vector<Int32LeafNodeType*> mIndexNodes, mUpdatedIndexNodes;
const ValueType mExteriorBandWidth, mInteriorBandWidth, mVoxelSize;
}; // struct ExpandNarrowband
template<typename TreeType>
struct AddNodes {
using LeafNodeType = typename TreeType::LeafNodeType;
AddNodes(TreeType& tree, std::vector<LeafNodeType*>& nodes)
: mTree(&tree) , mNodes(&nodes)
{
}
void operator()() const {
tree::ValueAccessor<TreeType> acc(*mTree);
std::vector<LeafNodeType*>& nodes = *mNodes;
for (size_t n = 0, N = nodes.size(); n < N; ++n) {
acc.addLeaf(nodes[n]);
}
}
TreeType * const mTree;
std::vector<LeafNodeType*> * const mNodes;
}; // AddNodes
template<typename TreeType, typename Int32TreeType, typename BoolTreeType, typename MeshDataAdapter>
inline void
expandNarrowband(
TreeType& distTree,
Int32TreeType& indexTree,
BoolTreeType& maskTree,
std::vector<typename BoolTreeType::LeafNodeType*>& maskNodes,
const MeshDataAdapter& mesh,
typename TreeType::ValueType exteriorBandWidth,
typename TreeType::ValueType interiorBandWidth,
typename TreeType::ValueType voxelSize)
{
ExpandNarrowband<TreeType, MeshDataAdapter> expandOp(maskNodes, maskTree,
distTree, indexTree, mesh, exteriorBandWidth, interiorBandWidth, voxelSize);
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, maskNodes.size()), expandOp);
tbb::parallel_for(tbb::blocked_range<size_t>(0, expandOp.updatedIndexNodes().size()),
UnionValueMasks<typename TreeType::LeafNodeType, typename Int32TreeType::LeafNodeType>(
expandOp.updatedDistNodes(), expandOp.updatedIndexNodes()));
tbb::task_group tasks;
tasks.run(AddNodes<TreeType>(distTree, expandOp.newDistNodes()));
tasks.run(AddNodes<Int32TreeType>(indexTree, expandOp.newIndexNodes()));
tasks.wait();
maskTree.clear();
maskTree.merge(expandOp.newMaskTree());
}
////////////////////////////////////////
// Transform values (sqrt, world space scaling and sign flip if sdf)
template<typename TreeType>
struct TransformValues
{
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename TreeType::ValueType;
TransformValues(std::vector<LeafNodeType*>& nodes,
ValueType voxelSize, bool unsignedDist)
: mNodes(&nodes[0])
, mVoxelSize(voxelSize)
, mUnsigned(unsignedDist)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
typename LeafNodeType::ValueOnIter iter;
const bool udf = mUnsigned;
const ValueType w[2] = { -mVoxelSize, mVoxelSize };
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
for (iter = mNodes[n]->beginValueOn(); iter; ++iter) {
ValueType& val = const_cast<ValueType&>(iter.getValue());
val = w[udf || (val < ValueType(0.0))] * std::sqrt(std::abs(val));
}
}
}
private:
LeafNodeType * * const mNodes;
const ValueType mVoxelSize;
const bool mUnsigned;
};
// Inactivate values outside the (exBandWidth, inBandWidth) range.
template<typename TreeType>
struct InactivateValues
{
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename TreeType::ValueType;
InactivateValues(std::vector<LeafNodeType*>& nodes,
ValueType exBandWidth, ValueType inBandWidth)
: mNodes(nodes.empty() ? nullptr : &nodes[0])
, mExBandWidth(exBandWidth)
, mInBandWidth(inBandWidth)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
typename LeafNodeType::ValueOnIter iter;
const ValueType exVal = mExBandWidth;
const ValueType inVal = -mInBandWidth;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
for (iter = mNodes[n]->beginValueOn(); iter; ++iter) {
ValueType& val = const_cast<ValueType&>(iter.getValue());
const bool inside = val < ValueType(0.0);
if (inside && !(val > inVal)) {
val = inVal;
iter.setValueOff();
} else if (!inside && !(val < exVal)) {
val = exVal;
iter.setValueOff();
}
}
}
}
private:
LeafNodeType * * const mNodes;
const ValueType mExBandWidth, mInBandWidth;
};
template<typename TreeType>
struct OffsetValues
{
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename TreeType::ValueType;
OffsetValues(std::vector<LeafNodeType*>& nodes, ValueType offset)
: mNodes(nodes.empty() ? nullptr : &nodes[0]), mOffset(offset)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
const ValueType offset = mOffset;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
typename LeafNodeType::ValueOnIter iter = mNodes[n]->beginValueOn();
for (; iter; ++iter) {
ValueType& val = const_cast<ValueType&>(iter.getValue());
val += offset;
}
}
}
private:
LeafNodeType * * const mNodes;
const ValueType mOffset;
};
template<typename TreeType>
struct Renormalize
{
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename TreeType::ValueType;
Renormalize(const TreeType& tree, const std::vector<LeafNodeType*>& nodes,
ValueType* buffer, ValueType voxelSize)
: mTree(&tree)
, mNodes(nodes.empty() ? nullptr : &nodes[0])
, mBuffer(buffer)
, mVoxelSize(voxelSize)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const
{
using Vec3Type = math::Vec3<ValueType>;
tree::ValueAccessor<const TreeType> acc(*mTree);
Coord ijk;
Vec3Type up, down;
const ValueType dx = mVoxelSize, invDx = ValueType(1.0) / mVoxelSize;
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
ValueType* bufferData = &mBuffer[n * LeafNodeType::SIZE];
typename LeafNodeType::ValueOnCIter iter = mNodes[n]->cbeginValueOn();
for (; iter; ++iter) {
const ValueType phi0 = *iter;
ijk = iter.getCoord();
up[0] = acc.getValue(ijk.offsetBy(1, 0, 0)) - phi0;
up[1] = acc.getValue(ijk.offsetBy(0, 1, 0)) - phi0;
up[2] = acc.getValue(ijk.offsetBy(0, 0, 1)) - phi0;
down[0] = phi0 - acc.getValue(ijk.offsetBy(-1, 0, 0));
down[1] = phi0 - acc.getValue(ijk.offsetBy(0, -1, 0));
down[2] = phi0 - acc.getValue(ijk.offsetBy(0, 0, -1));
const ValueType normSqGradPhi = math::GodunovsNormSqrd(phi0 > 0.0, down, up);
const ValueType diff = math::Sqrt(normSqGradPhi) * invDx - ValueType(1.0);
const ValueType S = phi0 / (math::Sqrt(math::Pow2(phi0) + normSqGradPhi));
bufferData[iter.pos()] = phi0 - dx * S * diff;
}
}
}
private:
TreeType const * const mTree;
LeafNodeType const * const * const mNodes;
ValueType * const mBuffer;
const ValueType mVoxelSize;
};
template<typename TreeType>
struct MinCombine
{
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename TreeType::ValueType;
MinCombine(std::vector<LeafNodeType*>& nodes, const ValueType* buffer)
: mNodes(nodes.empty() ? nullptr : &nodes[0]), mBuffer(buffer)
{
}
void operator()(const tbb::blocked_range<size_t>& range) const {
for (size_t n = range.begin(), N = range.end(); n < N; ++n) {
const ValueType* bufferData = &mBuffer[n * LeafNodeType::SIZE];
typename LeafNodeType::ValueOnIter iter = mNodes[n]->beginValueOn();
for (; iter; ++iter) {
ValueType& val = const_cast<ValueType&>(iter.getValue());
val = std::min(val, bufferData[iter.pos()]);
}
}
}
private:
LeafNodeType * * const mNodes;
ValueType const * const mBuffer;
};
} // mesh_to_volume_internal namespace
/// @endcond
////////////////////////////////////////
// Utility method implementation
template <typename FloatTreeT>
void
traceExteriorBoundaries(FloatTreeT& tree)
{
using ConnectivityTable = mesh_to_volume_internal::LeafNodeConnectivityTable<FloatTreeT>;
// Build a node connectivity table where each leaf node has an offset into a
// linearized list of nodes, and each leaf stores its six axis aligned neighbor
// offsets
ConnectivityTable nodeConnectivity(tree);
std::vector<size_t> zStartNodes, yStartNodes, xStartNodes;
// Store all nodes which do not have negative neighbors i.e. the nodes furthest
// in -X, -Y, -Z. We sweep from lowest coordinate positions +axis and then
// from the furthest positive coordinate positions -axis
for (size_t n = 0; n < nodeConnectivity.size(); ++n) {
if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevX()[n]) {
xStartNodes.push_back(n);
}
if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevY()[n]) {
yStartNodes.push_back(n);
}
if (ConnectivityTable::INVALID_OFFSET == nodeConnectivity.offsetsPrevZ()[n]) {
zStartNodes.push_back(n);
}
}
using SweepingOp = mesh_to_volume_internal::SweepExteriorSign<FloatTreeT>;
// Sweep the exterior value signs (make them negative) up until the voxel intersection
// with the isosurface. Do this in both lowest -> + and largest -> - directions
tbb::parallel_for(tbb::blocked_range<size_t>(0, zStartNodes.size()),
SweepingOp(SweepingOp::Z_AXIS, zStartNodes, nodeConnectivity));
tbb::parallel_for(tbb::blocked_range<size_t>(0, yStartNodes.size()),
SweepingOp(SweepingOp::Y_AXIS, yStartNodes, nodeConnectivity));
tbb::parallel_for(tbb::blocked_range<size_t>(0, xStartNodes.size()),
SweepingOp(SweepingOp::X_AXIS, xStartNodes, nodeConnectivity));
const size_t numLeafNodes = nodeConnectivity.size();
const size_t numVoxels = numLeafNodes * FloatTreeT::LeafNodeType::SIZE;
std::unique_ptr<bool[]> changedNodeMaskA{new bool[numLeafNodes]};
std::unique_ptr<bool[]> changedNodeMaskB{new bool[numLeafNodes]};
std::unique_ptr<bool[]> changedVoxelMask{new bool[numVoxels]};
mesh_to_volume_internal::fillArray(changedNodeMaskA.get(), true, numLeafNodes);
mesh_to_volume_internal::fillArray(changedNodeMaskB.get(), false, numLeafNodes);
mesh_to_volume_internal::fillArray(changedVoxelMask.get(), false, numVoxels);
const tbb::blocked_range<size_t> nodeRange(0, numLeafNodes);
bool nodesUpdated = false;
do {
// Perform per leaf node localized propagation of signs by looping over
// all voxels and checking to see if any of their neighbors (within the
// same leaf) are negative
tbb::parallel_for(nodeRange, mesh_to_volume_internal::SeedFillExteriorSign<FloatTreeT>(
nodeConnectivity.nodes(), changedNodeMaskA.get()));
// For each leaf, check its axis aligned neighbors and propagate any changes
// which occurred previously (in SeedFillExteriorSign OR in SyncVoxelMask) to
// the leaf faces. Note that this operation stores the propagated face results
// in a separate buffer (changedVoxelMask) to avoid writing to nodes being read
// from other threads. Additionally mark any leaf nodes which will absorb any
// changes from its neighbors in changedNodeMaskB
tbb::parallel_for(nodeRange, mesh_to_volume_internal::SeedPoints<FloatTreeT>(
nodeConnectivity, changedNodeMaskA.get(), changedNodeMaskB.get(),
changedVoxelMask.get()));
// Only nodes where a value was influenced by an adjacent node need to be
// processed on the next pass.
changedNodeMaskA.swap(changedNodeMaskB);
nodesUpdated = false;
for (size_t n = 0; n < numLeafNodes; ++n) {
nodesUpdated |= changedNodeMaskA[n];
if (nodesUpdated) break;
}
// Use the voxel mask updates in ::SeedPoints to actually assign the new values
// across leaf node faces
if (nodesUpdated) {
tbb::parallel_for(nodeRange, mesh_to_volume_internal::SyncVoxelMask<FloatTreeT>(
nodeConnectivity.nodes(), changedNodeMaskA.get(), changedVoxelMask.get()));
}
} while (nodesUpdated);
} // void traceExteriorBoundaries()
////////////////////////////////////////
template <typename T, Index Log2Dim, typename InteriorTest>
void
floodFillLeafNode(tree::LeafNode<T,Log2Dim>& leafNode, const InteriorTest& interiorTest) {
// Floods fills a single leaf node.
// Starts with all voxels in NOT_VISITED.
// Final result is voxels in either POSITIVE, NEGATIVE, or NOT_ASSIGNED.
// Voxels that were categorized as NEGATIVE are negated.
// The NOT_ASSIGNED is all voxels within 0.75 of the zero-crossing.
//
// NOT_VISITED voxels, if outside the 0.75 band, will query the oracle
// to get a POSITIVE Or NEGATIVE sign (with interior being POSITIVE!)
//
// After setting a NOT_VISITED to either POSITIVE or NEGATIVE, an 8-way
// depth-first floodfill is done, stopping at either the 0.75 boundary
// or visited voxels.
enum VoxelState {
NOT_VISITED = 0,
POSITIVE = 1,
NEGATIVE = 2,
NOT_ASSIGNED = 3
};
const auto DIM = leafNode.DIM;
const auto SIZE = leafNode.SIZE;
std::vector<VoxelState> voxelState(SIZE, NOT_VISITED);
std::vector<std::pair<Index, VoxelState>> offsetStack;
offsetStack.reserve(SIZE);
for (Index offset=0; offset<SIZE; offset++) {
const auto value = leafNode.getValue(offset);
// We do not assign anything for voxel close to the mesh
// This condition is aligned with the condition in traceVoxelLine
if (std::abs(value) <= 0.75) {
voxelState[offset] = NOT_ASSIGNED;
} else if (voxelState[offset] == NOT_VISITED) {
auto coord = leafNode.offsetToGlobalCoord(offset);
if (interiorTest(coord)){
// Yes we assigne positive values to interior points
// this is aligned with how meshToVolume works internally
offsetStack.push_back({offset, POSITIVE});
voxelState[offset] = POSITIVE;
} else {
offsetStack.push_back({offset, NEGATIVE});
voxelState[offset] = NEGATIVE;
}
while(!offsetStack.empty()){
auto [off, state] = offsetStack[offsetStack.size()-1];
offsetStack.pop_back();
if (state == NEGATIVE) {
leafNode.setValueOnly(off, -leafNode.getValue(off));
}
// iterate over all neighbours and assign identical state
// if they have not been visited and if they are far away
// from the mesh (the condition is same as in traceVoxelLine)
for (int dim=2; dim>=0; dim--){
for (int i = -1; i <=1; ++(++i)){
int dimIdx = (off >> dim * Log2Dim) % DIM;
auto neighOff = off + (1 << dim * Log2Dim) * i;
if ((0 < dimIdx) &&
(dimIdx < (int)DIM - 1) &&
(voxelState[neighOff] == NOT_VISITED)) {
if (std::abs(leafNode.getValue(neighOff)) <= 0.75) {
voxelState[neighOff] = NOT_ASSIGNED;
} else {
offsetStack.push_back({neighOff, state});
voxelState[neighOff] = state;
}
}
}
}
}
}
}
}
////////////////////////////////////////
/// @brief Sets the sign of voxel values of `tree` based on the `interiorTest`
///
/// Inside is set to positive and outside to negative. This is in reverse to the usual
/// level set convention, but `meshToVolume` uses the opposite convention at certain point.
///
/// InteriorTest has to be a function `Coord -> bool` which evaluates true
/// inside of the mesh and false outside.
///
/// Furthermore, InteriorTest does not have to be thread-safe, but it has to be
/// copy constructible and evaluating different coppy has to be thread-safe.
///
/// Example of a interior test
///
/// auto acc = tree->getAccessor();
///
/// auto test = [acc = grid.getConstAccessor()](const Cood& coord) -> bool {
/// return acc->get(coord) <= 0 ? true : false;
/// }
template <typename FloatTreeT, typename InteriorTest>
void
evaluateInteriorTest(FloatTreeT& tree, InteriorTest interiorTest, InteriorTestStrategy interiorTestStrategy)
{
static_assert(std::is_invocable_r<bool, InteriorTest, Coord>::value,
"InteriorTest has to be a function `Coord -> bool`!");
static_assert(std::is_copy_constructible_v<InteriorTest>,
"InteriorTest has to be copyable!");
using LeafT = typename FloatTreeT::LeafNodeType;
if (interiorTestStrategy == EVAL_EVERY_VOXEL) {
auto op = [interiorTest](auto& node) {
using Node = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<Node, LeafT>) {
for (auto iter = node.beginValueAll(); iter; ++iter) {
if (!interiorTest(iter.getCoord())) {
iter.setValue(-*iter);
}
}
} else {
for (auto iter = node.beginChildOff(); iter; ++iter) {
if (!interiorTest(iter.getCoord())) {
iter.setValue(-*iter);
}
}
}
};
openvdb::tree::NodeManager nodes(tree);
nodes.foreachBottomUp(op);
}
if (interiorTestStrategy == EVAL_EVERY_TILE) {
auto op = [interiorTest](auto& node) {
using Node = std::decay_t<decltype(node)>;
if constexpr (std::is_same_v<Node, LeafT>) {
// // leaf node
LeafT& leaf = static_cast<LeafT&>(node);
floodFillLeafNode(leaf, interiorTest);
} else {
for (auto iter = node.beginChildOff(); iter; ++iter) {
if (!interiorTest(iter.getCoord())) {
iter.setValue(-*iter);
}
}
}
};
openvdb::tree::NodeManager nodes(tree);
nodes.foreachBottomUp(op);
}
} // void evaluateInteriorTest()
////////////////////////////////////////
template <typename GridType, typename MeshDataAdapter, typename Interrupter, typename InteriorTest>
typename GridType::Ptr
meshToVolume(
Interrupter& interrupter,
const MeshDataAdapter& mesh,
const math::Transform& transform,
float exteriorBandWidth,
float interiorBandWidth,
int flags,
typename GridType::template ValueConverter<Int32>::Type * polygonIndexGrid,
InteriorTest interiorTest,
InteriorTestStrategy interiorTestStrat)
{
using GridTypePtr = typename GridType::Ptr;
using TreeType = typename GridType::TreeType;
using LeafNodeType = typename TreeType::LeafNodeType;
using ValueType = typename GridType::ValueType;
using Int32GridType = typename GridType::template ValueConverter<Int32>::Type;
using Int32TreeType = typename Int32GridType::TreeType;
using BoolTreeType = typename TreeType::template ValueConverter<bool>::Type;
//////////
// Setup
GridTypePtr distGrid(new GridType(std::numeric_limits<ValueType>::max()));
distGrid->setTransform(transform.copy());
ValueType exteriorWidth = ValueType(exteriorBandWidth);
ValueType interiorWidth = ValueType(interiorBandWidth);
// Note: inf interior width is all right, this value makes the converter fill
// interior regions with distance values.
if (!std::isfinite(exteriorWidth) || std::isnan(interiorWidth)) {
std::stringstream msg;
msg << "Illegal narrow band width: exterior = " << exteriorWidth
<< ", interior = " << interiorWidth;
OPENVDB_LOG_DEBUG(msg.str());
return distGrid;
}
const ValueType voxelSize = ValueType(transform.voxelSize()[0]);
if (!std::isfinite(voxelSize) || math::isZero(voxelSize)) {
std::stringstream msg;
msg << "Illegal transform, voxel size = " << voxelSize;
OPENVDB_LOG_DEBUG(msg.str());
return distGrid;
}
// Convert narrow band width from voxel units to world space units.
exteriorWidth *= voxelSize;
// Avoid the unit conversion if the interior band width is set to
// inf or std::numeric_limits<float>::max().
if (interiorWidth < std::numeric_limits<ValueType>::max()) {
interiorWidth *= voxelSize;
}
const bool computeSignedDistanceField = (flags & UNSIGNED_DISTANCE_FIELD) == 0;
const bool removeIntersectingVoxels = (flags & DISABLE_INTERSECTING_VOXEL_REMOVAL) == 0;
const bool renormalizeValues = (flags & DISABLE_RENORMALIZATION) == 0;
const bool trimNarrowBand = (flags & DISABLE_NARROW_BAND_TRIMMING) == 0;
Int32GridType* indexGrid = nullptr;
typename Int32GridType::Ptr temporaryIndexGrid;
if (polygonIndexGrid) {
indexGrid = polygonIndexGrid;
} else {
temporaryIndexGrid.reset(new Int32GridType(Int32(util::INVALID_IDX)));
indexGrid = temporaryIndexGrid.get();
}
indexGrid->newTree();
indexGrid->setTransform(transform.copy());
if (computeSignedDistanceField) {
distGrid->setGridClass(GRID_LEVEL_SET);
} else {
distGrid->setGridClass(GRID_UNKNOWN);
interiorWidth = ValueType(0.0);
}
TreeType& distTree = distGrid->tree();
Int32TreeType& indexTree = indexGrid->tree();
//////////
// Voxelize mesh
{
using VoxelizationDataType = mesh_to_volume_internal::VoxelizationData<TreeType>;
using DataTable = tbb::enumerable_thread_specific<typename VoxelizationDataType::Ptr>;
DataTable data;
using Voxelizer =
mesh_to_volume_internal::VoxelizePolygons<TreeType, MeshDataAdapter, Interrupter>;
const tbb::blocked_range<size_t> polygonRange(0, mesh.polygonCount());
tbb::parallel_for(polygonRange, Voxelizer(data, mesh, &interrupter));
for (typename DataTable::iterator i = data.begin(); i != data.end(); ++i) {
VoxelizationDataType& dataItem = **i;
mesh_to_volume_internal::combineData(
distTree, indexTree, dataItem.distTree, dataItem.indexTree);
}
}
// The progress estimates are based on the observed average time for a few different
// test cases and is only intended to provide some rough progression feedback to the user.
if (interrupter.wasInterrupted(30)) return distGrid;
//////////
// Classify interior and exterior regions
if (computeSignedDistanceField) {
/// If interior test is not provided
if constexpr (std::is_same_v<InteriorTest, std::nullptr_t>) {
// Determines the inside/outside state for the narrow band of voxels.
(void) interiorTest; // Trigger usage.
traceExteriorBoundaries(distTree);
} else {
evaluateInteriorTest(distTree, interiorTest, interiorTestStrat);
}
/// Do not fix intersecting voxels if we have evaluated interior test for every voxel.
bool signInitializedForEveryVoxel =
/// interior test was provided i.e. not null
!std::is_same_v<InteriorTest, std::nullptr_t> &&
/// interior test was evaluated for every voxel
interiorTestStrat == EVAL_EVERY_VOXEL;
if (!signInitializedForEveryVoxel) {
std::vector<LeafNodeType*> nodes;
nodes.reserve(distTree.leafCount());
distTree.getNodes(nodes);
const tbb::blocked_range<size_t> nodeRange(0, nodes.size());
using SignOp =
mesh_to_volume_internal::ComputeIntersectingVoxelSign<TreeType, MeshDataAdapter>;
tbb::parallel_for(nodeRange, SignOp(nodes, distTree, indexTree, mesh));
if (interrupter.wasInterrupted(45)) return distGrid;
// Remove voxels created by self intersecting portions of the mesh.
if (removeIntersectingVoxels) {
tbb::parallel_for(nodeRange,
mesh_to_volume_internal::ValidateIntersectingVoxels<TreeType>(distTree, nodes));
tbb::parallel_for(nodeRange,
mesh_to_volume_internal::RemoveSelfIntersectingSurface<TreeType>(
nodes, distTree, indexTree));
tools::pruneInactive(distTree, /*threading=*/true);
tools::pruneInactive(indexTree, /*threading=*/true);
}
}
}
if (interrupter.wasInterrupted(50)) return distGrid;
if (distTree.activeVoxelCount() == 0) {
distTree.clear();
distTree.root().setBackground(exteriorWidth, /*updateChildNodes=*/false);
return distGrid;
}
// Transform values (world space scaling etc.).
{
std::vector<LeafNodeType*> nodes;
nodes.reserve(distTree.leafCount());
distTree.getNodes(nodes);
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::TransformValues<TreeType>(
nodes, voxelSize, !computeSignedDistanceField));
}
// Propagate sign information into tile regions.
if (computeSignedDistanceField) {
distTree.root().setBackground(exteriorWidth, /*updateChildNodes=*/false);
tools::signedFloodFillWithValues(distTree, exteriorWidth, -interiorWidth);
} else {
tools::changeBackground(distTree, exteriorWidth);
}
if (interrupter.wasInterrupted(54)) return distGrid;
//////////
// Expand the narrow band region
const ValueType minBandWidth = voxelSize * ValueType(2.0);
if (interiorWidth > minBandWidth || exteriorWidth > minBandWidth) {
// Create the initial voxel mask.
BoolTreeType maskTree(false);
{
std::vector<LeafNodeType*> nodes;
nodes.reserve(distTree.leafCount());
distTree.getNodes(nodes);
mesh_to_volume_internal::ConstructVoxelMask<TreeType> op(maskTree, distTree, nodes);
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, nodes.size()), op);
}
// Progress estimation
unsigned maxIterations = std::numeric_limits<unsigned>::max();
float progress = 54.0f, step = 0.0f;
double estimated =
2.0 * std::ceil((std::max(interiorWidth, exteriorWidth) - minBandWidth) / voxelSize);
if (estimated < double(maxIterations)) {
maxIterations = unsigned(estimated);
step = 40.0f / float(maxIterations);
}
std::vector<typename BoolTreeType::LeafNodeType*> maskNodes;
unsigned count = 0;
while (true) {
if (interrupter.wasInterrupted(int(progress))) return distGrid;
const size_t maskNodeCount = maskTree.leafCount();
if (maskNodeCount == 0) break;
maskNodes.clear();
maskNodes.reserve(maskNodeCount);
maskTree.getNodes(maskNodes);
const tbb::blocked_range<size_t> range(0, maskNodes.size());
tbb::parallel_for(range,
mesh_to_volume_internal::DiffLeafNodeMask<TreeType>(distTree, maskNodes));
mesh_to_volume_internal::expandNarrowband(distTree, indexTree, maskTree, maskNodes,
mesh, exteriorWidth, interiorWidth, voxelSize);
if ((++count) >= maxIterations) break;
progress += step;
}
}
if (interrupter.wasInterrupted(94)) return distGrid;
if (!polygonIndexGrid) indexGrid->clear();
/////////
// Renormalize distances to smooth out bumps caused by self intersecting
// and overlapping portions of the mesh and renormalize the level set.
if (computeSignedDistanceField && renormalizeValues) {
std::vector<LeafNodeType*> nodes;
nodes.reserve(distTree.leafCount());
distTree.getNodes(nodes);
std::unique_ptr<ValueType[]> buffer{new ValueType[LeafNodeType::SIZE * nodes.size()]};
const ValueType offset = ValueType(0.8 * voxelSize);
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::OffsetValues<TreeType>(nodes, -offset));
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::Renormalize<TreeType>(
distTree, nodes, buffer.get(), voxelSize));
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::MinCombine<TreeType>(nodes, buffer.get()));
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::OffsetValues<TreeType>(
nodes, offset - mesh_to_volume_internal::Tolerance<ValueType>::epsilon()));
}
if (interrupter.wasInterrupted(99)) return distGrid;
/////////
// Remove active voxels that exceed the narrow band limits
if (trimNarrowBand && std::min(interiorWidth, exteriorWidth) < voxelSize * ValueType(4.0)) {
std::vector<LeafNodeType*> nodes;
nodes.reserve(distTree.leafCount());
distTree.getNodes(nodes);
tbb::parallel_for(tbb::blocked_range<size_t>(0, nodes.size()),
mesh_to_volume_internal::InactivateValues<TreeType>(
nodes, exteriorWidth, computeSignedDistanceField ? interiorWidth : exteriorWidth));
tools::pruneLevelSet(
distTree, exteriorWidth, computeSignedDistanceField ? -interiorWidth : -exteriorWidth);
}
return distGrid;
}
template <typename GridType, typename MeshDataAdapter, typename InteriorTest>
typename GridType::Ptr
meshToVolume(
const MeshDataAdapter& mesh,
const math::Transform& transform,
float exteriorBandWidth,
float interiorBandWidth,
int flags,
typename GridType::template ValueConverter<Int32>::Type * polygonIndexGrid,
InteriorTest /*interiorTest*/,
InteriorTestStrategy /*interiorTestStrat*/)
{
util::NullInterrupter nullInterrupter;
return meshToVolume<GridType>(nullInterrupter, mesh, transform,
exteriorBandWidth, interiorBandWidth, flags, polygonIndexGrid);
}
////////////////////////////////////////
//{
/// @cond OPENVDB_DOCS_INTERNAL
/// @internal This overload is enabled only for grids with a scalar, floating-point ValueType.
template<typename GridType, typename Interrupter>
inline typename std::enable_if<std::is_floating_point<typename GridType::ValueType>::value,
typename GridType::Ptr>::type
doMeshConversion(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float exBandWidth,
float inBandWidth,
bool unsignedDistanceField = false)
{
if (points.empty()) {
return typename GridType::Ptr(new GridType(typename GridType::ValueType(exBandWidth)));
}
const size_t numPoints = points.size();
std::unique_ptr<Vec3s[]> indexSpacePoints{new Vec3s[numPoints]};
// transform points to local grid index space
tbb::parallel_for(tbb::blocked_range<size_t>(0, numPoints),
mesh_to_volume_internal::TransformPoints<Vec3s>(
&points[0], indexSpacePoints.get(), xform));
const int conversionFlags = unsignedDistanceField ? UNSIGNED_DISTANCE_FIELD : 0;
if (quads.empty()) {
QuadAndTriangleDataAdapter<Vec3s, Vec3I>
mesh(indexSpacePoints.get(), numPoints, &triangles[0], triangles.size());
return meshToVolume<GridType>(
interrupter, mesh, xform, exBandWidth, inBandWidth, conversionFlags);
} else if (triangles.empty()) {
QuadAndTriangleDataAdapter<Vec3s, Vec4I>
mesh(indexSpacePoints.get(), numPoints, &quads[0], quads.size());
return meshToVolume<GridType>(
interrupter, mesh, xform, exBandWidth, inBandWidth, conversionFlags);
}
// pack primitives
const size_t numPrimitives = triangles.size() + quads.size();
std::unique_ptr<Vec4I[]> prims{new Vec4I[numPrimitives]};
for (size_t n = 0, N = triangles.size(); n < N; ++n) {
const Vec3I& triangle = triangles[n];
Vec4I& prim = prims[n];
prim[0] = triangle[0];
prim[1] = triangle[1];
prim[2] = triangle[2];
prim[3] = util::INVALID_IDX;
}
const size_t offset = triangles.size();
for (size_t n = 0, N = quads.size(); n < N; ++n) {
prims[offset + n] = quads[n];
}
QuadAndTriangleDataAdapter<Vec3s, Vec4I>
mesh(indexSpacePoints.get(), numPoints, prims.get(), numPrimitives);
return meshToVolume<GridType>(interrupter, mesh, xform,
exBandWidth, inBandWidth, conversionFlags);
}
/// @internal This overload is enabled only for grids that do not have a scalar,
/// floating-point ValueType.
template<typename GridType, typename Interrupter>
inline typename std::enable_if<!std::is_floating_point<typename GridType::ValueType>::value,
typename GridType::Ptr>::type
doMeshConversion(
Interrupter&,
const math::Transform& /*xform*/,
const std::vector<Vec3s>& /*points*/,
const std::vector<Vec3I>& /*triangles*/,
const std::vector<Vec4I>& /*quads*/,
float /*exBandWidth*/,
float /*inBandWidth*/,
bool /*unsignedDistanceField*/ = false)
{
OPENVDB_THROW(TypeError,
"mesh to volume conversion is supported only for scalar floating-point grids");
}
/// @endcond
//}
////////////////////////////////////////
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
float halfWidth)
{
util::NullInterrupter nullInterrupter;
return meshToLevelSet<GridType>(nullInterrupter, xform, points, triangles, halfWidth);
}
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
float halfWidth)
{
std::vector<Vec4I> quads(0);
return doMeshConversion<GridType>(interrupter, xform, points, triangles, quads,
halfWidth, halfWidth);
}
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec4I>& quads,
float halfWidth)
{
util::NullInterrupter nullInterrupter;
return meshToLevelSet<GridType>(nullInterrupter, xform, points, quads, halfWidth);
}
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec4I>& quads,
float halfWidth)
{
std::vector<Vec3I> triangles(0);
return doMeshConversion<GridType>(interrupter, xform, points, triangles, quads,
halfWidth, halfWidth);
}
template<typename GridType>
typename GridType::Ptr
meshToLevelSet(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float halfWidth)
{
util::NullInterrupter nullInterrupter;
return meshToLevelSet<GridType>(
nullInterrupter, xform, points, triangles, quads, halfWidth);
}
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToLevelSet(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float halfWidth)
{
return doMeshConversion<GridType>(interrupter, xform, points, triangles, quads,
halfWidth, halfWidth);
}
template<typename GridType>
typename GridType::Ptr
meshToSignedDistanceField(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float exBandWidth,
float inBandWidth)
{
util::NullInterrupter nullInterrupter;
return meshToSignedDistanceField<GridType>(
nullInterrupter, xform, points, triangles, quads, exBandWidth, inBandWidth);
}
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToSignedDistanceField(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float exBandWidth,
float inBandWidth)
{
return doMeshConversion<GridType>(interrupter, xform, points, triangles,
quads, exBandWidth, inBandWidth);
}
template<typename GridType>
typename GridType::Ptr
meshToUnsignedDistanceField(
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float bandWidth)
{
util::NullInterrupter nullInterrupter;
return meshToUnsignedDistanceField<GridType>(
nullInterrupter, xform, points, triangles, quads, bandWidth);
}
template<typename GridType, typename Interrupter>
typename GridType::Ptr
meshToUnsignedDistanceField(
Interrupter& interrupter,
const openvdb::math::Transform& xform,
const std::vector<Vec3s>& points,
const std::vector<Vec3I>& triangles,
const std::vector<Vec4I>& quads,
float bandWidth)
{
return doMeshConversion<GridType>(interrupter, xform, points, triangles, quads,
bandWidth, bandWidth, true);
}
////////////////////////////////////////////////////////////////////////////////
// Required by several of the tree nodes
inline std::ostream&
operator<<(std::ostream& ostr, const MeshToVoxelEdgeData::EdgeData& rhs)
{
ostr << "{[ " << rhs.mXPrim << ", " << rhs.mXDist << "]";
ostr << " [ " << rhs.mYPrim << ", " << rhs.mYDist << "]";
ostr << " [ " << rhs.mZPrim << ", " << rhs.mZDist << "]}";
return ostr;
}
// Required by math::Abs
inline MeshToVoxelEdgeData::EdgeData
Abs(const MeshToVoxelEdgeData::EdgeData& x)
{
return x;
}
////////////////////////////////////////
class MeshToVoxelEdgeData::GenEdgeData
{
public:
GenEdgeData(
const std::vector<Vec3s>& pointList,
const std::vector<Vec4I>& polygonList);
void run(bool threaded = true);
GenEdgeData(GenEdgeData& rhs, tbb::split);
inline void operator() (const tbb::blocked_range<size_t> &range);
inline void join(GenEdgeData& rhs);
inline TreeType& tree() { return mTree; }
private:
void operator=(const GenEdgeData&) {}
struct Primitive { Vec3d a, b, c, d; Int32 index; };
template<bool IsQuad>
inline void voxelize(const Primitive&);
template<bool IsQuad>
inline bool evalPrimitive(const Coord&, const Primitive&);
inline bool rayTriangleIntersection( const Vec3d& origin, const Vec3d& dir,
const Vec3d& a, const Vec3d& b, const Vec3d& c, double& t);
TreeType mTree;
Accessor mAccessor;
const std::vector<Vec3s>& mPointList;
const std::vector<Vec4I>& mPolygonList;
// Used internally for acceleration
using IntTreeT = TreeType::ValueConverter<Int32>::Type;
IntTreeT mLastPrimTree;
tree::ValueAccessor<IntTreeT> mLastPrimAccessor;
}; // class MeshToVoxelEdgeData::GenEdgeData
inline
MeshToVoxelEdgeData::GenEdgeData::GenEdgeData(
const std::vector<Vec3s>& pointList,
const std::vector<Vec4I>& polygonList)
: mTree(EdgeData())
, mAccessor(mTree)
, mPointList(pointList)
, mPolygonList(polygonList)
, mLastPrimTree(Int32(util::INVALID_IDX))
, mLastPrimAccessor(mLastPrimTree)
{
}
inline
MeshToVoxelEdgeData::GenEdgeData::GenEdgeData(GenEdgeData& rhs, tbb::split)
: mTree(EdgeData())
, mAccessor(mTree)
, mPointList(rhs.mPointList)
, mPolygonList(rhs.mPolygonList)
, mLastPrimTree(Int32(util::INVALID_IDX))
, mLastPrimAccessor(mLastPrimTree)
{
}
inline void
MeshToVoxelEdgeData::GenEdgeData::run(bool threaded)
{
if (threaded) {
tbb::parallel_reduce(tbb::blocked_range<size_t>(0, mPolygonList.size()), *this);
} else {
(*this)(tbb::blocked_range<size_t>(0, mPolygonList.size()));
}
}
inline void
MeshToVoxelEdgeData::GenEdgeData::join(GenEdgeData& rhs)
{
using RootNodeType = TreeType::RootNodeType;
using NodeChainType = RootNodeType::NodeChainType;
static_assert(NodeChainType::Size > 1, "expected tree height > 1");
using InternalNodeType = typename NodeChainType::template Get<1>;
Coord ijk;
Index offset;
rhs.mTree.clearAllAccessors();
TreeType::LeafIter leafIt = rhs.mTree.beginLeaf();
for ( ; leafIt; ++leafIt) {
ijk = leafIt->origin();
TreeType::LeafNodeType* lhsLeafPt = mTree.probeLeaf(ijk);
if (!lhsLeafPt) {
mAccessor.addLeaf(rhs.mAccessor.probeLeaf(ijk));
InternalNodeType* node = rhs.mAccessor.getNode<InternalNodeType>();
node->stealNode<TreeType::LeafNodeType>(ijk, EdgeData(), false);
rhs.mAccessor.clear();
} else {
TreeType::LeafNodeType::ValueOnCIter it = leafIt->cbeginValueOn();
for ( ; it; ++it) {
offset = it.pos();
const EdgeData& rhsValue = it.getValue();
if (!lhsLeafPt->isValueOn(offset)) {
lhsLeafPt->setValueOn(offset, rhsValue);
} else {
EdgeData& lhsValue = const_cast< EdgeData&>(lhsLeafPt->getValue(offset));
if (rhsValue.mXDist < lhsValue.mXDist) {
lhsValue.mXDist = rhsValue.mXDist;
lhsValue.mXPrim = rhsValue.mXPrim;
}
if (rhsValue.mYDist < lhsValue.mYDist) {
lhsValue.mYDist = rhsValue.mYDist;
lhsValue.mYPrim = rhsValue.mYPrim;
}
if (rhsValue.mZDist < lhsValue.mZDist) {
lhsValue.mZDist = rhsValue.mZDist;
lhsValue.mZPrim = rhsValue.mZPrim;
}
}
} // end value iteration
}
} // end leaf iteration
}
inline void
MeshToVoxelEdgeData::GenEdgeData::operator()(const tbb::blocked_range<size_t> &range)
{
Primitive prim;
for (size_t n = range.begin(); n < range.end(); ++n) {
const Vec4I& verts = mPolygonList[n];
prim.index = Int32(n);
prim.a = Vec3d(mPointList[verts[0]]);
prim.b = Vec3d(mPointList[verts[1]]);
prim.c = Vec3d(mPointList[verts[2]]);
if (util::INVALID_IDX != verts[3]) {
prim.d = Vec3d(mPointList[verts[3]]);
voxelize<true>(prim);
} else {
voxelize<false>(prim);
}
}
}
template<bool IsQuad>
inline void
MeshToVoxelEdgeData::GenEdgeData::voxelize(const Primitive& prim)
{
std::deque<Coord> coordList;
Coord ijk, nijk;
ijk = Coord::floor(prim.a);
coordList.push_back(ijk);
evalPrimitive<IsQuad>(ijk, prim);
while (!coordList.empty()) {
ijk = coordList.back();
coordList.pop_back();
for (Int32 i = 0; i < 26; ++i) {
nijk = ijk + util::COORD_OFFSETS[i];
if (prim.index != mLastPrimAccessor.getValue(nijk)) {
mLastPrimAccessor.setValue(nijk, prim.index);
if(evalPrimitive<IsQuad>(nijk, prim)) coordList.push_back(nijk);
}
}
}
}
template<bool IsQuad>
inline bool
MeshToVoxelEdgeData::GenEdgeData::evalPrimitive(const Coord& ijk, const Primitive& prim)
{
Vec3d uvw, org(ijk[0], ijk[1], ijk[2]);
bool intersecting = false;
double t;
EdgeData edgeData;
mAccessor.probeValue(ijk, edgeData);
// Evaluate first triangle
double dist = (org -
closestPointOnTriangleToPoint(prim.a, prim.c, prim.b, org, uvw)).lengthSqr();
if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.c, prim.b, t)) {
if (t < edgeData.mXDist) {
edgeData.mXDist = float(t);
edgeData.mXPrim = prim.index;
intersecting = true;
}
}
if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.c, prim.b, t)) {
if (t < edgeData.mYDist) {
edgeData.mYDist = float(t);
edgeData.mYPrim = prim.index;
intersecting = true;
}
}
if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.c, prim.b, t)) {
if (t < edgeData.mZDist) {
edgeData.mZDist = float(t);
edgeData.mZPrim = prim.index;
intersecting = true;
}
}
if (IsQuad) {
// Split quad into a second triangle and calculate distance.
double secondDist = (org -
closestPointOnTriangleToPoint(prim.a, prim.d, prim.c, org, uvw)).lengthSqr();
if (secondDist < dist) dist = secondDist;
if (rayTriangleIntersection(org, Vec3d(1.0, 0.0, 0.0), prim.a, prim.d, prim.c, t)) {
if (t < edgeData.mXDist) {
edgeData.mXDist = float(t);
edgeData.mXPrim = prim.index;
intersecting = true;
}
}
if (rayTriangleIntersection(org, Vec3d(0.0, 1.0, 0.0), prim.a, prim.d, prim.c, t)) {
if (t < edgeData.mYDist) {
edgeData.mYDist = float(t);
edgeData.mYPrim = prim.index;
intersecting = true;
}
}
if (rayTriangleIntersection(org, Vec3d(0.0, 0.0, 1.0), prim.a, prim.d, prim.c, t)) {
if (t < edgeData.mZDist) {
edgeData.mZDist = float(t);
edgeData.mZPrim = prim.index;
intersecting = true;
}
}
}
if (intersecting) mAccessor.setValue(ijk, edgeData);
return (dist < 0.86602540378443861);
}
inline bool
MeshToVoxelEdgeData::GenEdgeData::rayTriangleIntersection(
const Vec3d& origin, const Vec3d& dir,
const Vec3d& a, const Vec3d& b, const Vec3d& c,
double& t)
{
// Check if ray is parallel with triangle
Vec3d e1 = b - a;
Vec3d e2 = c - a;
Vec3d s1 = dir.cross(e2);
double divisor = s1.dot(e1);
if (!(std::abs(divisor) > 0.0)) return false;
// Compute barycentric coordinates
double inv_divisor = 1.0 / divisor;
Vec3d d = origin - a;
double b1 = d.dot(s1) * inv_divisor;
if (b1 < 0.0 || b1 > 1.0) return false;
Vec3d s2 = d.cross(e1);
double b2 = dir.dot(s2) * inv_divisor;
if (b2 < 0.0 || (b1 + b2) > 1.0) return false;
// Compute distance to intersection point
t = e2.dot(s2) * inv_divisor;
return (t < 0.0) ? false : true;
}
////////////////////////////////////////
inline
MeshToVoxelEdgeData::MeshToVoxelEdgeData()
: mTree(EdgeData())
{
}
inline void
MeshToVoxelEdgeData::convert(
const std::vector<Vec3s>& pointList,
const std::vector<Vec4I>& polygonList)
{
GenEdgeData converter(pointList, polygonList);
converter.run();
mTree.clear();
mTree.merge(converter.tree());
}
inline void
MeshToVoxelEdgeData::getEdgeData(
Accessor& acc,
const Coord& ijk,
std::vector<Vec3d>& points,
std::vector<Index32>& primitives)
{
EdgeData data;
Vec3d point;
Coord coord = ijk;
if (acc.probeValue(coord, data)) {
if (data.mXPrim != util::INVALID_IDX) {
point[0] = double(coord[0]) + data.mXDist;
point[1] = double(coord[1]);
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mXPrim);
}
if (data.mYPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]) + data.mYDist;
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mYPrim);
}
if (data.mZPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]);
point[2] = double(coord[2]) + data.mZDist;
points.push_back(point);
primitives.push_back(data.mZPrim);
}
}
coord[0] += 1;
if (acc.probeValue(coord, data)) {
if (data.mYPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]) + data.mYDist;
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mYPrim);
}
if (data.mZPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]);
point[2] = double(coord[2]) + data.mZDist;
points.push_back(point);
primitives.push_back(data.mZPrim);
}
}
coord[2] += 1;
if (acc.probeValue(coord, data)) {
if (data.mYPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]) + data.mYDist;
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mYPrim);
}
}
coord[0] -= 1;
if (acc.probeValue(coord, data)) {
if (data.mXPrim != util::INVALID_IDX) {
point[0] = double(coord[0]) + data.mXDist;
point[1] = double(coord[1]);
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mXPrim);
}
if (data.mYPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]) + data.mYDist;
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mYPrim);
}
}
coord[1] += 1;
if (acc.probeValue(coord, data)) {
if (data.mXPrim != util::INVALID_IDX) {
point[0] = double(coord[0]) + data.mXDist;
point[1] = double(coord[1]);
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mXPrim);
}
}
coord[2] -= 1;
if (acc.probeValue(coord, data)) {
if (data.mXPrim != util::INVALID_IDX) {
point[0] = double(coord[0]) + data.mXDist;
point[1] = double(coord[1]);
point[2] = double(coord[2]);
points.push_back(point);
primitives.push_back(data.mXPrim);
}
if (data.mZPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]);
point[2] = double(coord[2]) + data.mZDist;
points.push_back(point);
primitives.push_back(data.mZPrim);
}
}
coord[0] += 1;
if (acc.probeValue(coord, data)) {
if (data.mZPrim != util::INVALID_IDX) {
point[0] = double(coord[0]);
point[1] = double(coord[1]);
point[2] = double(coord[2]) + data.mZDist;
points.push_back(point);
primitives.push_back(data.mZPrim);
}
}
}
template<typename GridType, typename VecType>
typename GridType::Ptr
createLevelSetBox(const math::BBox<VecType>& bbox,
const openvdb::math::Transform& xform,
typename VecType::ValueType halfWidth)
{
const Vec3s pmin = Vec3s(xform.worldToIndex(bbox.min()));
const Vec3s pmax = Vec3s(xform.worldToIndex(bbox.max()));
Vec3s points[8];
points[0] = Vec3s(pmin[0], pmin[1], pmin[2]);
points[1] = Vec3s(pmin[0], pmin[1], pmax[2]);
points[2] = Vec3s(pmax[0], pmin[1], pmax[2]);
points[3] = Vec3s(pmax[0], pmin[1], pmin[2]);
points[4] = Vec3s(pmin[0], pmax[1], pmin[2]);
points[5] = Vec3s(pmin[0], pmax[1], pmax[2]);
points[6] = Vec3s(pmax[0], pmax[1], pmax[2]);
points[7] = Vec3s(pmax[0], pmax[1], pmin[2]);
Vec4I faces[6];
faces[0] = Vec4I(0, 1, 2, 3); // bottom
faces[1] = Vec4I(7, 6, 5, 4); // top
faces[2] = Vec4I(4, 5, 1, 0); // front
faces[3] = Vec4I(6, 7, 3, 2); // back
faces[4] = Vec4I(0, 3, 7, 4); // left
faces[5] = Vec4I(1, 5, 6, 2); // right
QuadAndTriangleDataAdapter<Vec3s, Vec4I> mesh(points, 8, faces, 6);
return meshToVolume<GridType>(mesh, xform, static_cast<float>(halfWidth), static_cast<float>(halfWidth));
}
////////////////////////////////////////
// Explicit Template Instantiation
#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION
#ifdef OPENVDB_INSTANTIATE_MESHTOVOLUME
#include <openvdb/util/ExplicitInstantiation.h>
#endif
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToVolume<Grid<TreeT>>(util::NullInterrupter&, \
const QuadAndTriangleDataAdapter<Vec3s, Vec3I>&, const openvdb::math::Transform&, \
float, float, int, Grid<TreeT>::ValueConverter<Int32>::Type*, std::nullptr_t, InteriorTestStrategy)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToVolume<Grid<TreeT>>(util::NullInterrupter&, \
const QuadAndTriangleDataAdapter<Vec3s, Vec4I>&, const openvdb::math::Transform&, \
float, float, int, Grid<TreeT>::ValueConverter<Int32>::Type*, std::nullptr_t, InteriorTestStrategy)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToLevelSet<Grid<TreeT>>(util::NullInterrupter&, \
const openvdb::math::Transform&, const std::vector<Vec3s>&, const std::vector<Vec3I>&, \
float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToLevelSet<Grid<TreeT>>(util::NullInterrupter&, \
const openvdb::math::Transform&, const std::vector<Vec3s>&, const std::vector<Vec4I>&, \
float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToLevelSet<Grid<TreeT>>(util::NullInterrupter&, \
const openvdb::math::Transform&, const std::vector<Vec3s>&, \
const std::vector<Vec3I>&, const std::vector<Vec4I>&, float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToSignedDistanceField<Grid<TreeT>>(util::NullInterrupter&, \
const openvdb::math::Transform&, const std::vector<Vec3s>&, \
const std::vector<Vec3I>&, const std::vector<Vec4I>&, float, float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr meshToUnsignedDistanceField<Grid<TreeT>>(util::NullInterrupter&, \
const openvdb::math::Transform&, const std::vector<Vec3s>&, \
const std::vector<Vec3I>&, const std::vector<Vec4I>&, float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr createLevelSetBox<Grid<TreeT>>(const math::BBox<Vec3s>&, \
const openvdb::math::Transform&, float)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr createLevelSetBox<Grid<TreeT>>(const math::BBox<Vec3d>&, \
const openvdb::math::Transform&, double)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#define _FUNCTION(TreeT) \
void traceExteriorBoundaries(TreeT&)
OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION
#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION
} // namespace tools
} // namespace OPENVDB_VERSION_NAME
} // namespace openvdb
#endif // OPENVDB_TOOLS_MESH_TO_VOLUME_HAS_BEEN_INCLUDED
ChangeBackground.h
Efficient multi-threaded replacement of the background values in tree.
OPENVDB_THROW
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
ExplicitInstantiation.h
FiniteDifference.h
operator<<
OPENVDB_API std::ostream & operator<<(std::ostream &os, half h)
Output h to os, formatted as a float.
NullInterrupter.h
Platform.h
Proximity.h
Prune.h
Defined various multi-threaded utility functions for trees.
SignedFloodFill.h
Propagate the signs of distance values from the active voxels in the narrow band to the inactive valu...
Types.h
Util.h
openvdb::v11_0::Coord::floor
static Coord floor(const Vec3< T > &xyz)
Return the largest integer coordinates that are not greater than xyz (node centered conversion).
Definition Coord.h:56
openvdb::v11_0::math::BBox
Axis-aligned bounding box.
Definition BBox.h:24
openvdb::v11_0::math::BBox::max
const Vec3T & max() const
Return a const reference to the maximum point of this bounding box.
Definition BBox.h:64
openvdb::v11_0::math::BBox::min
const Vec3T & min() const
Return a const reference to the minimum point of this bounding box.
Definition BBox.h:62
openvdb::v11_0::math::Coord
Signed (x, y, z) 32-bit integer coordinates.
Definition Coord.h:25
openvdb::v11_0::math::Transform
Definition Transform.h:40
openvdb::v11_0::math::Transform::voxelSize
Vec3d voxelSize() const
Return the size of a voxel using the linear component of the map.
Definition Transform.h:93
openvdb::v11_0::math::Transform::copy
Ptr copy() const
Definition Transform.h:50
openvdb::v11_0::math::Vec3::dot
T dot(const Vec3< T > &v) const
Dot product.
Definition Vec3.h:191
openvdb::v11_0::math::Vec3::cross
Vec3< T > cross(const Vec3< T > &v) const
Return the cross product of "this" vector and v;.
Definition Vec3.h:220
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData
Definition MeshToVolume.h:3930
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData::tree
TreeType & tree()
Definition MeshToVolume.h:3943
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData::operator()
void operator()(const tbb::blocked_range< size_t > &range)
Definition MeshToVolume.h:4073
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData::run
void run(bool threaded=true)
Definition MeshToVolume.h:4000
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData::join
void join(GenEdgeData &rhs)
Definition MeshToVolume.h:4011
openvdb::v11_0::tools::MeshToVoxelEdgeData::GenEdgeData::GenEdgeData
GenEdgeData(const std::vector< Vec3s > &pointList, const std::vector< Vec4I > &polygonList)
Definition MeshToVolume.h:3974
openvdb::v11_0::tools::MeshToVoxelEdgeData
Extracts and stores voxel edge intersection data from a mesh.
Definition MeshToVolume.h:456
openvdb::v11_0::tools::MeshToVoxelEdgeData::TreeType
tree::Tree4< EdgeData, 5, 4, 3 >::Type TreeType
Definition MeshToVolume.h:490
openvdb::v11_0::tools::MeshToVoxelEdgeData::Accessor
tree::ValueAccessor< TreeType > Accessor
Definition MeshToVolume.h:491
openvdb::v11_0::tools::MeshToVoxelEdgeData::convert
void convert(const std::vector< Vec3s > &pointList, const std::vector< Vec4I > &polygonList)
Threaded method to extract voxel edge data, the closest intersection point and corresponding primitiv...
Definition MeshToVolume.h:4248
openvdb::v11_0::tools::MeshToVoxelEdgeData::MeshToVoxelEdgeData
MeshToVoxelEdgeData()
Definition MeshToVolume.h:4241
openvdb::v11_0::tools::MeshToVoxelEdgeData::getAccessor
Accessor getAccessor()
Definition MeshToVolume.h:517
openvdb::v11_0::tools::MeshToVoxelEdgeData::getEdgeData
void getEdgeData(Accessor &acc, const Coord &ijk, std::vector< Vec3d > &points, std::vector< Index32 > &primitives)
Returns intersection points with corresponding primitive indices for the given ijk voxel.
Definition MeshToVolume.h:4261
openvdb::v11_0::tree::LeafNode
Templated block class to hold specific data types and a fixed number of values determined by Log2Dim....
Definition LeafNode.h:38
openvdb::v11_0::tree::LeafNode::setValueOnly
void setValueOnly(const Coord &xyz, const ValueType &val)
Set the value of the voxel at the given coordinates but don't change its active state.
Definition LeafNode.h:1103
openvdb::v11_0::tree::LeafNode::DIM
static const Index DIM
Definition LeafNode.h:50
openvdb::v11_0::tree::LeafNode::offsetToGlobalCoord
Coord offsetToGlobalCoord(Index n) const
Return the global coordinates for a linear table offset.
Definition LeafNode.h:1034
openvdb::v11_0::tree::LeafNode::getValue
const ValueType & getValue(const Coord &xyz) const
Return the value of the voxel at the given coordinates.
Definition LeafNode.h:1045
openvdb::v11_0::tree::LeafNode::SIZE
static const Index SIZE
Definition LeafNode.h:53
openvdb::v11_0::tree::Tree::clearAllAccessors
void clearAllAccessors()
Clear all registered accessors.
Definition Tree.h:1356
openvdb::v11_0::tree::Tree::RootNodeType
_RootNodeType RootNodeType
Definition Tree.h:183
openvdb::v11_0::tree::Tree::ValueOnCIter
TreeValueIteratorBase< const Tree, typename RootNodeType::ValueOnCIter > ValueOnCIter
Definition Tree.h:983
openvdb::v11_0::tree::Tree::LeafIter
LeafIteratorBase< Tree, typename RootNodeType::ChildOnIter > LeafIter
Iterator over all leaf nodes in this tree.
Definition Tree.h:962
openvdb::v11_0::tree::Tree::LeafNodeType
typename RootNodeType::LeafNodeType LeafNodeType
Definition Tree.h:186
openvdb::v11_0::tree::Tree::beginLeaf
LeafIter beginLeaf()
Return an iterator over all leaf nodes in this tree.
Definition Tree.h:975
openvdb::v11_0::tree::TreeValueIteratorBase::getValue
const ValueT & getValue() const
Return the tile or voxel value to which this iterator is currently pointing.
Definition TreeIterator.h:692
openvdb::v11_0::tree::ValueAccessorImpl::clear
void clear() override final
Remove all the cached nodes and invalidate the corresponding hash-keys.
Definition ValueAccessor.h:880
openvdb::v11_0::tree::ValueAccessorImpl::probeLeaf
LeafNodeT * probeLeaf(const Coord &xyz)
Return a pointer to the leaf node that contains the voxel coordinate xyz. If no LeafNode exists,...
Definition ValueAccessor.h:836
openvdb::v11_0::tree::ValueAccessorImpl::probeValue
bool probeValue(const Coord &xyz, ValueType &value) const
Return the active state of the value at a given coordinate as well as its value.
Definition ValueAccessor.h:492
openvdb::v11_0::tree::ValueAccessorImpl::getNode
NodeT * getNode()
Return the node of type NodeT that has been cached on this accessor. If this accessor does not cache ...
Definition ValueAccessor.h:848
MeshDataAdapter
Convert polygonal meshes that consist of quads and/or triangles into signed or unsigned distance fiel...
OPENVDB_LOG_DEBUG
#define OPENVDB_LOG_DEBUG(message)
In debug builds only, log a debugging message of the form 'someVar << "text" << .....
Definition logging.h:266
nanovdb::CoordBBox
BBox< Coord > CoordBBox
Definition NanoVDB.h:2535
openvdb::v11_0::math::operator<
bool operator<(const Tuple< SIZE, T0 > &t0, const Tuple< SIZE, T1 > &t1)
Definition Tuple.h:174
openvdb::v11_0::math::Vec3d
Vec3< double > Vec3d
Definition Vec3.h:664
openvdb::v11_0::math::isZero
bool isZero(const Type &x)
Return true if x is exactly equal to zero.
Definition Math.h:337
openvdb::v11_0::math::Axis
Axis
Definition Math.h:901
openvdb::v11_0::math::Z_AXIS
@ Z_AXIS
Definition Math.h:904
openvdb::v11_0::math::X_AXIS
@ X_AXIS
Definition Math.h:902
openvdb::v11_0::math::Y_AXIS
@ Y_AXIS
Definition Math.h:903
openvdb::v11_0::math::Vec3s
Vec3< float > Vec3s
Definition Vec3.h:663
openvdb::v11_0::math::closestPointOnTriangleToPoint
OPENVDB_API Vec3d closestPointOnTriangleToPoint(const Vec3d &a, const Vec3d &b, const Vec3d &c, const Vec3d &p, Vec3d &uvw)
Closest Point on Triangle to Point. Given a triangle abc and a point p, return the point on abc close...
openvdb::v11_0::points
Definition AttributeArray.h:41
openvdb::v11_0::tools::composite::max
const std::enable_if<!VecTraits< T >::IsVec, T >::type & max(const T &a, const T &b)
Definition Composite.h:110
openvdb::v11_0::tools::signedFloodFillWithValues
void signedFloodFillWithValues(TreeOrLeafManagerT &tree, const typename TreeOrLeafManagerT::ValueType &outsideWidth, const typename TreeOrLeafManagerT::ValueType &insideWidth, bool threaded=true, size_t grainSize=1, Index minLevel=0)
Set the values of all inactive voxels and tiles of a narrow-band level set from the signs of the acti...
Definition SignedFloodFill.h:253
openvdb::v11_0::tools::MeshToVolumeFlags
MeshToVolumeFlags
Mesh to volume conversion flags.
Definition MeshToVolume.h:59
openvdb::v11_0::tools::DISABLE_INTERSECTING_VOXEL_REMOVAL
@ DISABLE_INTERSECTING_VOXEL_REMOVAL
Definition MeshToVolume.h:69
openvdb::v11_0::tools::DISABLE_RENORMALIZATION
@ DISABLE_RENORMALIZATION
Definition MeshToVolume.h:73
openvdb::v11_0::tools::DISABLE_NARROW_BAND_TRIMMING
@ DISABLE_NARROW_BAND_TRIMMING
Definition MeshToVolume.h:77
openvdb::v11_0::tools::UNSIGNED_DISTANCE_FIELD
@ UNSIGNED_DISTANCE_FIELD
Definition MeshToVolume.h:65
openvdb::v11_0::tools::floodFillLeafNode
void floodFillLeafNode(tree::LeafNode< T, Log2Dim > &leafNode, const InteriorTest &interiorTest)
Definition MeshToVolume.h:3146
openvdb::v11_0::tools::Abs
MeshToVoxelEdgeData::EdgeData Abs(const MeshToVoxelEdgeData::EdgeData &x)
Definition MeshToVolume.h:3920
openvdb::v11_0::tools::pruneLevelSet
void pruneLevelSet(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing nodes whose values are all inactive with inactive ...
Definition Prune.h:390
openvdb::v11_0::tools::meshToLevelSet
GridType::Ptr meshToLevelSet(const openvdb::math::Transform &xform, const std::vector< Vec3s > &points, const std::vector< Vec3I > &triangles, float halfWidth=float(LEVEL_SET_HALF_WIDTH))
Convert a triangle mesh to a level set volume.
Definition MeshToVolume.h:3759
openvdb::v11_0::tools::traceExteriorBoundaries
void traceExteriorBoundaries(FloatTreeT &tree)
Traces the exterior voxel boundary of closed objects in the input volume tree. Exterior voxels are ma...
Definition MeshToVolume.h:3048
openvdb::v11_0::tools::meshToUnsignedDistanceField
GridType::Ptr meshToUnsignedDistanceField(const openvdb::math::Transform &xform, const std::vector< Vec3s > &points, const std::vector< Vec3I > &triangles, const std::vector< Vec4I > &quads, float bandWidth)
Convert a triangle and quad mesh to an unsigned distance field.
Definition MeshToVolume.h:3877
openvdb::v11_0::tools::meshToVolume
GridType::Ptr meshToVolume(const MeshDataAdapter &mesh, const math::Transform &transform, float exteriorBandWidth=3.0f, float interiorBandWidth=3.0f, int flags=0, typename GridType::template ValueConverter< Int32 >::Type *polygonIndexGrid=nullptr, InteriorTest interiorTest=nullptr, InteriorTestStrategy interiorTestStrat=EVAL_EVERY_VOXEL)
Definition MeshToVolume.h:3637
openvdb::v11_0::tools::createLevelSetBox
GridType::Ptr createLevelSetBox(const math::BBox< VecType > &bbox, const openvdb::math::Transform &xform, typename VecType::ValueType halfWidth=LEVEL_SET_HALF_WIDTH)
Return a grid of type GridType containing a narrow-band level set representation of a box.
Definition MeshToVolume.h:4418
openvdb::v11_0::tools::meshToSignedDistanceField
GridType::Ptr meshToSignedDistanceField(const openvdb::math::Transform &xform, const std::vector< Vec3s > &points, const std::vector< Vec3I > &triangles, const std::vector< Vec4I > &quads, float exBandWidth, float inBandWidth)
Convert a triangle and quad mesh to a signed distance field with an asymmetrical narrow band.
Definition MeshToVolume.h:3845
openvdb::v11_0::tools::changeBackground
void changeBackground(TreeOrLeafManagerT &tree, const typename TreeOrLeafManagerT::ValueType &background, bool threaded=true, size_t grainSize=32)
Replace the background value in all the nodes of a tree.
Definition ChangeBackground.h:204
openvdb::v11_0::tools::pruneInactive
void pruneInactive(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing with background tiles any nodes whose values are a...
Definition Prune.h:355
openvdb::v11_0::tools::evaluateInteriorTest
void evaluateInteriorTest(FloatTreeT &tree, InteriorTest interiorTest, InteriorTestStrategy interiorTestStrategy)
Sets the sign of voxel values of tree based on the interiorTest
Definition MeshToVolume.h:3252
openvdb::v11_0::tools::InteriorTestStrategy
InteriorTestStrategy
Different staregies how to determine sign of an SDF when using interior test.
Definition MeshToVolume.h:83
openvdb::v11_0::tools::EVAL_EVERY_VOXEL
@ EVAL_EVERY_VOXEL
Definition MeshToVolume.h:87
openvdb::v11_0::tools::EVAL_EVERY_TILE
@ EVAL_EVERY_TILE
Evaluates interior test at least once per tile and flood fills within the tile.
Definition MeshToVolume.h:90
openvdb::v11_0::tree
Definition PointDataGrid.h:169
openvdb::v11_0::tree::ValueAccessor
ValueAccessorImpl< TreeType, IsSafe, MutexType, openvdb::make_index_sequence< CacheLevels > > ValueAccessor
Default alias for a ValueAccessor. This is simply a helper alias for the generic definition but takes...
Definition ValueAccessor.h:86
openvdb::v11_0::util
Definition CpuTimer.h:18
openvdb::v11_0::util::INVALID_IDX
constexpr Index32 INVALID_IDX
Definition Util.h:19
openvdb::v11_0::util::COORD_OFFSETS
constexpr Coord COORD_OFFSETS[26]
coordinate offset table for neighboring voxels
Definition Util.h:22
openvdb::v11_0::util::wasInterrupted
bool wasInterrupted(T *i, int percent=-1)
Definition NullInterrupter.h:49
openvdb::v11_0::LEVEL_SET_HALF_WIDTH
static const Real LEVEL_SET_HALF_WIDTH
Definition Types.h:461
openvdb::v11_0::Index
Index32 Index
Definition Types.h:54
openvdb::v11_0::Vec4I
math::Vec4< Index32 > Vec4I
Definition Types.h:88
openvdb::v11_0::GRID_LEVEL_SET
@ GRID_LEVEL_SET
Definition Types.h:455
openvdb::v11_0::GRID_UNKNOWN
@ GRID_UNKNOWN
Definition Types.h:454
openvdb::v11_0::Index32
uint32_t Index32
Definition Types.h:52
openvdb::v11_0::Vec3I
math::Vec3< Index32 > Vec3I
Definition Types.h:73
openvdb::v11_0::Int32
int32_t Int32
Definition Types.h:56
openvdb
Definition Exceptions.h:13
openvdb.h
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData
Internal edge data type.
Definition MeshToVolume.h:462
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::EdgeData
EdgeData(float dist=1.0)
Definition MeshToVolume.h:463
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::operator==
bool operator==(const EdgeData &rhs) const
Definition MeshToVolume.h:481
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mXPrim
Index32 mXPrim
Definition MeshToVolume.h:487
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mZDist
float mZDist
Definition MeshToVolume.h:486
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::operator+
EdgeData operator+(const T &) const
Definition MeshToVolume.h:476
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mYDist
float mYDist
Definition MeshToVolume.h:486
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mXDist
float mXDist
Definition MeshToVolume.h:486
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mZPrim
Index32 mZPrim
Definition MeshToVolume.h:487
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::operator-
EdgeData operator-(const T &) const
Definition MeshToVolume.h:477
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::mYPrim
Index32 mYPrim
Definition MeshToVolume.h:487
openvdb::v11_0::tools::MeshToVoxelEdgeData::EdgeData::operator-
EdgeData operator-() const
Definition MeshToVolume.h:478
openvdb::v11_0::tools::QuadAndTriangleDataAdapter
Contiguous quad and triangle data adapter class.
Definition MeshToVolume.h:186
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::polygonCount
size_t polygonCount() const
Definition MeshToVolume.h:206
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::getIndexSpacePoint
void getIndexSpacePoint(size_t n, size_t v, Vec3d &pos) const
Returns position pos in local grid index space for polygon n and vertex v.
Definition MeshToVolume.h:216
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::QuadAndTriangleDataAdapter
QuadAndTriangleDataAdapter(const std::vector< PointType > &points, const std::vector< PolygonType > &polygons)
Definition MeshToVolume.h:188
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::pointCount
size_t pointCount() const
Definition MeshToVolume.h:207
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::QuadAndTriangleDataAdapter
QuadAndTriangleDataAdapter(const PointType *pointArray, size_t pointArraySize, const PolygonType *polygonArray, size_t polygonArraySize)
Definition MeshToVolume.h:197
openvdb::v11_0::tools::QuadAndTriangleDataAdapter::vertexCount
size_t vertexCount(size_t n) const
Vertex count for polygon n.
Definition MeshToVolume.h:210
openvdb::v11_0::tree::Tree4::Type
Tree< RootNode< InternalNode< InternalNode< LeafNode< T, N3 >, N2 >, N1 > > > Type
Definition Tree.h:1060
openvdb::v11_0::util::NullInterrupter
Base class for interrupters.
Definition NullInterrupter.h:26
OPENVDB_VERSION_NAME
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition version.h.in:121
OPENVDB_USE_VERSION_NAMESPACE
#define OPENVDB_USE_VERSION_NAMESPACE
Definition version.h.in:212
OPENVDB_REAL_TREE_INSTANTIATE
#define OPENVDB_REAL_TREE_INSTANTIATE(Function)
Definition version.h.in:157