Loading...
Searching...
No Matches
PointRasterizeSDFImpl.h
Go to the documentation of this file.
// Copyright Contributors to the OpenVDB Project
// SPDX-License-Identifier: MPL-2.0
//
/// @author Nick Avramoussis
///
/// @file PointRasterizeSDFImpl.h
///
#ifndef OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
#define OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
namespace openvdb {
OPENVDB_USE_VERSION_NAMESPACE
namespace OPENVDB_VERSION_NAME {
namespace points {
/// @cond OPENVDB_DOCS_INTERNAL
namespace rasterize_sdf_internal
{
/// @brief Define a fixed index space radius for point rasterization
template <typename ValueT>
struct FixedRadius
{
FixedRadius(const ValueT ris) : mR(ris) {}
inline void reset(const PointDataTree::LeafNodeType&) const {}
inline const FixedRadius& eval(const Index) const { return *this; }
inline ValueT get() const { return mR; }
private:
const ValueT mR;
};
/// @brief Define a fixed narrow band radius for point rasterization
/// @details Pass in an index space radius (relative to a PointDataGrid voxel
/// size) and the desired half band width of the target surface. The minimum
/// radius of influence is clamped to zero.
template <typename ValueT>
struct FixedBandRadius : public FixedRadius<ValueT>
{
FixedBandRadius(const ValueT ris, const ValueT hb)
: FixedRadius<ValueT>(ris)
, mMinSearchIS(math::Max(ValueT(0.0), ris - hb))
, mMaxSearchIS(ris + hb)
, mMinSearchSqIS(mMinSearchIS*mMinSearchIS)
, mMaxSearchSqIS(mMaxSearchIS*mMaxSearchIS) {}
inline void reset(const PointDataTree::LeafNodeType&) const {}
inline const FixedBandRadius& eval(const Index) const { return *this; }
inline ValueT min() const { return mMinSearchIS; }
inline ValueT minSq() const { return mMinSearchSqIS; }
inline ValueT max() const { return mMaxSearchIS; }
inline ValueT maxSq() const { return mMaxSearchSqIS; }
private:
const ValueT mMinSearchIS, mMaxSearchIS;
const ValueT mMinSearchSqIS, mMaxSearchSqIS;
};
/// @brief A varying per point radius with an optional scale
template <typename ValueT, typename CodecT = UnknownCodec>
struct VaryingRadius
{
using RadiusHandleT = AttributeHandle<ValueT, CodecT>;
VaryingRadius(const size_t ridx, const ValueT scale = 1.0)
: mRIdx(ridx), mRHandle(), mScale(scale) {}
VaryingRadius(const VaryingRadius& other)
: mRIdx(other.mRIdx), mRHandle(), mScale(other.mScale) {}
inline void reset(const PointDataTree::LeafNodeType& leaf)
{
mRHandle.reset(new RadiusHandleT(leaf.constAttributeArray(mRIdx)));
}
/// @brief Compute a fixed radius for a specific point
inline const FixedRadius<ValueT> eval(const Index id) const
{
assert(mRHandle);
return FixedRadius<ValueT>(mRHandle->get(id) * mScale);
}
private:
const size_t mRIdx;
typename RadiusHandleT::UniquePtr mRHandle;
const ValueT mScale;
};
/// @brief A varying per point narrow band radius with an optional scale
template <typename ValueT, typename CodecT = UnknownCodec>
struct VaryingBandRadius : public VaryingRadius<ValueT, CodecT>
{
using BaseT = VaryingRadius<ValueT, CodecT>;
VaryingBandRadius(const size_t ridx, const ValueT halfband,
const ValueT scale = 1.0)
: BaseT(ridx, scale), mHalfBand(halfband) {}
inline const FixedBandRadius<ValueT> eval(const Index id) const
{
const auto r = this->BaseT::eval(id).get();
return FixedBandRadius<ValueT>(ValueT(r), mHalfBand);
}
private:
const ValueT mHalfBand;
};
/// @brief Base class for SDF transfers which consolidates member data and
/// some required interface methods.
/// @note Derives from TransformTransfer for automatic transformation support
/// and VolumeTransfer<...T> for automatic buffer setup
template <typename SdfT,
typename PositionCodecT,
typename RadiusType,
bool CPG>
struct SignedDistanceFieldTransfer :
public TransformTransfer,
public std::conditional<CPG,
VolumeTransfer<typename SdfT::TreeType, Int64Tree>,
VolumeTransfer<typename SdfT::TreeType>>::type
{
using TreeT = typename SdfT::TreeType;
using ValueT = typename TreeT::ValueType;
static_assert(std::is_floating_point<ValueT>::value,
"Spherical transfer only supports floating point values.");
static_assert(!std::is_reference<RadiusType>::value && !std::is_pointer<RadiusType>::value,
"Templated radius type must not be a reference or pointer");
using VolumeTransferT = typename std::conditional<CPG,
VolumeTransfer<TreeT, Int64Tree>,
VolumeTransfer<TreeT>>::type;
using PositionHandleT = AttributeHandle<Vec3f, PositionCodecT>;
// typically the max radius of all points rounded up
inline Int32 range(const Coord&, size_t) const { return Int32(mMaxKernelWidth); }
inline bool startPointLeaf(const PointDataTree::LeafNodeType& leaf)
{
mPosition.reset(new PositionHandleT(leaf.constAttributeArray(mPIdx)));
mRadius.reset(leaf);
// if CPG, store leaf id in upper 32 bits of mask
if (CPG) mPLeafMask = (Index64(mIds->find(&leaf)->second) << 32);
return true;
}
protected:
/// @brief Constructor to use when a closet point grid is not in use
template <bool EnableT = CPG>
SignedDistanceFieldTransfer(const size_t pidx,
const size_t width,
const RadiusType& rt,
const math::Transform& source,
SdfT& surface,
Int64Tree* cpg,
const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids,
typename std::enable_if<EnableT>::type* = 0)
: TransformTransfer(source, surface.transform())
, VolumeTransferT(&(surface.tree()), cpg)
, mPIdx(pidx)
, mPosition()
, mMaxKernelWidth(width)
, mRadius(rt)
, mBackground(surface.background())
, mDx(surface.voxelSize()[0])
, mIds(ids)
, mPLeafMask(0) {
assert(cpg && ids);
}
/// @brief Constructor to use when a closet point grid is in use
template <bool EnableT = CPG>
SignedDistanceFieldTransfer(const size_t pidx,
const size_t width,
const RadiusType& rt,
const math::Transform& source,
SdfT& surface,
Int64Tree*,
const std::unordered_map<const PointDataTree::LeafNodeType*, Index>*,
typename std::enable_if<!EnableT>::type* = 0)
: TransformTransfer(source, surface.transform())
, VolumeTransferT(surface.tree())
, mPIdx(pidx)
, mPosition()
, mMaxKernelWidth(width)
, mRadius(rt)
, mBackground(surface.background())
, mDx(surface.voxelSize()[0])
, mIds(nullptr)
, mPLeafMask(0) {}
SignedDistanceFieldTransfer(const SignedDistanceFieldTransfer& other)
: TransformTransfer(other)
, VolumeTransferT(other)
, mPIdx(other.mPIdx)
, mPosition()
, mMaxKernelWidth(other.mMaxKernelWidth)
, mRadius(other.mRadius)
, mBackground(other.mBackground)
, mDx(other.mDx)
, mIds(other.mIds)
, mPLeafMask(0) {}
protected:
const size_t mPIdx;
typename PositionHandleT::UniquePtr mPosition;
const size_t mMaxKernelWidth;
RadiusType mRadius;
const ValueT mBackground;
const double mDx;
const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* mIds;
Index64 mPLeafMask;
};
/// @brief The transfer implementation for spherical stamping of narrow band
/// radius values.
template <typename SdfT,
typename PositionCodecT,
typename RadiusType,
bool CPG>
struct SphericalTransfer :
public SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, CPG>
{
using BaseT = SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, CPG>;
using typename BaseT::TreeT;
using typename BaseT::ValueT;
static const Index DIM = TreeT::LeafNodeType::DIM;
static const Index LOG2DIM = TreeT::LeafNodeType::LOG2DIM;
// The precision of the kernel arithmetic
using RealT = double;
SphericalTransfer(const size_t pidx,
const size_t width,
const RadiusType& rt,
const math::Transform& source,
SdfT& surface,
Int64Tree* cpg = nullptr,
const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
: BaseT(pidx, width, rt, source, surface, cpg, ids) {}
/// @brief For each point, stamp a sphere with a given radius by running
/// over all intersecting voxels and calculating if this point is closer
/// than the currently held distance value. Note that the default value
/// of the surface buffer should be the background value of the surface.
inline void rasterizePoint(const Coord& ijk,
const Index id,
const CoordBBox& bounds)
{
// @note GCC 6.3 warns here in optimised builds
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
Vec3d P = ijk.asVec3d() + Vec3d(this->mPosition->get(id));
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
P = this->transformSourceToTarget(P);
const auto& r = this->mRadius.eval(id);
const RealT max = r.max();
CoordBBox intersectBox(Coord::floor(P - max), Coord::ceil(P + max));
intersectBox.intersect(bounds);
if (intersectBox.empty()) return;
auto* const data = this->template buffer<0>();
auto* const cpg = CPG ? this->template buffer<CPG ? 1 : 0>() : nullptr;
auto& mask = *(this->template mask<0>());
// If min2 == 0.0, then the index space radius is equal to or less than
// the desired half band. In this case each sphere interior always needs
// to be filled with distance values as we won't ever reach the negative
// background value. If, however, a point overlaps a voxel coord exactly,
// x2y2z2 will be 0.0. Forcing min2 to be less than zero here avoids
// incorrectly setting these voxels to inactive -background values as
// x2y2z2 will never be < 0.0. We still want the lteq logic in the
// (x2y2z2 <= min2) check as this is valid when min2 > 0.0.
const RealT min2 = r.minSq() == 0.0 ? -1.0 : r.minSq();
const RealT max2 = r.maxSq();
const Coord& a(intersectBox.min());
const Coord& b(intersectBox.max());
for (Coord c = a; c.x() <= b.x(); ++c.x()) {
const RealT x2 = static_cast<RealT>(math::Pow2(c.x() - P[0]));
const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
const RealT x2y2 = static_cast<RealT>(x2 + math::Pow2(c.y() - P[1]));
const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
const Index offset = ij + /*k*/(c.z() & (DIM-1u));
if (!mask.isOn(offset)) continue; // inside existing level set or not in range
const RealT x2y2z2 = static_cast<RealT>(x2y2 + math::Pow2(c.z() - P[2]));
if (x2y2z2 >= max2) continue; //outside narrow band of particle in positive direction
if (x2y2z2 <= min2) { //outside narrow band of the particle in negative direction. can disable this to fill interior
data[offset] = -(this->mBackground);
mask.setOff(offset);
continue;
}
const ValueT d = ValueT(this->mDx * (math::Sqrt(x2y2z2) - r.get())); // back to world space
ValueT& v = data[offset];
if (d < v) {
v = d;
OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
if (CPG) cpg[offset] = Int64(this->mPLeafMask | Index64(id));
OPENVDB_NO_TYPE_CONVERSION_WARNING_END
// transfer attributes - we can't use this here as the exposed
// function signatures take vector of attributes (i.e. an unbounded
// size). If we instead clamped the attribute transfer to a fixed
// amount of attributes we could get rid of the closest point logic
// entirely. @todo consider adding another signature for increased
// performance
// this->foreach([&](auto* buffer, const size_t idx) {
// using Type = typename std::remove_pointer<decltype(buffer)>::type;
// buffer[offset] = mAttributes.template get<Type>(idx);
// })
}
}
}
} // outer sdf voxel
} // point idx
/// @brief Allow early termination if all voxels in the surface have been
/// deactivated (all interior)
inline bool endPointLeaf(const PointDataTree::LeafNodeType&)
{
// If the mask is off, terminate rasterization
return !(this->template mask<0>()->isOff());
}
inline bool finalize(const Coord&, const size_t)
{
// loop over voxels in the outer cube diagonals which won't have been
// hit by point rasterizations - these will be on because of the mask
// fill technique and need to be turned off.
auto& mask = *(this->template mask<0>());
auto* const data = this->template buffer<0>();
for (auto iter = mask.beginOn(); iter; ++iter) {
if (data[iter.pos()] == this->mBackground) mask.setOff(iter.pos());
}
// apply sdf mask to other grids
if (CPG) *(this->template mask<CPG ? 1 : 0>()) = mask;
return true;
}
};
/// @brief The transfer implementation for averaging of positions followed by
/// spherical stamping.
template <typename SdfT,
typename PositionCodecT,
typename RadiusType,
bool CPG>
struct AveragePositionTransfer :
public SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, CPG>
{
using BaseT = SignedDistanceFieldTransfer<SdfT, PositionCodecT, RadiusType, CPG>;
using typename BaseT::TreeT;
using typename BaseT::ValueT;
using VolumeTransferT = typename std::conditional<CPG,
VolumeTransfer<typename SdfT::TreeType, Int64Tree>,
VolumeTransfer<typename SdfT::TreeType>>::type;
static const Index DIM = TreeT::LeafNodeType::DIM;
static const Index LOG2DIM = TreeT::LeafNodeType::LOG2DIM;
static const Index NUM_VALUES = TreeT::LeafNodeType::NUM_VALUES;
// The precision of the kernel arithmetic
using RealT = double;
struct PosRadPair
{
template <typename S> inline void addP(const math::Vec3<S>& v) { P += v; }
template <typename S> inline void addR(const S r) { R += r; }
template <typename S> inline void multR(const S w) { R *= w; }
template <typename S> inline void multP(const S w) { P *= w; }
inline RealT length() const { return P.length() - R; }
math::Vec3<RealT> P = math::Vec3<RealT>(0.0);
RealT R = 0.0;
};
AveragePositionTransfer(const size_t pidx,
const size_t width,
const RadiusType& rt,
const RealT search,
const math::Transform& source,
SdfT& surface,
Int64Tree* cpg = nullptr,
const std::unordered_map<const PointDataTree::LeafNodeType*, Index>* ids = nullptr)
: BaseT(pidx, width, rt, source, surface, cpg, ids)
, mMaxSearchIS(search)
, mMaxSearchSqIS(search*search)
, mWeights()
, mDist() {}
AveragePositionTransfer(const AveragePositionTransfer& other)
: BaseT(other)
, mMaxSearchIS(other.mMaxSearchIS)
, mMaxSearchSqIS(other.mMaxSearchSqIS)
, mWeights()
, mDist() {}
inline void initialize(const Coord& origin, const size_t idx, const CoordBBox& bounds)
{
// init buffers
this->BaseT::initialize(origin, idx, bounds);
mWeights.assign(NUM_VALUES, PosRadPair());
if (CPG) mDist.assign(NUM_VALUES, std::numeric_limits<float>::max());
// We use the surface buffer to store the intermediate weights as
// defined by the sum of k(|x−xj|/R), where k(s) = max(0,(1−s^2)^3)
// and R is the maximum search distance. The active buffer currently
// holds background values. We could simply subtract the background away
// from the final result - however if the background value increases
// beyond 1, progressively larger floating point instabilities can be
// observed with the weight calculation. Instead, reset all active
// values to zero
// @todo The surface buffer may not be at RealT precision. Should we
// enforce this by storing the weights in another vector?
auto* const data = this->template buffer<0>();
const auto& mask = *(this->template mask<0>());
for (auto iter = mask.beginOn(); iter; ++iter) {
data[iter.pos()] = ValueT(0);
}
}
inline void rasterizePoint(const Coord& ijk,
const Index id,
const CoordBBox& bounds)
{
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif
const Vec3d PWS = this->sourceTransform().indexToWorld(ijk.asVec3d() + Vec3d(this->mPosition->get(id)));
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif
const Vec3d P = this->targetTransform().worldToIndex(PWS);
CoordBBox intersectBox(Coord::floor(P - mMaxSearchIS),
Coord::ceil(P + mMaxSearchIS));
intersectBox.intersect(bounds);
if (intersectBox.empty()) return;
auto* const data = this->template buffer<0>();
auto* const cpg = CPG ? this->template buffer<CPG ? 1 : 0>() : nullptr;
const auto& mask = *(this->template mask<0>());
// index space radius
const auto& r = this->mRadius.eval(id);
const RealT rad = r.get();
const RealT invsq = 1.0 / mMaxSearchSqIS;
const Coord& a(intersectBox.min());
const Coord& b(intersectBox.max());
for (Coord c = a; c.x() <= b.x(); ++c.x()) {
const RealT x2 = static_cast<RealT>(math::Pow2(c.x() - P[0]));
const Index i = ((c.x() & (DIM-1u)) << 2*LOG2DIM); // unsigned bit shift mult
for (c.y() = a.y(); c.y() <= b.y(); ++c.y()) {
const RealT x2y2 = static_cast<RealT>(x2 + math::Pow2(c.y() - P[1]));
const Index ij = i + ((c.y() & (DIM-1u)) << LOG2DIM);
for (c.z() = a.z(); c.z() <= b.z(); ++c.z()) {
RealT x2y2z2 = static_cast<RealT>(x2y2 + math::Pow2(c.z() - P[2]));
if (x2y2z2 >= mMaxSearchSqIS) continue; //outside search distance
const Index offset = ij + /*k*/(c.z() & (DIM-1u));
if (!mask.isOn(offset)) continue; // inside existing level set or not in range
// @note this algorithm is unable to deactivate voxels within
// a computed narrow band during rasterization as all points must
// visit their affected voxels.
if (CPG) {
// CPG still computed directly with each individual point
// @note Because voxels can't be discarded, it may be faster to
// do this as a post process (and avoid the sqrt per lookup)
// @note No need to scale back to world space
const float dist = static_cast<float>(math::Sqrt(x2y2z2) - r.get());
auto& d = mDist[offset];
if (dist < d) {
d = dist;
OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
cpg[offset] = Int64(this->mPLeafMask | Index64(id));
OPENVDB_NO_TYPE_CONVERSION_WARNING_END
}
}
x2y2z2 *= invsq; // x2y2z2 = (x - xi) / R
// k(s) = max(0,(1−s^2)^3). note that the max is unecessary
// as we early terminate above with x2y2z2 >= mMaxSearchSqIS
x2y2z2 = math::Pow3(1.0 - x2y2z2);
assert(x2y2z2 >= 0.0);
// @todo The surface buffer may not be at RealT precision. Should we
// enforce this by storing the weights in another vector?
OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
data[offset] += x2y2z2;
OPENVDB_NO_TYPE_CONVERSION_WARNING_END
auto& wt = mWeights[offset];
wt.addP(PWS * x2y2z2);
wt.addR(rad * x2y2z2);
}
}
} // outer sdf voxel
} // point idx
inline bool endPointLeaf(const PointDataTree::LeafNodeType&) { return true; }
inline bool finalize(const Coord& origin, const size_t)
{
auto& mask = *(this->template mask<0>());
auto* const data = this->template buffer<0>();
for (auto iter = mask.beginOn(); iter; ++iter) {
const Index idx = iter.pos();
auto& w = data[idx];
// if background, voxel was out of range. Guaranteed to be outside as
// all interior voxels will have at least a single point contribution
if (w == 0.0f) {
mask.setOff(idx);
w = this->mBackground;
}
else {
const Coord ijk = origin + TreeT::LeafNodeType::offsetToLocalCoord(idx);
const Vec3d ws = this->targetTransform().indexToWorld(ijk);
const RealT wi = RealT(1.0) / RealT(w); // wi
auto& wt = mWeights[idx];
wt.multP(wi); // sum of weighted positions
wt.multR(wi * this->mDx); // sum of weighted radii (scale to ws)
wt.addP(-ws); // (x - xi) (instead doing (-x + xi))
w = static_cast<typename SdfT::ValueType>(wt.length()); // (x - xi) - r
// clamp active region and value range to requested narrow band
if (std::fabs(w) >= this->mBackground) {
w = std::copysign(this->mBackground, w);
mask.setOff(idx);
}
}
}
// apply sdf mask to other grids
if (CPG) *(this->template mask<CPG ? 1 : 0>()) = mask;
return true;
}
private:
const RealT mMaxSearchIS, mMaxSearchSqIS;
std::vector<PosRadPair> mWeights;
std::vector<float> mDist;
};
/// @brief Base class for surfacing mask initialization
template <typename MaskTreeT = MaskTree,
typename InterrupterT = util::NullInterrupter>
struct SurfaceMaskOp
{
inline void join(SurfaceMaskOp& other)
{
if (mMask->leafCount() > other.mMask->leafCount()) {
mMask->topologyUnion(*other.mMask);
other.mMask.reset();
}
else {
other.mMask->topologyUnion(*mMask);
mMask.reset(other.mMask.release());
}
if (mMaskOff->leafCount() > other.mMaskOff->leafCount()) {
mMaskOff->topologyUnion(*other.mMaskOff);
other.mMaskOff.reset();
}
else {
other.mMaskOff->topologyUnion(*mMaskOff);
mMaskOff.reset(other.mMaskOff.release());
}
}
std::unique_ptr<MaskTreeT> mask() { return std::move(mMask); }
std::unique_ptr<MaskTreeT> maskoff() { return std::move(mMaskOff); }
protected:
SurfaceMaskOp(const math::Transform& points,
const math::Transform& surface,
InterrupterT* interrupter = nullptr)
: mMask(new MaskTreeT)
, mMaskOff(new MaskTreeT)
, mPointsTransform(points)
, mSurfaceTransform(surface)
, mInterrupter(interrupter)
, mVoxelOffset(static_cast<int>(math::RoundUp(points.voxelSize()[0] /
surface.voxelSize()[0]))) {}
SurfaceMaskOp(const SurfaceMaskOp& other)
: mMask(new MaskTreeT)
, mMaskOff(new MaskTreeT)
, mPointsTransform(other.mPointsTransform)
, mSurfaceTransform(other.mSurfaceTransform)
, mInterrupter(other.mInterrupter)
, mVoxelOffset(other.mVoxelOffset) {}
// @brief Sparse fill a tree with activated bounding boxes expanded from
// each active voxel.
// @note This method used to fill from each individual voxel. Whilst more
// accurate, this was slower in comparison to using the active node bounds.
// As the rasterization is so fast (discarding of voxels out of range)
// this overzealous activation results in far superior performance here.
template <typename LeafT>
inline void fill(const LeafT& leaf, const int dist, const bool active)
{
auto doFill = [&](CoordBBox b) {
b = mSurfaceTransform.worldToIndexCellCentered(
mPointsTransform.indexToWorld(b));
b.expand(dist);
if (active) mMask->sparseFill(b, true, true);
else mMaskOff->sparseFill(b, true, true);
};
const auto& mask = leaf.getValueMask();
if (mask.isOn()) {
doFill(leaf.getNodeBoundingBox());
}
else {
CoordBBox bounds;
for (auto iter = mask.beginOn(); iter; ++iter) {
bounds.expand(leaf.offsetToLocalCoord(iter.pos()));
}
if (!bounds.empty()) {
bounds.translate(leaf.origin());
doFill(bounds);
}
}
}
inline bool interrupted()
{
if (util::wasInterrupted(mInterrupter)) {
thread::cancelGroupExecution();
return true;
}
return false;
}
protected:
std::unique_ptr<MaskTreeT> mMask;
std::unique_ptr<MaskTreeT> mMaskOff;
const math::Transform& mPointsTransform;
const math::Transform& mSurfaceTransform;
InterrupterT* mInterrupter;
// add the size of a single points voxel at the surface resolution
// to the distance limits - this is becasue we don't query exact point
// positions in SurfaceMaskOp, which means we have to
// actviate the "worst case scenario" bounds. The closer the points
// voxel size is to the target sdf voxel size the better the topology
// estimate
const int mVoxelOffset;
};
/// @brief Initializes a fixed activity mask
template <typename MaskTreeT,
typename InterrupterT = util::NullInterrupter>
struct FixedSurfaceMaskOp
: public SurfaceMaskOp<MaskTreeT, InterrupterT>
{
using BaseT = SurfaceMaskOp<MaskTreeT, InterrupterT>;
using LeafManagerT =
tree::LeafManager<const points::PointDataTree>;
FixedSurfaceMaskOp(const math::Transform& points,
const math::Transform& surface,
const double minBandRadius,
const double maxBandRadius,
InterrupterT* interrupter = nullptr)
: BaseT(points, surface, interrupter)
, mMin(), mMax() {
// calculate the min interior cube area of activity. this is the side
// of the largest possible cube that fits into the radius "min":
// d = 2r -> 2r = 3x^2 -> x = 2r / sqrt(3)
// finally half it to the new radius
const Real min = ((2.0 * minBandRadius) / std::sqrt(3.0)) / 2.0;
const int d = static_cast<int>(math::RoundDown(min)) - this->mVoxelOffset;
mMin = math::Max(0, d);
mMax = static_cast<int>(math::RoundUp(maxBandRadius));
mMax += this->mVoxelOffset;
}
FixedSurfaceMaskOp(const FixedSurfaceMaskOp& other, tbb::split)
: BaseT(other), mMin(other.mMin), mMax(other.mMax) {}
void operator()(const typename LeafManagerT::LeafRange& range)
{
if (this->interrupted()) return;
for (auto leafIter = range.begin(); leafIter; ++leafIter) {
this->fill(*leafIter, mMax, true);
}
if (mMin <= 0) return;
for (auto leafIter = range.begin(); leafIter; ++leafIter) {
this->fill(*leafIter, mMin, false);
}
}
private:
int mMin, mMax;
};
/// @brief Initializes a variable activity mask
template <typename RadiusTreeT,
typename MaskTreeT,
typename InterrupterT = util::NullInterrupter>
struct VariableSurfaceMaskOp
: public SurfaceMaskOp<MaskTreeT, InterrupterT>
{
using BaseT = SurfaceMaskOp<MaskTreeT, InterrupterT>;
using LeafManagerT =
tree::LeafManager<const points::PointDataTree>;
VariableSurfaceMaskOp(const math::Transform& pointsTransform,
const math::Transform& surfaceTransform,
const RadiusTreeT& min,
const RadiusTreeT& max,
const Real scale,
const Real halfband,
InterrupterT* interrupter = nullptr)
: BaseT(pointsTransform, surfaceTransform, interrupter)
, mMin(min), mMax(max), mScale(scale), mHalfband(halfband) {}
VariableSurfaceMaskOp(const VariableSurfaceMaskOp& other, tbb::split)
: BaseT(other), mMin(other.mMin), mMax(other.mMax)
, mScale(other.mScale), mHalfband(other.mHalfband) {}
void operator()(const typename LeafManagerT::LeafRange& range)
{
if (this->interrupted()) return;
const tree::ValueAccessor<const RadiusTreeT> maxacc(mMax);
for (auto leafIter = range.begin(); leafIter; ++leafIter) {
const int max = this->maxDist(maxacc.getValue(leafIter->origin()));
this->fill(*leafIter, max, true);
}
const tree::ValueAccessor<const RadiusTreeT> minacc(mMin);
for (auto leafIter = range.begin(); leafIter; ++leafIter) {
const int min = this->minDist(minacc.getValue(leafIter->origin()));
if (min <= 0) continue;
this->fill(*leafIter, min, false);
}
}
private:
inline int maxDist(const typename RadiusTreeT::ValueType& max) const
{
// max radius in index space
const Real v = (Real(max) * mScale) + mHalfband;
int d = int(math::RoundUp(v)); // next voxel
d += this->mVoxelOffset; // final offset
return d;
}
inline int minDist(const typename RadiusTreeT::ValueType& min) const
{
// min radius in index space
Real v = math::Max(0.0, (Real(min) * mScale) - mHalfband);
v = math::Max(0.0, Real(v - this->mVoxelOffset)); // final offset
// calculate the min interior cube area of activity. this is the side
// of the largest possible cube that fits into the radius "v":
// d = 2r -> 2r = 3x^2 -> x = 2r / sqrt(3)
// finally half it to the new radius
v = ((2.0 * v) / std::sqrt(3.0)) / 2.0;
return static_cast<int>(v); // round down (v is always >= 0)
}
private:
const RadiusTreeT& mMin;
const RadiusTreeT& mMax;
const Real mScale, mHalfband;
};
template <typename SdfT, typename InterrupterT, typename PointDataGridT>
inline typename SdfT::Ptr
initFixedSdf(const PointDataGridT& points,
math::Transform::Ptr transform,
const typename SdfT::ValueType bg,
const double minBandRadius,
const double maxBandRadius,
InterrupterT* interrupter)
{
using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
typename SdfT::Ptr surface = SdfT::create(bg);
surface->setTransform(transform);
surface->setGridClass(GRID_LEVEL_SET);
FixedSurfaceMaskOp<MaskTreeT, InterrupterT> op(points.transform(),
*transform, minBandRadius, maxBandRadius, interrupter);
if (interrupter) interrupter->start("Generating uniform surface topology");
LeafManagerT leafManager(points.tree());
tbb::parallel_reduce(leafManager.leafRange(), op);
auto mask = op.mask();
if (minBandRadius > 0.0) {
auto maskoff = op.maskoff();
mask->topologyDifference(*maskoff);
// union will copy empty nodes so prune them
tools::pruneInactive(*mask);
surface->tree().topologyUnion(*mask);
mask.reset();
// set maskoff values to -background
tree::ValueAccessor<const MaskTreeT> acc(*maskoff);
auto setOffOp = [acc](auto& iter) {
if (acc.isValueOn(iter.getCoord())) {
iter.modifyValue([](auto& v) { v = -v; });
}
};
tools::foreach(surface->beginValueOff(), setOffOp,
/*thread=*/true, /*shared=*/false);
maskoff.reset();
}
else {
surface->tree().topologyUnion(*mask);
mask.reset();
}
surface->tree().voxelizeActiveTiles();
if (interrupter) interrupter->end();
return surface;
}
template <typename SdfT,
typename InterrupterT,
typename PointDataGridT,
typename RadiusTreeT>
inline typename SdfT::Ptr
initVariableSdf(const PointDataGridT& points,
math::Transform::Ptr transform,
const typename SdfT::ValueType bg,
const RadiusTreeT& min,
const RadiusTreeT& max,
const Real scale,
const Real halfband,
InterrupterT* interrupter)
{
using LeafManagerT = tree::LeafManager<const typename PointDataGridT::TreeType>;
using MaskTreeT = typename SdfT::TreeType::template ValueConverter<ValueMask>::Type;
typename SdfT::Ptr surface = SdfT::create(bg);
surface->setTransform(transform);
surface->setGridClass(GRID_LEVEL_SET);
VariableSurfaceMaskOp<RadiusTreeT, MaskTreeT, InterrupterT>
op(points.transform(), *transform, min, max, scale, halfband, interrupter);
if (interrupter) interrupter->start("Generating variable surface topology");
LeafManagerT leafManager(points.tree());
tbb::parallel_reduce(leafManager.leafRange(), op);
auto mask = op.mask();
auto maskoff = op.maskoff();
if (!maskoff->empty()) {
mask->topologyDifference(*maskoff);
// union will copy empty nodes so prune them
tools::pruneInactive(*mask);
surface->tree().topologyUnion(*mask);
// set maskoff values to -background
tree::ValueAccessor<const MaskTreeT> acc(*maskoff);
auto setOffOp = [acc](auto& iter) {
if (acc.isValueOn(iter.getCoord())) {
iter.modifyValue([](auto& v) { v = -v; });
}
};
tools::foreach(surface->beginValueOff(), setOffOp,
/*thread=*/true, /*shared=*/false);
}
else {
surface->tree().topologyUnion(*mask);
}
mask.reset();
maskoff.reset();
surface->tree().voxelizeActiveTiles();
if (interrupter) interrupter->end();
return surface;
}
template <typename PointDataTreeT,
typename AttributeTypes>
inline GridPtrVec
transferAttributes(const tree::LeafManager<const PointDataTreeT>& manager,
const std::vector<std::string>& attributes,
const Int64Tree& cpg,
const math::Transform::Ptr transform)
{
assert(manager.leafCount() != 0);
// masking uses upper 32 bits for leaf node id
// @note we can use a point list impl to support larger counts
// if necessary but this is far faster
assert(manager.leafCount() <
size_t(std::numeric_limits<Index>::max()));
// linearise cpg to avoid having to probe data
const tree::LeafManager<const Int64Tree> cpmanager(cpg);
auto transfer = [&](auto& tree, const size_t attrIdx) {
using TreeType = typename std::decay<decltype(tree)>::type;
using HandleT = AttributeHandle<typename TreeType::ValueType>;
// init topology
tree.topologyUnion(cpg);
tree::LeafManager<TreeType> lm(tree);
// init values
lm.foreach([&manager, &cpmanager, attrIdx]
(auto& leaf, const size_t idx) {
auto voxel = leaf.beginValueOn();
if (!voxel) return;
auto* data = leaf.buffer().data();
const Int64* ids = cpmanager.leaf(idx).buffer().data();
Index prev = Index(ids[voxel.pos()] >> 32);
typename HandleT::UniquePtr handle(
new HandleT(manager.leaf(prev).constAttributeArray(attrIdx)));
for (; voxel; ++voxel) {
const Int64 hash = ids[voxel.pos()];
const Index lfid = Index(hash >> 32); // upper 32 bits to leaf id
const Index ptid = static_cast<Index>(hash); // lower
if (lfid != prev) {
handle.reset(new HandleT(manager.leaf(lfid).constAttributeArray(attrIdx)));
prev = lfid;
}
data[voxel.pos()] = handle->get(ptid);
}
});
};
GridPtrVec grids;
grids.reserve(attributes.size());
const auto& attrSet = manager.leaf(0).attributeSet();
tbb::task_group tasks;
for (const auto& name : attributes) {
const size_t attrIdx = attrSet.find(name);
if (attrIdx == points::AttributeSet::INVALID_POS) continue;
if (attrSet.get(attrIdx)->stride() != 1) {
OPENVDB_THROW(RuntimeError, "Transfer of attribute " + name +
" not supported since it is strided");
}
const std::string& type = attrSet.descriptor().valueType(attrIdx);
GridBase::Ptr grid = nullptr;
AttributeTypes::foreach([&](const auto& v) {
using ValueType = typename std::remove_const<typename std::decay<decltype(v)>::type>::type;
using TreeT = typename PointDataTreeT::template ValueConverter<ValueType>::Type;
if (!grid && typeNameAsString<ValueType>() == type) {
auto typed = Grid<TreeT>::create();
grid = typed;
typed->setName(name);
typed->setTransform(transform);
tasks.run([typed, attrIdx, transfer] { transfer(typed->tree(), attrIdx); });
grids.emplace_back(grid);
}
});
if (!grid) {
OPENVDB_THROW(RuntimeError, "No support for attribute type " + type +
" built during closest point surface transfer");
}
}
tasks.wait();
return grids;
}
template <typename SdfT,
template <typename, typename, typename, bool> class TransferInterfaceT,
typename AttributeTypes,
typename InterrupterT,
typename PointDataGridT,
typename FilterT,
typename ...Args>
inline GridPtrVec
rasterizeSurface(const PointDataGridT& points,
const std::vector<std::string>& attributes,
const FilterT& filter,
SdfT& surface,
InterrupterT* interrupter,
Args&&... args)
{
using RadRefT = typename std::tuple_element<1, std::tuple<Args...>>::type;
using RadT = typename std::remove_reference<RadRefT>::type;
GridPtrVec grids;
auto leaf = points.constTree().cbeginLeaf();
if (!leaf) return grids;
const size_t pidx = leaf->attributeSet().find("P");
if (pidx == AttributeSet::INVALID_POS) {
OPENVDB_THROW(RuntimeError, "Failed to find position attribute");
}
// @note Can't split this out into a generic lambda yet as there
// are compiler issues with capturing variadic arguments
const NamePair& ptype = leaf->attributeSet().descriptor().type(pidx);
if (attributes.empty()) {
if (ptype.second == NullCodec::name()) {
using TransferT = TransferInterfaceT<SdfT, NullCodec, RadT, false>;
TransferT transfer(pidx, args...);
rasterize<PointDataGridT, TransferT, FilterT, InterrupterT>
(points, transfer, filter, interrupter);
}
else {
using TransferT = TransferInterfaceT<SdfT, UnknownCodec, RadT, false>;
TransferT transfer(pidx, args...);
rasterize<PointDataGridT, TransferT, FilterT, InterrupterT>
(points, transfer, filter, interrupter);
}
}
else {
Int64Tree cpg;
cpg.topologyUnion(surface.tree());
tree::LeafManager<const PointDataTree> manager(points.tree());
// map point leaf nodes to their linear id
// @todo sorted vector of leaf ptr-> index pair then lookup with binary search?
std::unordered_map<const PointDataTree::LeafNodeType*, Index> ids;
manager.foreach([&](auto& leaf, size_t idx) { ids[&leaf] = Index(idx); }, false);
if (ptype.second == NullCodec::name()) {
using TransferT = TransferInterfaceT<SdfT, NullCodec, RadT, true>;
TransferT transfer(pidx, args..., &cpg, &ids);
rasterize<PointDataGridT, TransferT, FilterT, InterrupterT>
(points, transfer, filter, interrupter);
}
else {
using TransferT = TransferInterfaceT<SdfT, UnknownCodec, RadT, true>;
TransferT transfer(pidx, args..., &cpg, &ids);
rasterize<PointDataGridT, TransferT, FilterT, InterrupterT>
(points, transfer, filter, interrupter);
}
ids.clear();
tools::pruneInactive(cpg);
// Build attribute transfer grids
grids = transferAttributes
<typename PointDataGrid::TreeType, AttributeTypes>
(manager, attributes, cpg, surface.transformPtr());
}
return grids;
}
} // namespace rasterize_sdf_internal
/// @endcond
///////////////////////////////////////////////////
///////////////////////////////////////////////////
template <typename PointDataGridT,
typename SdfT,
typename FilterT,
typename InterrupterT>
typename SdfT::Ptr
rasterizeSpheres(const PointDataGridT& points,
const Real radius,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
auto grids =
rasterizeSpheres<PointDataGridT, TypeList<>, SdfT, FilterT, InterrupterT>
(points, radius, {}, halfband, transform, filter, interrupter);
return StaticPtrCast<SdfT>(grids.front());
}
template <typename PointDataGridT,
typename RadiusT,
typename SdfT,
typename FilterT,
typename InterrupterT>
typename SdfT::Ptr
rasterizeSpheres(const PointDataGridT& points,
const std::string& radius,
const Real scale,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
auto grids =
rasterizeSpheres<PointDataGridT, TypeList<>, RadiusT, SdfT, FilterT, InterrupterT>
(points, radius, {}, scale, halfband, transform, filter, interrupter);
return StaticPtrCast<SdfT>(grids.front());
}
template <typename PointDataGridT,
typename AttributeTypes,
typename SdfT,
typename FilterT,
typename InterrupterT>
GridPtrVec
rasterizeSpheres(const PointDataGridT& points,
const Real radius,
const std::vector<std::string>& attributes,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
using namespace rasterize_sdf_internal;
if (!transform) transform = points.transform().copy();
const Real vs = transform->voxelSize()[0];
const float background = static_cast<float>(vs * halfband);
// search distance at the SDF transform, including its half band
const Real radiusIndexSpace = radius / vs;
const FixedBandRadius<Real> rad(radiusIndexSpace, halfband);
const Real minBandRadius = rad.min();
const Real maxBandRadius = rad.max();
const size_t width = static_cast<size_t>(math::RoundUp(maxBandRadius));
typename SdfT::Ptr surface =
initFixedSdf<SdfT, InterrupterT>
(points, transform, background, minBandRadius, maxBandRadius, interrupter);
if (interrupter) interrupter->start("Rasterizing particles to level set using constant Spheres");
GridPtrVec grids =
rasterizeSurface<SdfT, SphericalTransfer, AttributeTypes, InterrupterT>
(points, attributes, filter, *surface, interrupter,
width, rad, points.transform(), *surface); // args
tools::pruneLevelSet(surface->tree());
if (interrupter) interrupter->end();
grids.insert(grids.begin(), surface);
return grids;
}
template <typename PointDataGridT,
typename AttributeTypes,
typename RadiusT,
typename SdfT,
typename FilterT,
typename InterrupterT>
GridPtrVec
rasterizeSpheres(const PointDataGridT& points,
const std::string& radius,
const std::vector<std::string>& attributes,
const Real scale,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
using namespace rasterize_sdf_internal;
using PointDataTreeT = typename PointDataGridT::TreeType;
using RadTreeT = typename PointDataTreeT::template ValueConverter<RadiusT>::Type;
if (!transform) transform = points.transform().copy();
const Real vs = transform->voxelSize()[0];
const float background = static_cast<float>(vs * halfband);
RadiusT min(0), max(0);
typename RadTreeT::Ptr mintree(new RadTreeT), maxtree(new RadTreeT);
points::evalMinMax<RadiusT, UnknownCodec>
(points.tree(), radius, min, max, filter, mintree.get(), maxtree.get());
// search distance at the SDF transform
const RadiusT indexSpaceScale = RadiusT(scale / vs);
typename SdfT::Ptr surface =
initVariableSdf<SdfT, InterrupterT>
(points, transform, background, *mintree, *maxtree,
indexSpaceScale, halfband, interrupter);
mintree.reset();
maxtree.reset();
auto leaf = points.constTree().cbeginLeaf();
if (!leaf) return GridPtrVec(1, surface);
// max possible index space radius
const size_t width = static_cast<size_t>
(math::RoundUp((Real(max) * indexSpaceScale) + Real(halfband)));
const size_t ridx = leaf->attributeSet().find(radius);
if (ridx == AttributeSet::INVALID_POS) {
OPENVDB_THROW(RuntimeError, "Failed to find radius attribute \"" + radius + "\"");
}
VaryingBandRadius<RadiusT> rad(ridx, RadiusT(halfband), indexSpaceScale);
if (interrupter) interrupter->start("Rasterizing particles to level set using variable Spheres");
GridPtrVec grids =
rasterizeSurface<SdfT, SphericalTransfer, AttributeTypes, InterrupterT>
(points, attributes, filter, *surface, interrupter,
width, rad, points.transform(), *surface); // args
if (interrupter) interrupter->end();
tools::pruneLevelSet(surface->tree());
grids.insert(grids.begin(), surface);
return grids;
}
///////////////////////////////////////////////////
template <typename PointDataGridT,
typename SdfT,
typename FilterT,
typename InterrupterT>
typename SdfT::Ptr
rasterizeSmoothSpheres(const PointDataGridT& points,
const Real radius,
const Real searchRadius,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
auto grids =
rasterizeSmoothSpheres<PointDataGridT, TypeList<>, SdfT, FilterT, InterrupterT>
(points, radius, searchRadius, {}, halfband, transform, filter, interrupter);
return StaticPtrCast<SdfT>(grids.front());
}
template <typename PointDataGridT,
typename RadiusT,
typename SdfT,
typename FilterT,
typename InterrupterT>
typename SdfT::Ptr
rasterizeSmoothSpheres(const PointDataGridT& points,
const std::string& radius,
const Real radiusScale,
const Real searchRadius,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
auto grids =
rasterizeSmoothSpheres<PointDataGridT, TypeList<>, RadiusT, SdfT, FilterT, InterrupterT>
(points, radius, radiusScale, searchRadius, {}, halfband, transform, filter, interrupter);
return StaticPtrCast<SdfT>(grids.front());
}
template <typename PointDataGridT,
typename AttributeTypes,
typename SdfT,
typename FilterT,
typename InterrupterT>
GridPtrVec
rasterizeSmoothSpheres(const PointDataGridT& points,
const Real radius,
const Real searchRadius,
const std::vector<std::string>& attributes,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
using namespace rasterize_sdf_internal;
if (!transform) transform = points.transform().copy();
const Real vs = transform->voxelSize()[0];
const float background = static_cast<float>(vs * halfband);
const Real indexSpaceSearch = searchRadius / vs;
const FixedBandRadius<Real> bands(radius / vs, halfband);
const Real max = bands.max();
typename SdfT::Ptr surface =
initFixedSdf<SdfT, InterrupterT>
(points, transform, background, /*min*/0.0, max, interrupter);
auto leaf = points.constTree().cbeginLeaf();
if (!leaf) return GridPtrVec(1, surface);
// max possible index space search radius
const size_t width = static_cast<size_t>(math::RoundUp(indexSpaceSearch));
const FixedRadius<Real> rad(radius / vs);
if (interrupter) interrupter->start("Rasterizing particles to level set using constant Zhu-Bridson");
GridPtrVec grids =
rasterizeSurface<SdfT, AveragePositionTransfer, AttributeTypes, InterrupterT>
(points, attributes, filter, *surface, interrupter,
width, rad, indexSpaceSearch, points.transform(), *surface); // args
tools::pruneInactive(surface->tree());
if (interrupter) interrupter->end();
grids.insert(grids.begin(), surface);
return grids;
}
template <typename PointDataGridT,
typename AttributeTypes,
typename RadiusT,
typename SdfT,
typename FilterT,
typename InterrupterT>
GridPtrVec
rasterizeSmoothSpheres(const PointDataGridT& points,
const std::string& radius,
const Real radiusScale,
const Real searchRadius,
const std::vector<std::string>& attributes,
const Real halfband,
math::Transform::Ptr transform,
const FilterT& filter,
InterrupterT* interrupter)
{
using namespace rasterize_sdf_internal;
using PointDataTreeT = typename PointDataGridT::TreeType;
using RadTreeT = typename PointDataTreeT::template ValueConverter<RadiusT>::Type;
if (!transform) transform = points.transform().copy();
const Real vs = transform->voxelSize()[0];
const float background = static_cast<float>(vs * halfband);
const Real indexSpaceSearch = searchRadius / vs;
RadiusT min(0), max(0);
typename RadTreeT::Ptr mintree(new RadTreeT), maxtree(new RadTreeT);
points::evalMinMax<RadiusT, UnknownCodec>
(points.tree(), radius, min, max, filter, mintree.get(), maxtree.get());
// search distance at the SDF transform
const RadiusT indexSpaceScale = RadiusT(radiusScale / vs);
typename SdfT::Ptr surface =
initVariableSdf<SdfT, InterrupterT>
(points, transform, background, *mintree, *maxtree,
indexSpaceScale, halfband, interrupter);
mintree.reset();
maxtree.reset();
auto leaf = points.constTree().cbeginLeaf();
if (!leaf) return GridPtrVec(1, surface);
// max possible index space search radius
const size_t width = static_cast<size_t>(math::RoundUp(indexSpaceSearch));
const size_t ridx = leaf->attributeSet().find(radius);
if (ridx == AttributeSet::INVALID_POS) {
OPENVDB_THROW(RuntimeError, "Failed to find radius attribute");
}
VaryingRadius<RadiusT> rad(ridx, indexSpaceScale);
if (interrupter) interrupter->start("Rasterizing particles to level set using variable Zhu-Bridson");
GridPtrVec grids =
rasterizeSurface<SdfT, AveragePositionTransfer, AttributeTypes, InterrupterT>
(points, attributes, filter, *surface, interrupter,
width, rad, indexSpaceSearch, points.transform(), *surface); // args
tools::pruneInactive(surface->tree());
if (interrupter) interrupter->end();
grids.insert(grids.begin(), surface);
return grids;
}
} // namespace points
} // namespace OPENVDB_VERSION_NAME
} // namespace openvdb
#endif //OPENVDB_POINTS_RASTERIZE_SDF_IMPL_HAS_BEEN_INCLUDED
OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
Bracket code with OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN/_END, to inhibit warnings about type conve...
Definition Platform.h:204
OPENVDB_NO_TYPE_CONVERSION_WARNING_END
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition Platform.h:205
openvdb::v10_0::RuntimeError
Definition Exceptions.h:63
openvdb::v10_0::math::Transform::Ptr
SharedPtr< Transform > Ptr
Definition Transform.h:42
openvdb::v10_0::points::AttributeSet::INVALID_POS
@ INVALID_POS
Definition AttributeSet.h:41
hash
__hostdev__ uint32_t hash(uint32_t x)
Definition common.h:13
nanovdb::CoordBBox
BBox< Coord > CoordBBox
Definition NanoVDB.h:1809
openvdb::v10_0::math::RoundUp
float RoundUp(float x)
Return x rounded up to the nearest integer.
Definition Math.h:787
openvdb::v10_0::math::Max
const Type & Max(const Type &a, const Type &b)
Return the maximum of two values.
Definition Math.h:595
openvdb::v10_0::math::scale
MatType scale(const Vec3< typename MatType::value_type > &s)
Return a matrix that scales by s.
Definition Mat.h:615
openvdb::v10_0::math::Vec3d
Vec3< double > Vec3d
Definition Vec3.h:664
openvdb::v10_0::points
Definition AttributeArray.h:41
openvdb::v10_0::points::rasterizeSmoothSpheres
SdfT::Ptr rasterizeSmoothSpheres(const PointDataGridT &points, const Real radius, const Real searchRadius, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), InterrupterT *interrupter=nullptr)
Smoothed point distribution based sphere stamping with a uniform radius.
Definition PointRasterizeSDFImpl.h:1209
openvdb::v10_0::points::rasterizeSpheres
SdfT::Ptr rasterizeSpheres(const PointDataGridT &points, const Real radius, const Real halfband=LEVEL_SET_HALF_WIDTH, math::Transform::Ptr transform=nullptr, const FilterT &filter=NullFilter(), InterrupterT *interrupter=nullptr)
Narrow band sphere stamping with a uniform radius.
Definition PointRasterizeSDFImpl.h:1061
openvdb::v10_0::points::evalMinMax
std::pair< ValueT, ValueT > evalMinMax(const PointDataTreeT &points, const std::string &attribute, const FilterT &filter=NullFilter())
Evaluates the minimum and maximum values of a point attribute.
Definition PointStatistics.h:697
openvdb::v10_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::v10_0::tools::composite::min
const std::enable_if<!VecTraits< T >::IsVec, T >::type & min(const T &a, const T &b)
Definition Composite.h:106
openvdb::v10_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::v10_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::v10_0::GridPtrVec
std::vector< GridBase::Ptr > GridPtrVec
Definition Grid.h:508
openvdb::v10_0::Index
Index32 Index
Definition Types.h:54
openvdb::v10_0::NamePair
std::pair< Name, Name > NamePair
Definition AttributeArray.h:39
openvdb::v10_0::MaskTree
tree::Tree4< ValueMask, 5, 4, 3 >::Type MaskTree
Definition openvdb.h:58
openvdb::v10_0::Real
double Real
Definition Types.h:60
openvdb::v10_0::Int64
int64_t Int64
Definition Types.h:57
openvdb::v10_0::initialize
OPENVDB_IMPORT void initialize()
Global registration of native Grid, Transform, Metadata and Point attribute types....
openvdb::v10_0::Int64Tree
tree::Tree4< int64_t, 5, 4, 3 >::Type Int64Tree
Definition openvdb.h:57
openvdb::v10_0::AttributeTypes
NumericAttributeTypes:: Append< Vec3AttributeTypes >:: Append< Mat3AttributeTypes >:: Append< Mat4AttributeTypes >:: Append< QuatAttributeTypes >:: Append< points::GroupAttributeArray >:: Append< points::StringAttributeArray >:: Append< points::TypedAttributeArray< bool > > AttributeTypes
The attribute array types which OpenVDB will register by default.
Definition openvdb.h:178
openvdb::v10_0::Int32
int32_t Int32
Definition Types.h:56
openvdb::v10_0::Index64
uint64_t Index64
Definition Types.h:53
openvdb::v10_0::StaticPtrCast
SharedPtr< T > StaticPtrCast(const SharedPtr< U > &ptr)
Return a new shared pointer that points to the same object as the given pointer after a static_cast.
Definition Types.h:146
openvdb
Definition Exceptions.h:13
OPENVDB_THROW
#define OPENVDB_THROW(exception, message)
Definition Exceptions.h:74
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