From d0fcb5518e8fada1a32c41a6d04a0cb891dcc364 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:58:22 -0400 Subject: [PATCH 01/19] level set constructors * Base class ConvexVoxelizer which provides the groundwork to create a level set of a convex region * Level set constructor methods: createLevelSetCapsule, createLevelSetTaperedCapsule, createLevelSetTubeComplex, createLevelSetThickenedMesh Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/CMakeLists.txt | 3 + openvdb/openvdb/tools/ConvexVoxelizer.h | 1295 +++++++++++++ openvdb/openvdb/tools/LevelSetThickenedMesh.h | 1657 +++++++++++++++++ openvdb/openvdb/tools/LevelSetTubes.h | 1602 ++++++++++++++++ openvdb/openvdb/unittest/TestTools.cc | 122 ++ 5 files changed, 4679 insertions(+) create mode 100644 openvdb/openvdb/tools/ConvexVoxelizer.h create mode 100644 openvdb/openvdb/tools/LevelSetThickenedMesh.h create mode 100644 openvdb/openvdb/tools/LevelSetTubes.h diff --git a/openvdb/openvdb/CMakeLists.txt b/openvdb/openvdb/CMakeLists.txt index 6f0826eb81..4c708f85ee 100644 --- a/openvdb/openvdb/CMakeLists.txt +++ b/openvdb/openvdb/CMakeLists.txt @@ -473,6 +473,7 @@ set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES tools/ChangeBackground.h tools/Clip.h tools/Composite.h + tools/ConvexVoxelizer.h tools/Count.h tools/Dense.h tools/DenseSparseTools.h @@ -491,7 +492,9 @@ set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES tools/LevelSetPlatonic.h tools/LevelSetRebuild.h tools/LevelSetSphere.h + tools/LevelSetThickenedMesh.h tools/LevelSetTracker.h + tools/LevelSetTubes.h tools/LevelSetUtil.h tools/Mask.h tools/Merge.h diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h new file mode 100644 index 0000000000..885800b64b --- /dev/null +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -0,0 +1,1295 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 +/// +/// @author Greg Hurst +/// +/// @file ConvexVoxelizer.h +/// +/// @brief Base class used to generate the narrow-band level set of a convex region. +/// +/// @note By definition a level set has a fixed narrow band width +/// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), +/// whereas an SDF can have a variable narrow band width. + +#ifndef OPENVDB_TOOLS_CONVEXVOXELIZER_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_CONVEXVOXELIZER_HAS_BEEN_INCLUDED + +#include +#include +#include + +#include "Merge.h" +#include "SignedFloodFill.h" + +#include +#include +#include +#include + +#include +#include + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +namespace lvlset { + +/// @brief Internal class used by derived @c ConvexVoxelizer classes that make use of PointPartitioner. +template +struct PointArray +{ + using PosType = VectorType; + + PointArray() = default; + + PointArray(const std::vector& vec) + : mVec(vec) + { + } + + inline const VectorType& operator[](const Index& i) { return mVec[i]; } + + inline size_t size() const { return mVec.size(); }; + + inline void getPos(size_t n, VectorType& xyz) const { xyz = mVec[n]; } + +private: + + const std::vector& mVec; + +}; // struct PointArray + +} // namespace lvlset + +/// @brief Base class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of a convex region. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// @note @c Derived is the derived class that implements the base class (curiously recurring template pattern). +/// +/// @par Example of derived level set sphere class +/// @code +/// template +/// class SphereVoxelizer : public ConvexVoxelizer> +/// { +/// using GridPtr = typename GridType::Ptr; +/// using BaseT = ConvexVoxelizer>; +/// +/// using BaseT::mXYData; +/// using BaseT::tileCeil; +/// +/// public: +/// +/// friend class ConvexVoxelizer>; +/// +/// SphereVoxelizer(GridPtr& grid, const bool& threaded = true) +/// : BaseT(grid, threaded) +/// { +/// } +/// +/// void operator()(const Vec3s& pt, const float& r) +/// { +/// if (r <= 0.0f) +/// return; +/// +/// initialize(pt, r); +/// +/// BaseT::iterate(); +/// } +/// +/// private: +/// +/// inline float +/// signedDistance(const Vec3s& p) const +/// { +/// return (p - mPt).length() - mRad; +/// } +/// +/// inline void +/// setXYRangeData(const Index& step = 1) override +/// { +/// mXYData.reset(mX - mORad, mX + mORad, step); +/// +/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) +/// mXYData.expandYRange(x, BaseT::circleBottom(x), BaseT::circleTop(x)); +/// } +/// +/// std::function sphereBottomTop = +/// [this](float& zb, float& zt, const float& x, const float& y) +/// { +/// zb = BaseT::sphereBottom(mX, mY, mZ, mORad, x, y); +/// zt = BaseT::sphereTop(mX, mY, mZ, mORad, x, y); +/// +/// return std::isfinite(zb) && std::isfinite(zt); +/// }; +/// +/// inline void +/// initialize(const Vec3s& pt, const float& r) +/// { +/// const float vx = BaseT::voxelSize(), +/// hw = BaseT::halfWidth(); +/// +/// // sphere data in index space +/// mPt = pt/vx; +/// mRad = r/vx; +/// +/// mX = mPt.x(); mY = mPt.y(); mZ = mPt.z(); +/// +/// // padded radius used to populate the outer halfwidth of the sdf +/// mORad = mRad + hw; +/// +/// BaseT::bottomTop = sphereBottomTop; +/// } +/// +/// Vec3s mPt; +/// float mRad, mORad, mX, mY, mZ; +/// }; +/// +/// // usage: +/// +/// // initialize level set grid with voxel size 0.1 and half width 3.0 +/// FloatGrid::Ptr grid = createLevelSet(0.1f, 3.0f); +/// +/// // populate grid with a sphere centered at (0, 1, 2) and radius 5 +/// SphereVoxelizer op(grid); +/// op(Vec3s(0.0f, 1.0f, 2.0f), 5.0f); +/// +/// @endcode +template +class ConvexVoxelizer +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + using TreeT = typename GridType::TreeType; + using RootT = typename TreeT::RootNodeType; + using LeafT = typename TreeT::LeafNodeType; + + using NodeChainT = typename RootT::NodeChainType; + + using AccessorT = typename GridType::Accessor; + +public: + + /// @brief Constructor + /// + /// @param grid scalar grid to populate the level set in + /// @param threaded center of the sphere in world units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note The voxel size and half width are determined from the input grid, + /// meaning the voxel size and background value need to be set prior to voxelization + ConvexVoxelizer(GridPtr& grid, const bool& threaded = false, InterruptType* interrupter = nullptr) + : mTree(grid->tree()) + , mVox((grid->voxelSize())[0]) + , mBg(grid->background()) + , mNegBg(-(grid->background())) + , mBgF(float(grid->background())) + , mNegBgF(float(-(grid->background()))) + , mHw(float(grid->background())/float((grid->voxelSize())[0])) + , mSerial(!threaded) + , mInterrupter(interrupter) + { + } + + /// @brief Return the voxel size of the grid. + inline float voxelSize() const { return mVox; } + + /// @brief Return the half width of the narrow-band level set. + inline float halfWidth() const { return mHw; } + +private: + + class CacheLastLeafAccessor; + +protected: + + // ------------ Main APIs for derived classes ------------ + + /// @brief The function the derived class calls to create the level set, + /// working in index space other than setting signed distance values. + /// + /// @note This function handles both parallel and serial iterations. If running in serial mode, + /// it flood fills the tile topology immediately; otherwise, it avoids duplicating nontrivial + /// tree topology over multiple threads. This method also checks for background tiles + /// that are too thin to fit and delegates accordingly. + inline void + iterate() + { + static const Index LEAFDIM = LeafT::DIM; + + // objects too thin to have negative background tiles + if (!tileCanFit(LEAFDIM)) { + thinIterate(); + return; + } + + // iterate over all non root nodes + using ChainT = typename NodeChainT::PopBack; + + // if we're working in parallel, we avoid flood filling the tile topology until the end + // this avoids duplicating nontrivial tree topology over multiple threads + if (mSerial) + iterateTile(); + + iterateLeaf(); + + if (!checkInterrupter()) + return; + + if (!mSerial) + tools::signedFloodFill(mTree); + } + + // ------------ derived classes need to implement these ------------ + + /// @brief Determines the x bounds in index space of the convex region dilated by the half width. + /// For each x value in index space, the y range in index space of the dilated region is computed. + /// This function should store the data in @c mXYData. + /// + /// @param step The step size for setting the XY range data, defaults to 1. + /// @note Virtual function to be implemented by derived classes to set XY range data. + /// This function is called at most 4 times within @c iterate(). + virtual void setXYRangeData(const Index& step = 1) = 0; + + /// @brief Checks if the tile of a given dimension can possibly fit within the region. + /// + /// This is a virtual function and can be overridden by derived classes. However, the derived + /// class does not need to implement it if the default behavior is acceptable, which assumes a + /// tile can always possibly fit. + /// + /// @param dim The dimension of the tile in which to check if the tile fits. + /// @note This is meant as a short-circuting method: if a tile of a given dimension + /// can't fit then @c iterate will not try to populate the level set with background + /// tiles of this dimension. + /// @return true if the tile can possibly fit; otherwise false. + virtual inline bool tileCanFit(const Index& dim) const { return true; } + + // distance in index space + /// @brief Computes the signed distance from a point to the convex region in index space. + /// + /// @param p The point in 3D space for which to compute the signed distance. + inline float signedDistance(const Vec3s& p) const { return 0.0f; } + + /// @brief Computes the signed distance for tiles in index space, + /// considering the center of the tile. + /// This method is optional to override and defaults to @c signedDistance. + /// + /// @param p The point at the center of the tile in 3D space. + /// @note This can be useful for cases that build objects from multiple primitives, e.g. + /// thickened mesh is built by constructing and unioning _open_ prisms and _open_ tube wedges. + /// A tile might not fully fit in an open prism but might fit in the union of a prism and wedge, + /// and so in this case it might make sense to use the sdf for an offset triangle on tiles + /// during the open prism scan. + inline float + tilePointSignedDistance(const Vec3s& p) const + { + return static_cast(this)->signedDistance(p); + } + + /// @brief Find where a vertical infinite line intersects + /// a convex region dilated by the half width. + /// + /// @param[out] zb Reference to the z ordinate where the bottom intersection occurs. + /// @param[out] zt Reference to the z ordinate where the top intersection occurs. + /// @param[in] x The x ordinate of the infinte line. + /// @param[in] y The y ordinate of the infinte line. + /// @return true if an intersection occurs; otherwise false. + /// @note The derived class can override this lambda to implement different behavior for degenerate cases. + /// This function is called many times, so a lambda is used to avoid virtual table overhead. + std::function bottomTop = + [this](float& zb, float& zt, const float& x, const float& y) { return false; }; + + // ------------ utilities ------------ + + /// @brief Rounds an input scalar up to the nearest valid ordinate of tile of a specified size. + /// @param x Input value. + /// @param step Tile step size. + /// @return The ceiling of the value based on the tile size. + inline static float + tileCeil(const float& x, const Index& step) + { + const float s = static_cast(step); + const float offset = 0.5f * (s - 1.0f); + + return step == 1 + ? static_cast(math::Ceil(perturbDown(x))) + : s * static_cast(math::Ceil(perturbDown((x - offset)/s))) + offset; + } + + /// @brief Rounds an input scalar down to the nearest valid ordinate of tile of a specified size. + /// @param x Input value. + /// @param step Tile step size. + /// @return The ceiling of the value based on the tile size. + inline static float + tileFloor(const float& x, const Index& step) + { + const float s = static_cast(step); + const float offset = 0.5f * (s - 1.0f); + + return step == 1 + ? static_cast(math::Floor(perturbUp(x))) + : s * static_cast(math::Floor(perturbUp((x - offset)/s))) + offset; + } + + /// @brief Computes the bottom y-coordinate of a circle at a given x position. + /// @param x0 X-coordinate of the circle's center. + /// @param y0 Y-coordinate of the circle's center. + /// @param r Radius of the circle. + /// @param x X-coordinate for which to compute the bottom y-coordinate. + /// @return The y-coordinate at the bottom of the circle for the given x position. + inline static float + circleBottom(const float& x0, const float& y0, + const float& r, const float& x) + { + return y0 - math::Sqrt(math::Pow2(r) - math::Pow2(x-x0)); + } + + /// @brief Computes the top y-coordinate of a circle at a given x position. + /// @param x0 X-coordinate of the circle's center. + /// @param y0 Y-coordinate of the circle's center. + /// @param r Radius of the circle. + /// @param x X-coordinate for which to compute the top y-coordinate. + /// @return The y-coordinate at the top of the circle for the given x position. + inline static float + circleTop(const float& x0, const float& y0, + const float& r, const float& x) + { + return y0 + math::Sqrt(math::Pow2(r) - math::Pow2(x-x0)); + } + + /// @brief Computes the bottom z-coordinate of a sphere at a given (x, y) position. + /// @param x0 X-coordinate of the sphere's center. + /// @param y0 Y-coordinate of the sphere's center. + /// @param z0 Z-coordinate of the sphere's center. + /// @param r Radius of the sphere. + /// @param x X-coordinate for which to compute the bottom z-coordinate. + /// @param y Y-coordinate for which to compute the bottom z-coordinate. + /// @return The z-coordinate at the bottom of the sphere for the given (x, y) position. + inline static float + sphereBottom(const float& x0, const float& y0, const float& z0, + const float& r, const float& x, const float& y) + { + return z0 - math::Sqrt(math::Pow2(r) - math::Pow2(x-x0) - math::Pow2(y-y0)); + } + + /// @brief Computes the top z-coordinate of a sphere at a given (x, y) position. + /// @param x0 X-coordinate of the sphere's center. + /// @param y0 Y-coordinate of the sphere's center. + /// @param z0 Z-coordinate of the sphere's center. + /// @param r Radius of the sphere. + /// @param x X-coordinate for which to compute the top z-coordinate. + /// @param y Y-coordinate for which to compute the top z-coordinate. + /// @return The z-coordinate at the top of the sphere for the given (x, y) position. + inline static float + sphereTop(const float& x0, const float& y0, const float& z0, + const float& r, const float& x, const float& y) + { + return z0 + math::Sqrt(math::Pow2(r) - math::Pow2(x-x0) - math::Pow2(y-y0)); + } + + // ------------ nested classes ------------ + + /// @brief Class that stores endpoints of a y range for each x value within a specified range and step size. + /// @details This class tracks y ranges (defined by ymin and ymax) for each x value over a defined interval, + /// using a configurable step size. + /// It allows updating, expanding, and resetting the y ranges, as well as merging data from other instances + /// and trimming invalid entries. + /// @note @c ValueType must be a scalar or integral type. + template + class XYRangeData + { + static_assert(std::is_arithmetic_v, "Not an arithmetic type"); + + public: + + XYRangeData() = default; + + /// @brief Constructor that sets the x range to span a given interval with a specific step size. + /// This initializes all y ranges as empty. + /// @param xmin The lower bound of the x range. + /// @param xmax The upper bound of the x range. + /// @param step The step size between x values. Defaults to 1. + XYRangeData(const ValueType& xmin, const ValueType& xmax, const Index& step = 1) + { + reset(xmin, xmax, step); + } + + /// @brief Expands the y range for a given x value by updating the minimum and maximum + /// y values if the new ones extend the current range. + /// @param x The x value. + /// @param ymin The new minimum y value to compare with and possibly update + /// the current minimum at x. + /// @param ymax The new maximum y value to compare with and possibly update + /// the current maximum at x. + inline void + expandYRange(const ValueType& x, const ValueType& ymin, const ValueType& ymax) + { + expandYMin(x, ymin); + expandYMax(x, ymax); + } + + /// @brief Sets the minimum y value for a given x value, if the provided ymin + /// is smaller than the current value. + /// @param x The x value. + /// @param ymin The minimum y value to possibly be set. + inline void + expandYMin(const ValueType& x, const ValueType& ymin) + { + const Index i = worldToIndex(x); + + if (std::isfinite(ymin) && ymin < mYMins[i]) + mYMins[i] = ymin; + } + + /// @brief Sets the maximum y value for a given x value, if the provided ymax + /// is larger than the current value. + /// @param x The x value. + /// @param ymax The maximum y value to possibly be set. + inline void + expandYMax(const ValueType& x, const ValueType& ymax) + { + const Index i = worldToIndex(x); + + if (std::isfinite(ymax) && ymax > mYMaxs[i]) + mYMaxs[i] = ymax; + } + + /// @brief Expands the y range for a given x by adjusting ymin or ymax if the + /// given y is smaller or larger. + /// @param x The x value. + /// @param y The y value to use for expanding the range. + inline void + expandYRange(const ValueType& x, const ValueType& y) + { + if (std::isfinite(y)) { + const Index i = worldToIndex(x); + + if (y < mYMins[i]) + mYMins[i] = y; + + if (y > mYMaxs[i]) + mYMaxs[i] = y; + } + } + + /// @brief Set the minimum y value for a given x value, + /// even if its larger than the current value. + /// @param x The x value. + /// @param ymin The minimum y value to reset. + inline void + setYMin(const ValueType& x, const ValueType& ymin) + { + const Index i = worldToIndex(x); + + mYMins[i] = ymin; + } + + /// @brief Set the maximum y value for a given x value, + /// even if its larger than the current value. + /// @param x The x value. + /// @param ymax The maximum y value to reset. + inline void + setYMax(const ValueType& x, const ValueType& ymax) + { + const Index i = worldToIndex(x); + + mYMaxs[i] = ymax; + } + + /// @brief Clears the y range for a given x value, setting it to an empty interval. + /// @param x The x value. + inline void + clearYRange(const ValueType& x) + { + const Index i = worldToIndex(x); + + mYMins[i] = MAXVALUE; + mYMaxs[i] = MINVALUE; + } + + /// @brief Resets the x range to span a given interval with a specific step size. + /// This initializes all y ranges as empty. + /// @param xmin The lower bound of the x range. + /// @param xmax The upper bound of the x range. + /// @param step The step size between x values. Defaults to 1. + inline void + reset(const ValueType& xmin, const ValueType& xmax, const Index& step = 1) + { + assert(step != 0); + + mStep = step; + mStepInv = ValueType(1)/static_cast(mStep); + + mXStart = tileCeil(xmin, mStep); + mXEnd = tileFloor(xmax, mStep); + + mSize = 1 + indexDistance(mXEnd, mXStart); + + mYMins.assign(mSize, MAXVALUE); + mYMaxs.assign(mSize, MINVALUE); + } + + /// @brief Retrieves the step size used for the x values. + /// @return The step size. + inline Index step() const { return mStep; } + + /// @brief Returns the number of x points in the current range. + /// @return The size of the x range. + inline Index size() const { return mSize; } + + /// @brief Retrieves the starting x value in the range. + /// @return The start of the x range. + inline ValueType start() const { return mXStart; } + + /// @brief Retrieves the ending x value in the range. + /// @return The end of the x range. + inline ValueType end() const { return mXEnd; } + + /// @brief Converts an index to its corresponding x value. + /// @param i The index value. + /// @return The corresponding x value. + inline ValueType getX(const Index& i) const { return indexToWorld(i); } + + /// @brief Gets the minimum y value for a given index. + /// @param i The index value. + /// @return The minimum y value. + inline ValueType getYMin(const Index& i) const { assert(i < mSize); return mYMins[i]; } + + /// @brief Gets the maximum y value for a given index. + /// @param i The index value. + /// @return The maximum y value. + inline ValueType getYMax(const Index& i) const { assert(i < mSize); return mYMaxs[i]; } + + /// @brief Gets the minimum y value for a given x value. + /// @param x The x value. + /// @return The minimum y value at the given x. + /// @note @c x is rounded to the nearest value in the x range. + inline ValueType getYMin(const float& x) const { return mYMins[worldToIndex(x)]; } + + /// @brief Gets the maximum y value for a given x value. + /// @param x The x value. + /// @return The maximum y value at the given x. + /// @note @c x is rounded to the nearest value in the x range. + inline ValueType getYMax(const float& x) const { return mYMaxs[worldToIndex(x)]; } + + /// @brief Retrieves the x, ymin, and ymax values for a given index. + /// @param x Output parameter for the x value. + /// @param ymin Output parameter for the minimum y value. + /// @param ymax Output parameter for the maximum y value. + /// @param i The index to query. + inline void + XYData(ValueType& x, ValueType& ymin, ValueType& ymax, const Index& i) const + { + x = indexToWorld(i); + ymin = mYMins[i]; + ymax = mYMaxs[i]; + } + + /// @brief Merges another XYRangeData into the current instance by combining y ranges + /// over the overlapping x range. + /// @param xydata The XYRangeData to merge with. + inline void + merge(const XYRangeData& xydata) + { + assert(mStep == xydata.step()); + + const ValueType start = xydata.start(), end = xydata.end(); + + const std::vector& ymins = xydata.mYMins; + const std::vector& ymaxs = xydata.mYMaxs; + + if (start < mXStart) { + const Index n = indexDistance(mXStart, start); + mYMins.insert(mYMins.begin(), n, MAXVALUE); + mYMaxs.insert(mYMaxs.begin(), n, MINVALUE); + mXStart = start; + } + + if (mXEnd < end) { + const Index m = indexDistance(end, mXEnd); + mYMins.insert(mYMins.end(), m, MAXVALUE); + mYMaxs.insert(mYMaxs.end(), m, MINVALUE); + mXEnd = end; + } + + mSize = 1 + indexDistance(mXEnd, mXStart); + + const Index offset = start < mXStart ? 0 : indexDistance(start, mXStart); + for (Index i = 0, j = offset; i < ymins.size(); ++i, ++j) { + if (mYMins[j] > ymins[i]) { mYMins[j] = ymins[i]; } + if (mYMaxs[j] < ymaxs[i]) { mYMaxs[j] = ymaxs[i]; } + } + } + + /// @brief Trims the x range by removing empty or invalid y ranges from the beginning and end. + /// Truncates the range if all values are invalid. + inline void + trim() + { + Index i = 0; + while(i < mSize) { + if (mYMins[i] > mYMaxs[i]) i++; + else break; + } + + if (i == mSize) { + mSize = 0; mXStart = ValueType(0); mXEnd = ValueType(0); + mYMins.clear(); mYMaxs.clear(); + return; + } + + Index j = 0; + while(j < mSize) { + const Index k = mSize - (j + 1); + if (mYMins[k] > mYMaxs[k]) j++; + else break; + } + + if (i == 0 && j == 0) + return; + + mSize -= i + j; + mXStart += i * mStep; + mXEnd -= j * mStep; + + if (i > 0) { + mYMins.erase(mYMins.begin(), mYMins.begin() + i); + mYMaxs.erase(mYMaxs.begin(), mYMaxs.begin() + i); + } + + if (j > 0) { + mYMins.erase(mYMins.end() - j, mYMins.end()); + mYMaxs.erase(mYMaxs.end() - j, mYMaxs.end()); + } + } + + private: + + inline static const float + MINVALUE = std::numeric_limits::lowest(), + MAXVALUE = std::numeric_limits::max(); + + inline Index + indexDistance(const ValueType& a, const ValueType& b) + { + return math::Round(mStepInv*math::Abs(a - b)); + } + + inline Index + worldToIndex(const ValueType& x) const + { + const Index i = math::Round(mStepInv*(x - mXStart)); + assert(i < mSize); + + return i; + } + + inline ValueType + indexToWorld(const Index i) const + { + assert(i < mSize); + + return mXStart + i*mStep; + } + + Index mStep, mSize; + + ValueType mStepInv, mXStart, mXEnd; + + std::vector mYMins, mYMaxs; + + }; // class XYRangeData + + // ------------ protected members ------------ + + XYRangeData mXYData; + +private: + +#define EPS 0.0005f + inline static float perturbDown(const float& x) { return x - EPS; } + inline static float perturbUp(const float& x) { return x + EPS; } +#undef EPS + + inline static float + voxelCeil(const float& x) + { + return static_cast(math::Ceil(perturbDown(x))); + } + + inline static float + voxelFloor(const float& x) + { + return static_cast(math::Floor(perturbUp(x))); + } + + // skips the need for negative tile population and internal leap frogging + inline void thinIterate() + { + setXYRangeData(); + + // false means disable internal leap frogging + iterateXYZ(); + } + + template + inline void iterateTile() + { + using NodeT = typename ChainT::Back; + using PoppedChainT = typename ChainT::PopBack; + + static const Index DIM = NodeT::DIM; + + // only attempt to add negative background tiles at this level if they can fit + if (tileCanFit(DIM)) { + setXYRangeData(DIM); + + tileIterateXYZ(); + } + + iterateTile(); + } + + template <> + inline void iterateTile>() {} + + inline void iterateLeaf() + { + setXYRangeData(); + + // true means enable internal leap frogging + iterateXYZ(); + } + + template + void iterateXYZ() + { + // borrowing parallel logic from tools/LevelSetSphere.h + + const int n = mXYData.size(); + + if (mSerial) { + CacheLastLeafAccessor acc(mTree); + for (int i = 0; i < n; ++i) { + if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) + return; + + iterateYZ(i, acc); + } + } else { + tbb::enumerable_thread_specific pool(mTree); + + auto kernel = [&](const tbb::blocked_range& rng) { + TreeT &tree = pool.local(); + CacheLastLeafAccessor acc(tree); + + if (!checkInterrupter()) + return; + + for (int i = rng.begin(); i != rng.end(); ++i) { + if constexpr (LeapFrog) + iterateNoTilesYZ(i, acc); + else + iterateYZ(i, acc); + } + }; + + tbb::parallel_for(tbb::blocked_range(0, n, 128), kernel); + using RangeT = tbb::blocked_range::iterator>; + struct Op { + const bool mDelete; + TreeT *mTree; + Op(TreeT &tree) : mDelete(false), mTree(&tree) {} + Op(const Op& other, tbb::split) : mDelete(true), mTree(new TreeT(other.mTree->background())) {} + ~Op() { if (mDelete) delete mTree; } + void operator()(RangeT &r) { for (auto i=r.begin(); i!=r.end(); ++i) this->merge(*i);} + void join(Op &other) { this->merge(*(other.mTree)); } + void merge(TreeT &tree) { mTree->merge(tree, MERGE_ACTIVE_STATES); } + } op( mTree ); + tbb::parallel_reduce(RangeT(pool.begin(), pool.end(), 4), op); + } + } + + + // for a given x value, create a filled slice of the object by populating + // active voxels and inactive negative background voxels + // if we're leap frogging, we may assume the negative background tiles are already populated + // for each x ordinate and y-scan range + // find the z-range for each y and then populate the grid with distance values + template + inline void iterateYZ(const int& i, CacheLastLeafAccessor& acc) + { + // initialize x value and y-range + float x, yb, yt; + mXYData.XYData(x, yb, yt, i); + + if (!std::isfinite(yb) || !std::isfinite(yt)) + return; + + float zb, zt; + + for (float y = voxelCeil(yb); y <= perturbUp(yt); ++y) { + if (!bottomTop(zb, zt, x, y)) + continue; + + Coord ijk(x, y, 0); + Vec3s p(x, y, 0.0f); + + ijk[2] = voxelCeil(zb)-1; + acc.reset(ijk); + + for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { + ijk[2] = z; + const float val = float(acc.template getValue<1>(ijk)); + + if (val == mNegBgF) { + if constexpr (LeapFrog) acc.template leapUp(ijk, z); + continue; + } + + p[2] = z; + const float dist = mVox * sDist(p); + + if (dist <= mNegBgF) { + acc.template setValueOff<1,false>(ijk, mNegBg); + } else if (dist < val) { + acc.template setValueOn<1,false>(ijk, ValueT(dist)); + } else { // dist >= val + acc.template checkReset<1>(ijk); + } + } + } + } + + // for a given x value, create a hollow slice of the object by only populating active voxels + // for each x ordinate and y-scan range + // find the z-range for each y and then populate the grid with distance values + inline void iterateNoTilesYZ(const int& i, CacheLastLeafAccessor& acc) + { + // initialize x value and y-range + float x, yb, yt; + mXYData.XYData(x, yb, yt, i); + + if (!std::isfinite(yb) || !std::isfinite(yt)) + return; + + float zb, zt; + + for (float y = voxelCeil(yb); y <= perturbUp(yt); ++y) { + if (!bottomTop(zb, zt, x, y)) + continue; + + Coord ijk(x, y, 0); + Vec3s p(x, y, 0.0f); + + bool early_break = false; + float z_stop; + + ijk[2] = voxelCeil(zb)-1; + acc.reset(ijk); + for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { + ijk[2] = z; + p[2] = z; + const float dist = mVox * sDist(p); + + if (dist <= mNegBgF) { + early_break = true; + z_stop = z; + break; + } else if (dist < mBgF) { + acc.template setValueOn<1>(ijk, ValueT(dist)); + } else { // dist >= mBg + acc.template checkReset<1>(ijk); + } + } + if (early_break) { + ijk[2] = voxelFloor(zt)+1; + acc.reset(ijk); + for (float z = voxelFloor(zt); z > z_stop; --z) { + ijk[2] = z; + p[2] = z; + const float dist = mVox * sDist(p); + + if (dist <= mNegBgF) { + break; + } else if (dist < mBgF) { + acc.template setValueOn<-1>(ijk, ValueT(dist)); + } else { // dist >= mBg + acc.template checkReset<-1>(ijk); + } + } + } + } + } + + template + void tileIterateXYZ() + { + AccessorT acc(mTree); + for (int i = 0; i < mXYData.size(); ++i) { + if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) + return; + + tileIterateYZ(i, acc); + } + } + + template + inline void tileIterateYZ(const int& i, AccessorT& acc) + { + // initialize x value and y-range + float x, yb, yt; + mXYData.XYData(x, yb, yt, i); + + if (!std::isfinite(yb) || !std::isfinite(yt)) + return; + + static const Index TILESIZE = NodeT::DIM; + + float zb, zt; + + for (float y = tileCeil(yb, TILESIZE); y <= perturbUp(yt); y += TILESIZE) { + if (!bottomTop(zb, zt, x, y)) + continue; + + Coord ijk(x, y, 0); + Vec3s p(x, y, 0.0f); + + bool tiles_added = false; + float z = tileCeil(zb, TILESIZE) - 2*TILESIZE; + while (z <= tileFloor(zt, TILESIZE) + TILESIZE) { + ijk[2] = z; + p[2] = z; + + if (leapFrogToNextTile(ijk, z, acc)) + continue; + + if (addTile(p, ijk, acc)) tiles_added = true; + else if (tiles_added) break; + } + } + } + + template + inline bool leapFrogToNextTile(const Coord& ijk, float& z, AccessorT& acc) const + { + static const int offset = NodeT::DIM; + static const Index nodeDepth = TreeT::DEPTH - NodeT::LEVEL - 1; + + // we have not encountered an already populated tile + if (acc.getValue(ijk) != mNegBg) { + z += dir*offset; + return false; + } + + const int depth = acc.getValueDepth(ijk); + + // tile is not larger than current node + if (depth >= nodeDepth) { + z += dir*offset; + return false; + } + + const float sz = (float)mTileSizes[depth]; + + z = dir > 0 + ? sz * math::Ceil(z/sz) + 0.5f * (offset - 1.0f) + : sz * math::Floor(z/sz) - 0.5f * (offset + 1.0f); + + return true; + } + + // add negative background tile inside the object if it fits and return true iff it was added + template + inline bool addTile(const Vec3s& p, const Coord& ijk, AccessorT& acc) + { + static const Index LEVEL = NodeT::LEVEL + 1; + + if (tileFits(p)) { + acc.addTile(LEVEL, ijk, mNegBg, false); + return true; + } else { + return false; + } + } + + template + inline bool tileFits(const Vec3s& p) const + { + static const Index TILESIZE = NodeT::DIM; + + static const float R1 = 0.500f * (TILESIZE-1), + R2 = 0.866f * (TILESIZE-1); + + const float dist = tpDist(p); + + // fast positive criterion: circumsribed ball is in the object + if (dist <= -R2-mHw) + return true; + + // fast negative criterion: inscribed ball is not in the object + else if (dist >= -R1-mHw) + return false; + + // convexity: the tile is in the object iff all corners are in the object + return tpDist(p + Vec3s(-R1, -R1, -R1)) < -mHw + && tpDist(p + Vec3s(-R1, -R1, R1)) < -mHw + && tpDist(p + Vec3s(-R1, R1, -R1)) < -mHw + && tpDist(p + Vec3s(-R1, R1, R1)) < -mHw + && tpDist(p + Vec3s(R1, -R1, -R1)) < -mHw + && tpDist(p + Vec3s(R1, -R1, R1)) < -mHw + && tpDist(p + Vec3s(R1, R1, -R1)) < -mHw + && tpDist(p + Vec3s(R1, R1, R1)) < -mHw; + } + + inline float sDist(const Vec3s& p) const + { + return static_cast(this)->signedDistance(p); + } + + inline float tpDist(const Vec3s& p) const + { + return static_cast(this)->tilePointSignedDistance(p); + } + + // misc + + static inline std::vector treeTileSizes() + { + // iterate over all non root nodes + using ChainT = typename NodeChainT::PopBack; + + std::vector sizes; + doTreeTileSizes(sizes); + + return sizes; + } + + template + static inline void doTreeTileSizes(std::vector& sizes) + { + using NodeT = typename ChainT::Back; + using PoppedChainT = typename ChainT::PopBack; + + sizes.push_back(NodeT::DIM); + + doTreeTileSizes(sizes); + } + + template <> + static inline void doTreeTileSizes>(std::vector&) {} + + inline bool checkInterrupter() + { + if (util::wasInterrupted(mInterrupter)) { + openvdb::thread::cancelGroupExecution(); + return false; + } + return true; + } + + // ------------ private nested classes ------------ + + /// @brief A class that caches access to the last leaf node. + /// @tparam TreeT The type of the tree being accessed. + /// @note This class optimizes repeated accesses to the same + /// leaf node by caching the last accessed leaf. + class CacheLastLeafAccessor + { + using NodeMaskType = util::NodeMask; + + public: + + /// @brief Constructs the CacheLastLeafAccessor and initializes + /// the internal accessor with the provided tree. + /// @param tree Reference to the tree being accessed. + CacheLastLeafAccessor(TreeT& tree) + : mAcc(tree) + { + } + + /// @brief Resets the cache by caching new leaf data for the given coordinates. + /// @param ijk The coordinate for which to cache the new leaf data. + inline void reset(const Coord& ijk) { cacheNewLeafData(ijk); } + + /// @brief Checks if the given coordinates are at a new leaf position, + /// and resets the cache if needed. + /// @tparam ZDir The direction in the Z-axis (default: 0, other choices are -1 and 1). + /// @param ijk The coordinate to check and potentially reset. + template + inline void checkReset(const Coord& ijk) + { + if (atNewLeafPos(ijk)) + cacheNewLeafData(ijk); + } + + /// @brief Retrieves the value at the given coordinate, + /// checking and resetting the cache if needed. + /// @tparam ZDir The direction in the Z-axis (default: 0, other choices are -1 and 1). + /// @tparam Check If true, checks if the coordinate is at a new leaf position (default: true). + /// @param ijk The coordinate for which to retrieve the value. + /// @return The value at the specified coordinate. + template + inline ValueT getValue(const Coord& ijk) + { + if (Check && atNewLeafPos(ijk)) + cacheNewLeafData(ijk); + + return mLeaf + ? mLeafData[coordToOffset(ijk)] + : mAcc.getValue(ijk); + } + + /// @brief Sets the value at the given coordinates and marks the voxel as active, + /// checking the cache if needed. + /// @tparam ZDir The direction in the Z-axis (default: 0, other choices are -1 and 1). + /// @tparam Check If true, checks if the coordinate is at a new leaf position (default: true). + /// @param ijk The coordinate where the value is to be set. + /// @param val The value to set at the specified coordinate. + template + inline void setValueOn(const Coord& ijk, const ValueT& val) + { + if (Check && atNewLeafPos(ijk)) + cacheNewLeafData(ijk); + + if (mLeaf) { + const Index n = coordToOffset(ijk); + mLeafData[n] = val; + mValueMask->setOn(n); + } else { + mAcc.setValueOn(ijk, val); + cacheNewLeafData(ijk); + } + } + + /// @brief Sets the value at the given coordinate and marks the voxel as inactive, + /// checking the cache if needed. + /// @tparam ZDir The direction in the Z-axis (default: 0, other choices are -1 and 1). + /// @tparam Check If true, checks if the coordinate is at a new leaf position (default: true). + /// @param ijk The coordinates where the value is to be set. + /// @param val The value to set at the specified coordinate. + template + inline void setValueOff(const Coord& ijk, const ValueT& val) + { + if (Check && atNewLeafPos(ijk)) + cacheNewLeafData(ijk); + + if (mLeaf) { + const Index n = coordToOffset(ijk); + mLeafData[n] = val; + mValueMask->setOff(n); + } else { + mAcc.setValueOff(ijk, val); + cacheNewLeafData(ijk); + } + } + + /// @brief Return @c true if the value of a voxel resides at the (possibly cached) + /// leaf level of the tree, i.e., if it is not a tile value. + /// @tparam ZDir The direction in the Z-axis (default: 0, other choices are -1 and 1). + /// @tparam Check If true, checks if the coordinate is at a new leaf position (default: true). + /// @param ijk The coordinate of the voxel to check. + /// @return True if the voxel exists, otherwise false. + template + inline bool isVoxel(const Coord& ijk) + { + return Check && atNewLeafPos(ijk) ? mLeaf != nullptr : mAcc.isVoxel(ijk); + } + + /// @brief If the input coordinate lies within a tile, + /// the input z value is set to the top of the tile bounding box, in index space. + /// @tparam Check If true, checks if the voxel exists before leaping (default: true). + /// @param ijk The coordinate to be examined. + /// @param z The Z-coordinate to be adjusted. + template + inline void leapUp(const Coord& ijk, float& z) + { + if (isVoxel<1,Check>(ijk)) + return; + + const int depth = mAcc.getValueDepth(ijk); + const float sz = (float)mTileSizes[depth]; + + z = sz*math::Ceil((z+1.0f)/sz) - 1.0f; + } + + private: + + static const Index + DIM = LeafT::DIM, + LOG2DIM = LeafT::LOG2DIM; + + template + bool atNewLeafPos(const Coord& ijk) const + { + return Coord::lessThan(ijk, mOrigin) + || Coord::lessThan(mOrigin.offsetBy(DIM-1u), ijk); + } + + // assumes value just above has been cached already! + template<> + inline bool atNewLeafPos<-1>(const Coord& ijk) const { return (ijk[2] & (DIM-1u)) == DIM-1u; } + + // assumes value just below has been cached already! + template<> + inline bool atNewLeafPos<1>(const Coord& ijk) const { return (ijk[2] & (DIM-1u)) == 0; } + + inline void cacheNewLeafData(const Coord& ijk) + { + mLeaf = mAcc.probeLeaf(ijk); + mOrigin = ijk & (~(DIM - 1)); + + if (mLeaf) { + mLeafData = mLeaf->buffer().data(); + mValueMask = &(mLeaf->getValueMask()); + } else { + mLeafData = nullptr; + } + } + + inline static Index coordToOffset(const Coord& ijk) + { + return ((ijk[0] & (DIM-1u)) << 2*LOG2DIM) + + ((ijk[1] & (DIM-1u)) << LOG2DIM) + + (ijk[2] & (DIM-1u)); + } + + AccessorT mAcc; + LeafT* mLeaf; + + ValueT* mLeafData; + NodeMaskType* mValueMask; + Coord mOrigin; + + }; // class CacheLastLeafAccessor + + // ------------ private members ------------ + + // grid & tree data + + TreeT &mTree; + + inline static const std::vector mTileSizes = treeTileSizes(); + + const float mVox, mBgF, mNegBgF, mHw; + + const ValueT mBg, mNegBg; + + // misc + + const bool mSerial; + + InterruptType* mInterrupter; + +}; // class ConvexVoxelizer + + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_CONVEXVOXELIZER_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h new file mode 100644 index 0000000000..7cf06b0135 --- /dev/null +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -0,0 +1,1657 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 +/// +/// @author Greg Hurst +/// +/// @file LevelSetThickenedMesh.h +/// +/// @brief Generate a narrow-band level set of a thickened mesh. +/// +/// @note By definition a level set has a fixed narrow band width +/// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), +/// whereas an SDF can have a variable narrow band width. + +#ifndef OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED + +#include "ConvexVoxelizer.h" +#include "LevelSetTubes.h" +#include "PointPartitioner.h" +#include "Prune.h" + +#include +#include + +#include +#include + +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a thickened triangle mesh (thickened by a radius in all directions). +/// +/// @param vertices Vertices of the mesh in world units. +/// @param triangles Triangle indices of the mesh. +/// @param radius Radius of the sphere in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetThickenedMesh( + const std::vector& vertices, const std::vector& triangles, + float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a thickened quad mesh (thickened by a radius in all directions). +/// +/// @param vertices Vertices of the mesh in world units. +/// @param quads Quad indices of the mesh. +/// @param radius Radius of the sphere in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetThickenedMesh( + const std::vector& vertices, const std::vector& quads, + float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a thickened triangle & quad mesh (thickened by a radius in all directions). +/// +/// @param vertices Vertices of the mesh in world units. +/// @param triangles Triangle indices of the mesh. +/// @param quads Quad indices of the mesh. +/// @param radius Radius of the sphere in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetThickenedMesh(const std::vector& vertices, + const std::vector& triangles, const std::vector& quads, + float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr); + +namespace lvlset { + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of an _open_ prism. +/// The only parts of the level set populated are along both normals of the triangle. +/// Negative background tiles that fit inside the closed dilated triangle are also populated. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +class OpenTriangularPrismVoxelizer + : public ConvexVoxelizer< + GridT, + OpenTriangularPrismVoxelizer, + InterruptT> +{ + using GridPtr = typename GridT::Ptr; + using ValueT = typename GridT::ValueType; + + // ------------ base class members ------------ + + using BaseT = ConvexVoxelizer< + GridT, + OpenTriangularPrismVoxelizer, + InterruptT + >; + + using BaseT::mXYData; + using BaseT::tileCeil; + +public: + + friend class ConvexVoxelizer< + GridT, + OpenTriangularPrismVoxelizer, + InterruptT + >; + + /// @brief Constructor + /// + /// @param grid scalar grid to populate the level set in + /// @param threaded center of the sphere in world units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note The voxel size and half width are determined from the input grid, + /// meaning the voxel size and background value need to be set prior to voxelization + OpenTriangularPrismVoxelizer(GridPtr& grid, + const bool& threaded = false, + InterruptT* interrupter = nullptr) + : BaseT(grid, threaded, interrupter) + { + } + + /// @brief Create an open prism + /// + /// @param pt1 point 1 of the triangle in world units + /// @param pt2 point 2 of the triangle in world units + /// @param pt3 point 3 of the triangle in world units + /// @param radius radius of the open prism in world units + void + operator()(const Vec3s& pt1, const Vec3s& pt2, const Vec3s& pt3, const float& radius) + { + if (initialize(pt1, pt2, pt3, radius)) + BaseT::iterate(); + } + +private: + + inline void + setXYRangeData(const Index& step = 1) override + { + const float &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(), + &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x(); + + const float &y1 = mPts[0].y(), &y2 = mPts[1].y(), &y3 = mPts[2].y(); + + const float xmin = math::Min(x1, x2, x3, x4, x5, x6); + const float xmax = math::Max(x1, x2, x3, x4, x5, x6); + mXYData.reset(xmin, xmax, step); + + // TODO add logic to ignore edges in the interior of the projection + // TODO add logic that classifies each segment as being either on 'top' or 'bottom' + + setXYSegmentRangeData<0,1,0>(step); + setXYSegmentRangeData<1,2,0>(step); + setXYSegmentRangeData<2,0,0>(step); + + setXYSegmentRangeData<3,4,0>(step); + setXYSegmentRangeData<4,5,0>(step); + setXYSegmentRangeData<5,3,0>(step); + + setXYSegmentRangeData<0,3,0>(step); + setXYSegmentRangeData<1,4,0>(step); + setXYSegmentRangeData<2,5,0>(step); + } + + template + inline void + setXYSegmentRangeData(const Index& step = 1) + { + const float &x1 = mPts[i].x(), &x2 = mPts[j].x(); + + // nothing to do if segment does not span across more than on voxel in x + // other segments will handle this segment's range + if (tileCeil(x1, step) == tileCeil(x2, step)) + return; + + const float x_start = tileCeil(math::Min(x1, x2), step); + const float x_end = math::Max(x1, x2); + + for (float x = x_start; x <= x_end; x += step) { + if constexpr (MinMax <= 0) + mXYData.expandYMin(x, line2D(x)); + if constexpr (MinMax >= 0) + mXYData.expandYMax(x, line2D(x)); + } + } + + // simply offset distance to the center plane, we may assume any CPQ falls in inside the prism + inline float + signedDistance(const Vec3s& p) const + { + return math::Abs(mTriNrml.dot(p - mA)) - mRad; + } + + // allows for tiles to poke outside of the open prism into the tubes + // adaptation of udTriangle at https://iquilezles.org/articles/distfunctions/ + inline float + tilePointSignedDistance(const Vec3s& p) const + { + const Vec3s pa = p - mA, + pb = p - mB, + pc = p - mC; + + const float udist = + math::Sign(mBAXNrml.dot(pa)) + + math::Sign(mCBXNrml.dot(pb)) + + math::Sign(mACXNrml.dot(pc)) < 2.0f + ? + math::Sqrt(math::Min( + (mBA * math::Clamp01(mBANorm2.dot(pa)) - pa).lengthSqr(), + (mCB * math::Clamp01(mCBNorm2.dot(pb)) - pb).lengthSqr(), + (mAC * math::Clamp01(mACNorm2.dot(pc)) - pc).lengthSqr() + )) + : + math::Abs(mTriNrml.dot(p - mA)); + + return udist - mRad; + } + + inline bool + tileCanFit(const Index& dim) const override + { + return mRad >= BaseT::halfWidth() + 0.5f * (dim-1u); + } + + std::function prismBottomTop = + [this](float& zb, float& zt, const float& x, const float& y) + { + zb = std::numeric_limits::lowest(); + zt = std::numeric_limits::max(); + + // TODO with proper book keeping we can know apriori which 2 indexes will set zb & zt + // basically figure out a poor man's cylindrical decomposition... + setPlaneBottomTop<0>(zb, zt, x, y); + setPlaneBottomTop<1>(zb, zt, x, y); + setPlaneBottomTop<2>(zb, zt, x, y); + setPlaneBottomTop<3>(zb, zt, x, y); + setPlaneBottomTop<4>(zb, zt, x, y); + + return true; + }; + + template + inline void + setPlaneBottomTop(float& zb, float& zt, const float& x, const float& y) const + { + if (math::isApproxZero(mFaceNrmls[i].z())) + return; + + const float z = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; + + if (mFaceNrmls[i].z() < 0) { + if (zb < z) + zb = z; + } else { + if (zt > z) + zt = z; + } + } + + // world space points and radius inputs + // initializes class members in index space + inline bool + initialize(const Vec3s& pt1, const Vec3s& pt2, const Vec3s& pt3, const float& r) + { + const float vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); + + mA = pt1/vx; + mB = pt2/vx; + mC = pt3/vx; + + mRad = r/vx; + if (math::isApproxZero(mRad) || mRad < 0) + return false; // nothing to voxelize, prism has no volume + + mBA = mB-mA; + mCB = mC-mB; + mAC = mA-mC; + + mTriNrml = mBA.cross(mC-mA); + + mBAXNrml = mTriNrml.cross(mBA); + mCBXNrml = mTriNrml.cross(mCB); + mACXNrml = mTriNrml.cross(mAC); + + mBANorm2 = math::isApproxZero(mBA.lengthSqr()) ? mBA : mBA/mBA.lengthSqr(); + mCBNorm2 = math::isApproxZero(mCB.lengthSqr()) ? mCB : mCB/mCB.lengthSqr(); + mACNorm2 = math::isApproxZero(mAC.lengthSqr()) ? mAC : mAC/mAC.lengthSqr(); + + const float len = mTriNrml.length(); + if (math::isApproxZero(len)) { + return false; // nothing to voxelize, prism has no volume + } else { + mTriNrml /= len; + } + + const float hwRad = mRad + hw; + mPts = { + mA + hwRad * mTriNrml, mB + hwRad * mTriNrml, mC + hwRad * mTriNrml, + mA - hwRad * mTriNrml, mB - hwRad * mTriNrml, mC - hwRad * mTriNrml + }; + + // tri1, tri2, quad1, quad2, quad3 + mFaceNrmls = { + mTriNrml, + -mTriNrml, + mTriNrml.cross(mA-mB).unitSafe(), + mTriNrml.cross(mB-mC).unitSafe(), + mTriNrml.cross(mC-mA).unitSafe() + }; + + { + static const std::vector p_ind = {0, 3, 0, 1, 2}; + + mPlaneXCoeffs.assign(5, 0.0f); + mPlaneYCoeffs.assign(5, 0.0f); + mPlaneOffsets.assign(5, 0.0f); + + for (Index i = 0; i < 5; ++i) { + if (!math::isApproxZero(mFaceNrmls[i].z())) { + const float cx = mFaceNrmls[i].x()/mFaceNrmls[i].z(), + cy = mFaceNrmls[i].y()/mFaceNrmls[i].z(); + const Vec3s p = mPts[p_ind[i]]; + mPlaneXCoeffs[i] = -cx; + mPlaneYCoeffs[i] = -cy; + mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z(); + } + } + } + + BaseT::bottomTop = prismBottomTop; + + return true; + } + + // ------------ general utilities ------------ + + template + float + line2D(const float& x) const + { + const float &x1 = mPts[i].x(), &y1 = mPts[i].y(), + &x2 = mPts[j].x(), &y2 = mPts[j].y(); + + const float m = (y2-y1)/(x2-x1); + + return y1 + m * (x-x1); + } + + // ------------ private members ------------ + + Vec3s mA, mB, mC; + float mRad; + + Vec3s mBA, mCB, mAC; + Vec3s mBAXNrml, mCBXNrml, mACXNrml; + Vec3s mBANorm2, mCBNorm2, mACNorm2; + + std::vector mPts = std::vector(6); + + Vec3s mTriNrml; + std::vector mFaceNrmls = std::vector(5); + + std::vector mPlaneXCoeffs = std::vector(5), + mPlaneYCoeffs = std::vector(5), + mPlaneOffsets = std::vector(5); + +}; // class OpenTriangularPrismVoxelizer + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of an _open_ wedge. +/// The only parts of the level set populated are within a sector of a capsule. +/// The sector is defined by the intersection of two half spaces. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +class OpenTubeWedgeVoxelizer + : public ConvexVoxelizer< + GridT, + OpenTubeWedgeVoxelizer, + InterruptT> +{ + using GridPtr = typename GridT::Ptr; + using ValueT = typename GridT::ValueType; + + // ------------ base class members ------------ + + using BaseT = ConvexVoxelizer< + GridT, + OpenTubeWedgeVoxelizer, + InterruptT + >; + + using BaseT::mXYData; + using BaseT::tileCeil; + +public: + + friend class ConvexVoxelizer< + GridT, + OpenTubeWedgeVoxelizer, + InterruptT + >; + + /// @brief Constructor + /// + /// @param grid scalar grid to populate the level set in + /// @param threaded center of the sphere in world units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note The voxel size and half width are determined from the input grid, + /// meaning the voxel size and background value need to be set prior to voxelization + OpenTubeWedgeVoxelizer(GridPtr& grid, const bool& threaded = false, + InterruptT* interrupter = nullptr) + : BaseT(grid, threaded, interrupter) + { + } + + /// @brief Create an open wedge + /// + /// @param pt1 first endpoint open wedge in world units + /// @param pt2 second endpoint open wedge in world units + /// @param radius radius of the open prism in world units + /// @param nrml1 normal of a half space the the capsule is clipped with to form the open wedge + /// @param nrml2 normal of the other half space the the capsule is clipped with to form the open wedge + /// + /// @note The normal vectors @f$n @f$ point outward from the open wedge, + /// and the clipping half space is defined by the set of points @f$p @f$ that satisfy @f$n . (p - pt1) \leq 0@f$. + void + operator()(const Vec3s& pt1, const Vec3s& pt2, const float& radius, + const Vec3s& nrml1, const Vec3s& nrml2) + { + if (initialize(pt1, pt2, radius, nrml1, nrml2)) + BaseT::iterate(); + } + +private: + + // computes *approximate* xy-range data: the projected caps might contain over-inclusive values + inline void + setXYRangeData(const Index& step = 1) override + { + // short circuit a vertical cylinder + if (mIsVertical) { + mXYData.reset(mX1 - mORad, mX1 + mORad, step); + + for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); + + intersectWithXYWedgeLines(); + return; + } + + const float v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); + + const float a0 = mX1 - mORad, + a1 = mX1 - v, + a2 = mX1 + v, + a3 = mX2 - v, + a4 = mX2 + v, + a5 = mX2 + mORad; + + const float tc0 = tileCeil(a0, step), + tc1 = tileCeil(a1, step), + tc2 = tileCeil(a2, step), + tc3 = tileCeil(a3, step), + tc4 = tileCeil(a4, step); + + mXYData.reset(a0, a5, step); + + for (float x = tc0; x <= a1; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); + + if (!math::isApproxZero(mXdiff)) { + if (mY1 > mY2) { + for (float x = tc1; x <= math::Min(a2, a3); x += step) + mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); + } else { + for (float x = tc1; x <= math::Min(a2, a3); x += step) + mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); + } + } + + if (a2 < a3) { + for (float x = tc2; x <= a3; x += step) + mXYData.expandYRange(x, lineBottom(x), lineTop(x)); + } else { + if (mY2 <= mY1) { + for (float x = tc3; x <= a2; x += step) + mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); + } else { + for (float x = tc3; x <= a2; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); + } + } + + if (!math::isApproxZero(mXdiff)) { + if (mY1 > mY2) { + for (float x = math::Max(tc2, tc3); x <= a4; x += step) + mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); + } else { + for (float x = math::Max(tc2, tc3); x <= a4; x += step) + mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); + } + } + + for (float x = tc4; x <= a5; x += step) + mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); + + intersectWithXYStrip(); + intersectWithXYWedgeLines(); + } + + inline void + intersectWithXYStrip() + { + // these strips are vertical when the capsule is + if (mIsVertical) + return; + + const Vec3s &pp1 = mPlanePts[0], &pp2 = mPlanePts[1]; + const float &vx = mV.x(), &vy = mV.y(), &vz = mV.z(); + + Vec2s n = Vec2s(-vy, vx).unitSafe(); + Vec3s cvec = mORad * Vec3s(-vy, vx, 0.0f).unitSafe(); + + if (math::isApproxZero(vy)) cvec.y() = math::Abs(cvec.y()); + else if (vy > 0.0f) cvec *= -1.0f; + + const Vec3s cpmin(mPt1 - cvec), cpmax(mPt1 + cvec); + + if (math::isApproxZero(mXdiff)) { + const float px = mPt1.x(), py = mPt1.y(); + const float xmin = math::Min(px, pp1.x(), pp2.x()), + xmax = math::Max(px, pp1.x(), pp2.x()); + + if (!inWedge(cpmin)) + intersectWithXYHalfSpace(n.x() < 0 ? n : -n, Vec2s(xmin, 0.0f)); + + if (!inWedge(cpmax)) + intersectWithXYHalfSpace(n.x() > 0 ? n : -n, Vec2s(xmax, 0.0f)); + } else { + const float m = mYdiff/mXdiff; + const float y1 = mPt1.y() - m * mPt1.x(), + y2 = pp1.y() - m * pp1.x(), + y3 = pp2.y() - m * pp2.x(); + const float ymin = math::Min(y1, y2, y3), + ymax = math::Max(y1, y2, y3); + + if (!inWedge(vy <= 0.0f ? cpmin : cpmax)) + intersectWithXYHalfSpace(n.y() < 0 ? n : -n, Vec2s(0.0f, ymin)); + + if (!inWedge(vy > 0.0f ? cpmin : cpmax)) + intersectWithXYHalfSpace(n.y() > 0 ? n : -n, Vec2s(0.0f, ymax)); + } + } + + inline void + intersectWithXYWedgeLines() + { + const Vec3s v(mORad * mV.unitSafe()), + p1(mPt1 - v), + p2(mPt2 + v); + + const Vec2s p1_2d(p1.x(), p1.y()), p2_2d(p2.x(), p2.y()); + + Vec2s d(-mPlaneNrmls[0].x() - mPlaneNrmls[1].x(), + -mPlaneNrmls[0].y() - mPlaneNrmls[1].y()); + + Vec2s n0(-mDirVectors[0].y(), mDirVectors[0].x()), + n1(-mDirVectors[1].y(), mDirVectors[1].x()); + + if (n0.dot(d) > 0.0f) + n0 *= -1; + if (n1.dot(d) > 0.0f) + n1 *= -1; + + if (!math::isApproxZero(n0.lengthSqr())) + intersectWithXYHalfSpace(n0, n0.dot(p2_2d - p1_2d) < 0.0f ? p1_2d : p2_2d); + + if (!math::isApproxZero(n1.lengthSqr())) + intersectWithXYHalfSpace(n1, n1.dot(p2_2d - p1_2d) < 0.0f ? p1_2d : p2_2d); + } + + inline void + intersectWithXYHalfSpace(const Vec2s& n, const Vec2s& p) + { + if (mXYData.size() == 0) + return; + + if (math::isApproxZero(n.y())) { + const float px = p.x(); + if (n.x() < 0) { + const Index m = mXYData.size(); + for (Index i = 0; i < m; ++i) { + const float x = mXYData.getX(i); + + if (x < px) mXYData.clearYRange(x); + else break; + } + } else { + Index i = mXYData.size()-1; + while (true) { + const float x = mXYData.getX(i); + + if (x > px) mXYData.clearYRange(x); + else break; + + if (i != 0) --i; + else break; + } + } + } else { + const bool set_min = n.y() < 0; + const Index m = mXYData.size(); + + const float b = -n.x()/n.y(); + const float a = p.y() - b * p.x(); + + float x, ymin, ymax; + for (Index i = 0; i < m; ++i) { + mXYData.XYData(x, ymin, ymax, i); + const float yint = a + b * x; + + if (ymin <= yint && yint <= ymax) { + if (set_min) mXYData.setYMin(x, yint); + else mXYData.setYMax(x, yint); + } else { + if (set_min ? yint > ymax : yint < ymin) + mXYData.clearYRange(x); + } + } + } + + mXYData.trim(); + } + + // distance in index space + inline float + signedDistance(const Vec3s& p) const + { + const Vec3s w = p - mPt1; + const float dot = w.dot(mV); + + // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr + if (dot <= math::Tolerance::value()) + return w.length() - mRad; + + if (dot >= mVLenSqr) + return (p - mPt2).length() - mRad; + + const float t = w.dot(mV)/mVLenSqr; + + return (w - t * mV).length() - mRad; + } + + inline bool + tileCanFit(const Index& dim) const override + { + return mRad >= BaseT::halfWidth() + 0.5f * (dim-1u); + } + + std::function tubeBottomTopVertical = + [this](float& zb, float& zt, const float& x, const float& y) + { + zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y); + zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y); + + return std::isfinite(zb) && std::isfinite(zt); + }; + + std::function tubeBottomTop = + [this](float& zb, float& zt, const float& x, const float& y) + { + float cylptb, cylptt; + if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y)) + return false; + + const float dotb = (Vec3s(x, y, cylptb) - mPt1).dot(mV); + const float dott = (Vec3s(x, y, cylptt) - mPt1).dot(mV); + + if (dotb < 0) + zb = sphere1Bottom(x, y); + else if (dotb > mVLenSqr) + zb = sphere2Bottom(x, y); + else + zb = cylptb; + + if (dott < 0) + zt = sphere1Top(x, y); + else if (dott > mVLenSqr) + zt = sphere2Top(x, y); + else + zt = cylptt; + + if (!std::isfinite(zb) || !std::isfinite(zt)) + return false; + + intersectWedge<0,1>(zb, zt, x, y); + intersectWedge<1,0>(zb, zt, x, y); + + return inWedge(x, y, 0.5f*(zb+zt)); + }; + + template + inline void + intersectWedge(float& zb, float& zt, const float& x, const float& y) + { + const Vec3s& n0 = mPlaneNrmls[i]; + + if (math::isApproxZero(n0.z())) + return; + + const float zp = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; + + if (zb <= zp && zp <= zt && inHalfSpace(Vec3s(x, y, zp))) { + if (n0.z() < 0) + zb = zp; + else + zt = zp; + } + } + + inline bool + inWedge(const float& x, const float& y, const float& z) + { + return inWedge(Vec3s(x, y, z)); + } + + inline bool + inWedge(const Vec3s& pt) + { + return inHalfSpace<0>(pt) && inHalfSpace<1>(pt); + } + + template + inline bool + inHalfSpace(const Vec3s& pt) + { + // allow points within a fuzzy fractional (index space) distance to the halfspace + // this ensures the seams between open wedges and open prisms are completely filled in + // assumes mPlaneNrmls[i] is a unit vector + static const float VOXFRAC = 0.125f; + + return mPlaneNrmls[i].dot(pt-mPt1) <= VOXFRAC; + } + + // assumes tube is not vertical! + inline bool + infiniteCylinderBottomTop(float& cylptb, float& cylptt, + const float& x, const float& y) const + { + const Vec2s q(x, y); + + const Vec2s qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; + + const float t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; + + const Vec3s qproj3D = mPt1 + t * mV; + + const float d2 = (q - qproj).lengthSqr(); + + // outside of cylinder's 2D projection + if (mORad2 < d2) + return false; + + const float h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); + + cylptb = qproj3D[2] - h; + cylptt = qproj3D[2] + h; + + return true; + } + + inline float + lineBottom(const float& x) const + { + return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff; + } + + inline float + lineTop(const float& x) const + { + return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff; + } + + inline float + circle1Bottom(const float& x) const + { + return BaseT::circleBottom(mX1, mY1, mORad, x); + } + + inline float + circle1Top(const float& x) const + { + return BaseT::circleTop(mX1, mY1, mORad, x); + } + + inline float + circle2Bottom(const float& x) const + { + return BaseT::circleBottom(mX2, mY2, mORad, x); + } + + inline float + circle2Top(const float& x) const + { + return BaseT::circleTop(mX2, mY2, mORad, x); + } + + inline float + sphere1Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y); + } + + inline float + sphere1Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y); + } + + inline float + sphere2Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y); + } + + inline float + sphere2Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y); + } + + // world space points and radius inputs + // initializes class members in index space + inline bool + initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r, + const Vec3s& n1, const Vec3s& n2) + { + const float vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); + + // forces x1 <= x2 + if (pt1[0] <= pt2[0]) { + mPt1 = pt1/vx; + mPt2 = pt2/vx; + } else { + mPt1 = pt2/vx; + mPt2 = pt1/vx; + } + + mRad = r/vx; + + // tube has no volume + if (math::isApproxZero(mRad)) + return false; + + // padded radius used to populate the outer halfwidth of the sdf + mORad = mRad + hw; + mORad2 = mORad * mORad; + + mV = mPt2 - mPt1; + mVLenSqr = mV.lengthSqr(); + + // no direction to form the wedge on a sphere + if (math::isApproxZero(mVLenSqr)) + return false; + + mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2]; + mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2]; + + mXdiff = mX2 - mX1; + mYdiff = mY2 - mY1; + mZdiff = mZ2 - mZ1; + + mPt12d = Vec2s(mX1, mY1); + mPt22d = Vec2s(mX2, mY2); + mV2d = mPt22d - mPt12d; + + mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); + mXYNorm = math::Sqrt(mXYNorm2); + mIsVertical = math::isApproxZero(mXYNorm); + + { + // no direction to form the wedge + if (math::isApproxZero(n1.lengthSqr()) || math::isApproxZero(n2.lengthSqr())) + return false; + + mPlaneNrmls[0] = (n1 - n1.projection(mV)).unitSafe(); + mPlaneNrmls[1] = (n2 - n2.projection(mV)).unitSafe(); + + // degenerate wedge + if (approxAntiParallel(mPlaneNrmls[0], mPlaneNrmls[1])) + return false; + + mDirVectors[0] = mORad * mV.cross(mPlaneNrmls[0]).unitSafe(); + mDirVectors[1] = mORad * mV.cross(mPlaneNrmls[1]).unitSafe(); + + if (approxParallel(mPlaneNrmls[0], mPlaneNrmls[1])) { + mDirVectors[1] = -mDirVectors[0]; + } else { + if (mPlaneNrmls[1].dot(mDirVectors[0]) > 0.0f) + mDirVectors[0] *= -1.0f; + if (mPlaneNrmls[0].dot(mDirVectors[1]) > 0.0f) + mDirVectors[1] *= -1.0f; + } + + mPlanePts[0] = mPt1 + mDirVectors[0] + 0.025f * mPlaneNrmls[0]; + mPlanePts[1] = mPt1 + mDirVectors[1] + 0.025f * mPlaneNrmls[1]; + } + + { + mPlaneXCoeffs.assign(2, 0.0f); + mPlaneYCoeffs.assign(2, 0.0f); + mPlaneOffsets.assign(2, 0.0f); + + for (Index i = 0; i < 2; ++i) { + if (!math::isApproxZero(mPlaneNrmls[i].z())) { + const float cx = mPlaneNrmls[i].x()/mPlaneNrmls[i].z(), + cy = mPlaneNrmls[i].y()/mPlaneNrmls[i].z(); + const Vec3s p = mPlanePts[i]; + mPlaneXCoeffs[i] = -cx; + mPlaneYCoeffs[i] = -cy; + mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z(); + } + } + } + + BaseT::bottomTop = mIsVertical ? tubeBottomTopVertical : tubeBottomTop; + + return true; + } + + inline bool + approxAntiParallel(const Vec3s& n1, const Vec3s& n2) + { + return approxParallel(n1, -n2); + } + + inline bool + approxParallel(const Vec3s& n1, const Vec3s& n2) + { + return n1.unitSafe().eq(n2.unitSafe()); + } + + // ------------ private members ------------ + + // tube data -- populated via initialize() + + Vec3s mPt1, mPt2, mV; + + Vec2s mPt12d, mPt22d, mV2d; + + float mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; + + float mX1, mY1, mZ1, mX2, mY2, mZ2; + + bool mIsVertical; + + std::vector mPlaneNrmls = std::vector(2), + mDirVectors = std::vector(2), + mPlanePts = std::vector(2); + + std::vector mPlaneXCoeffs = std::vector(2), + mPlaneYCoeffs = std::vector(2), + mPlaneOffsets = std::vector(2); + +}; // class OpenTubeWedgeVoxelizer + + +/// @brief Class representing the connectivity of edges in a triangle mesh, +/// where each edge is associated with the cells (triangles) sharing it. +/// Provides methods to retrieve adjacent cells, +/// vertex coordinates, normals, and other geometric properties. +class TriangleMeshEdgeConnectivity { + +public: + + /// @brief Constructs the TriangleMeshEdgeConnectivity object with given coordinates and cell data. + /// Populates edge-to-cell adjacency and computes cell normals. + /// + /// @param coords Vector of vertex coordinates. + /// @param cells Vector of cell (triangle) indices. + TriangleMeshEdgeConnectivity(const std::vector& coords, const std::vector& cells) + : mCoords(coords), mCells(cells) + { + const Index n = coords.size(); + + mNormals.resize(cells.size()); + + for (Index i = 0; i < cells.size(); i++) { + const Vec3I& cell = mCells[i]; + + Edge edges[3] = { + Edge(cell[0], cell[1]), + Edge(cell[1], cell[2]), + Edge(cell[2], cell[0]) + }; + + for (const Edge& edge : edges) { + mEdgeMap[edge].push_back(i); + } + + if (cell[0] >= n || cell[1] >= n || cell[2] >= n) + OPENVDB_THROW(ValueError, "out of bounds index"); + + const Vec3s &p1 = mCoords[cell[0]], + &p2 = mCoords[cell[1]], + &p3 = mCoords[cell[2]]; + + mNormals[i] = (p2 - p1).cross(p3 - p1).unitSafe(); + } + + for (auto& [edge, cells] : mEdgeMap) + sortAdjacentCells(edge, cells); + } + + /// @brief Retrieves the IDs of cells adjacent to an edge formed by two vertices. + /// + /// @param v1 First vertex index. + /// @param v2 Second vertex index. + /// @param cellIds Output vector to hold the IDs of adjacent cells. + /// @return True if adjacent cells are found, false otherwise. + bool + getAdjacentCells(const Index& v1, const Index& v2, std::vector& cellIds) const + { + Edge edge(v1, v2); + auto it = mEdgeMap.find(edge); + if (it != mEdgeMap.end()) { + cellIds = it->second; + return true; + } + return false; + } + + /// @brief Retrieves the 3D coordinate at a given index. + /// + /// @param i Index of the vertex. + /// @return The 3D coordinate as a constant reference to Vec3s. + inline const Vec3s& + getCoord(const Index& i) const + { + return mCoords[i]; + } + + /// @brief Retrieves the cell (triangle) at a given index. + /// + /// @param i Index of the cell. + /// @return Constant reference to the triangle's vertex indices. + inline const Vec3I& + getCell(const Index& i) const + { + return mCells[i]; + } + + /// @brief Retrieves the 3D coordinates of the vertices forming a + /// primitive (triangle) at a given cell index. + /// + /// @param i Index of the cell (triangle). + /// @return A vector of three Vec3s representing the coordinates of the triangle's vertices. + inline std::vector + getPrimitive(const Index& i) const + { + const Vec3I cell = mCells[i]; + return {mCoords[cell[0]], mCoords[cell[1]], mCoords[cell[2]]}; + } + + /// @brief Retrieves the unit normal vector of a cell (triangle) at a given index. + /// + /// @param i Index of the cell. + /// @return The normal vector of the triangle as a Vec3s. + inline Vec3s + getNormal(const Index& i) const + { + return mNormals[i]; + } + + /// @brief Retrieves the total number of coordinates in the mesh. + /// + /// @return The number of coordinates as an Index. + inline Index + coordCount() const + { + return mCoords.size(); + } + + /// @brief Retrieves the total number of cells (triangles) in the mesh. + /// + /// @return The number of cells as an Index. + inline Index + cellCount() const + { + return mCells.size(); + } + +private: + struct Edge { + Index mV1, mV2; + + Edge(Index v1, Index v2) + : mV1(std::min(v1, v2)), mV2(std::max(v1, v2)) + { + } + + bool operator<(const Edge& e) const + { + return mV1 < e.mV1 || (mV1 == e.mV1 && mV2 < e.mV2); + } + }; + + inline Vec3s + centroid(Index cellIdx) const + { + const Vec3I cell = mCells[cellIdx]; + return (mCoords[cell[0]] + mCoords[cell[1]] + mCoords[cell[2]]) / 3.0; + } + + inline bool + onSameHalfPlane(const Vec3s &n, const Vec3s& p0, const Vec3s &p1, const Vec3s &p2) + { + return math::Abs(math::Sign(n.dot(p1-p0)) - math::Sign(n.dot(p2-p0))) != 2; + } + + inline void + sortAdjacentCells(const Edge& edge, std::vector& cells) + { + if (cells.size() <= 2) return; + + const Vec3I &base_cell = mCells[cells[0]]; + const Index offset = edge.mV1 + edge.mV2; + + const Index p1Ind = base_cell[0] + base_cell[1] + base_cell[2] - offset; + + const Vec3s &p1 = mCoords[p1Ind], + &n1 = mNormals[cells[0]]; + + const Vec3s p0 = mCoords[edge.mV1]; + + Vec3s bi_nrml = n1.cross(p0 - mCoords[edge.mV2]); + if (bi_nrml.dot(p1 - p0) > 0) bi_nrml *= -1.0f; + + auto windingamount = [&](Index cellIdx) + { + if (cellIdx == 0) return 0.0f; + + const Vec3I &cell = mCells[cellIdx]; + const Index p2Ind = cell[0] + cell[1] + cell[2] - offset; + + const Vec3s &p2 = mCoords[p2Ind], + &n2 = mNormals[cellIdx]; + + const float cos_theta = math::Abs(n1.dot(n2)); + const int sgn = math::Sign(n1.dot(p2 - p1)), + sgn2 = math::Sign(bi_nrml.dot(p2 - p0)); + + return sgn != 0 + ? (sgn == 1 ? 1.0f + sgn2 * cos_theta : 3.0f - sgn2 * cos_theta) + : (onSameHalfPlane(bi_nrml, p0, p1, p2) ? 0.0f : 2.0f); + }; + + std::sort(cells.begin(), cells.end(), [&](const Index& t1, const Index& t2) { + return windingamount(t1) < windingamount(t2); + }); + } + + // ------------ private members ------------ + + const std::vector& mCoords; + const std::vector& mCells; + + std::vector mNormals; + + std::map> mEdgeMap; + +}; // class TriangleMeshEdgeConnectivity + + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of a thckened mesh (surface mesh thickened by a radius in all directions). +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +class ThickenedMeshVoxelizer { + + using GridPtr = typename GridT::Ptr; + using TreeT = typename GridT::TreeType; + using LeafT = typename TreeT::LeafNodeType; + + using PartitionerT = tools::PointPartitioner; + + using PrismVoxelizer = OpenTriangularPrismVoxelizer; + using WedgeVoxelizer = OpenTubeWedgeVoxelizer; + + using MeshConnectivity = TriangleMeshEdgeConnectivity; + +public: + + /// @brief Constructor for constant radius + /// + /// @param vertices vertices of the mesh in world units + /// @param triangles triangle indices indices in the mesh + /// @param radius radius of all tubes in world units + /// @param voxelSize voxel size in world units + /// @param background background value in voxel units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// @param grid optional grid to populate into (grid need not be empty). + ThickenedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, + float radius, float voxelSize, float background, + InterruptT* interrupter, GridPtr grid = nullptr) + : mVox(voxelSize), mBg(background) + , mMesh(std::make_shared(MeshConnectivity(vertices, triangles))) + , mRad(radius), mInterrupter(interrupter) + { + if (!grid) + initializeGrid(); + else + mGrid = grid; + + if constexpr (PtPartition) + initializePartitioner(); + + mPVoxelizer = std::make_shared(mGrid, false); + mWVoxelizer = std::make_shared(mGrid, false); + } + + ThickenedMeshVoxelizer(ThickenedMeshVoxelizer& other, tbb::split) + : mVox(other.mVox), mBg(other.mBg) + , mPtPartitioner(other.mPtPartitioner), mMesh(other.mMesh) + , mRad(other.mRad), mInterrupter(other.mInterrupter) + { + initializeGrid(); + + mPVoxelizer = std::make_shared(mGrid, false); + mWVoxelizer = std::make_shared(mGrid, false); + } + + void operator()(const tbb::blocked_range& rng) + { + if (!checkInterrupter()) + return; + + if constexpr (PtPartition) { + for (size_t i = rng.begin(); i < rng.end(); ++i) + for (auto it = mPtPartitioner->indices(i); it; ++it) + voxelizeTriangle(*it); + } else { + for (size_t i = rng.begin(); i < rng.end(); ++i) + voxelizeTriangle(i); + } + } + + void join(ThickenedMeshVoxelizer& other) + { + tools::CsgUnionOp op(other.mGrid->tree(), Steal()); + tree::DynamicNodeManager nodeManager(mGrid->tree()); + nodeManager.foreachTopDown(op, true); + + other.mGrid = nullptr; + } + + inline Index bucketSize() const { return mPtPartitioner->size(); } + + inline Index cellSize() const { return mMesh->cellCount(); } + + inline GridPtr getGrid() const { return mGrid; } + +private: + + inline bool + affinelyIndependent(const Vec3s& p1, const Vec3s& p2, const Vec3s& p3) const + { + const Vec3s n = (p2-p1).cross(p3-p1); + return !math::isApproxZero(n.x()) + || !math::isApproxZero(n.y()) + || !math::isApproxZero(n.z()); + } + + inline void + voxelizeTriangle(const size_t& i) + { + const Vec3I &cell = mMesh->getCell(i); + const std::vector pts = mMesh->getPrimitive(i); + + if (!affinelyIndependent(pts[0], pts[1], pts[2])) { + voxelizeTube(pts[0], pts[1], pts[2]); + return; + } + + // prism + (*mPVoxelizer)(pts[0], pts[1], pts[2], mRad); + + std::vector cellIds; + Vec3s n1, n2; + + // wedges + for (Index j = 0; j < 3; ++j) { + const bool success = mMesh->getAdjacentCells(cell[j], cell[(j+1) % 3], cellIds); + if (success && cellIds[0] == i) { + if (findWedgeNormals(i, j, cellIds, n1, n2)) + (*mWVoxelizer)(pts[j], pts[(j+1) % 3], mRad, n1, n2); + } + } + } + + inline void + voxelizeTube(const Vec3s& p1, const Vec3s& p2, const Vec3s& p3) + { + lvlset::CapsuleVoxelizer voxelizer(mGrid, false); + + float d1 = (p2-p1).lengthSqr(), + d2 = (p3-p2).lengthSqr(), + d3 = (p1-p3).lengthSqr(); + + float maxd = math::Max(d1, d2, d3); + + if (maxd == d1) + voxelizer(p1, p2, mRad); + else if (maxd == d2) + voxelizer(p2, p3, mRad); + else + voxelizer(p3, p1, mRad); + } + + inline bool + findWedgeNormals(const Index& cellIdx, const Index& vIdx, + const std::vector& cellIds, Vec3s& n1, Vec3s& n2) const + { + if (cellIds.size() == 1) + return findWedgeNormals1(cellIdx, vIdx, n1, n2); + else if (cellIds.size() == 2) + return findWedgeNormals2(cellIdx, vIdx, cellIds[1], n1, n2); + else if (cellIds.size() > 2) + return findWedgeNormals3(cellIdx, vIdx, cellIds, n1, n2); + + return false; + } + + inline bool + findWedgeNormals1(const Index& cellIdx, const Index& vIdx, + Vec3s& n1, Vec3s& n2) const + { + const Vec3I &cell = mMesh->getCell(cellIdx); + const Vec3s &p1 = mMesh->getCoord(cell[vIdx]), + &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]), + &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]); + + const Vec3s &n = mMesh->getNormal(cellIdx); + + n1 = n.cross(p2-p1).unitSafe(); + if (n1.dot(p3-p1) < 0) n1 *= -1.0f; + + n2 = n1; + + return true; + } + + inline bool + findWedgeNormals2(const Index& cellIdx, const Index& vIdx, + const Index& cellIdx2, Vec3s& n1, Vec3s& n2) const + { + const Vec3I &cell = mMesh->getCell(cellIdx), + &cell2 = mMesh->getCell(cellIdx2); + + const Index cIdx2 = cell2[0] + cell2[1] + cell2[2] - cell[vIdx] - cell[(vIdx+1) % 3]; + + const Vec3s &p1 = mMesh->getCoord(cell[vIdx]), + &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]), + &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]), + &p4 = mMesh->getCoord(cIdx2); + + const Vec3s &nrml1 = mMesh->getNormal(cellIdx), + &nrml2 = mMesh->getNormal(cellIdx2); + + n1 = nrml1.cross(p2-p1).unitSafe(), + n2 = nrml2.cross(p2-p1).unitSafe(); + + if (n1.dot(p3-p1) < 0) n1 *= -1.0f; + if (n2.dot(p4-p1) < 0) n2 *= -1.0f; + + return true; + } + + inline bool + findWedgeNormals3(const Index& cellIdx, const Index& vIdx, + const std::vector& cellIds, Vec3s& n1, Vec3s& n2) const + { + const Vec3I &cell = mMesh->getCell(cellIdx); + + const Index n = cellIds.size(), + offset = cell[vIdx] + cell[(vIdx+1) % 3]; + + for (Index i = 0; i < n; ++i) { + const Vec3I &cell0 = mMesh->getCell(cellIds[i]), + &cell1 = mMesh->getCell(cellIds[(i+1) % n]), + &cell2 = mMesh->getCell(cellIds[(i+2) % n]); + + const Index cIdx0 = cell0[0] + cell0[1] + cell0[2] - offset, + cIdx1 = cell1[0] + cell1[1] + cell1[2] - offset, + cIdx2 = cell2[0] + cell2[1] + cell2[2] - offset; + + const Vec3s &p0 = mMesh->getCoord(cIdx0), + &p1 = mMesh->getCoord(cIdx1), + &p2 = mMesh->getCoord(cIdx2); + + Vec3s nrml0 = mMesh->getNormal(cellIds[i]), + nrml1 = mMesh->getNormal(cellIds[(i+1) % n]); + + if (nrml0.dot(p1-p0) > 0) nrml0 *= -1.0f; + if (nrml1.dot(p0-p1) > 0) nrml1 *= -1.0f; + + if (nrml0.dot(p2-p0) > 0 || nrml1.dot(p2-p1) > 0) + continue; + + Index vIdxi; + if (cell0[0] == cell[vIdx]) + vIdxi = cell0[1] == cell[(vIdx+1) % 3] ? 0 : 2; + else if (cell0[1] == cell[vIdx]) + vIdxi = cell0[2] == cell[(vIdx+1) % 3] ? 1 : 0; + else + vIdxi = cell0[0] == cell[(vIdx+1) % 3] ? 2 : 1; + + return findWedgeNormals2(cellIds[i], vIdxi, cellIds[(i+1) % n], n1, n2); + } + + return false; + } + + inline void + computeCentroids(std::vector& centroids) + { + centroids.resize(mMesh->cellCount()); + + tbb::parallel_for(tbb::blocked_range(0, centroids.size()), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + const std::vector prim = mMesh->getPrimitive(i); + centroids[i] = (prim[0] + prim[1] + prim[2]) / 3.0f; + } + }); + } + + inline void + initializeGrid() + { + math::Transform transform(*(math::Transform::createLinearTransform(mVox))); + mGrid = GridPtr(new GridT(mBg)); + mGrid->setTransform(transform.copy()); + mGrid->setGridClass(GRID_LEVEL_SET); + } + + inline void + initializePartitioner() + { + std::vector centroids; + computeCentroids(centroids); + + lvlset::PointArray points(centroids); + + mPtPartitioner = std::make_shared(); + mPtPartitioner->construct(points, mGrid->transform()); + } + + inline bool + checkInterrupter() + { + if (util::wasInterrupted(mInterrupter)) { + openvdb::thread::cancelGroupExecution(); + return false; + } + return true; + } + + // ------------ private members ------------ + + std::shared_ptr mMesh; + + const float mVox, mBg, mRad; + + std::shared_ptr mPtPartitioner; + + GridPtr mGrid; + + std::shared_ptr mPVoxelizer; + std::shared_ptr mWVoxelizer; + + InterruptT* mInterrupter; + +}; // class ThickenedMeshVoxelizer + +} // namespace lvlset + + +// ------------ createLevelSetThickenedMesh ------------- // + +template +typename GridType::Ptr +createLevelSetThickenedMesh( + const std::vector& vertices, const std::vector& triangles, + float radius, float voxelSize, float halfWidth, InterruptT* interrupter) +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + using Voxelizer = typename lvlset::ThickenedMeshVoxelizer; + + static_assert(std::is_floating_point::value, + "createLevelSetThickenedMesh must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + Voxelizer op(vertices, triangles, radius, voxelSize, voxelSize * halfWidth, interrupter); + + const tbb::blocked_range triangleRange(0, op.bucketSize()); + tbb::parallel_reduce(triangleRange, op); + + GridPtr grid = op.getGrid(); + tools::pruneLevelSet(grid->tree()); + + return grid; +} + +template +typename GridType::Ptr +createLevelSetThickenedMesh( + const std::vector& vertices, const std::vector& quads, + float radius, float voxelSize, float halfWidth, InterruptT* interrupter) +{ + using ValueT = typename GridType::ValueType; + + static_assert(std::is_floating_point::value, + "createLevelSetThickenedMesh must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + const Index n = quads.size(); + std::vector triangles(2*n); + + tbb::parallel_for(tbb::blocked_range(0, n), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + const Vec4I& q = quads[i]; + triangles[i] = Vec3I(q.x(), q.y(), q.z()); + triangles[i + n] = Vec3I(q.x(), q.z(), q.w()); + } + }); + + return createLevelSetThickenedMesh(vertices, triangles, radius, + voxelSize, halfWidth, interrupter); +} + +template +typename GridType::Ptr +createLevelSetThickenedMesh(const std::vector& vertices, + const std::vector& triangles, const std::vector& quads, + float radius, float voxelSize, float halfWidth, InterruptT* interrupter) +{ + using ValueT = typename GridType::ValueType; + + static_assert(std::is_floating_point::value, + "createLevelSetThickenedMesh must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + if (quads.empty()) + return createLevelSetThickenedMesh(vertices, triangles, radius, + voxelSize, halfWidth, interrupter); + + const Index tn = triangles.size(), qn = quads.size(); + const Index qn2 = tn + qn; + std::vector tris(tn + 2*qn); + + tbb::parallel_for(tbb::blocked_range(0, tn), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + tris[i] = triangles[i]; + } + }); + + tbb::parallel_for(tbb::blocked_range(0, qn), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + const Vec4I& q = quads[i]; + tris[i + tn] = Vec3I(q.x(), q.y(), q.z()); + tris[i + qn2] = Vec3I(q.x(), q.z(), q.w()); + } + }); + + return createLevelSetThickenedMesh(vertices, tris, radius, + voxelSize, halfWidth, interrupter); +} + + +//////////////////////////////////////// + + +// Explicit Template Instantiation + +#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION + +#ifdef OPENVDB_INSTANTIATE_LEVELSETTHICKENEDMESH +#include +#endif + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + const std::vector&, float, float, float, util::NullInterrupter*) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + const std::vector&, float, float, float, util::NullInterrupter*) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + const std::vector&, const std::vector&, float, float, float, \ + util::NullInterrupter*) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h new file mode 100644 index 0000000000..daea10eb78 --- /dev/null +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -0,0 +1,1602 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: MPL-2.0 +/// +/// @author Greg Hurst +/// +/// @file LevelSetTubes.h +/// +/// @brief Generate a narrow-band level set of a capsule, tapered capsule, and tube complex. +/// +/// @note By definition a level set has a fixed narrow band width +/// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), +/// whereas an SDF can have a variable narrow band width. + +#ifndef OPENVDB_TOOLS_LEVELSETTUBES_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETTUBES_HAS_BEEN_INCLUDED + +#include "ConvexVoxelizer.h" +#include "PointPartitioner.h" +#include "Prune.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + + +namespace openvdb { +OPENVDB_USE_VERSION_NAMESPACE +namespace OPENVDB_VERSION_NAME { +namespace tools { + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a capsule (tube with constant radius and sphere caps). +/// +/// @param pt1 First capsule endpoint in world units. +/// @param pt2 Second capsule endpoint in world units. +/// @param radius Radius of the capsule in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// @param threaded If true multi-threading is enabled (true by default). +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr, bool threaded = true); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a capsule (tube with constant radius and sphere caps). +/// +/// @param pt1 First capsule endpoint in world units. +/// @param pt2 Second capsule endpoint in world units. +/// @param radius Radius of the capsule in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param threaded If true multi-threading is enabled (true by default). +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), bool threaded = true); + + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a tapered capsule (tube with sphere caps and different radii at both ends, +/// or equivalently the convex hull of two spheres with possibly different centers and radii). +/// +/// @param pt1 First tapered capsule endpoint in world units. +/// @param pt2 Second tapered capsule endpoint in world units. +/// @param radius1 Radius of the tapered capsule at @c pt1 in world units. +/// @param radius2 Radius of the tapered capsule at @c pt2 in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// @param threaded If true multi-threading is enabled (true by default). +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, + float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr, bool threaded = true); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a tapered capsule (tube with sphere caps and different radii at both ends, +/// or equivalently the convex hull of two spheres with possibly different centers and radii). +/// +/// @param pt1 First tapered capsule endpoint in world units. +/// @param pt2 Second tapered capsule endpoint in world units. +/// @param radius1 Radius of the tapered capsule at @c pt1 in world units. +/// @param radius2 Radius of the tapered capsule at @c pt2 in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param threaded If true multi-threading is enabled (true by default). +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, + float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), bool threaded = true); + +/// @brief Different policies when creating a tube complex with varying radii +/// @details +///
+///
TUBE_VERTEX_RADII +///
Specify that the tube complex radii are per-vertex, +/// meaning each tube has different radii at its two endpoints +/// and the complex is a collection of tapered capsules. +/// +///
TUBE_SEGMENT_RADII +///
Specify that the tube complex radii are per-segment, +/// meaning each tube has a constant radius and the complex is a collection of capsules. +/// +///
TUBE_AUTOMATIC +///
Specify that the only valid setting is to be chosen, +/// defaulting to the per-vertex policy if both are valid. +///
+enum TubeRadiiPolicy { TUBE_AUTOMATIC = 0, TUBE_VERTEX_RADII, TUBE_SEGMENT_RADII }; + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a tube complex (a collection of capsules defined by endpoint coordinates and segment indices). +/// +/// @param vertices Endpoint vertices in the tube complex in world units. +/// @param segments Segment indices in the tube complex. +/// @param radius Radius of all tubes in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +typename GridType::Ptr +createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, + float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + InterruptT* interrupter = nullptr); + +/// @brief Return a grid of type @c GridType containing a narrow-band level set +/// representation of a tube complex (a collection of tubes defined by endpoint coordinates, segment indices, and radii). +/// +/// @param vertices Endpoint vertices in the tube complex in world units. +/// @param segments Segment indices in the tube complex. +/// @param radii Radii specification for all tubes in world units. +/// @param voxelSize Voxel size in world units. +/// @param halfWidth Half the width of the narrow band, in voxel units. +/// @param TubeRadiiPolicy Policies: per-segment, per-vertex, or automatic (default). +/// @param interrupter Interrupter adhering to the util::NullInterrupter interface. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The automatic @c TubeRadiiPolicy chooses the valid per-segment or per-vertex policy, defaulting to per-vertex if both are valid. +template +typename GridType::Ptr +createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, + const std::vector& radii, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), TubeRadiiPolicy radii_policy = TUBE_AUTOMATIC, + InterruptT* interrupter = nullptr); + + +//////////////////////////////////////// + + +namespace lvlset { + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of a capsule. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +class CapsuleVoxelizer + : public ConvexVoxelizer< + GridType, + CapsuleVoxelizer, + InterruptT> +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + // ------------ base class members ------------ + + using BaseT = ConvexVoxelizer< + GridType, + CapsuleVoxelizer, + InterruptT + >; + + using BaseT::mXYData; + using BaseT::tileCeil; + +public: + + friend class ConvexVoxelizer< + GridType, + CapsuleVoxelizer, + InterruptT + >; + + /// @brief Constructor + /// + /// @param grid scalar grid to populate the level set in + /// @param threaded center of the sphere in world units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note The voxel size and half width are determined from the input grid, + /// meaning the voxel size and background value need to be set prior to voxelization + CapsuleVoxelizer(GridPtr& grid, const bool& threaded = true, + InterruptT* interrupter = nullptr) + : BaseT(grid, threaded, interrupter) + { + } + + /// @brief Create a capsule + /// + /// @param pt1 first endpoint of the capsule in world units + /// @param pt2 second endpoint of the capsule in world units + /// @param radius radius of the capsule in world units + void + operator()(const Vec3s& pt1, const Vec3s& pt2, const float& r) + { + initialize(pt1, pt2, r); + + BaseT::iterate(); + } + +private: + + // setXYRangeData inputs: + // step: step size in index space (default is 1) + // + // setXYRangeData class member inputs: + // mPt1, mPt2, mORad: a tube with points p1, p2 and radius r (padded for halfwidth) + // + // setXYRangeData class member outputs: + // mXs: list of x ordinates to scan over + // mYbs: list of bottom y ordinates to start scanning with, for each x + // mYts: list of top y ordinates to stop scanning at, for each x + // + // + // This routine projects the tube on the xy-plane, giving a stadium shape. + // It detemines the x-scan range, and the y-range for each x-value. + // The x-range is divided into intervals depending on if each y endpoint hits a circle or line. + // + // The x-range is partitioned by ordinates a0, a1, a2, a3, a4, a5 + // and based on some cases listed below, some ai will be permuted. + // + // The x-range intervals have a few flavors depending on + // * if the stadium points right-down or right-up + // * the bottom circle ends before the top circle starts, or not + // + // 2D projection of capsule onto xy-plane, giving a stadium: + // + // ∧ y ******** + // | *** *** + // | * * + // | / | * + // | / | * + // | / | * + // | / | p2 * + // | / | / \ r * + // | / | / \ *| + // | / | / \ *| + // | / |/ * | + // | / /| / | | + // | / / | / | | + // | / / | / | | + // | * / | / | | + // | *| / | / | | + // | *| / | / | | + // | * | / | / | | + // | * | p1 | | | + // | * | / | | | + // | |*| / | | | + // | |*| / | | | + // | | | * | | | + // | | |*** *** | | | | + // | | | ******** | | | | + // | | | | | | | + // | / \ | | / \ + // | a0 a1 a2 a3 a4 a5 + // | x + // └-----------------------------------------------------------> + // + // In this schematic, we have a0 < a1 < a2 < a3 < a4 < a5, + // but, for examples, it's possible for a2 and a3 to swap if the stadium is more vertical + + inline void + setXYRangeData(const Index& step = 1) override + { + // short circuit a vertical cylinder + if (mIsVertical) { + mXYData.reset(mX1 - mORad, mX1 + mORad, step); + + for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); + return; + } + + const float v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); + + const float a0 = mX1 - mORad, + a1 = mX1 - v, + a2 = mX1 + v, + a3 = mX2 - v, + a4 = mX2 + v, + a5 = mX2 + mORad; + + const float tc0 = tileCeil(a0, step), + tc1 = tileCeil(a1, step), + tc2 = tileCeil(a2, step), + tc3 = tileCeil(a3, step), + tc4 = tileCeil(a4, step); + + mXYData.reset(a0, a5, step); + + for (float x = tc0; x <= a1; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); + + if (!math::isApproxZero(mXdiff)) { + if (mY1 > mY2) { + for (float x = tc1; x <= math::Min(a2, a3); x += step) + mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); + } else { + for (float x = tc1; x <= math::Min(a2, a3); x += step) + mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); + } + } + + if (a2 < a3) { + for (float x = tc2; x <= a3; x += step) + mXYData.expandYRange(x, lineBottom(x), lineTop(x)); + } else { + if (mY2 <= mY1) { + for (float x = tc3; x <= a2; x += step) + mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); + } else { + for (float x = tc3; x <= a2; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); + } + } + + if (!math::isApproxZero(mXdiff)) { + if (mY1 > mY2) { + for (float x = math::Max(tc2, tc3); x <= a4; x += step) + mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); + } else { + for (float x = math::Max(tc2, tc3); x <= a4; x += step) + mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); + } + } + + for (float x = tc4; x <= a5; x += step) + mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); + + mXYData.trim(); + } + + // distance in index space + inline float + signedDistance(const Vec3s& p) const + { + const Vec3s w = p - mPt1; + const float dot = w.dot(mV); + + // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr + if (dot <= math::Tolerance::value()) + return w.length() - mRad; + + if (dot >= mVLenSqr) + return (p - mPt2).length() - mRad; + + const float t = w.dot(mV)/mVLenSqr; + + return (w - t * mV).length() - mRad; + } + + inline bool + tileCanFit(const Index& dim) const override + { + return mRad >= BaseT::halfWidth() + 0.70711f * (dim-1u); + } + + // vertical capsule + // for a given x,y pair, find the z-range of a tube + // z-range is bottom sphere cap to the top sphere cap in vertical case + std::function capsuleBottomTopVertical = + [this](float& zb, float& zt, const float& x, const float& y) + { + zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y); + zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y); + + return std::isfinite(zb) && std::isfinite(zt); + }; + + // non vertical capsule + // for a given x,y pair, find the z-range of a tube + // first find the z-range as if its an infinite cylinder + // then for each z-range endpoint, determine if it should be on a sphere cap + std::function capsuleBottomTop = + [this](float& zb, float& zt, const float& x, const float& y) + { + float cylptb, cylptt; + if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y)) + return false; + + const float dotb = (Vec3s(x, y, cylptb) - mPt1).dot(mV); + const float dott = (Vec3s(x, y, cylptt) - mPt1).dot(mV); + + if (dotb < 0) + zb = sphere1Bottom(x, y); + else if (dotb > mVLenSqr) + zb = sphere2Bottom(x, y); + else + zb = cylptb; + + if (dott < 0) + zt = sphere1Top(x, y); + else if (dott > mVLenSqr) + zt = sphere2Top(x, y); + else + zt = cylptt; + + return std::isfinite(zb) && std::isfinite(zt); + }; + + // assumes capsule is not vertical! + inline bool + infiniteCylinderBottomTop(float& cylptb, float& cylptt, const float& x, const float& y) const + { + const Vec2s q(x, y); + + const Vec2s qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; + + const float t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; + + const Vec3s qproj3D = mPt1 + t * mV; + + const float d2 = (q - qproj).lengthSqr(); + + // outside of cylinder's 2D projection + if (mORad2 < d2) + return false; + + const float h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); + + cylptb = qproj3D[2] - h; + cylptt = qproj3D[2] + h; + + return true; + } + + inline float + lineBottom(const float& x) const + { + return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff; + } + + inline float + lineTop(const float& x) const + { + return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff; + } + + inline float + circle1Bottom(const float& x) const + { + return BaseT::circleBottom(mX1, mY1, mORad, x); + } + + inline float + circle1Top(const float& x) const + { + return BaseT::circleTop(mX1, mY1, mORad, x); + } + + inline float + circle2Bottom(const float& x) const + { + return BaseT::circleBottom(mX2, mY2, mORad, x); + } + + inline float + circle2Top(const float& x) const + { + return BaseT::circleTop(mX2, mY2, mORad, x); + } + + inline float + sphere1Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y); + } + + inline float + sphere1Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y); + } + + inline float + sphere2Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y); + } + + inline float + sphere2Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y); + } + + // world space points and radius inputs + // initializes class members in index space + inline void initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r) + { + const float vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); + + if (pt1[0] <= pt2[0]) { + mPt1 = pt1/vx; + mPt2 = pt2/vx; + } else { + mPt1 = pt2/vx; + mPt2 = pt1/vx; + } + + mRad = r/vx; + + // padded radius used to populate the outer halfwidth of the sdf + mORad = mRad + hw; + mORad2 = mORad * mORad; + + mV = mPt2 - mPt1; + mVLenSqr = mV.lengthSqr(); + + mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2]; + mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2]; + + mXdiff = mX2 - mX1; + mYdiff = mY2 - mY1; + mZdiff = mZ2 - mZ1; + + mPt12d = Vec2s(mX1, mY1); + mPt22d = Vec2s(mX2, mY2); + mV2d = mPt22d - mPt12d; + + mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); + mXYNorm = math::Sqrt(mXYNorm2); + mIsVertical = math::isApproxZero(mXYNorm); + + BaseT::bottomTop = mIsVertical ? capsuleBottomTopVertical : capsuleBottomTop; + } + + // ------------ private members ------------ + + // tube data -- populated via initialize() + + Vec3s mPt1, mPt2, mV; + + Vec2s mPt12d, mPt22d, mV2d; + + float mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; + + float mX1, mY1, mZ1, mX2, mY2, mZ2; + + bool mIsVertical; + +}; // class CapsuleVoxelizer + + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of a tapered capsule. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +template +class TaperedCapsuleVoxelizer + : public ConvexVoxelizer< + GridType, + TaperedCapsuleVoxelizer, + InterruptT> +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + // ------------ base class members ------------ + + using BaseT = ConvexVoxelizer< + GridType, + TaperedCapsuleVoxelizer, + InterruptT + >; + + using BaseT::mXYData; + using BaseT::tileCeil; + +public: + + friend class ConvexVoxelizer< + GridType, + TaperedCapsuleVoxelizer, + InterruptT + >; + + /// @brief Constructor + /// + /// @param grid scalar grid to populate the level set in + /// @param threaded center of the sphere in world units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note The voxel size and half width are determined from the input grid, + /// meaning the voxel size and background value need to be set prior to voxelization + TaperedCapsuleVoxelizer(GridPtr& grid, const bool& threaded = true, + InterruptT* interrupter = nullptr) + : BaseT(grid, threaded, interrupter) + { + } + + /// @brief Create a tapered capsule + /// + /// @param pt1 first endpoint of the tapered capsule in world units + /// @param pt2 second endpoint of the tapered capsule in world units + /// @param radius1 radius of the tapered capsule at @c pt1 in world units + /// @param radius2 radius of the tapered capsule at @c pt2 in world units + void + operator()(const Vec3s& pt1, const Vec3s& pt2, const float& radius1, const float& radius2) + { + // fail on degenerate inputs for now + + // ball + if ((pt1 - pt2).lengthSqr() <= math::Pow2(radius1 - radius2)) { + OPENVDB_THROW(RuntimeError, + "The tapered capsule is degenerate, in this case it is a ball. Consider using the CapsuleVoxelizer class instead."); + } + + // tube + if (math::Abs(radius1 - radius2) < 0.001f*BaseT::voxelSize()) { + OPENVDB_THROW(RuntimeError, + "The tapered capsule is degenerate, in this case it is a capsule. Consider using the CapsuleVoxelizer class instead."); + } + + initialize(pt1, pt2, radius1, radius2); + + BaseT::iterate(); + } + +private: + + inline void + setXYRangeData(const Index& step = 1) override + { + // short circuit when one circle is in the other + if (mXYNorm2 <= mRdiff2) { + if (mX1 - mORad1 <= mX2 - mORad2) { + mXYData.reset(mX1 - mORad1, mX1 + mORad1, step); + for (float x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += step) + mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); + } else { + mXYData.reset(mX2 - mORad2, mX2 + mORad2, step); + for (float x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += step) + mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); + } + return; + } + + mXYData.reset( + math::Min(mX1 - mORad1, mX2 - mORad2), + math::Max(mX1 + mORad1, mX2 + mORad2), + step + ); + + Vec2s p1t, p2t, p1b, p2b; + const bool success = pullyPoints(p1t, p2t, p1b, p2b); + + if (success) { + setLineXYData(p1t, p2t, step); + setLineXYData(p1b, p2b, step); + + setCircleXYData(p1t, p1b, step, true); // mPt1 + setCircleXYData(p2t, p2b, step, false); // mPt2 + } + + mXYData.trim(); + } + + // https://en.wikipedia.org/wiki/Belt_problem#Pulley_problem + inline bool + pullyPoints(Vec2s& p1t, Vec2s& p2t, Vec2s& p1b, Vec2s& p2b) const + { + const float diff = mXYNorm2 - mRdiff2; + if (diff < 0.0f) + return false; + + const float alpha = std::atan2(mYdiff, mXdiff), + theta = std::atan2(math::Sqrt(diff), mRdiff); + + const float sin1 = math::Sin(theta + alpha), sin2 = math::Sin(theta - alpha), + cos1 = math::Cos(theta + alpha), cos2 = math::Cos(theta - alpha); + + p1t.x() = mX1 + mORad1*cos1; p1t.y() = mY1 + mORad1*sin1; + p2t.x() = mX2 + mORad2*cos1; p2t.y() = mY2 + mORad2*sin1; + p2b.x() = mX2 + mORad2*cos2; p2b.y() = mY2 - mORad2*sin2; + p1b.x() = mX1 + mORad1*cos2; p1b.y() = mY1 - mORad1*sin2; + + return true; + } + + inline void + setLineXYData(const Vec2s& q1, const Vec2s& q2, const float& step) + { + if (math::Abs(q1.x() - q2.x()) < math::Tolerance::value()) { + float x = tileCeil(q1.x(), step); + if (q1.x() == x) { + mXYData.expandYRange(x, q1.y()); + mXYData.expandYRange(x, q2.y()); + } + } else { + const bool q1_left = q1.x() < q2.x(); + const float &x1 = q1_left ? q1.x() : q2.x(), + &y1 = q1_left ? q1.y() : q2.y(), + &x2 = q1_left ? q2.x() : q1.x(), + &y2 = q1_left ? q2.y() : q1.y(); + + float m = (y2 - y1)/(x2 - x1), + x = tileCeil(x1, step), + y = y1 + m * (x-x1), + delta = m * step; + for (; x <= x2; x += step, y += delta) + mXYData.expandYRange(x, y); + } + } + + inline void + setCircleXYData(const Vec2s& q1, const Vec2s& q2, + const float& step, const bool is_pt1) + { + const Vec3s &p1 = is_pt1 ? mPt1 : mPt2; + const float &r1 = is_pt1 ? mORad1 : mORad2; + + const std::vector xs = { + tileCeil(p1.x() - r1, step), + tileCeil(math::Min(q1.x(), q2.x()), step), + tileCeil(math::Max(q1.x(), q2.x()), step), + tileCeil(p1.x() + r1, step) + }; + + for (int i = 0; i < xs.size()-1; ++i) { + setCircleHiXYData(xs[i], xs[i+1], step, is_pt1); + setCircleLoXYData(xs[i], xs[i+1], step, is_pt1); + } + } + + inline void + setCircleHiXYData(const float& x1, const float& x2, + const float& step, const bool& is_pt1) + { + const float x_test = static_cast(math::Floor(0.5f*(x1+x2))); + + if (is_pt1) { + // if |x2-x1| is small, our test point might be too close to the pulley point + if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle1Top(x_test)) { + for (float x = x1; x < x2; x += step) + mXYData.expandYMax(x, circle1Top(x)); + } + } else { + if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle2Top(x_test)) { + for (float x = x1; x < x2; x += step) + mXYData.expandYMax(x, circle2Top(x)); + } + } + } + + inline void + setCircleLoXYData(const float& x1, const float& x2, + const float& step, const bool& is_pt1) + { + const float x_test = static_cast(math::Floor(0.5f*(x1+x2))); + + if (is_pt1) { + // if |x2-x1| is small, our test point might be too close to the pulley point + if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle1Bottom(x_test)) { + for (float x = x1; x < x2; x += step) + mXYData.expandYMin(x, circle1Bottom(x)); + } + } else { + if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle2Bottom(x_test)) { + for (float x = x1; x < x2; x += step) + mXYData.expandYMin(x, circle2Bottom(x)); + } + } + } + + // Round Cone: https://iquilezles.org/articles/distfunctions/ + // distance in index space + inline float + signedDistance(const Vec3s& p) const + { + const Vec3s w = p - mPt1; + const ValueT y = w.dot(mV), + z = y - mVLenSqr, + x2 = (w*mVLenSqr - mV*y).lengthSqr(), + y2 = y*y*mVLenSqr, + z2 = z*z*mVLenSqr, + k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive + + if (math::Sign(z)*mA2*z2 >= k) + return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2; + + if (math::Sign(y)*mA2*y2 <= k) + return math::Sqrt(x2 + y2)*mInvVLenSqr - mRad1; + + return (math::Sqrt(x2*mA2*mInvVLenSqr) + y*mRdiff)*mInvVLenSqr - mRad1; + } + + inline bool + tileCanFit(const Index& dim) const override + { + return math::Max(mRad1, mRad2) >= BaseT::halfWidth() + 0.70711f * (dim-1u); + } + + std::function TaperedCapsuleBottomTop = + [this](float& zb, float& zt, const float& x, const float& y) + { + const Vec2s q(x, y); + + const bool in_ball1 = (q - mPt12d).lengthSqr() <= mORad1Sqr, + in_ball2 = (q - mPt22d).lengthSqr() <= mORad2Sqr; + + if (in_ball1) { + zt = sphere1Top(x, y); + zb = 2.0f*mZ1 - zt; + } + + if (in_ball2) { + if (in_ball1) { + const float zt2 = sphere2Top(x, y), + zb2 = 2.0f*mZ2 - zt2; + + zt = math::Max(zt, zt2); + zb = math::Min(zb, zb2); + } else { + zt = sphere2Top(x, y); + zb = 2.0f*mZ2 - zt; + } + } + + // attempt to short circuit when top and bottom hits are on sphere caps + if (in_ball1 || in_ball2) { + const float ht = mConeD.dot(Vec3s(x,y,zt) - mConeV); + // top point is in one of the half spaces pointing away from the cone + if (mH1 > ht || ht > mH2) { + const float hb = mConeD.dot(Vec3s(x,y,zb) - mConeV); + // bottom point is in one of the half spaces pointing away from the cone + if (mH1 > hb || hb > mH2) + return true; + } + } + + float conezb, conezt; + int cint_cnt; + openConeFrustumBottomTop(conezb, conezt, cint_cnt, x, y); + + if (in_ball1 && in_ball2) { + if (cint_cnt == 2) { + zb = math::Min(zb, conezb); + zt = math::Max(zt, conezt); + } else if (cint_cnt == 1) { + zb = math::Min(zb, conezb); + zt = math::Max(zt, conezb); + } + + // cint_cnt == 0 implies zb and zt are already set correctly + return true; + } + + if (cint_cnt == 2 || (!in_ball1 && !in_ball2)) { + zb = conezb; zt = conezt; + return cint_cnt == 2; + } + + // we know at this point that in_ball1 ^ in_ball2 + + // zt and zb have been assigned values already based on the ball their in + if (cint_cnt == 0) + return true; + + // cint_cnt == 1 and we're only in one ball + + zt = math::Max(zt, conezb); + zb = math::Min(zb, conezb); + + return true; + }; + + // https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf + inline void + openConeFrustumBottomTop(float& conezb, float& conezt, int& cint_cnt, + const float& x, const float& y) const + { + cint_cnt = 0; + const Vec3d p(x, y, mRayZ); + const Vec3d diff = p - mConeV; + + const double ddotdiff = mConeD.dot(diff); + + const double c1 = mGamma * diff.z() - mConeD.z() * ddotdiff; + const double c0 = ddotdiff * ddotdiff - mGamma * diff.lengthSqr(); + + if (mC2 != 0.0f) { + const double delta = c1*c1 - c0*mC2; + if (delta >= 0.0f) { + const double sqrt = math::Sqrt(delta); + const double t1 = mC2Inv*(-c1 + sqrt); + if (validFrustumRange(t1, ddotdiff)) { + cint_cnt++; + conezb = mRayZ - t1; + } + const double t2 = mC2Inv*(-c1 - sqrt); + if (validFrustumRange(t2, ddotdiff)) { + cint_cnt++; + if (cint_cnt == 2 && t1 > t2) + conezt = mRayZ - t2; + else { + conezt = conezb; + conezb = mRayZ - t2; + } + } + } + } else if (c1 != 0.0f) { + const double t = -c0/(2.0f*c1); + if (validFrustumRange(t, ddotdiff)) { + cint_cnt = 1; + conezb = mRayZ - t; + } + } + + // ignore the c2 == c1 == 0 case, where the ray is on the boundary of the cone + } + + inline bool + validFrustumRange(const double& t, const double& ddotdiff) const + { + const double h = ddotdiff - t * mConeD.z(); + + return mH1 <= h && h <= mH2; + } + + inline float + circle1Bottom(const float& x) const + { + return BaseT::circleBottom(mX1, mY1, mORad1, x); + } + + inline float + circle1Top(const float& x) const + { + return BaseT::circleTop(mX1, mY1, mORad1, x); + } + + inline float + circle2Bottom(const float& x) const + { + return BaseT::circleBottom(mX2, mY2, mORad2, x); + } + + inline float + circle2Top(const float& x) const + { + return BaseT::circleTop(mX2, mY2, mORad2, x); + } + + inline float + sphere1Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX1, mY1, mZ1, mORad1, x, y); + } + + inline float + sphere1Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX1, mY1, mZ1, mORad1, x, y); + } + + inline float + sphere2Bottom(const float& x, const float& y) const + { + return BaseT::sphereBottom(mX2, mY2, mZ2, mORad2, x, y); + } + + inline float + sphere2Top(const float& x, const float& y) const + { + return BaseT::sphereTop(mX2, mY2, mZ2, mORad2, x, y); + } + + // world space points and radius inputs + // initializes class members in index space + inline void + initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r1, const float& r2) + { + const float vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); + + // enforce mRad1 > mRad2 + if (r2 <= r1) { + mPt1 = pt1/vx; + mPt2 = pt2/vx; + mRad1 = r1/vx; + mRad2 = r2/vx; + } else { + mPt1 = pt2/vx; + mPt2 = pt1/vx; + mRad1 = r2/vx; + mRad2 = r1/vx; + } + + // padded radii used to populate the outer halfwidth of the sdf + mORad1 = mRad1 + hw; + mORad2 = mRad2 + hw; + mORad1Sqr = mORad1 * mORad1; + mORad2Sqr = mORad2 * mORad2; + + mV = mPt2 - mPt1; + mVLenSqr = mV.lengthSqr(); + mInvVLenSqr = mVLenSqr != 0.0f ? 1.0f/mVLenSqr : 1.0f; + + mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2]; + mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2]; + + mXdiff = mX2 - mX1; + mYdiff = mY2 - mY1; + mZdiff = mZ2 - mZ1; + + mPt12d = Vec2s(mX1, mY1); + mPt22d = Vec2s(mX2, mY2); + mV2d = mPt22d - mPt12d; + + mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); + mXYNorm = math::Sqrt(mXYNorm2); + mIXYNorm2 = mXYNorm2 != 0.0f ? 1.0f/mXYNorm2 : 1.0f; + + // mRdiff is non negative + mRdiff = mRad1 - mRad2; + mRdiff2 = mRdiff * mRdiff; + mA2 = mVLenSqr - mRdiff2; + + // we assume + // alpha is solid angle of cone + // r1 != r2, since the object is not a capsule + // P > abs(r1-r2), since one ball is not contained in the other + const double P = mV.length(), + csc = P/mRdiff, // csc(alpha/2) + sin = mRdiff/P; // sin(alpha/2) + mGamma = 1.0 - mRdiff2/(P*P); // cos(alpha/2)^2 + mH1 = mORad2*(csc-sin); + mH2 = mORad1*(csc-sin); + + mConeD = -((Vec3d)mV).unitSafe(); + mConeV = (Vec3d)mPt1 - (double)mORad1 * csc * mConeD; + + mRayZ = math::Max(mZ1 + mORad1, mZ2 + mORad2) + 2.0; + mC2 = math::Pow2(mConeD.z()) - mGamma; + mC2Inv = mC2 != 0.0 ? 1.0/mC2 : 1.0; + + BaseT::bottomTop = TaperedCapsuleBottomTop; + } + + // ------------ private members ------------ + + // tapered capsule data -- populated via initialize() + + Vec3s mPt1, mPt2, mV; + + Vec3d mConeV, mConeD; + + Vec2s mPt12d, mPt22d, mV2d; + + float mORad1, mORad2, mORad1Sqr, mORad2Sqr, mRad1, mRad2, mVLenSqr, mInvVLenSqr, + mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2, mIXYNorm2, mRdiff, mRdiff2, mA2; + + double mRayZ, mGamma, mC2, mC2Inv, mH1, mH2; + + float mX1, mY1, mZ1, mX2, mY2, mZ2; + +}; // class TaperedCapsuleVoxelizer + + +/// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set +/// representation of a tube complex. +/// +/// @note @c GridType::ValueType must be a floating-point scalar. +/// @note Setting @c PerSegmentRadii to @c true gives a complex of capsules and a complex of tapered capsules otherwise. +template +class TubeComplexVoxelizer { + + using GridPtr = typename GridType::Ptr; + using TreeT = typename GridType::TreeType; + using LeafT = typename TreeT::LeafNodeType; + + using PartitionerT = tools::PointPartitioner; + +public: + + /// @brief Constructor for constant radius + /// + /// @param vertices endpoint vertices in the tube complex in world units + /// @param segments segment indices in the tube complex + /// @param radius radius of all tubes in world units + /// @param voxelSize voxel size in world units + /// @param background background value in voxel units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, + float radius, float voxelSize, float background, InterruptT* interrupter) + : mVox(voxelSize), mBg(background) + , mCoords(vertices), mCells(segments) + , mRad(radius), mRadii(mEmptyVector) + , mInterrupter(interrupter) + { + initializeGrid(); + initializePartitioner(); + } + + /// @brief Constructor for varying radii + /// + /// @param vertices endpoint vertices in the tube complex in world units + /// @param segments segment indices in the tube complex + /// @param radii radii specification for all tubes in world units + /// @param voxelSize voxel size in world units + /// @param background background value in voxel units + /// @param interrupter pointer to optional interrupter. Use template + /// argument util::NullInterrupter if no interruption is desired. + /// + /// @note If @c PerSegmentRadii is set to @c true then @c segments and @c radii must have + /// the same size. If @c PerSegmentRadii is set to @c false then @c vertices and @c radii + /// must have the same size. + TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, + const std::vector& radii, float voxelSize, float background, + InterruptT* interrupter) + : mVox(voxelSize), mBg(background) + , mCoords(vertices), mCells(segments) + , mRadii(radii), mRad(0.0) + , mInterrupter(interrupter) + { + if constexpr (PerSegmentRadii) { + if (mCells.size() != mRadii.size()) + OPENVDB_THROW(RuntimeError, + "TubeComplexVoxelizer needs the same number of segments and radii"); + } else { + if (mCoords.size() != mRadii.size()) + OPENVDB_THROW(RuntimeError, + "TubeComplexVoxelizer needs the same number of coordinates and radii"); + } + + initializeGrid(); + initializePartitioner(); + } + + TubeComplexVoxelizer(TubeComplexVoxelizer& other, tbb::split) + : mVox(other.mVox), mBg(other.mBg) + , mCoords(other.mCoords), mCells(other.mCells) + , mRadii(other.mRadii), mRad(other.mRad) + , mPtPartitioner(other.mPtPartitioner), mInterrupter(other.mInterrupter) + { + initializeGrid(); + } + + template + inline typename std::enable_if_t + operator()(const tbb::blocked_range& rng) + { + if (!checkInterrupter()) + return; + + if (mRadii.size() == 0) + constantRadiusVoxelize(rng); + else + perSegmentRadiusVoxelize(rng); + } + + template + inline typename std::enable_if_t + operator()(const tbb::blocked_range& rng) + { + if (!checkInterrupter()) + return; + + if (mRadii.size() == 0) + constantRadiusVoxelize(rng); + else + perVertexRadiusVoxelize(rng); + } + + void join(TubeComplexVoxelizer& other) + { + tools::CsgUnionOp op(other.mGrid->tree(), Steal()); + tree::DynamicNodeManager nodeManager(mGrid->tree()); + nodeManager.foreachTopDown(op, true); + + other.mGrid = nullptr; + } + + inline Index bucketSize() const { return mPtPartitioner->size(); } + + inline GridPtr getGrid() const { return mGrid; } + +private: + + /// TODO increase performance by not creating parts of caps that overlap with other tubes: + /// + /// * Determine segment adjacency + /// * Create _open_ cylinders and conical frustums + /// * Create _open_ & _partial_ caps + /// + /// This should help speed up creation of complexes that contain + /// a bunch of short segments that approximate a smooth curve. + /// + /// Idea is similar to _open_ prisms and _open_ wedges speeding up + /// creation of thickened mesh level sets from finely triangulated meshes. + + inline void + constantRadiusVoxelize(const tbb::blocked_range& rng) + { + CapsuleVoxelizer voxelizer(mGrid, false); + + for (size_t i = rng.begin(); i < rng.end(); ++i) { + for (auto it = mPtPartitioner->indices(i); it; ++it) { + const Index k = *it; + const Vec2I& cell = mCells[k]; + voxelizer(mCoords[cell[0]], mCoords[cell[1]], mRad); + } + } + } + + inline void + perSegmentRadiusVoxelize(const tbb::blocked_range& rng) + { + CapsuleVoxelizer voxelizer(mGrid, false); + + for (size_t i = rng.begin(); i < rng.end(); ++i) { + for (auto it = mPtPartitioner->indices(i); it; ++it) { + const Index k = *it; + const Vec2I& cell = mCells[k]; + voxelizer(mCoords[cell[0]], mCoords[cell[1]], mRadii[k]); + } + } + } + + inline void + perVertexRadiusVoxelize(const tbb::blocked_range& rng) + { + TaperedCapsuleVoxelizer rc_voxelizer(mGrid, false); + + CapsuleVoxelizer c_voxelizer(mGrid, false); + + for (size_t i = rng.begin(); i < rng.end(); ++i) { + for (auto it = mPtPartitioner->indices(i); it; ++it) { + const Index k = *it; + const Vec2I& cell = mCells[k]; + const Index32 &i = cell.x(), &j = cell.y(); + + const Vec3s &pt1 = mCoords[i], &pt2 = mCoords[j]; + const float &r1 = mRadii[i], &r2 = mRadii[j]; + + if ((pt1 - pt2).lengthSqr() <= math::Pow2(r1-r2)) { // ball + if (r1 >= r2) + c_voxelizer(pt1, pt1, r1); + else + c_voxelizer(pt2, pt2, r2); + } else if (math::Abs(r1-r2) < 0.001f*mVox) { // tube + c_voxelizer(pt1, pt2, r1); + } else { + rc_voxelizer(pt1, pt2, r1, r2); + } + } + } + } + + inline void + computeCentroids(std::vector& centroids) + { + const Index n = mCoords.size(); + + centroids.resize(mCells.size()); + + tbb::parallel_for(tbb::blocked_range(0, centroids.size()), + [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + const Vec2I &cell = mCells[i]; + + if (cell[0] >= n || cell[1] >= n) + OPENVDB_THROW(ValueError, "out of bounds index"); + + centroids[i] = 0.5f * (mCoords[cell[0]] + mCoords[cell[1]]); + } + }); + } + + inline void + initializeGrid() + { + math::Transform transform(*(math::Transform::createLinearTransform(mVox))); + mGrid = GridPtr(new GridType(mBg)); + mGrid->setTransform(transform.copy()); + mGrid->setGridClass(GRID_LEVEL_SET); + } + + inline void + initializePartitioner() + { + std::vector centroids; + computeCentroids(centroids); + + lvlset::PointArray points(centroids); + + mPtPartitioner = std::make_shared(); + mPtPartitioner->construct(points, mGrid->transform()); + } + + inline bool + checkInterrupter() + { + if (util::wasInterrupted(mInterrupter)) { + openvdb::thread::cancelGroupExecution(); + return false; + } + return true; + } + + // ------------ private members ------------ + + const std::vector& mCoords; + const std::vector& mCells; + const std::vector& mRadii; + + inline static const std::vector mEmptyVector = {}; + + const float mVox, mBg, mRad; + + std::shared_ptr mPtPartitioner; + + GridPtr mGrid; + + InterruptT* mInterrupter; + +}; // class TubeComplexVoxelizer + +} // namespace lvlset + + +// ------------ createLevelSetTubeComplex ------------- // + +// constant radius + +template +typename GridType::Ptr +createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, + float radius, float voxelSize, float halfWidth, InterruptT* interrupter) +{ + using GridPtr = typename GridType::Ptr; + using TreeT = typename GridType::TreeType; + using ValueT = typename GridType::ValueType; + + using ComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + + static_assert(std::is_floating_point::value, + "createLevelSetTubeComplex must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + const float background = voxelSize * halfWidth; + ComplexVoxelizer op(vertices, segments, radius, voxelSize, background, interrupter); + + const tbb::blocked_range segmentRange(0, op.bucketSize()); + tbb::parallel_reduce(segmentRange, op); + + GridPtr tubegrid = op.getGrid(); + tools::pruneLevelSet(tubegrid->tree()); + + return tubegrid; +} + +// varying radii + +template +typename GridType::Ptr +createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, + const std::vector& radii, float voxelSize, float halfWidth, + TubeRadiiPolicy radii_policy, InterruptT* interrupter) +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + using CapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + using TaperedCapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + + static_assert(std::is_floating_point::value, + "createLevelSetTubeComplex must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + switch(radii_policy) { + case TUBE_AUTOMATIC : { + if (vertices.size() != radii.size() && segments.size() != radii.size()) + OPENVDB_THROW(ValueError, + "createLevelSetTubeComplex requires equal number of vertices and radii, or segments and radii, with automatic radii policy."); + break; + } + case TUBE_VERTEX_RADII : { + if (vertices.size() != radii.size()) + OPENVDB_THROW(ValueError, + "createLevelSetTubeComplex requires equal number of vertices and radii with per-vertex radii policy."); + break; + } + case TUBE_SEGMENT_RADII : { + if (segments.size() != radii.size()) + OPENVDB_THROW(ValueError, + "createLevelSetTubeComplex requires equal number of segments and radii with per-segment radii policy."); + break; + } + default: + OPENVDB_THROW(ValueError, "Invalid tube radii policy."); + } + + const float background = voxelSize * halfWidth; + GridPtr tubegrid; + + if (vertices.size() == radii.size()) { + TaperedCapsuleComplexVoxelizer op(vertices, segments, radii, + voxelSize, background, interrupter); + + const tbb::blocked_range segmentRange(0, op.bucketSize()); + tbb::parallel_reduce(segmentRange, op); + + tubegrid = op.getGrid(); + } else { + CapsuleComplexVoxelizer op(vertices, segments, radii, + voxelSize, background, interrupter); + + const tbb::blocked_range segmentRange(0, op.bucketSize()); + tbb::parallel_reduce(segmentRange, op); + + tubegrid = op.getGrid(); + } + + tools::pruneLevelSet(tubegrid->tree()); + + return tubegrid; +} + + +// ------------ createLevelSetCapsule ------------- // + +template +typename GridType::Ptr +createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, + float halfWidth, InterruptT* interrupter, bool threaded) +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + using CapsuleVoxelizer = typename lvlset::CapsuleVoxelizer; + + static_assert(std::is_floating_point::value, + "createLevelSetCapsule must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + const ValueT background = voxelSize * halfWidth; + math::Transform transform(*(math::Transform::createLinearTransform(voxelSize))); + + GridPtr grid(new GridType(background)); + grid->setTransform(transform.copy()); + grid->setGridClass(GRID_LEVEL_SET); + + CapsuleVoxelizer voxelizer(grid, threaded, interrupter); + voxelizer(pt1, pt2, radius); + + return grid; +} + +template +typename GridType::Ptr +createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, + float halfWidth, bool threaded) +{ + return createLevelSetCapsule( + pt1, pt2, radius, voxelSize, halfWidth, nullptr, threaded); +} + + +// ------------ createLevelSetTaperedCapsule ------------- // + +template +typename GridType::Ptr +createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, + float voxelSize, float halfWidth, InterruptT* interrupter, bool threaded) +{ + using GridPtr = typename GridType::Ptr; + using ValueT = typename GridType::ValueType; + + using CapsuleVoxelizer = typename lvlset::CapsuleVoxelizer; + using TaperedCapsuleVoxelizer = typename lvlset::TaperedCapsuleVoxelizer; + + static_assert(std::is_floating_point::value, + "createLevelSetTaperedCapsule must return a scalar grid"); + + if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); + if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); + + const ValueT background = voxelSize * halfWidth; + math::Transform transform(*(math::Transform::createLinearTransform(voxelSize))); + + GridPtr grid(new GridType(background)); + grid->setTransform(transform.copy()); + grid->setGridClass(GRID_LEVEL_SET); + + if ((pt1 - pt2).lengthSqr() <= math::Pow2(radius1 - radius2)) { // ball + + CapsuleVoxelizer voxelizer(grid, threaded, interrupter); + if (radius1 >= radius2) + voxelizer(pt1, pt1, radius1); + else + voxelizer(pt2, pt2, radius2); + + } else if (math::Abs(radius1 - radius2) < 0.001f*voxelSize) { // tube + + CapsuleVoxelizer voxelizer(grid, threaded, interrupter); + voxelizer(pt1, pt2, radius1); + + } else { // tapered capsule + + TaperedCapsuleVoxelizer voxelizer(grid, threaded, interrupter); + voxelizer(pt1, pt2, radius1, radius2); + } + + return grid; +} + +template +typename GridType::Ptr +createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, + float voxelSize, float halfWidth, bool threaded) +{ + return createLevelSetTaperedCapsule( + pt1, pt2, radius1, radius2, voxelSize, halfWidth, nullptr, threaded); +} + + +//////////////////////////////////////// + + +// Explicit Template Instantiation + +#ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION + +#ifdef OPENVDB_INSTANTIATE_LEVELSETTUBES +#include +#endif + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetTubeComplex>(const std::vector&, \ + const std::vector&, float, float, float, util::NullInterrupter*) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetTubeComplex>(const std::vector&, \ + const std::vector&, const std::vector&, float, float, TubeRadiiPolicy, \ + util::NullInterrupter*) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetCapsule>(const Vec3s&, const Vec3s&, \ + float, float, float, util::NullInterrupter*, bool) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#define _FUNCTION(TreeT) \ + Grid::Ptr createLevelSetTaperedCapsule>(const Vec3s&, const Vec3s&, \ + float, float, float, float, util::NullInterrupter*, bool) +OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) +#undef _FUNCTION + +#endif // OPENVDB_USE_EXPLICIT_INSTANTIATION + +} // namespace tools +} // namespace OPENVDB_VERSION_NAME +} // namespace openvdb + +#endif // OPENVDB_TOOLS_LEVELSETTUBES_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/unittest/TestTools.cc b/openvdb/openvdb/unittest/TestTools.cc index 07105cf888..ed961d7836 100644 --- a/openvdb/openvdb/unittest/TestTools.cc +++ b/openvdb/openvdb/unittest/TestTools.cc @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include #include #include @@ -217,6 +219,126 @@ TEST_F(TestTools, testLevelSetPlatonic) }// testLevelSetPlatonic +TEST_F(TestTools, testLevelSetThickenedMesh) +{ + using namespace openvdb; + + const float r = 2.9f; + const Vec3s p0(15.8f, 13.2f, 16.7f), p1(4.3f, 7.9f, -4.8f); + const Vec3s p2(-3.0f, -7.4f, 8.9f), p3(-2.7f, 8.9f, 30.4f); + const Vec3s p4(23.0f, 17.4f, -10.9f), p5(5.2f, -5.7f, 29.0f); + const Vec3s p6(-14.6f, 3.7f, 10.9f), p7(35.8f, 23.4f, 5.8f); + + const std::vector vertices({p0, p1, p2, p3, p4, p5, p6, p7}); + const std::vector triangles1({Vec3I(0, 1, 2), Vec3I(0, 1, 3), Vec3I(0, 1, 4)}); + const std::vector triangles2({Vec3I(0, 1, 4)}); + const std::vector quads1({Vec4I(0, 1, 2, 5), Vec4I(0, 1, 6, 3), Vec4I(0, 1, 4, 7)}); + const std::vector quads2({Vec4I(0, 1, 4, 7)}); + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + {// test thickened triangle mesh + FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + vertices, triangles1, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(30, 0, -50)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + {// test thickened quad mesh + FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + vertices, quads1, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(30, 0, -50)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + {// test thickened mixed triangle & quad mesh + FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + vertices, triangles2, quads2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(30, 0, -50)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + +}// testLevelSetThickenedMesh + +TEST_F(TestTools, testLevelSetTubes) +{ + using namespace openvdb; + + const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f); + const Vec3s p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); + + const std::vector vertices({p1, p2, p3, p4}); + const std::vector segments({Vec2I(0, 1), Vec2I(0, 2), Vec2I(0, 3)}); + const std::vector radii({r1, r2, r3, r4}); + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + {// test capsule + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r1, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + {// test tapered capsule + FloatGrid::Ptr ls = tools::createLevelSetTaperedCapsule( + p1, p2, r1, r2, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + {// test tube complex with constant radius + FloatGrid::Ptr ls = tools::createLevelSetTubeComplex( + vertices, segments, r3, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + {// test tube complex with variable radius + FloatGrid::Ptr ls = tools::createLevelSetTubeComplex( + vertices, segments, radii, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + +}// testLevelSetTubes + TEST_F(TestTools, testLevelSetAdvect) { // Uncomment sections below to run this (time-consuming) test From 89746ad051e05cde698c7a124a542025aad78412 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Sun, 13 Oct 2024 23:14:23 -0400 Subject: [PATCH 02/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 45 ++++++++++--------- openvdb/openvdb/tools/LevelSetThickenedMesh.h | 37 +++++++-------- openvdb/openvdb/tools/LevelSetTubes.h | 42 +++++++++-------- 3 files changed, 66 insertions(+), 58 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 885800b64b..ec9137cf87 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -110,9 +110,10 @@ struct PointArray /// inline void /// setXYRangeData(const Index& step = 1) override /// { +/// const float stepf = float(step); /// mXYData.reset(mX - mORad, mX + mORad, step); /// -/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) +/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += stepf) /// mXYData.expandYRange(x, BaseT::circleBottom(x), BaseT::circleTop(x)); /// } /// @@ -195,6 +196,8 @@ class ConvexVoxelizer { } + virtual ~ConvexVoxelizer() = default; + /// @brief Return the voxel size of the grid. inline float voxelSize() const { return mVox; } @@ -266,13 +269,13 @@ class ConvexVoxelizer /// can't fit then @c iterate will not try to populate the level set with background /// tiles of this dimension. /// @return true if the tile can possibly fit; otherwise false. - virtual inline bool tileCanFit(const Index& dim) const { return true; } + virtual inline bool tileCanFit(const Index&) const { return true; } // distance in index space /// @brief Computes the signed distance from a point to the convex region in index space. /// /// @param p The point in 3D space for which to compute the signed distance. - inline float signedDistance(const Vec3s& p) const { return 0.0f; } + inline float signedDistance(const Vec3s&) const { return 0.0f; } /// @brief Computes the signed distance for tiles in index space, /// considering the center of the tile. @@ -301,7 +304,7 @@ class ConvexVoxelizer /// @note The derived class can override this lambda to implement different behavior for degenerate cases. /// This function is called many times, so a lambda is used to avoid virtual table overhead. std::function bottomTop = - [this](float& zb, float& zt, const float& x, const float& y) { return false; }; + [this](float&, float&, const float&, const float&) { return false; }; // ------------ utilities ------------ @@ -770,11 +773,11 @@ class ConvexVoxelizer { // borrowing parallel logic from tools/LevelSetSphere.h - const int n = mXYData.size(); + const Index n = mXYData.size(); if (mSerial) { CacheLastLeafAccessor acc(mTree); - for (int i = 0; i < n; ++i) { + for (Index i = 0; i < n; ++i) { if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) return; @@ -783,14 +786,14 @@ class ConvexVoxelizer } else { tbb::enumerable_thread_specific pool(mTree); - auto kernel = [&](const tbb::blocked_range& rng) { + auto kernel = [&](const tbb::blocked_range& rng) { TreeT &tree = pool.local(); CacheLastLeafAccessor acc(tree); if (!checkInterrupter()) return; - for (int i = rng.begin(); i != rng.end(); ++i) { + for (Index i = rng.begin(); i != rng.end(); ++i) { if constexpr (LeapFrog) iterateNoTilesYZ(i, acc); else @@ -798,7 +801,7 @@ class ConvexVoxelizer } }; - tbb::parallel_for(tbb::blocked_range(0, n, 128), kernel); + tbb::parallel_for(tbb::blocked_range(Index(0), n, Index(128)), kernel); using RangeT = tbb::blocked_range::iterator>; struct Op { const bool mDelete; @@ -821,7 +824,7 @@ class ConvexVoxelizer // for each x ordinate and y-scan range // find the z-range for each y and then populate the grid with distance values template - inline void iterateYZ(const int& i, CacheLastLeafAccessor& acc) + inline void iterateYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range float x, yb, yt; @@ -836,14 +839,14 @@ class ConvexVoxelizer if (!bottomTop(zb, zt, x, y)) continue; - Coord ijk(x, y, 0); + Coord ijk(Int32(x), Int32(y), Int32(0)); Vec3s p(x, y, 0.0f); ijk[2] = voxelCeil(zb)-1; acc.reset(ijk); for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { - ijk[2] = z; + ijk[2] = Int32(z); const float val = float(acc.template getValue<1>(ijk)); if (val == mNegBgF) { @@ -868,7 +871,7 @@ class ConvexVoxelizer // for a given x value, create a hollow slice of the object by only populating active voxels // for each x ordinate and y-scan range // find the z-range for each y and then populate the grid with distance values - inline void iterateNoTilesYZ(const int& i, CacheLastLeafAccessor& acc) + inline void iterateNoTilesYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range float x, yb, yt; @@ -883,7 +886,7 @@ class ConvexVoxelizer if (!bottomTop(zb, zt, x, y)) continue; - Coord ijk(x, y, 0); + Coord ijk(Int32(x), Int32(y), Int32(0)); Vec3s p(x, y, 0.0f); bool early_break = false; @@ -892,7 +895,7 @@ class ConvexVoxelizer ijk[2] = voxelCeil(zb)-1; acc.reset(ijk); for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { - ijk[2] = z; + ijk[2] = Int32(z); p[2] = z; const float dist = mVox * sDist(p); @@ -910,7 +913,7 @@ class ConvexVoxelizer ijk[2] = voxelFloor(zt)+1; acc.reset(ijk); for (float z = voxelFloor(zt); z > z_stop; --z) { - ijk[2] = z; + ijk[2] = Int32(z); p[2] = z; const float dist = mVox * sDist(p); @@ -930,7 +933,7 @@ class ConvexVoxelizer void tileIterateXYZ() { AccessorT acc(mTree); - for (int i = 0; i < mXYData.size(); ++i) { + for (Index i = 0; i < mXYData.size(); ++i) { if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) return; @@ -939,7 +942,7 @@ class ConvexVoxelizer } template - inline void tileIterateYZ(const int& i, AccessorT& acc) + inline void tileIterateYZ(const Index& i, AccessorT& acc) { // initialize x value and y-range float x, yb, yt; @@ -956,13 +959,13 @@ class ConvexVoxelizer if (!bottomTop(zb, zt, x, y)) continue; - Coord ijk(x, y, 0); + Coord ijk(Int32(x), Int32(y), Int32(0)); Vec3s p(x, y, 0.0f); bool tiles_added = false; float z = tileCeil(zb, TILESIZE) - 2*TILESIZE; while (z <= tileFloor(zt, TILESIZE) + TILESIZE) { - ijk[2] = z; + ijk[2] = Int32(z); p[2] = z; if (leapFrogToNextTile(ijk, z, acc)) @@ -978,7 +981,7 @@ class ConvexVoxelizer inline bool leapFrogToNextTile(const Coord& ijk, float& z, AccessorT& acc) const { static const int offset = NodeT::DIM; - static const Index nodeDepth = TreeT::DEPTH - NodeT::LEVEL - 1; + static const int nodeDepth = int(TreeT::DEPTH - NodeT::LEVEL - 1); // we have not encountered an already populated tile if (acc.getValue(ijk) != mNegBg) { diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h index 7cf06b0135..3984890170 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -162,8 +162,6 @@ class OpenTriangularPrismVoxelizer const float &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(), &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x(); - const float &y1 = mPts[0].y(), &y2 = mPts[1].y(), &y3 = mPts[2].y(); - const float xmin = math::Min(x1, x2, x3, x4, x5, x6); const float xmax = math::Max(x1, x2, x3, x4, x5, x6); mXYData.reset(xmin, xmax, step); @@ -195,10 +193,11 @@ class OpenTriangularPrismVoxelizer if (tileCeil(x1, step) == tileCeil(x2, step)) return; - const float x_start = tileCeil(math::Min(x1, x2), step); - const float x_end = math::Max(x1, x2); + const float x_start = tileCeil(math::Min(x1, x2), step), + x_end = math::Max(x1, x2), + stepf = float(step); - for (float x = x_start; x <= x_end; x += step) { + for (float x = x_start; x <= x_end; x += stepf) { if constexpr (MinMax <= 0) mXYData.expandYMin(x, line2D(x)); if constexpr (MinMax >= 0) @@ -463,11 +462,13 @@ class OpenTubeWedgeVoxelizer inline void setXYRangeData(const Index& step = 1) override { + const float stepf = float(step); + // short circuit a vertical cylinder if (mIsVertical) { mXYData.reset(mX1 - mORad, mX1 + mORad, step); - for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += step) + for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); intersectWithXYWedgeLines(); @@ -491,43 +492,43 @@ class OpenTubeWedgeVoxelizer mXYData.reset(a0, a5, step); - for (float x = tc0; x <= a1; x += step) + for (float x = tc0; x <= a1; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = tc1; x <= math::Min(a2, a3); x += step) + for (float x = tc1; x <= math::Min(a2, a3); x += stepf) mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); } else { - for (float x = tc1; x <= math::Min(a2, a3); x += step) + for (float x = tc1; x <= math::Min(a2, a3); x += stepf) mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); } } if (a2 < a3) { - for (float x = tc2; x <= a3; x += step) + for (float x = tc2; x <= a3; x += stepf) mXYData.expandYRange(x, lineBottom(x), lineTop(x)); } else { if (mY2 <= mY1) { - for (float x = tc3; x <= a2; x += step) + for (float x = tc3; x <= a2; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); } else { - for (float x = tc3; x <= a2; x += step) + for (float x = tc3; x <= a2; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); } } if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = math::Max(tc2, tc3); x <= a4; x += step) + for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); } else { - for (float x = math::Max(tc2, tc3); x <= a4; x += step) + for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); } } - for (float x = tc4; x <= a5; x += step) + for (float x = tc4; x <= a5; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); intersectWithXYStrip(); @@ -542,7 +543,7 @@ class OpenTubeWedgeVoxelizer return; const Vec3s &pp1 = mPlanePts[0], &pp2 = mPlanePts[1]; - const float &vx = mV.x(), &vy = mV.y(), &vz = mV.z(); + const float &vx = mV.x(), &vy = mV.y(); Vec2s n = Vec2s(-vy, vx).unitSafe(); Vec3s cvec = mORad * Vec3s(-vy, vx, 0.0f).unitSafe(); @@ -553,8 +554,8 @@ class OpenTubeWedgeVoxelizer const Vec3s cpmin(mPt1 - cvec), cpmax(mPt1 + cvec); if (math::isApproxZero(mXdiff)) { - const float px = mPt1.x(), py = mPt1.y(); - const float xmin = math::Min(px, pp1.x(), pp2.x()), + const float px = mPt1.x(), + xmin = math::Min(px, pp1.x(), pp2.x()), xmax = math::Max(px, pp1.x(), pp2.x()); if (!inWedge(cpmin)) diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index daea10eb78..9ab2fa3b32 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -283,8 +283,8 @@ class CapsuleVoxelizer // | | |*** *** | | | | // | | | ******** | | | | // | | | | | | | - // | / \ | | / \ - // | a0 a1 a2 a3 a4 a5 + // | | | | | | | + // | a0 a1 a2 a3 a4 a5 // | x // └-----------------------------------------------------------> // @@ -294,11 +294,13 @@ class CapsuleVoxelizer inline void setXYRangeData(const Index& step = 1) override { + const float stepf = float(step); + // short circuit a vertical cylinder if (mIsVertical) { mXYData.reset(mX1 - mORad, mX1 + mORad, step); - for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += step) + for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); return; } @@ -320,43 +322,43 @@ class CapsuleVoxelizer mXYData.reset(a0, a5, step); - for (float x = tc0; x <= a1; x += step) + for (float x = tc0; x <= a1; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = tc1; x <= math::Min(a2, a3); x += step) + for (float x = tc1; x <= math::Min(a2, a3); x += stepf) mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); } else { - for (float x = tc1; x <= math::Min(a2, a3); x += step) + for (float x = tc1; x <= math::Min(a2, a3); x += stepf) mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); } } if (a2 < a3) { - for (float x = tc2; x <= a3; x += step) + for (float x = tc2; x <= a3; x += stepf) mXYData.expandYRange(x, lineBottom(x), lineTop(x)); } else { if (mY2 <= mY1) { - for (float x = tc3; x <= a2; x += step) + for (float x = tc3; x <= a2; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); } else { - for (float x = tc3; x <= a2; x += step) + for (float x = tc3; x <= a2; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); } } if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = math::Max(tc2, tc3); x <= a4; x += step) + for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); } else { - for (float x = math::Max(tc2, tc3); x <= a4; x += step) + for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); } } - for (float x = tc4; x <= a5; x += step) + for (float x = tc4; x <= a5; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); mXYData.trim(); @@ -656,15 +658,17 @@ class TaperedCapsuleVoxelizer inline void setXYRangeData(const Index& step = 1) override { + const float stepf = float(step); + // short circuit when one circle is in the other if (mXYNorm2 <= mRdiff2) { if (mX1 - mORad1 <= mX2 - mORad2) { mXYData.reset(mX1 - mORad1, mX1 + mORad1, step); - for (float x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += step) + for (float x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += stepf) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); } else { mXYData.reset(mX2 - mORad2, mX2 + mORad2, step); - for (float x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += step) + for (float x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += stepf) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); } return; @@ -680,11 +684,11 @@ class TaperedCapsuleVoxelizer const bool success = pullyPoints(p1t, p2t, p1b, p2b); if (success) { - setLineXYData(p1t, p2t, step); - setLineXYData(p1b, p2b, step); + setLineXYData(p1t, p2t, stepf); + setLineXYData(p1b, p2b, stepf); - setCircleXYData(p1t, p1b, step, true); // mPt1 - setCircleXYData(p2t, p2b, step, false); // mPt2 + setCircleXYData(p1t, p1b, stepf, true); // mPt1 + setCircleXYData(p2t, p2b, stepf, false); // mPt2 } mXYData.trim(); @@ -751,7 +755,7 @@ class TaperedCapsuleVoxelizer tileCeil(p1.x() + r1, step) }; - for (int i = 0; i < xs.size()-1; ++i) { + for (int i = 0; i < int(xs.size()-1); ++i) { setCircleHiXYData(xs[i], xs[i+1], step, is_pt1); setCircleLoXYData(xs[i], xs[i+1], step, is_pt1); } From 98c1fd3f176b974c285a071e3ba95734140559f8 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 09:53:28 -0400 Subject: [PATCH 03/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 76 +++++++++++++----- openvdb/openvdb/tools/LevelSetThickenedMesh.h | 80 +++++++++++-------- openvdb/openvdb/tools/LevelSetTubes.h | 62 +++++++------- 3 files changed, 132 insertions(+), 86 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index ec9137cf87..ded5c2ab5e 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -185,12 +185,12 @@ class ConvexVoxelizer /// meaning the voxel size and background value need to be set prior to voxelization ConvexVoxelizer(GridPtr& grid, const bool& threaded = false, InterruptType* interrupter = nullptr) : mTree(grid->tree()) - , mVox((grid->voxelSize())[0]) - , mBg(grid->background()) - , mNegBg(-(grid->background())) + , mVox(float((grid->voxelSize())[0])) , mBgF(float(grid->background())) , mNegBgF(float(-(grid->background()))) , mHw(float(grid->background())/float((grid->voxelSize())[0])) + , mBg(grid->background()) + , mNegBg(-(grid->background())) , mSerial(!threaded) , mInterrupter(interrupter) { @@ -304,7 +304,7 @@ class ConvexVoxelizer /// @note The derived class can override this lambda to implement different behavior for degenerate cases. /// This function is called many times, so a lambda is used to avoid virtual table overhead. std::function bottomTop = - [this](float&, float&, const float&, const float&) { return false; }; + [](float&, float&, const float&, const float&) { return false; }; // ------------ utilities ------------ @@ -313,29 +313,59 @@ class ConvexVoxelizer /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. inline static float - tileCeil(const float& x, const Index& step) + tileCeil(const float& x, const float& step) { - const float s = static_cast(step); - const float offset = 0.5f * (s - 1.0f); + const float offset = 0.5f * (step - 1.0f); - return step == 1 + return step == 1.0f ? static_cast(math::Ceil(perturbDown(x))) - : s * static_cast(math::Ceil(perturbDown((x - offset)/s))) + offset; + : step * static_cast(math::Ceil(perturbDown((x - offset)/step))) + offset; } - /// @brief Rounds an input scalar down to the nearest valid ordinate of tile of a specified size. + /// @brief Rounds an input scalar up to the nearest valid ordinate of tile of a specified size. + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param x Input value. /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. + template inline static float - tileFloor(const float& x, const Index& step) + tileCeil(const float& x, const T& step) { + static_assert(std::is_integral::value, "Index must be an integral type"); + const float s = static_cast(step); - const float offset = 0.5f * (s - 1.0f); - return step == 1 + return tileCeil(x, s); + } + + /// @brief Rounds an input scalar down to the nearest valid ordinate of tile of a specified size. + /// @param x Input value. + /// @param step Tile step size. + /// @return The ceiling of the value based on the tile size. + inline static float + tileFloor(const float& x, const float& step) + { + const float offset = 0.5f * (step - 1.0f); + + return step == 1.0f ? static_cast(math::Floor(perturbUp(x))) - : s * static_cast(math::Floor(perturbUp((x - offset)/s))) + offset; + : step * static_cast(math::Floor(perturbUp((x - offset)/step))) + offset; + } + + /// @brief Rounds an input scalar down to the nearest valid ordinate of tile of a specified size. + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) + /// @param x Input value. + /// @param step Tile step size. + /// @return The ceiling of the value based on the tile size. + template + inline static float + tileFloor(const float& x, const T& step) + { + static_assert(std::is_integral::value, "Index must be an integral type"); + + const float s = static_cast(step); + + return tileFloor(x, s); } /// @brief Computes the bottom y-coordinate of a circle at a given x position. @@ -679,13 +709,13 @@ class ConvexVoxelizer inline Index indexDistance(const ValueType& a, const ValueType& b) { - return math::Round(mStepInv*math::Abs(a - b)); + return Index(math::Round(mStepInv*math::Abs(a - b))); } inline Index worldToIndex(const ValueType& x) const { - const Index i = math::Round(mStepInv*(x - mXStart)); + const Index i = Index(math::Round(mStepInv*(x - mXStart))); assert(i < mSize); return i; @@ -696,7 +726,7 @@ class ConvexVoxelizer { assert(i < mSize); - return mXStart + i*mStep; + return mXStart + ValueType(i)*mStep; } Index mStep, mSize; @@ -842,7 +872,7 @@ class ConvexVoxelizer Coord ijk(Int32(x), Int32(y), Int32(0)); Vec3s p(x, y, 0.0f); - ijk[2] = voxelCeil(zb)-1; + ijk[2] = Int32(voxelCeil(zb))-1; acc.reset(ijk); for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { @@ -892,7 +922,7 @@ class ConvexVoxelizer bool early_break = false; float z_stop; - ijk[2] = voxelCeil(zb)-1; + ijk[2] = Int32(voxelCeil(zb))-1; acc.reset(ijk); for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { ijk[2] = Int32(z); @@ -910,7 +940,7 @@ class ConvexVoxelizer } } if (early_break) { - ijk[2] = voxelFloor(zt)+1; + ijk[2] = Int32(voxelFloor(zt))+1; acc.reset(ijk); for (float z = voxelFloor(zt); z > z_stop; --z) { ijk[2] = Int32(z); @@ -1111,7 +1141,7 @@ class ConvexVoxelizer /// the internal accessor with the provided tree. /// @param tree Reference to the tree being accessed. CacheLastLeafAccessor(TreeT& tree) - : mAcc(tree) + : mAcc(tree), mTileSizes(treeTileSizes()) { } @@ -1268,6 +1298,8 @@ class ConvexVoxelizer NodeMaskType* mValueMask; Coord mOrigin; + const std::vector mTileSizes; + }; // class CacheLastLeafAccessor // ------------ private members ------------ @@ -1276,7 +1308,7 @@ class ConvexVoxelizer TreeT &mTree; - inline static const std::vector mTileSizes = treeTileSizes(); + const std::vector mTileSizes = treeTileSizes(); const float mVox, mBgF, mNegBgF, mHw; diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h index 3984890170..b5de06fc49 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -1009,11 +1009,11 @@ class TriangleMeshEdgeConnectivity { TriangleMeshEdgeConnectivity(const std::vector& coords, const std::vector& cells) : mCoords(coords), mCells(cells) { - const Index n = coords.size(); + const Index n = Index(coords.size()); mNormals.resize(cells.size()); - for (Index i = 0; i < cells.size(); i++) { + for (Index i = 0; i < cells.size(); ++i) { const Vec3I& cell = mCells[i]; Edge edges[3] = { @@ -1059,51 +1059,64 @@ class TriangleMeshEdgeConnectivity { } /// @brief Retrieves the 3D coordinate at a given index. - /// + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the vertex. /// @return The 3D coordinate as a constant reference to Vec3s. + template inline const Vec3s& - getCoord(const Index& i) const + getCoord(const T& i) const { + static_assert(std::is_integral::value, "Index must be an integral type"); + return mCoords[i]; } /// @brief Retrieves the cell (triangle) at a given index. - /// + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the cell. /// @return Constant reference to the triangle's vertex indices. + template inline const Vec3I& - getCell(const Index& i) const + getCell(const T& i) const { + static_assert(std::is_integral::value, "Index must be an integral type"); + return mCells[i]; } /// @brief Retrieves the 3D coordinates of the vertices forming a /// primitive (triangle) at a given cell index. - /// + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the cell (triangle). /// @return A vector of three Vec3s representing the coordinates of the triangle's vertices. + template inline std::vector - getPrimitive(const Index& i) const + getPrimitive(const T& i) const { + static_assert(std::is_integral::value, "Index must be an integral type"); + const Vec3I cell = mCells[i]; + return {mCoords[cell[0]], mCoords[cell[1]], mCoords[cell[2]]}; } /// @brief Retrieves the unit normal vector of a cell (triangle) at a given index. - /// + /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the cell. /// @return The normal vector of the triangle as a Vec3s. + template inline Vec3s - getNormal(const Index& i) const + getNormal(const T& i) const { + static_assert(std::is_integral::value, "Index must be an integral type"); + return mNormals[i]; } /// @brief Retrieves the total number of coordinates in the mesh. /// /// @return The number of coordinates as an Index. - inline Index + inline Index64 coordCount() const { return mCoords.size(); @@ -1112,7 +1125,7 @@ class TriangleMeshEdgeConnectivity { /// @brief Retrieves the total number of cells (triangles) in the mesh. /// /// @return The number of cells as an Index. - inline Index + inline Index64 cellCount() const { return mCells.size(); @@ -1233,9 +1246,9 @@ class ThickenedMeshVoxelizer { ThickenedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, float radius, float voxelSize, float background, InterruptT* interrupter, GridPtr grid = nullptr) - : mVox(voxelSize), mBg(background) - , mMesh(std::make_shared(MeshConnectivity(vertices, triangles))) - , mRad(radius), mInterrupter(interrupter) + : mMesh(std::make_shared(MeshConnectivity(vertices, triangles))) + , mVox(voxelSize), mBg(background), mRad(radius) + , mInterrupter(interrupter) { if (!grid) initializeGrid(); @@ -1250,9 +1263,10 @@ class ThickenedMeshVoxelizer { } ThickenedMeshVoxelizer(ThickenedMeshVoxelizer& other, tbb::split) - : mVox(other.mVox), mBg(other.mBg) - , mPtPartitioner(other.mPtPartitioner), mMesh(other.mMesh) - , mRad(other.mRad), mInterrupter(other.mInterrupter) + : mMesh(other.mMesh) + , mVox(other.mVox), mBg(other.mBg), mRad(other.mRad) + , mInterrupter(other.mInterrupter) + , mPtPartitioner(other.mPtPartitioner) { initializeGrid(); @@ -1284,9 +1298,9 @@ class ThickenedMeshVoxelizer { other.mGrid = nullptr; } - inline Index bucketSize() const { return mPtPartitioner->size(); } + inline Index64 bucketSize() const { return mPtPartitioner->size(); } - inline Index cellSize() const { return mMesh->cellCount(); } + inline Index64 cellSize() const { return mMesh->cellCount(); } inline GridPtr getGrid() const { return mGrid; } @@ -1322,7 +1336,7 @@ class ThickenedMeshVoxelizer { for (Index j = 0; j < 3; ++j) { const bool success = mMesh->getAdjacentCells(cell[j], cell[(j+1) % 3], cellIds); if (success && cellIds[0] == i) { - if (findWedgeNormals(i, j, cellIds, n1, n2)) + if (findWedgeNormals(Index(i), j, cellIds, n1, n2)) (*mWVoxelizer)(pts[j], pts[(j+1) % 3], mRad, n1, n2); } } @@ -1412,10 +1426,10 @@ class ThickenedMeshVoxelizer { { const Vec3I &cell = mMesh->getCell(cellIdx); - const Index n = cellIds.size(), - offset = cell[vIdx] + cell[(vIdx+1) % 3]; + const Index64 n = cellIds.size(); + const Index offset = cell[vIdx] + cell[(vIdx+1) % 3]; - for (Index i = 0; i < n; ++i) { + for (Index64 i = 0; i < n; ++i) { const Vec3I &cell0 = mMesh->getCell(cellIds[i]), &cell1 = mMesh->getCell(cellIds[(i+1) % n]), &cell2 = mMesh->getCell(cellIds[(i+2) % n]); @@ -1502,15 +1516,15 @@ class ThickenedMeshVoxelizer { const float mVox, mBg, mRad; - std::shared_ptr mPtPartitioner; + InterruptT* mInterrupter; GridPtr mGrid; + std::shared_ptr mPtPartitioner; + std::shared_ptr mPVoxelizer; std::shared_ptr mWVoxelizer; - InterruptT* mInterrupter; - }; // class ThickenedMeshVoxelizer } // namespace lvlset @@ -1560,12 +1574,12 @@ createLevelSetThickenedMesh( if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - const Index n = quads.size(); + const Index64 n = quads.size(); std::vector triangles(2*n); tbb::parallel_for(tbb::blocked_range(0, n), [&](const tbb::blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { + for (Index64 i = r.begin(); i != r.end(); ++i) { const Vec4I& q = quads[i]; triangles[i] = Vec3I(q.x(), q.y(), q.z()); triangles[i + n] = Vec3I(q.x(), q.z(), q.w()); @@ -1594,20 +1608,20 @@ createLevelSetThickenedMesh(const std::vector& vertices, return createLevelSetThickenedMesh(vertices, triangles, radius, voxelSize, halfWidth, interrupter); - const Index tn = triangles.size(), qn = quads.size(); - const Index qn2 = tn + qn; + const Index64 tn = triangles.size(), qn = quads.size(); + const Index64 qn2 = tn + qn; std::vector tris(tn + 2*qn); tbb::parallel_for(tbb::blocked_range(0, tn), [&](const tbb::blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { + for (Index64 i = r.begin(); i != r.end(); ++i) { tris[i] = triangles[i]; } }); tbb::parallel_for(tbb::blocked_range(0, qn), [&](const tbb::blocked_range& r) { - for (size_t i = r.begin(); i != r.end(); ++i) { + for (Index64 i = r.begin(); i != r.end(); ++i) { const Vec4I& q = quads[i]; tris[i + tn] = Vec3I(q.x(), q.y(), q.z()); tris[i + qn2] = Vec3I(q.x(), q.z(), q.w()); diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 9ab2fa3b32..d478129ef4 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -806,13 +806,13 @@ class TaperedCapsuleVoxelizer inline float signedDistance(const Vec3s& p) const { - const Vec3s w = p - mPt1; - const ValueT y = w.dot(mV), - z = y - mVLenSqr, - x2 = (w*mVLenSqr - mV*y).lengthSqr(), - y2 = y*y*mVLenSqr, - z2 = z*z*mVLenSqr, - k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive + const Vec3s w = p - mPt1; + const float y = w.dot(mV), + z = y - mVLenSqr, + x2 = (w*mVLenSqr - mV*y).lengthSqr(), + y2 = y*y*mVLenSqr, + z2 = z*z*mVLenSqr, + k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive if (math::Sign(z)*mA2*z2 >= k) return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2; @@ -857,10 +857,10 @@ class TaperedCapsuleVoxelizer // attempt to short circuit when top and bottom hits are on sphere caps if (in_ball1 || in_ball2) { - const float ht = mConeD.dot(Vec3s(x,y,zt) - mConeV); + const float ht = float(mConeD.dot(Vec3s(x,y,zt) - mConeV)); // top point is in one of the half spaces pointing away from the cone if (mH1 > ht || ht > mH2) { - const float hb = mConeD.dot(Vec3s(x,y,zb) - mConeV); + const float hb = float(mConeD.dot(Vec3s(x,y,zb) - mConeV)); // bottom point is in one of the half spaces pointing away from the cone if (mH1 > hb || hb > mH2) return true; @@ -924,16 +924,16 @@ class TaperedCapsuleVoxelizer const double t1 = mC2Inv*(-c1 + sqrt); if (validFrustumRange(t1, ddotdiff)) { cint_cnt++; - conezb = mRayZ - t1; + conezb = float(mRayZ - t1); } const double t2 = mC2Inv*(-c1 - sqrt); if (validFrustumRange(t2, ddotdiff)) { cint_cnt++; if (cint_cnt == 2 && t1 > t2) - conezt = mRayZ - t2; + conezt = float(mRayZ - t2); else { conezt = conezb; - conezb = mRayZ - t2; + conezb = float(mRayZ - t2); } } } @@ -941,7 +941,7 @@ class TaperedCapsuleVoxelizer const double t = -c0/(2.0f*c1); if (validFrustumRange(t, ddotdiff)) { cint_cnt = 1; - conezb = mRayZ - t; + conezb = float(mRayZ - t); } } @@ -1123,9 +1123,8 @@ class TubeComplexVoxelizer { /// argument util::NullInterrupter if no interruption is desired. TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, float radius, float voxelSize, float background, InterruptT* interrupter) - : mVox(voxelSize), mBg(background) - , mCoords(vertices), mCells(segments) - , mRad(radius), mRadii(mEmptyVector) + : mVox(voxelSize), mBg(background), mRad(radius) + , mCoords(vertices), mCells(segments), mRadii(getEmptyVector()) , mInterrupter(interrupter) { initializeGrid(); @@ -1148,9 +1147,8 @@ class TubeComplexVoxelizer { TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, const std::vector& radii, float voxelSize, float background, InterruptT* interrupter) - : mVox(voxelSize), mBg(background) - , mCoords(vertices), mCells(segments) - , mRadii(radii), mRad(0.0) + : mVox(voxelSize), mBg(background), mRad(0.0) + , mCoords(vertices), mCells(segments), mRadii(radii) , mInterrupter(interrupter) { if constexpr (PerSegmentRadii) { @@ -1168,9 +1166,8 @@ class TubeComplexVoxelizer { } TubeComplexVoxelizer(TubeComplexVoxelizer& other, tbb::split) - : mVox(other.mVox), mBg(other.mBg) - , mCoords(other.mCoords), mCells(other.mCells) - , mRadii(other.mRadii), mRad(other.mRad) + : mVox(other.mVox), mBg(other.mBg), mRad(other.mRad) + , mCoords(other.mCoords), mCells(other.mCells), mRadii(other.mRadii) , mPtPartitioner(other.mPtPartitioner), mInterrupter(other.mInterrupter) { initializeGrid(); @@ -1211,7 +1208,7 @@ class TubeComplexVoxelizer { other.mGrid = nullptr; } - inline Index bucketSize() const { return mPtPartitioner->size(); } + inline Index64 bucketSize() const { return mPtPartitioner->size(); } inline GridPtr getGrid() const { return mGrid; } @@ -1290,7 +1287,7 @@ class TubeComplexVoxelizer { inline void computeCentroids(std::vector& centroids) { - const Index n = mCoords.size(); + const Index n = Index(mCoords.size()); centroids.resize(mCells.size()); @@ -1338,22 +1335,26 @@ class TubeComplexVoxelizer { return true; } + static const std::vector& + getEmptyVector() { + static const std::vector empty; + return empty; + } + // ------------ private members ------------ + const float mVox, mBg, mRad; + const std::vector& mCoords; const std::vector& mCells; const std::vector& mRadii; - inline static const std::vector mEmptyVector = {}; - - const float mVox, mBg, mRad; - std::shared_ptr mPtPartitioner; - GridPtr mGrid; - InterruptT* mInterrupter; + GridPtr mGrid; + }; // class TubeComplexVoxelizer } // namespace lvlset @@ -1369,7 +1370,6 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< float radius, float voxelSize, float halfWidth, InterruptT* interrupter) { using GridPtr = typename GridType::Ptr; - using TreeT = typename GridType::TreeType; using ValueT = typename GridType::ValueType; using ComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; From ed6492c0bbcc01d91b7032423f7a3662b30ef6ef Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 12:17:19 -0400 Subject: [PATCH 04/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/math/Vec2.h | 2 +- openvdb/openvdb/math/Vec3.h | 2 +- openvdb/openvdb/tools/ConvexVoxelizer.h | 15 +++++++-------- openvdb/openvdb/tools/LevelSetThickenedMesh.h | 10 +++++----- openvdb/openvdb/tools/LevelSetTubes.h | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/openvdb/openvdb/math/Vec2.h b/openvdb/openvdb/math/Vec2.h index 73caa15243..a89b84a91d 100644 --- a/openvdb/openvdb/math/Vec2.h +++ b/openvdb/openvdb/math/Vec2.h @@ -246,7 +246,7 @@ class Vec2: public Tuple<2, T> Vec2 unitSafe() const { T l2 = lengthSqr(); - return l2 ? *this/static_cast(sqrt(l2)) : Vec2(1,0); + return l2 != T(0) ? *this/static_cast(sqrt(l2)) : Vec2(1,0); } /// Multiply each element of this vector by @a scalar. diff --git a/openvdb/openvdb/math/Vec3.h b/openvdb/openvdb/math/Vec3.h index 2ea5d32838..180ce62f9b 100644 --- a/openvdb/openvdb/math/Vec3.h +++ b/openvdb/openvdb/math/Vec3.h @@ -392,7 +392,7 @@ class Vec3: public Tuple<3, T> Vec3 unitSafe() const { T l2 = lengthSqr(); - return l2 ? *this / static_cast(sqrt(l2)) : Vec3(1, 0 ,0); + return l2 != T(0) ? *this / static_cast(sqrt(l2)) : Vec3(1, 0 ,0); } // Number of cols, rows, elements diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index ded5c2ab5e..ce79f8f3d5 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -110,10 +110,9 @@ struct PointArray /// inline void /// setXYRangeData(const Index& step = 1) override /// { -/// const float stepf = float(step); /// mXYData.reset(mX - mORad, mX + mORad, step); /// -/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += stepf) +/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) /// mXYData.expandYRange(x, BaseT::circleBottom(x), BaseT::circleTop(x)); /// } /// @@ -686,8 +685,8 @@ class ConvexVoxelizer return; mSize -= i + j; - mXStart += i * mStep; - mXEnd -= j * mStep; + mXStart += ValueType(i * mStep); + mXEnd -= ValueType(j * mStep); if (i > 0) { mYMins.erase(mYMins.begin(), mYMins.begin() + i); @@ -726,7 +725,7 @@ class ConvexVoxelizer { assert(i < mSize); - return mXStart + ValueType(i)*mStep; + return mXStart + ValueType(i * mStep); } Index mStep, mSize; @@ -1030,8 +1029,8 @@ class ConvexVoxelizer const float sz = (float)mTileSizes[depth]; z = dir > 0 - ? sz * math::Ceil(z/sz) + 0.5f * (offset - 1.0f) - : sz * math::Floor(z/sz) - 0.5f * (offset + 1.0f); + ? sz * float(math::Ceil(z/sz)) + 0.5f * (offset - 1.0f) + : sz * float(math::Floor(z/sz)) - 0.5f * (offset + 1.0f); return true; } @@ -1247,7 +1246,7 @@ class ConvexVoxelizer const int depth = mAcc.getValueDepth(ijk); const float sz = (float)mTileSizes[depth]; - z = sz*math::Ceil((z+1.0f)/sz) - 1.0f; + z = sz * float(math::Ceil((z+1.0f)/sz)) - 1.0f; } private: diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h index b5de06fc49..d8718e303b 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -240,7 +240,7 @@ class OpenTriangularPrismVoxelizer inline bool tileCanFit(const Index& dim) const override { - return mRad >= BaseT::halfWidth() + 0.5f * (dim-1u); + return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); } std::function prismBottomTop = @@ -595,9 +595,9 @@ class OpenTubeWedgeVoxelizer n1(-mDirVectors[1].y(), mDirVectors[1].x()); if (n0.dot(d) > 0.0f) - n0 *= -1; + n0 *= -1.0f; if (n1.dot(d) > 0.0f) - n1 *= -1; + n1 *= -1.0f; if (!math::isApproxZero(n0.lengthSqr())) intersectWithXYHalfSpace(n0, n0.dot(p2_2d - p1_2d) < 0.0f ? p1_2d : p2_2d); @@ -681,7 +681,7 @@ class OpenTubeWedgeVoxelizer inline bool tileCanFit(const Index& dim) const override { - return mRad >= BaseT::halfWidth() + 0.5f * (dim-1u); + return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); } std::function tubeBottomTopVertical = @@ -1192,7 +1192,7 @@ class TriangleMeshEdgeConnectivity { sgn2 = math::Sign(bi_nrml.dot(p2 - p0)); return sgn != 0 - ? (sgn == 1 ? 1.0f + sgn2 * cos_theta : 3.0f - sgn2 * cos_theta) + ? (sgn == 1 ? 1.0f + float(sgn2) * cos_theta : 3.0f - float(sgn2) * cos_theta) : (onSameHalfPlane(bi_nrml, p0, p1, p2) ? 0.0f : 2.0f); }; diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index d478129ef4..9895b38ccb 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -386,7 +386,7 @@ class CapsuleVoxelizer inline bool tileCanFit(const Index& dim) const override { - return mRad >= BaseT::halfWidth() + 0.70711f * (dim-1u); + return mRad >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); } // vertical capsule From 7173225ea22969213a6afd82c8b157045926264c Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 13:24:34 -0400 Subject: [PATCH 05/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/LevelSetThickenedMesh.h | 2 +- openvdb/openvdb/tools/LevelSetTubes.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h index d8718e303b..745ae8fdb3 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -224,7 +224,7 @@ class OpenTriangularPrismVoxelizer const float udist = math::Sign(mBAXNrml.dot(pa)) + math::Sign(mCBXNrml.dot(pb)) + - math::Sign(mACXNrml.dot(pc)) < 2.0f + math::Sign(mACXNrml.dot(pc)) < 2 ? math::Sqrt(math::Min( (mBA * math::Clamp01(mBANorm2.dot(pa)) - pa).lengthSqr(), diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 9895b38ccb..bbd6340865 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -814,10 +814,10 @@ class TaperedCapsuleVoxelizer z2 = z*z*mVLenSqr, k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive - if (math::Sign(z)*mA2*z2 >= k) + if (float(math::Sign(z))*mA2*z2 >= k) return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2; - if (math::Sign(y)*mA2*y2 <= k) + if (float(math::Sign(y))*mA2*y2 <= k) return math::Sqrt(x2 + y2)*mInvVLenSqr - mRad1; return (math::Sqrt(x2*mA2*mInvVLenSqr) + y*mRdiff)*mInvVLenSqr - mRad1; @@ -826,7 +826,7 @@ class TaperedCapsuleVoxelizer inline bool tileCanFit(const Index& dim) const override { - return math::Max(mRad1, mRad2) >= BaseT::halfWidth() + 0.70711f * (dim-1u); + return math::Max(mRad1, mRad2) >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); } std::function TaperedCapsuleBottomTop = From a058051a268cb64249fa480002a59162954d219b Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 15:32:40 -0400 Subject: [PATCH 06/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 32 +++++++++++-------------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index ce79f8f3d5..775d1e94bd 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -783,12 +783,11 @@ class ConvexVoxelizer tileIterateXYZ(); } - iterateTile(); + if constexpr (!std::is_same_v>) { + iterateTile(); + } } - template <> - inline void iterateTile>() {} - inline void iterateLeaf() { setXYRangeData(); @@ -1109,12 +1108,11 @@ class ConvexVoxelizer sizes.push_back(NodeT::DIM); - doTreeTileSizes(sizes); + if constexpr (!std::is_same_v>) { + doTreeTileSizes(sizes); + } } - template <> - static inline void doTreeTileSizes>(std::vector&) {} - inline bool checkInterrupter() { if (util::wasInterrupted(mInterrupter)) { @@ -1258,18 +1256,16 @@ class ConvexVoxelizer template bool atNewLeafPos(const Coord& ijk) const { - return Coord::lessThan(ijk, mOrigin) - || Coord::lessThan(mOrigin.offsetBy(DIM-1u), ijk); + if constexpr (ZDir == -1) { + return (ijk[2] & (DIM-1u)) == DIM-1u; + } else if constexpr (ZDir == 1) { + return (ijk[2] & (DIM-1u)) == 0; + } else { + return Coord::lessThan(ijk, mOrigin) + || Coord::lessThan(mOrigin.offsetBy(DIM-1u), ijk); + } } - // assumes value just above has been cached already! - template<> - inline bool atNewLeafPos<-1>(const Coord& ijk) const { return (ijk[2] & (DIM-1u)) == DIM-1u; } - - // assumes value just below has been cached already! - template<> - inline bool atNewLeafPos<1>(const Coord& ijk) const { return (ijk[2] & (DIM-1u)) == 0; } - inline void cacheNewLeafData(const Coord& ijk) { mLeaf = mAcc.probeLeaf(ijk); From 70543241edcde67efd5d2944d8fde987257f6486 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:34:19 -0400 Subject: [PATCH 07/19] CI Linux Warnings Addresses CI Linux compilation warnings Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 2 ++ openvdb/openvdb/tools/LevelSetTubes.h | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 775d1e94bd..fd76110aae 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -1257,8 +1257,10 @@ class ConvexVoxelizer bool atNewLeafPos(const Coord& ijk) const { if constexpr (ZDir == -1) { + // assumes value just above has been cached already! return (ijk[2] & (DIM-1u)) == DIM-1u; } else if constexpr (ZDir == 1) { + // assumes value just below has been cached already! return (ijk[2] & (DIM-1u)) == 0; } else { return Coord::lessThan(ijk, mOrigin) diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index bbd6340865..1d942c36a2 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -867,7 +867,7 @@ class TaperedCapsuleVoxelizer } } - float conezb, conezt; + float conezb = 0.0f, conezt = 0.0f; int cint_cnt; openConeFrustumBottomTop(conezb, conezt, cint_cnt, x, y); From b575c85666c8ee85b68d938a7e7b456a535c0477 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:15:38 -0400 Subject: [PATCH 08/19] Update ConvexVoxelizer.h Adhere to OpenVDB style guideline "The return type in a function definition should go on a line by itself." Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 42 ++++++++++++++++--------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index fd76110aae..1d71f49138 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -788,7 +788,8 @@ class ConvexVoxelizer } } - inline void iterateLeaf() + inline void + iterateLeaf() { setXYRangeData(); @@ -797,7 +798,8 @@ class ConvexVoxelizer } template - void iterateXYZ() + void + iterateXYZ() { // borrowing parallel logic from tools/LevelSetSphere.h @@ -852,7 +854,8 @@ class ConvexVoxelizer // for each x ordinate and y-scan range // find the z-range for each y and then populate the grid with distance values template - inline void iterateYZ(const Index& i, CacheLastLeafAccessor& acc) + inline void + iterateYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range float x, yb, yt; @@ -899,7 +902,8 @@ class ConvexVoxelizer // for a given x value, create a hollow slice of the object by only populating active voxels // for each x ordinate and y-scan range // find the z-range for each y and then populate the grid with distance values - inline void iterateNoTilesYZ(const Index& i, CacheLastLeafAccessor& acc) + inline void + iterateNoTilesYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range float x, yb, yt; @@ -958,7 +962,8 @@ class ConvexVoxelizer } template - void tileIterateXYZ() + void + tileIterateXYZ() { AccessorT acc(mTree); for (Index i = 0; i < mXYData.size(); ++i) { @@ -970,7 +975,8 @@ class ConvexVoxelizer } template - inline void tileIterateYZ(const Index& i, AccessorT& acc) + inline void + tileIterateYZ(const Index& i, AccessorT& acc) { // initialize x value and y-range float x, yb, yt; @@ -1006,7 +1012,8 @@ class ConvexVoxelizer } template - inline bool leapFrogToNextTile(const Coord& ijk, float& z, AccessorT& acc) const + inline bool + leapFrogToNextTile(const Coord& ijk, float& z, AccessorT& acc) const { static const int offset = NodeT::DIM; static const int nodeDepth = int(TreeT::DEPTH - NodeT::LEVEL - 1); @@ -1036,7 +1043,8 @@ class ConvexVoxelizer // add negative background tile inside the object if it fits and return true iff it was added template - inline bool addTile(const Vec3s& p, const Coord& ijk, AccessorT& acc) + inline bool + addTile(const Vec3s& p, const Coord& ijk, AccessorT& acc) { static const Index LEVEL = NodeT::LEVEL + 1; @@ -1049,7 +1057,8 @@ class ConvexVoxelizer } template - inline bool tileFits(const Vec3s& p) const + inline bool + tileFits(const Vec3s& p) const { static const Index TILESIZE = NodeT::DIM; @@ -1077,19 +1086,22 @@ class ConvexVoxelizer && tpDist(p + Vec3s(R1, R1, R1)) < -mHw; } - inline float sDist(const Vec3s& p) const + inline float + sDist(const Vec3s& p) const { return static_cast(this)->signedDistance(p); } - inline float tpDist(const Vec3s& p) const + inline float + tpDist(const Vec3s& p) const { return static_cast(this)->tilePointSignedDistance(p); } // misc - static inline std::vector treeTileSizes() + static inline std::vector + treeTileSizes() { // iterate over all non root nodes using ChainT = typename NodeChainT::PopBack; @@ -1101,7 +1113,8 @@ class ConvexVoxelizer } template - static inline void doTreeTileSizes(std::vector& sizes) + static inline void + doTreeTileSizes(std::vector& sizes) { using NodeT = typename ChainT::Back; using PoppedChainT = typename ChainT::PopBack; @@ -1113,7 +1126,8 @@ class ConvexVoxelizer } } - inline bool checkInterrupter() + inline bool + checkInterrupter() { if (util::wasInterrupted(mInterrupter)) { openvdb::thread::cancelGroupExecution(); From af3c72b3143b112925d1806beab1a7d750757cef Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Wed, 16 Oct 2024 22:45:37 -0400 Subject: [PATCH 09/19] Update ConvexVoxelizer.h Fixes some typos Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 1d71f49138..53cc8cc63f 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -89,7 +89,8 @@ struct PointArray /// { /// } /// -/// void operator()(const Vec3s& pt, const float& r) +/// void +/// operator()(const Vec3s& pt, const float& r) /// { /// if (r <= 0.0f) /// return; @@ -509,7 +510,7 @@ class ConvexVoxelizer } /// @brief Set the minimum y value for a given x value, - /// even if its larger than the current value. + /// even if it is larger than the current value. /// @param x The x value. /// @param ymin The minimum y value to reset. inline void @@ -521,7 +522,7 @@ class ConvexVoxelizer } /// @brief Set the maximum y value for a given x value, - /// even if its larger than the current value. + /// even if it is smaller than the current value. /// @param x The x value. /// @param ymax The maximum y value to reset. inline void @@ -554,7 +555,7 @@ class ConvexVoxelizer assert(step != 0); mStep = step; - mStepInv = ValueType(1)/static_cast(mStep); + mStepInv = ValueType(1)/static_cast(mStep); mXStart = tileCeil(xmin, mStep); mXEnd = tileFloor(xmax, mStep); From b6c9d77b7cb87ff157ea9d6b4445eeb09d1642bb Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:17:32 -0400 Subject: [PATCH 10/19] Correct SPDX-License-Identifier Correct SPDX-License-Identifier Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 2 +- openvdb/openvdb/tools/LevelSetThickenedMesh.h | 2 +- openvdb/openvdb/tools/LevelSetTubes.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 53cc8cc63f..8f17311518 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenVDB Project -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: Apache-2.0 /// /// @author Greg Hurst /// diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetThickenedMesh.h index 745ae8fdb3..3095ccba65 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetThickenedMesh.h @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenVDB Project -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: Apache-2.0 /// /// @author Greg Hurst /// diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 1d942c36a2..2f643d6aa7 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -1,5 +1,5 @@ // Copyright Contributors to the OpenVDB Project -// SPDX-License-Identifier: MPL-2.0 +// SPDX-License-Identifier: Apache-2.0 /// /// @author Greg Hurst /// From ca62d3ed42fb1747bc8f8d9037bf7494cdd609b8 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:03:48 -0400 Subject: [PATCH 11/19] thick -> dilate Changes createLevelSetThickenedMesh to createLevelSetDilatedMesh. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/CMakeLists.txt | 2 +- openvdb/openvdb/tools/ConvexVoxelizer.h | 5 +- ...tThickenedMesh.h => LevelSetDilatedMesh.h} | 85 ++++++++++--------- openvdb/openvdb/tools/LevelSetTubes.h | 2 +- openvdb/openvdb/unittest/TestTools.cc | 18 ++-- 5 files changed, 61 insertions(+), 51 deletions(-) rename openvdb/openvdb/tools/{LevelSetThickenedMesh.h => LevelSetDilatedMesh.h} (94%) diff --git a/openvdb/openvdb/CMakeLists.txt b/openvdb/openvdb/CMakeLists.txt index 5367c798c2..9d0e25f25b 100644 --- a/openvdb/openvdb/CMakeLists.txt +++ b/openvdb/openvdb/CMakeLists.txt @@ -485,6 +485,7 @@ set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES tools/GridTransformer.h tools/Interpolation.h tools/LevelSetAdvect.h + tools/LevelSetDilatedMesh.h tools/LevelSetFilter.h tools/LevelSetFracture.h tools/LevelSetMeasure.h @@ -492,7 +493,6 @@ set(OPENVDB_LIBRARY_TOOLS_INCLUDE_FILES tools/LevelSetPlatonic.h tools/LevelSetRebuild.h tools/LevelSetSphere.h - tools/LevelSetThickenedMesh.h tools/LevelSetTracker.h tools/LevelSetTubes.h tools/LevelSetUtil.h diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 8f17311518..8215096a92 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -114,7 +114,8 @@ struct PointArray /// mXYData.reset(mX - mORad, mX + mORad, step); /// /// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) -/// mXYData.expandYRange(x, BaseT::circleBottom(x), BaseT::circleTop(x)); +/// mXYData.expandYRange(x, BaseT::circleBottom(mX, mY, mORad, x), +/// BaseT::circleTop(mX, mY, mORad, x)); /// } /// /// std::function sphereBottomTop = @@ -283,7 +284,7 @@ class ConvexVoxelizer /// /// @param p The point at the center of the tile in 3D space. /// @note This can be useful for cases that build objects from multiple primitives, e.g. - /// thickened mesh is built by constructing and unioning _open_ prisms and _open_ tube wedges. + /// dilated mesh is built by constructing and unioning _open_ prisms and _open_ tube wedges. /// A tile might not fully fit in an open prism but might fit in the union of a prism and wedge, /// and so in this case it might make sense to use the sdf for an offset triangle on tiles /// during the open prism scan. diff --git a/openvdb/openvdb/tools/LevelSetThickenedMesh.h b/openvdb/openvdb/tools/LevelSetDilatedMesh.h similarity index 94% rename from openvdb/openvdb/tools/LevelSetThickenedMesh.h rename to openvdb/openvdb/tools/LevelSetDilatedMesh.h index 3095ccba65..ce9f1f4803 100644 --- a/openvdb/openvdb/tools/LevelSetThickenedMesh.h +++ b/openvdb/openvdb/tools/LevelSetDilatedMesh.h @@ -3,16 +3,16 @@ /// /// @author Greg Hurst /// -/// @file LevelSetThickenedMesh.h +/// @file LevelSetDilatedMesh.h /// -/// @brief Generate a narrow-band level set of a thickened mesh. +/// @brief Generate a narrow-band level set of a dilated surface mesh. /// /// @note By definition a level set has a fixed narrow band width /// (the half width is defined by LEVEL_SET_HALF_WIDTH in Types.h), /// whereas an SDF can have a variable narrow band width. -#ifndef OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED -#define OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED +#ifndef OPENVDB_TOOLS_LEVELSETDILATEDMESH_HAS_BEEN_INCLUDED +#define OPENVDB_TOOLS_LEVELSETDILATEDMESH_HAS_BEEN_INCLUDED #include "ConvexVoxelizer.h" #include "LevelSetTubes.h" @@ -34,56 +34,65 @@ namespace OPENVDB_VERSION_NAME { namespace tools { /// @brief Return a grid of type @c GridType containing a narrow-band level set -/// representation of a thickened triangle mesh (thickened by a radius in all directions). +/// representation of a dilated triangle surface mesh (dilated by a radius in all directions). /// /// @param vertices Vertices of the mesh in world units. /// @param triangles Triangle indices of the mesh. -/// @param radius Radius of the sphere in world units. +/// @param radius Dilation radius in world units. /// @param voxelSize Voxel size in world units. /// @param halfWidth Half the width of the narrow band, in voxel units. /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. +/// This includes meshes that could represent valid BRep solids, dilation occurs both +/// inward and outward, forming a 'shell' rather than only expanding outward. template typename GridType::Ptr -createLevelSetThickenedMesh( +createLevelSetDilatedMesh( const std::vector& vertices, const std::vector& triangles, float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); /// @brief Return a grid of type @c GridType containing a narrow-band level set -/// representation of a thickened quad mesh (thickened by a radius in all directions). +/// representation of a dilated quad surface mesh (dilated by a radius in all directions). /// /// @param vertices Vertices of the mesh in world units. /// @param quads Quad indices of the mesh. -/// @param radius Radius of the sphere in world units. +/// @param radius Dilation radius in world units. /// @param voxelSize Voxel size in world units. /// @param halfWidth Half the width of the narrow band, in voxel units. /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. +/// This includes meshes that could represent valid BRep solids, dilation occurs both +/// inward and outward, forming a 'shell' rather than only expanding outward. template typename GridType::Ptr -createLevelSetThickenedMesh( +createLevelSetDilatedMesh( const std::vector& vertices, const std::vector& quads, float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); /// @brief Return a grid of type @c GridType containing a narrow-band level set -/// representation of a thickened triangle & quad mesh (thickened by a radius in all directions). +/// representation of a dilated triangle & quad surface mesh (dilated by a radius in all directions). /// /// @param vertices Vertices of the mesh in world units. /// @param triangles Triangle indices of the mesh. /// @param quads Quad indices of the mesh. -/// @param radius Radius of the sphere in world units. +/// @param radius Dilation radius in world units. /// @param voxelSize Voxel size in world units. /// @param halfWidth Half the width of the narrow band, in voxel units. /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. +/// This includes meshes that could represent valid BRep solids, dilation occurs both +/// inward and outward, forming a 'shell' rather than only expanding outward. template typename GridType::Ptr -createLevelSetThickenedMesh(const std::vector& vertices, +createLevelSetDilatedMesh(const std::vector& vertices, const std::vector& triangles, const std::vector& quads, float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); @@ -1214,11 +1223,11 @@ class TriangleMeshEdgeConnectivity { /// @brief Class used to generate a grid of type @c GridType containing a narrow-band level set -/// representation of a thckened mesh (surface mesh thickened by a radius in all directions). +/// representation of a dilated mesh (surface mesh dilated by a radius in all directions). /// /// @note @c GridType::ValueType must be a floating-point scalar. template -class ThickenedMeshVoxelizer { +class DilatedMeshVoxelizer { using GridPtr = typename GridT::Ptr; using TreeT = typename GridT::TreeType; @@ -1243,7 +1252,7 @@ class ThickenedMeshVoxelizer { /// @param interrupter pointer to optional interrupter. Use template /// argument util::NullInterrupter if no interruption is desired. /// @param grid optional grid to populate into (grid need not be empty). - ThickenedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, + DilatedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, float radius, float voxelSize, float background, InterruptT* interrupter, GridPtr grid = nullptr) : mMesh(std::make_shared(MeshConnectivity(vertices, triangles))) @@ -1262,7 +1271,7 @@ class ThickenedMeshVoxelizer { mWVoxelizer = std::make_shared(mGrid, false); } - ThickenedMeshVoxelizer(ThickenedMeshVoxelizer& other, tbb::split) + DilatedMeshVoxelizer(DilatedMeshVoxelizer& other, tbb::split) : mMesh(other.mMesh) , mVox(other.mVox), mBg(other.mBg), mRad(other.mRad) , mInterrupter(other.mInterrupter) @@ -1289,7 +1298,7 @@ class ThickenedMeshVoxelizer { } } - void join(ThickenedMeshVoxelizer& other) + void join(DilatedMeshVoxelizer& other) { tools::CsgUnionOp op(other.mGrid->tree(), Steal()); tree::DynamicNodeManager nodeManager(mGrid->tree()); @@ -1525,26 +1534,26 @@ class ThickenedMeshVoxelizer { std::shared_ptr mPVoxelizer; std::shared_ptr mWVoxelizer; -}; // class ThickenedMeshVoxelizer +}; // class DilatedMeshVoxelizer } // namespace lvlset -// ------------ createLevelSetThickenedMesh ------------- // +// ------------ createLevelSetDilatedMesh ------------- // template typename GridType::Ptr -createLevelSetThickenedMesh( +createLevelSetDilatedMesh( const std::vector& vertices, const std::vector& triangles, float radius, float voxelSize, float halfWidth, InterruptT* interrupter) { using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; - using Voxelizer = typename lvlset::ThickenedMeshVoxelizer; + using Voxelizer = typename lvlset::DilatedMeshVoxelizer; static_assert(std::is_floating_point::value, - "createLevelSetThickenedMesh must return a scalar grid"); + "createLevelSetDilatedMesh must return a scalar grid"); if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); @@ -1562,14 +1571,14 @@ createLevelSetThickenedMesh( template typename GridType::Ptr -createLevelSetThickenedMesh( +createLevelSetDilatedMesh( const std::vector& vertices, const std::vector& quads, float radius, float voxelSize, float halfWidth, InterruptT* interrupter) { using ValueT = typename GridType::ValueType; static_assert(std::is_floating_point::value, - "createLevelSetThickenedMesh must return a scalar grid"); + "createLevelSetDilatedMesh must return a scalar grid"); if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); @@ -1586,27 +1595,27 @@ createLevelSetThickenedMesh( } }); - return createLevelSetThickenedMesh(vertices, triangles, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh(vertices, triangles, radius, + voxelSize, halfWidth, interrupter); } template typename GridType::Ptr -createLevelSetThickenedMesh(const std::vector& vertices, +createLevelSetDilatedMesh(const std::vector& vertices, const std::vector& triangles, const std::vector& quads, float radius, float voxelSize, float halfWidth, InterruptT* interrupter) { using ValueT = typename GridType::ValueType; static_assert(std::is_floating_point::value, - "createLevelSetThickenedMesh must return a scalar grid"); + "createLevelSetDilatedMesh must return a scalar grid"); if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); if (quads.empty()) - return createLevelSetThickenedMesh(vertices, triangles, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh(vertices, triangles, radius, + voxelSize, halfWidth, interrupter); const Index64 tn = triangles.size(), qn = quads.size(); const Index64 qn2 = tn + qn; @@ -1628,8 +1637,8 @@ createLevelSetThickenedMesh(const std::vector& vertices, } }); - return createLevelSetThickenedMesh(vertices, tris, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh(vertices, tris, radius, + voxelSize, halfWidth, interrupter); } @@ -1640,24 +1649,24 @@ createLevelSetThickenedMesh(const std::vector& vertices, #ifdef OPENVDB_USE_EXPLICIT_INSTANTIATION -#ifdef OPENVDB_INSTANTIATE_LEVELSETTHICKENEDMESH +#ifdef OPENVDB_INSTANTIATE_LEVELSETDILATEDMESH #include #endif #define _FUNCTION(TreeT) \ - Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + Grid::Ptr createLevelSetDilatedMesh>(const std::vector&, \ const std::vector&, float, float, float, util::NullInterrupter*) OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) #undef _FUNCTION #define _FUNCTION(TreeT) \ - Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + Grid::Ptr createLevelSetDilatedMesh>(const std::vector&, \ const std::vector&, float, float, float, util::NullInterrupter*) OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) #undef _FUNCTION #define _FUNCTION(TreeT) \ - Grid::Ptr createLevelSetThickenedMesh>(const std::vector&, \ + Grid::Ptr createLevelSetDilatedMesh>(const std::vector&, \ const std::vector&, const std::vector&, float, float, float, \ util::NullInterrupter*) OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) @@ -1669,4 +1678,4 @@ OPENVDB_REAL_TREE_INSTANTIATE(_FUNCTION) } // namespace OPENVDB_VERSION_NAME } // namespace openvdb -#endif // OPENVDB_TOOLS_LEVELSETTHICKENEDMESH_HAS_BEEN_INCLUDED +#endif // OPENVDB_TOOLS_LEVELSETDILATEDMESH_HAS_BEEN_INCLUDED diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 2f643d6aa7..4d61113233 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -1224,7 +1224,7 @@ class TubeComplexVoxelizer { /// a bunch of short segments that approximate a smooth curve. /// /// Idea is similar to _open_ prisms and _open_ wedges speeding up - /// creation of thickened mesh level sets from finely triangulated meshes. + /// creation of dilated mesh level sets from finely triangulated meshes. inline void constantRadiusVoxelize(const tbb::blocked_range& rng) diff --git a/openvdb/openvdb/unittest/TestTools.cc b/openvdb/openvdb/unittest/TestTools.cc index 0c6d082701..bc47f0fb93 100644 --- a/openvdb/openvdb/unittest/TestTools.cc +++ b/openvdb/openvdb/unittest/TestTools.cc @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include #include #include @@ -219,7 +219,7 @@ TEST_F(TestTools, testLevelSetPlatonic) }// testLevelSetPlatonic -TEST_F(TestTools, testLevelSetThickenedMesh) +TEST_F(TestTools, testLevelSetDilatedMesh) { using namespace openvdb; @@ -240,8 +240,8 @@ TEST_F(TestTools, testLevelSetThickenedMesh) int(p1[1]/voxelSize), int(p1[2]/voxelSize));// inside - {// test thickened triangle mesh - FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + {// test dilated triangle mesh + FloatGrid::Ptr ls = tools::createLevelSetDilatedMesh( vertices, triangles1, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() > 0); @@ -251,8 +251,8 @@ TEST_F(TestTools, testLevelSetThickenedMesh) EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(30, 0, -50)), 1e-6); EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// test thickened quad mesh - FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + {// test dilated quad mesh + FloatGrid::Ptr ls = tools::createLevelSetDilatedMesh( vertices, quads1, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() > 0); @@ -262,8 +262,8 @@ TEST_F(TestTools, testLevelSetThickenedMesh) EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(30, 0, -50)), 1e-6); EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// test thickened mixed triangle & quad mesh - FloatGrid::Ptr ls = tools::createLevelSetThickenedMesh( + {// test dilated mixed triangle & quad mesh + FloatGrid::Ptr ls = tools::createLevelSetDilatedMesh( vertices, triangles2, quads2, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() > 0); @@ -274,7 +274,7 @@ TEST_F(TestTools, testLevelSetThickenedMesh) EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } -}// testLevelSetThickenedMesh +}// testLevelSetDilatedMesh TEST_F(TestTools, testLevelSetTubes) { From f9f3ba04d89e63bd531597a11aabe5ae88ba0276 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:51:39 -0400 Subject: [PATCH 12/19] Create level_set_constructors.txt Adds the new level set constructors to pendingchanges. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- pendingchanges/level_set_constructors.txt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pendingchanges/level_set_constructors.txt diff --git a/pendingchanges/level_set_constructors.txt b/pendingchanges/level_set_constructors.txt new file mode 100644 index 0000000000..693a3e2807 --- /dev/null +++ b/pendingchanges/level_set_constructors.txt @@ -0,0 +1,8 @@ +OpenVDB: + New Features: + - Various level set constructors. + - tools::createLevelSetCapsule creates a grid containing a narrow-band level set representation of a capsule (tube with constant radius and sphere caps). + - tools::createLevelSetTaperedCapsule creates a grid containing a narrow-band level set representation of a tapered capsule (tube with sphere caps and different radii at both ends, or equivalently the convex hull of two spheres with possibly different centers and radii). + - tools::createLevelSetTubeComplex creates a grid containing a narrow-band level set representation of a tube complex (a collection of capsules or tapered capsules defined by endpoint coordinates and segment indices). + - tools::createLevelSetDilatedMesh creates a grid containing a narrow-band level set representation of a dilated surface mesh (dilated by a radius in all directions). + - Base class tools::ConvexVoxelizer, which can be inherited to implement a convex level set constructor. From f30921583083494798fb2b5bc37ba1b0ac4504c6 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:55:33 -0400 Subject: [PATCH 13/19] ConvexVoxelizer CRTP setXYRangeData and tileCanFit are no longer virtual, making ConvexVoxelizer fully embrace CRTP. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 64 ++++++++++++--------- openvdb/openvdb/tools/LevelSetDilatedMesh.h | 8 +-- openvdb/openvdb/tools/LevelSetTubes.h | 11 ++-- 3 files changed, 47 insertions(+), 36 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 8215096a92..a7cc4cfb53 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -109,7 +109,7 @@ struct PointArray /// } /// /// inline void -/// setXYRangeData(const Index& step = 1) override +/// setXYRangeData(const Index& step = 1) /// { /// mXYData.reset(mX - mORad, mX + mORad, step); /// @@ -226,7 +226,7 @@ class ConvexVoxelizer static const Index LEAFDIM = LeafT::DIM; // objects too thin to have negative background tiles - if (!tileCanFit(LEAFDIM)) { + if (!invokeTileCanFit(LEAFDIM)) { thinIterate(); return; } @@ -255,22 +255,21 @@ class ConvexVoxelizer /// This function should store the data in @c mXYData. /// /// @param step The step size for setting the XY range data, defaults to 1. - /// @note Virtual function to be implemented by derived classes to set XY range data. + /// @note Function to be implemented by derived classes to set XY range data. /// This function is called at most 4 times within @c iterate(). - virtual void setXYRangeData(const Index& step = 1) = 0; + void setXYRangeData(const Index& step = 1) {} /// @brief Checks if the tile of a given dimension can possibly fit within the region. /// - /// This is a virtual function and can be overridden by derived classes. However, the derived - /// class does not need to implement it if the default behavior is acceptable, which assumes a - /// tile can always possibly fit. + /// The derived class does not need to implement it if the default behavior is acceptable, + /// which assumes a tile can always possibly fit. /// /// @param dim The dimension of the tile in which to check if the tile fits. /// @note This is meant as a short-circuting method: if a tile of a given dimension /// can't fit then @c iterate will not try to populate the level set with background /// tiles of this dimension. /// @return true if the tile can possibly fit; otherwise false. - virtual inline bool tileCanFit(const Index&) const { return true; } + inline bool tileCanFit(const Index&) const { return true; } // distance in index space /// @brief Computes the signed distance from a point to the convex region in index space. @@ -303,7 +302,6 @@ class ConvexVoxelizer /// @param[in] y The y ordinate of the infinte line. /// @return true if an intersection occurs; otherwise false. /// @note The derived class can override this lambda to implement different behavior for degenerate cases. - /// This function is called many times, so a lambda is used to avoid virtual table overhead. std::function bottomTop = [](float&, float&, const float&, const float&) { return false; }; @@ -764,7 +762,7 @@ class ConvexVoxelizer // skips the need for negative tile population and internal leap frogging inline void thinIterate() { - setXYRangeData(); + invokeSetXYRangeData(); // false means disable internal leap frogging iterateXYZ(); @@ -779,8 +777,8 @@ class ConvexVoxelizer static const Index DIM = NodeT::DIM; // only attempt to add negative background tiles at this level if they can fit - if (tileCanFit(DIM)) { - setXYRangeData(DIM); + if (invokeTileCanFit(DIM)) { + invokeSetXYRangeData(DIM); tileIterateXYZ(); } @@ -793,7 +791,7 @@ class ConvexVoxelizer inline void iterateLeaf() { - setXYRangeData(); + invokeSetXYRangeData(); // true means enable internal leap frogging iterateXYZ(); @@ -888,7 +886,7 @@ class ConvexVoxelizer } p[2] = z; - const float dist = mVox * sDist(p); + const float dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { acc.template setValueOff<1,false>(ijk, mNegBg); @@ -931,7 +929,7 @@ class ConvexVoxelizer for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { ijk[2] = Int32(z); p[2] = z; - const float dist = mVox * sDist(p); + const float dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { early_break = true; @@ -949,7 +947,7 @@ class ConvexVoxelizer for (float z = voxelFloor(zt); z > z_stop; --z) { ijk[2] = Int32(z); p[2] = z; - const float dist = mVox * sDist(p); + const float dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { break; @@ -1067,7 +1065,7 @@ class ConvexVoxelizer static const float R1 = 0.500f * (TILESIZE-1), R2 = 0.866f * (TILESIZE-1); - const float dist = tpDist(p); + const float dist = invokeTilePointSignedDistance(p); // fast positive criterion: circumsribed ball is in the object if (dist <= -R2-mHw) @@ -1078,24 +1076,36 @@ class ConvexVoxelizer return false; // convexity: the tile is in the object iff all corners are in the object - return tpDist(p + Vec3s(-R1, -R1, -R1)) < -mHw - && tpDist(p + Vec3s(-R1, -R1, R1)) < -mHw - && tpDist(p + Vec3s(-R1, R1, -R1)) < -mHw - && tpDist(p + Vec3s(-R1, R1, R1)) < -mHw - && tpDist(p + Vec3s(R1, -R1, -R1)) < -mHw - && tpDist(p + Vec3s(R1, -R1, R1)) < -mHw - && tpDist(p + Vec3s(R1, R1, -R1)) < -mHw - && tpDist(p + Vec3s(R1, R1, R1)) < -mHw; + return invokeTilePointSignedDistance(p + Vec3s(-R1, -R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(-R1, -R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(-R1, R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(-R1, R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(R1, -R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(R1, -R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(R1, R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3s(R1, R1, R1)) < -mHw; + } + + inline void + invokeSetXYRangeData(const Index& step = 1) + { + static_cast(this)->setXYRangeData(step); + } + + inline bool + invokeTileCanFit(const Index& dim) const + { + return static_cast(this)->tileCanFit(dim); } inline float - sDist(const Vec3s& p) const + invokeSignedDistance(const Vec3s& p) const { return static_cast(this)->signedDistance(p); } inline float - tpDist(const Vec3s& p) const + invokeTilePointSignedDistance(const Vec3s& p) const { return static_cast(this)->tilePointSignedDistance(p); } diff --git a/openvdb/openvdb/tools/LevelSetDilatedMesh.h b/openvdb/openvdb/tools/LevelSetDilatedMesh.h index ce9f1f4803..00e8c7b92c 100644 --- a/openvdb/openvdb/tools/LevelSetDilatedMesh.h +++ b/openvdb/openvdb/tools/LevelSetDilatedMesh.h @@ -166,7 +166,7 @@ class OpenTriangularPrismVoxelizer private: inline void - setXYRangeData(const Index& step = 1) override + setXYRangeData(const Index& step = 1) { const float &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(), &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x(); @@ -247,7 +247,7 @@ class OpenTriangularPrismVoxelizer } inline bool - tileCanFit(const Index& dim) const override + tileCanFit(const Index& dim) const { return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); } @@ -469,7 +469,7 @@ class OpenTubeWedgeVoxelizer // computes *approximate* xy-range data: the projected caps might contain over-inclusive values inline void - setXYRangeData(const Index& step = 1) override + setXYRangeData(const Index& step = 1) { const float stepf = float(step); @@ -688,7 +688,7 @@ class OpenTubeWedgeVoxelizer } inline bool - tileCanFit(const Index& dim) const override + tileCanFit(const Index& dim) const { return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); } diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 4d61113233..81287ddfdc 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -292,7 +292,7 @@ class CapsuleVoxelizer // but, for examples, it's possible for a2 and a3 to swap if the stadium is more vertical inline void - setXYRangeData(const Index& step = 1) override + setXYRangeData(const Index& step = 1) { const float stepf = float(step); @@ -384,7 +384,7 @@ class CapsuleVoxelizer } inline bool - tileCanFit(const Index& dim) const override + tileCanFit(const Index& dim) const { return mRad >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); } @@ -520,7 +520,8 @@ class CapsuleVoxelizer // world space points and radius inputs // initializes class members in index space - inline void initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r) + inline void + initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r) { const float vx = BaseT::voxelSize(), hw = BaseT::halfWidth(); @@ -656,7 +657,7 @@ class TaperedCapsuleVoxelizer private: inline void - setXYRangeData(const Index& step = 1) override + setXYRangeData(const Index& step = 1) { const float stepf = float(step); @@ -824,7 +825,7 @@ class TaperedCapsuleVoxelizer } inline bool - tileCanFit(const Index& dim) const override + tileCanFit(const Index& dim) const { return math::Max(mRad1, mRad2) >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); } From ece8f8a42239d92beb1152e365e11fbc7108d7af Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Thu, 31 Oct 2024 13:24:40 -0400 Subject: [PATCH 14/19] float -> ValueT ConvexVoxelizer and derived classes now work with the grid's ValueType precision instead of float. The createLevelSetXXX constructors now are templated on a scalar type for the precision of the input data. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 325 ++++----- openvdb/openvdb/tools/LevelSetDilatedMesh.h | 706 +++++++++++--------- openvdb/openvdb/tools/LevelSetTubes.h | 613 +++++++++-------- 3 files changed, 869 insertions(+), 775 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index a7cc4cfb53..683b856d05 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -75,11 +75,14 @@ struct PointArray /// class SphereVoxelizer : public ConvexVoxelizer> /// { /// using GridPtr = typename GridType::Ptr; -/// using BaseT = ConvexVoxelizer>; /// +/// using BaseT = ConvexVoxelizer>; /// using BaseT::mXYData; /// using BaseT::tileCeil; /// +/// using ValueT = typename BaseT::ValueT; +/// using Vec3T = typename BaseT::Vec3T; +/// /// public: /// /// friend class ConvexVoxelizer>; @@ -89,21 +92,24 @@ struct PointArray /// { /// } /// +/// template /// void -/// operator()(const Vec3s& pt, const float& r) +/// operator()(const math::Vec3& pt, const ScalarT& r) /// { -/// if (r <= 0.0f) +/// static_assert(std::is_floating_point::value); +/// +/// if (r <= 0) /// return; /// -/// initialize(pt, r); +/// initialize(pt, r); /// /// BaseT::iterate(); /// } /// /// private: /// -/// inline float -/// signedDistance(const Vec3s& p) const +/// inline ValueT +/// signedDistance(const Vec3T& p) const /// { /// return (p - mPt).length() - mRad; /// } @@ -113,13 +119,13 @@ struct PointArray /// { /// mXYData.reset(mX - mORad, mX + mORad, step); /// -/// for (float x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) +/// for (ValueT x = tileCeil(mX - mORad, step); x <= mX + mORad; x += step) /// mXYData.expandYRange(x, BaseT::circleBottom(mX, mY, mORad, x), /// BaseT::circleTop(mX, mY, mORad, x)); /// } /// -/// std::function sphereBottomTop = -/// [this](float& zb, float& zt, const float& x, const float& y) +/// std::function sphereBottomTop = +/// [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) /// { /// zb = BaseT::sphereBottom(mX, mY, mZ, mORad, x, y); /// zt = BaseT::sphereTop(mX, mY, mZ, mORad, x, y); @@ -127,15 +133,16 @@ struct PointArray /// return std::isfinite(zb) && std::isfinite(zt); /// }; /// +/// template /// inline void -/// initialize(const Vec3s& pt, const float& r) +/// initialize(const math::Vec3& pt, const ScalarT& r) /// { -/// const float vx = BaseT::voxelSize(), -/// hw = BaseT::halfWidth(); +/// const ValueT vx = BaseT::voxelSize(), +/// hw = BaseT::halfWidth(); /// /// // sphere data in index space -/// mPt = pt/vx; -/// mRad = r/vx; +/// mPt = Vec3T(pt)/vx; +/// mRad = ValueT(r)/vx; /// /// mX = mPt.x(); mY = mPt.y(); mZ = mPt.z(); /// @@ -145,8 +152,8 @@ struct PointArray /// BaseT::bottomTop = sphereBottomTop; /// } /// -/// Vec3s mPt; -/// float mRad, mORad, mX, mY, mZ; +/// Vec3T mPt; +/// ValueT mRad, mORad, mX, mY, mZ; /// }; /// /// // usage: @@ -163,7 +170,6 @@ template ; + using Vec2T = math::Vec2; + + static_assert(std::is_floating_point::value); + public: /// @brief Constructor @@ -186,10 +200,10 @@ class ConvexVoxelizer /// meaning the voxel size and background value need to be set prior to voxelization ConvexVoxelizer(GridPtr& grid, const bool& threaded = false, InterruptType* interrupter = nullptr) : mTree(grid->tree()) - , mVox(float((grid->voxelSize())[0])) - , mBgF(float(grid->background())) - , mNegBgF(float(-(grid->background()))) - , mHw(float(grid->background())/float((grid->voxelSize())[0])) + , mVox(ValueT((grid->voxelSize())[0])) + , mBgF(ValueT(grid->background())) + , mNegBgF(ValueT(-(grid->background()))) + , mHw(ValueT(grid->background()/(grid->voxelSize())[0])) , mBg(grid->background()) , mNegBg(-(grid->background())) , mSerial(!threaded) @@ -200,10 +214,10 @@ class ConvexVoxelizer virtual ~ConvexVoxelizer() = default; /// @brief Return the voxel size of the grid. - inline float voxelSize() const { return mVox; } + inline ValueT voxelSize() const { return mVox; } /// @brief Return the half width of the narrow-band level set. - inline float halfWidth() const { return mHw; } + inline ValueT halfWidth() const { return mHw; } private: @@ -257,7 +271,7 @@ class ConvexVoxelizer /// @param step The step size for setting the XY range data, defaults to 1. /// @note Function to be implemented by derived classes to set XY range data. /// This function is called at most 4 times within @c iterate(). - void setXYRangeData(const Index& step = 1) {} + void setXYRangeData(const Index&) {} /// @brief Checks if the tile of a given dimension can possibly fit within the region. /// @@ -275,7 +289,7 @@ class ConvexVoxelizer /// @brief Computes the signed distance from a point to the convex region in index space. /// /// @param p The point in 3D space for which to compute the signed distance. - inline float signedDistance(const Vec3s&) const { return 0.0f; } + inline ValueT signedDistance(const Vec3T&) const { return ValueT(0); } /// @brief Computes the signed distance for tiles in index space, /// considering the center of the tile. @@ -287,8 +301,8 @@ class ConvexVoxelizer /// A tile might not fully fit in an open prism but might fit in the union of a prism and wedge, /// and so in this case it might make sense to use the sdf for an offset triangle on tiles /// during the open prism scan. - inline float - tilePointSignedDistance(const Vec3s& p) const + inline ValueT + tilePointSignedDistance(const Vec3T& p) const { return static_cast(this)->signedDistance(p); } @@ -302,8 +316,8 @@ class ConvexVoxelizer /// @param[in] y The y ordinate of the infinte line. /// @return true if an intersection occurs; otherwise false. /// @note The derived class can override this lambda to implement different behavior for degenerate cases. - std::function bottomTop = - [](float&, float&, const float&, const float&) { return false; }; + std::function bottomTop = + [](ValueT&, ValueT&, const ValueT&, const ValueT&) { return false; }; // ------------ utilities ------------ @@ -311,14 +325,14 @@ class ConvexVoxelizer /// @param x Input value. /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. - inline static float - tileCeil(const float& x, const float& step) + inline static ValueT + tileCeil(const ValueT& x, const ValueT& step) { - const float offset = 0.5f * (step - 1.0f); + const ValueT offset = ValueT(0.5) * (step - ValueT(1)); - return step == 1.0f - ? static_cast(math::Ceil(perturbDown(x))) - : step * static_cast(math::Ceil(perturbDown((x - offset)/step))) + offset; + return step == ValueT(1) + ? static_cast(math::Ceil(perturbDown(x))) + : step * static_cast(math::Ceil(perturbDown((x - offset)/step))) + offset; } /// @brief Rounds an input scalar up to the nearest valid ordinate of tile of a specified size. @@ -327,12 +341,12 @@ class ConvexVoxelizer /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. template - inline static float - tileCeil(const float& x, const T& step) + inline static ValueT + tileCeil(const ValueT& x, const T& step) { static_assert(std::is_integral::value, "Index must be an integral type"); - const float s = static_cast(step); + const ValueT s = static_cast(step); return tileCeil(x, s); } @@ -341,14 +355,14 @@ class ConvexVoxelizer /// @param x Input value. /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. - inline static float - tileFloor(const float& x, const float& step) + inline static ValueT + tileFloor(const ValueT& x, const ValueT& step) { - const float offset = 0.5f * (step - 1.0f); + const ValueT offset = ValueT(0.5) * (step - ValueT(1)); - return step == 1.0f - ? static_cast(math::Floor(perturbUp(x))) - : step * static_cast(math::Floor(perturbUp((x - offset)/step))) + offset; + return step == ValueT(1) + ? static_cast(math::Floor(perturbUp(x))) + : step * static_cast(math::Floor(perturbUp((x - offset)/step))) + offset; } /// @brief Rounds an input scalar down to the nearest valid ordinate of tile of a specified size. @@ -357,12 +371,12 @@ class ConvexVoxelizer /// @param step Tile step size. /// @return The ceiling of the value based on the tile size. template - inline static float - tileFloor(const float& x, const T& step) + inline static ValueT + tileFloor(const ValueT& x, const T& step) { static_assert(std::is_integral::value, "Index must be an integral type"); - const float s = static_cast(step); + const ValueT s = static_cast(step); return tileFloor(x, s); } @@ -373,9 +387,9 @@ class ConvexVoxelizer /// @param r Radius of the circle. /// @param x X-coordinate for which to compute the bottom y-coordinate. /// @return The y-coordinate at the bottom of the circle for the given x position. - inline static float - circleBottom(const float& x0, const float& y0, - const float& r, const float& x) + inline static ValueT + circleBottom(const ValueT& x0, const ValueT& y0, + const ValueT& r, const ValueT& x) { return y0 - math::Sqrt(math::Pow2(r) - math::Pow2(x-x0)); } @@ -386,9 +400,9 @@ class ConvexVoxelizer /// @param r Radius of the circle. /// @param x X-coordinate for which to compute the top y-coordinate. /// @return The y-coordinate at the top of the circle for the given x position. - inline static float - circleTop(const float& x0, const float& y0, - const float& r, const float& x) + inline static ValueT + circleTop(const ValueT& x0, const ValueT& y0, + const ValueT& r, const ValueT& x) { return y0 + math::Sqrt(math::Pow2(r) - math::Pow2(x-x0)); } @@ -401,9 +415,9 @@ class ConvexVoxelizer /// @param x X-coordinate for which to compute the bottom z-coordinate. /// @param y Y-coordinate for which to compute the bottom z-coordinate. /// @return The z-coordinate at the bottom of the sphere for the given (x, y) position. - inline static float - sphereBottom(const float& x0, const float& y0, const float& z0, - const float& r, const float& x, const float& y) + inline static ValueT + sphereBottom(const ValueT& x0, const ValueT& y0, const ValueT& z0, + const ValueT& r, const ValueT& x, const ValueT& y) { return z0 - math::Sqrt(math::Pow2(r) - math::Pow2(x-x0) - math::Pow2(y-y0)); } @@ -416,9 +430,9 @@ class ConvexVoxelizer /// @param x X-coordinate for which to compute the top z-coordinate. /// @param y Y-coordinate for which to compute the top z-coordinate. /// @return The z-coordinate at the top of the sphere for the given (x, y) position. - inline static float - sphereTop(const float& x0, const float& y0, const float& z0, - const float& r, const float& x, const float& y) + inline static ValueT + sphereTop(const ValueT& x0, const ValueT& y0, const ValueT& z0, + const ValueT& r, const ValueT& x, const ValueT& y) { return z0 + math::Sqrt(math::Pow2(r) - math::Pow2(x-x0) - math::Pow2(y-y0)); } @@ -430,11 +444,8 @@ class ConvexVoxelizer /// using a configurable step size. /// It allows updating, expanding, and resetting the y ranges, as well as merging data from other instances /// and trimming invalid entries. - /// @note @c ValueType must be a scalar or integral type. - template class XYRangeData { - static_assert(std::is_arithmetic_v, "Not an arithmetic type"); public: @@ -445,7 +456,7 @@ class ConvexVoxelizer /// @param xmin The lower bound of the x range. /// @param xmax The upper bound of the x range. /// @param step The step size between x values. Defaults to 1. - XYRangeData(const ValueType& xmin, const ValueType& xmax, const Index& step = 1) + XYRangeData(const ValueT& xmin, const ValueT& xmax, const Index& step = 1) { reset(xmin, xmax, step); } @@ -458,7 +469,7 @@ class ConvexVoxelizer /// @param ymax The new maximum y value to compare with and possibly update /// the current maximum at x. inline void - expandYRange(const ValueType& x, const ValueType& ymin, const ValueType& ymax) + expandYRange(const ValueT& x, const ValueT& ymin, const ValueT& ymax) { expandYMin(x, ymin); expandYMax(x, ymax); @@ -469,7 +480,7 @@ class ConvexVoxelizer /// @param x The x value. /// @param ymin The minimum y value to possibly be set. inline void - expandYMin(const ValueType& x, const ValueType& ymin) + expandYMin(const ValueT& x, const ValueT& ymin) { const Index i = worldToIndex(x); @@ -482,7 +493,7 @@ class ConvexVoxelizer /// @param x The x value. /// @param ymax The maximum y value to possibly be set. inline void - expandYMax(const ValueType& x, const ValueType& ymax) + expandYMax(const ValueT& x, const ValueT& ymax) { const Index i = worldToIndex(x); @@ -495,7 +506,7 @@ class ConvexVoxelizer /// @param x The x value. /// @param y The y value to use for expanding the range. inline void - expandYRange(const ValueType& x, const ValueType& y) + expandYRange(const ValueT& x, const ValueT& y) { if (std::isfinite(y)) { const Index i = worldToIndex(x); @@ -513,7 +524,7 @@ class ConvexVoxelizer /// @param x The x value. /// @param ymin The minimum y value to reset. inline void - setYMin(const ValueType& x, const ValueType& ymin) + setYMin(const ValueT& x, const ValueT& ymin) { const Index i = worldToIndex(x); @@ -525,7 +536,7 @@ class ConvexVoxelizer /// @param x The x value. /// @param ymax The maximum y value to reset. inline void - setYMax(const ValueType& x, const ValueType& ymax) + setYMax(const ValueT& x, const ValueT& ymax) { const Index i = worldToIndex(x); @@ -535,7 +546,7 @@ class ConvexVoxelizer /// @brief Clears the y range for a given x value, setting it to an empty interval. /// @param x The x value. inline void - clearYRange(const ValueType& x) + clearYRange(const ValueT& x) { const Index i = worldToIndex(x); @@ -549,12 +560,12 @@ class ConvexVoxelizer /// @param xmax The upper bound of the x range. /// @param step The step size between x values. Defaults to 1. inline void - reset(const ValueType& xmin, const ValueType& xmax, const Index& step = 1) + reset(const ValueT& xmin, const ValueT& xmax, const Index& step = 1) { assert(step != 0); mStep = step; - mStepInv = ValueType(1)/static_cast(mStep); + mStepInv = ValueT(1)/static_cast(mStep); mXStart = tileCeil(xmin, mStep); mXEnd = tileFloor(xmax, mStep); @@ -575,38 +586,38 @@ class ConvexVoxelizer /// @brief Retrieves the starting x value in the range. /// @return The start of the x range. - inline ValueType start() const { return mXStart; } + inline ValueT start() const { return mXStart; } /// @brief Retrieves the ending x value in the range. /// @return The end of the x range. - inline ValueType end() const { return mXEnd; } + inline ValueT end() const { return mXEnd; } /// @brief Converts an index to its corresponding x value. /// @param i The index value. /// @return The corresponding x value. - inline ValueType getX(const Index& i) const { return indexToWorld(i); } + inline ValueT getX(const Index& i) const { return indexToWorld(i); } /// @brief Gets the minimum y value for a given index. /// @param i The index value. /// @return The minimum y value. - inline ValueType getYMin(const Index& i) const { assert(i < mSize); return mYMins[i]; } + inline ValueT getYMin(const Index& i) const { assert(i < mSize); return mYMins[i]; } /// @brief Gets the maximum y value for a given index. /// @param i The index value. /// @return The maximum y value. - inline ValueType getYMax(const Index& i) const { assert(i < mSize); return mYMaxs[i]; } + inline ValueT getYMax(const Index& i) const { assert(i < mSize); return mYMaxs[i]; } /// @brief Gets the minimum y value for a given x value. /// @param x The x value. /// @return The minimum y value at the given x. /// @note @c x is rounded to the nearest value in the x range. - inline ValueType getYMin(const float& x) const { return mYMins[worldToIndex(x)]; } + inline ValueT getYMin(const ValueT& x) const { return mYMins[worldToIndex(x)]; } /// @brief Gets the maximum y value for a given x value. /// @param x The x value. /// @return The maximum y value at the given x. /// @note @c x is rounded to the nearest value in the x range. - inline ValueType getYMax(const float& x) const { return mYMaxs[worldToIndex(x)]; } + inline ValueT getYMax(const ValueT& x) const { return mYMaxs[worldToIndex(x)]; } /// @brief Retrieves the x, ymin, and ymax values for a given index. /// @param x Output parameter for the x value. @@ -614,7 +625,7 @@ class ConvexVoxelizer /// @param ymax Output parameter for the maximum y value. /// @param i The index to query. inline void - XYData(ValueType& x, ValueType& ymin, ValueType& ymax, const Index& i) const + XYData(ValueT& x, ValueT& ymin, ValueT& ymax, const Index& i) const { x = indexToWorld(i); ymin = mYMins[i]; @@ -625,14 +636,14 @@ class ConvexVoxelizer /// over the overlapping x range. /// @param xydata The XYRangeData to merge with. inline void - merge(const XYRangeData& xydata) + merge(const XYRangeData& xydata) { assert(mStep == xydata.step()); - const ValueType start = xydata.start(), end = xydata.end(); + const ValueT start = xydata.start(), end = xydata.end(); - const std::vector& ymins = xydata.mYMins; - const std::vector& ymaxs = xydata.mYMaxs; + const std::vector& ymins = xydata.mYMins; + const std::vector& ymaxs = xydata.mYMaxs; if (start < mXStart) { const Index n = indexDistance(mXStart, start); @@ -669,7 +680,7 @@ class ConvexVoxelizer } if (i == mSize) { - mSize = 0; mXStart = ValueType(0); mXEnd = ValueType(0); + mSize = 0; mXStart = ValueT(0); mXEnd = ValueT(0); mYMins.clear(); mYMaxs.clear(); return; } @@ -685,8 +696,8 @@ class ConvexVoxelizer return; mSize -= i + j; - mXStart += ValueType(i * mStep); - mXEnd -= ValueType(j * mStep); + mXStart += ValueT(i * mStep); + mXEnd -= ValueT(j * mStep); if (i > 0) { mYMins.erase(mYMins.begin(), mYMins.begin() + i); @@ -701,18 +712,18 @@ class ConvexVoxelizer private: - inline static const float - MINVALUE = std::numeric_limits::lowest(), - MAXVALUE = std::numeric_limits::max(); + inline static const ValueT + MINVALUE = std::numeric_limits::lowest(), + MAXVALUE = std::numeric_limits::max(); inline Index - indexDistance(const ValueType& a, const ValueType& b) + indexDistance(const ValueT& a, const ValueT& b) { return Index(math::Round(mStepInv*math::Abs(a - b))); } inline Index - worldToIndex(const ValueType& x) const + worldToIndex(const ValueT& x) const { const Index i = Index(math::Round(mStepInv*(x - mXStart))); assert(i < mSize); @@ -720,43 +731,43 @@ class ConvexVoxelizer return i; } - inline ValueType + inline ValueT indexToWorld(const Index i) const { assert(i < mSize); - return mXStart + ValueType(i * mStep); + return mXStart + ValueT(i * mStep); } Index mStep, mSize; - ValueType mStepInv, mXStart, mXEnd; + ValueT mStepInv, mXStart, mXEnd; - std::vector mYMins, mYMaxs; + std::vector mYMins, mYMaxs; }; // class XYRangeData // ------------ protected members ------------ - XYRangeData mXYData; + XYRangeData mXYData; private: #define EPS 0.0005f - inline static float perturbDown(const float& x) { return x - EPS; } - inline static float perturbUp(const float& x) { return x + EPS; } + inline static ValueT perturbDown(const ValueT& x) { return x - ValueT(EPS); } + inline static ValueT perturbUp(const ValueT& x) { return x + ValueT(EPS); } #undef EPS - inline static float - voxelCeil(const float& x) + inline static ValueT + voxelCeil(const ValueT& x) { - return static_cast(math::Ceil(perturbDown(x))); + return static_cast(math::Ceil(perturbDown(x))); } - inline static float - voxelFloor(const float& x) + inline static ValueT + voxelFloor(const ValueT& x) { - return static_cast(math::Floor(perturbUp(x))); + return static_cast(math::Floor(perturbUp(x))); } // skips the need for negative tile population and internal leap frogging @@ -858,27 +869,27 @@ class ConvexVoxelizer iterateYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range - float x, yb, yt; + ValueT x, yb, yt; mXYData.XYData(x, yb, yt, i); if (!std::isfinite(yb) || !std::isfinite(yt)) return; - float zb, zt; + ValueT zb, zt; - for (float y = voxelCeil(yb); y <= perturbUp(yt); ++y) { + for (ValueT y = voxelCeil(yb); y <= perturbUp(yt); ++y) { if (!bottomTop(zb, zt, x, y)) continue; Coord ijk(Int32(x), Int32(y), Int32(0)); - Vec3s p(x, y, 0.0f); + Vec3T p(x, y, ValueT(0)); ijk[2] = Int32(voxelCeil(zb))-1; acc.reset(ijk); - for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { + for (ValueT z = voxelCeil(zb); z <= perturbUp(zt); ++z) { ijk[2] = Int32(z); - const float val = float(acc.template getValue<1>(ijk)); + const ValueT val = acc.template getValue<1>(ijk); if (val == mNegBgF) { if constexpr (LeapFrog) acc.template leapUp(ijk, z); @@ -886,12 +897,12 @@ class ConvexVoxelizer } p[2] = z; - const float dist = mVox * invokeSignedDistance(p); + const ValueT dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { acc.template setValueOff<1,false>(ijk, mNegBg); } else if (dist < val) { - acc.template setValueOn<1,false>(ijk, ValueT(dist)); + acc.template setValueOn<1,false>(ijk, dist); } else { // dist >= val acc.template checkReset<1>(ijk); } @@ -906,37 +917,37 @@ class ConvexVoxelizer iterateNoTilesYZ(const Index& i, CacheLastLeafAccessor& acc) { // initialize x value and y-range - float x, yb, yt; + ValueT x, yb, yt; mXYData.XYData(x, yb, yt, i); if (!std::isfinite(yb) || !std::isfinite(yt)) return; - float zb, zt; + ValueT zb, zt; - for (float y = voxelCeil(yb); y <= perturbUp(yt); ++y) { + for (ValueT y = voxelCeil(yb); y <= perturbUp(yt); ++y) { if (!bottomTop(zb, zt, x, y)) continue; Coord ijk(Int32(x), Int32(y), Int32(0)); - Vec3s p(x, y, 0.0f); + Vec3T p(x, y, ValueT(0)); bool early_break = false; - float z_stop; + ValueT z_stop; ijk[2] = Int32(voxelCeil(zb))-1; acc.reset(ijk); - for (float z = voxelCeil(zb); z <= perturbUp(zt); ++z) { + for (ValueT z = voxelCeil(zb); z <= perturbUp(zt); ++z) { ijk[2] = Int32(z); p[2] = z; - const float dist = mVox * invokeSignedDistance(p); + const ValueT dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { early_break = true; z_stop = z; break; } else if (dist < mBgF) { - acc.template setValueOn<1>(ijk, ValueT(dist)); + acc.template setValueOn<1>(ijk, dist); } else { // dist >= mBg acc.template checkReset<1>(ijk); } @@ -944,15 +955,15 @@ class ConvexVoxelizer if (early_break) { ijk[2] = Int32(voxelFloor(zt))+1; acc.reset(ijk); - for (float z = voxelFloor(zt); z > z_stop; --z) { + for (ValueT z = voxelFloor(zt); z > z_stop; --z) { ijk[2] = Int32(z); p[2] = z; - const float dist = mVox * invokeSignedDistance(p); + const ValueT dist = mVox * invokeSignedDistance(p); if (dist <= mNegBgF) { break; } else if (dist < mBgF) { - acc.template setValueOn<-1>(ijk, ValueT(dist)); + acc.template setValueOn<-1>(ijk, dist); } else { // dist >= mBg acc.template checkReset<-1>(ijk); } @@ -979,7 +990,7 @@ class ConvexVoxelizer tileIterateYZ(const Index& i, AccessorT& acc) { // initialize x value and y-range - float x, yb, yt; + ValueT x, yb, yt; mXYData.XYData(x, yb, yt, i); if (!std::isfinite(yb) || !std::isfinite(yt)) @@ -987,17 +998,17 @@ class ConvexVoxelizer static const Index TILESIZE = NodeT::DIM; - float zb, zt; + ValueT zb, zt; - for (float y = tileCeil(yb, TILESIZE); y <= perturbUp(yt); y += TILESIZE) { + for (ValueT y = tileCeil(yb, TILESIZE); y <= perturbUp(yt); y += TILESIZE) { if (!bottomTop(zb, zt, x, y)) continue; Coord ijk(Int32(x), Int32(y), Int32(0)); - Vec3s p(x, y, 0.0f); + Vec3T p(x, y, ValueT(0)); bool tiles_added = false; - float z = tileCeil(zb, TILESIZE) - 2*TILESIZE; + ValueT z = tileCeil(zb, TILESIZE) - 2*TILESIZE; while (z <= tileFloor(zt, TILESIZE) + TILESIZE) { ijk[2] = Int32(z); p[2] = z; @@ -1013,7 +1024,7 @@ class ConvexVoxelizer template inline bool - leapFrogToNextTile(const Coord& ijk, float& z, AccessorT& acc) const + leapFrogToNextTile(const Coord& ijk, ValueT& z, AccessorT& acc) const { static const int offset = NodeT::DIM; static const int nodeDepth = int(TreeT::DEPTH - NodeT::LEVEL - 1); @@ -1032,11 +1043,11 @@ class ConvexVoxelizer return false; } - const float sz = (float)mTileSizes[depth]; + const ValueT sz = ValueT(mTileSizes[depth]); z = dir > 0 - ? sz * float(math::Ceil(z/sz)) + 0.5f * (offset - 1.0f) - : sz * float(math::Floor(z/sz)) - 0.5f * (offset + 1.0f); + ? sz * ValueT(math::Ceil(z/sz)) + ValueT(0.5)*(offset-1) + : sz * ValueT(math::Floor(z/sz)) - ValueT(0.5)*(offset+1); return true; } @@ -1044,7 +1055,7 @@ class ConvexVoxelizer // add negative background tile inside the object if it fits and return true iff it was added template inline bool - addTile(const Vec3s& p, const Coord& ijk, AccessorT& acc) + addTile(const Vec3T& p, const Coord& ijk, AccessorT& acc) { static const Index LEVEL = NodeT::LEVEL + 1; @@ -1058,14 +1069,14 @@ class ConvexVoxelizer template inline bool - tileFits(const Vec3s& p) const + tileFits(const Vec3T& p) const { static const Index TILESIZE = NodeT::DIM; - static const float R1 = 0.500f * (TILESIZE-1), - R2 = 0.866f * (TILESIZE-1); + static const ValueT R1 = ValueT(0.500)*(TILESIZE-1), + R2 = ValueT(0.866)*(TILESIZE-1); - const float dist = invokeTilePointSignedDistance(p); + const ValueT dist = invokeTilePointSignedDistance(p); // fast positive criterion: circumsribed ball is in the object if (dist <= -R2-mHw) @@ -1076,36 +1087,36 @@ class ConvexVoxelizer return false; // convexity: the tile is in the object iff all corners are in the object - return invokeTilePointSignedDistance(p + Vec3s(-R1, -R1, -R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(-R1, -R1, R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(-R1, R1, -R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(-R1, R1, R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(R1, -R1, -R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(R1, -R1, R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(R1, R1, -R1)) < -mHw - && invokeTilePointSignedDistance(p + Vec3s(R1, R1, R1)) < -mHw; + return invokeTilePointSignedDistance(p + Vec3T(-R1, -R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(-R1, -R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(-R1, R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(-R1, R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(R1, -R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(R1, -R1, R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(R1, R1, -R1)) < -mHw + && invokeTilePointSignedDistance(p + Vec3T(R1, R1, R1)) < -mHw; } - + inline void invokeSetXYRangeData(const Index& step = 1) { static_cast(this)->setXYRangeData(step); } - + inline bool invokeTileCanFit(const Index& dim) const { return static_cast(this)->tileCanFit(dim); } - inline float - invokeSignedDistance(const Vec3s& p) const + inline ValueT + invokeSignedDistance(const Vec3T& p) const { return static_cast(this)->signedDistance(p); } - inline float - invokeTilePointSignedDistance(const Vec3s& p) const + inline ValueT + invokeTilePointSignedDistance(const Vec3T& p) const { return static_cast(this)->tilePointSignedDistance(p); } @@ -1262,15 +1273,15 @@ class ConvexVoxelizer /// @param ijk The coordinate to be examined. /// @param z The Z-coordinate to be adjusted. template - inline void leapUp(const Coord& ijk, float& z) + inline void leapUp(const Coord& ijk, ValueT& z) { if (isVoxel<1,Check>(ijk)) return; const int depth = mAcc.getValueDepth(ijk); - const float sz = (float)mTileSizes[depth]; + const ValueT sz = ValueT(mTileSizes[depth]); - z = sz * float(math::Ceil((z+1.0f)/sz)) - 1.0f; + z = sz * ValueT(math::Ceil((z+ValueT(1))/sz)) - ValueT(1); } private: @@ -1333,9 +1344,7 @@ class ConvexVoxelizer const std::vector mTileSizes = treeTileSizes(); - const float mVox, mBgF, mNegBgF, mHw; - - const ValueT mBg, mNegBg; + const ValueT mVox, mBgF, mNegBgF, mHw, mBg, mNegBg; // misc diff --git a/openvdb/openvdb/tools/LevelSetDilatedMesh.h b/openvdb/openvdb/tools/LevelSetDilatedMesh.h index 00e8c7b92c..9a3a60c079 100644 --- a/openvdb/openvdb/tools/LevelSetDilatedMesh.h +++ b/openvdb/openvdb/tools/LevelSetDilatedMesh.h @@ -19,6 +19,7 @@ #include "PointPartitioner.h" #include "Prune.h" +#include #include #include @@ -26,6 +27,7 @@ #include #include +#include namespace openvdb { @@ -44,14 +46,16 @@ namespace tools { /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note @c ScalarType represents the mesh vertex and radius type +/// and must be a floating-point scalar. /// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. /// This includes meshes that could represent valid BRep solids, dilation occurs both /// inward and outward, forming a 'shell' rather than only expanding outward. -template +template typename GridType::Ptr createLevelSetDilatedMesh( - const std::vector& vertices, const std::vector& triangles, - float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + const std::vector>& vertices, const std::vector& triangles, + ScalarType radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); /// @brief Return a grid of type @c GridType containing a narrow-band level set @@ -65,14 +69,16 @@ createLevelSetDilatedMesh( /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note @c ScalarType represents the mesh vertex and radius type +/// and must be a floating-point scalar. /// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. /// This includes meshes that could represent valid BRep solids, dilation occurs both /// inward and outward, forming a 'shell' rather than only expanding outward. -template +template typename GridType::Ptr createLevelSetDilatedMesh( - const std::vector& vertices, const std::vector& quads, - float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + const std::vector>& vertices, const std::vector& quads, + ScalarType radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); /// @brief Return a grid of type @c GridType containing a narrow-band level set @@ -87,14 +93,16 @@ createLevelSetDilatedMesh( /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note @c ScalarType represents the mesh vertex and radius type +/// and must be a floating-point scalar. /// @note The input mesh is always treated as a surface, and so dilation occurs in every direction. /// This includes meshes that could represent valid BRep solids, dilation occurs both /// inward and outward, forming a 'shell' rather than only expanding outward. -template +template typename GridType::Ptr -createLevelSetDilatedMesh(const std::vector& vertices, +createLevelSetDilatedMesh(const std::vector>& vertices, const std::vector& triangles, const std::vector& quads, - float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + ScalarType radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); namespace lvlset { @@ -105,32 +113,32 @@ namespace lvlset { /// Negative background tiles that fit inside the closed dilated triangle are also populated. /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +template class OpenTriangularPrismVoxelizer : public ConvexVoxelizer< - GridT, - OpenTriangularPrismVoxelizer, + GridType, + OpenTriangularPrismVoxelizer, InterruptT> { - using GridPtr = typename GridT::Ptr; - using ValueT = typename GridT::ValueType; - - // ------------ base class members ------------ + using GridPtr = typename GridType::Ptr; using BaseT = ConvexVoxelizer< - GridT, - OpenTriangularPrismVoxelizer, + GridType, + OpenTriangularPrismVoxelizer, InterruptT >; using BaseT::mXYData; using BaseT::tileCeil; + using ValueT = typename BaseT::ValueT; + using Vec3T = typename BaseT::Vec3T; + public: friend class ConvexVoxelizer< - GridT, - OpenTriangularPrismVoxelizer, + GridType, + OpenTriangularPrismVoxelizer, InterruptT >; @@ -156,9 +164,13 @@ class OpenTriangularPrismVoxelizer /// @param pt2 point 2 of the triangle in world units /// @param pt3 point 3 of the triangle in world units /// @param radius radius of the open prism in world units + template void - operator()(const Vec3s& pt1, const Vec3s& pt2, const Vec3s& pt3, const float& radius) + operator()(const math::Vec3& pt1, const math::Vec3& pt2, + const math::Vec3& pt3, const ScalarType& radius) { + static_assert(std::is_floating_point::value); + if (initialize(pt1, pt2, pt3, radius)) BaseT::iterate(); } @@ -168,11 +180,11 @@ class OpenTriangularPrismVoxelizer inline void setXYRangeData(const Index& step = 1) { - const float &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(), - &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x(); + const ValueT &x1 = mPts[0].x(), &x2 = mPts[1].x(), &x3 = mPts[2].x(), + &x4 = mPts[3].x(), &x5 = mPts[4].x(), &x6 = mPts[5].x(); - const float xmin = math::Min(x1, x2, x3, x4, x5, x6); - const float xmax = math::Max(x1, x2, x3, x4, x5, x6); + const ValueT xmin = math::Min(x1, x2, x3, x4, x5, x6); + const ValueT xmax = math::Max(x1, x2, x3, x4, x5, x6); mXYData.reset(xmin, xmax, step); // TODO add logic to ignore edges in the interior of the projection @@ -195,18 +207,18 @@ class OpenTriangularPrismVoxelizer inline void setXYSegmentRangeData(const Index& step = 1) { - const float &x1 = mPts[i].x(), &x2 = mPts[j].x(); + const ValueT &x1 = mPts[i].x(), &x2 = mPts[j].x(); // nothing to do if segment does not span across more than on voxel in x // other segments will handle this segment's range if (tileCeil(x1, step) == tileCeil(x2, step)) return; - const float x_start = tileCeil(math::Min(x1, x2), step), - x_end = math::Max(x1, x2), - stepf = float(step); + const ValueT x_start = tileCeil(math::Min(x1, x2), step), + x_end = math::Max(x1, x2), + stepv = ValueT(step); - for (float x = x_start; x <= x_end; x += stepf) { + for (ValueT x = x_start; x <= x_end; x += stepv) { if constexpr (MinMax <= 0) mXYData.expandYMin(x, line2D(x)); if constexpr (MinMax >= 0) @@ -215,22 +227,22 @@ class OpenTriangularPrismVoxelizer } // simply offset distance to the center plane, we may assume any CPQ falls in inside the prism - inline float - signedDistance(const Vec3s& p) const + inline ValueT + signedDistance(const Vec3T& p) const { return math::Abs(mTriNrml.dot(p - mA)) - mRad; } // allows for tiles to poke outside of the open prism into the tubes // adaptation of udTriangle at https://iquilezles.org/articles/distfunctions/ - inline float - tilePointSignedDistance(const Vec3s& p) const + inline ValueT + tilePointSignedDistance(const Vec3T& p) const { - const Vec3s pa = p - mA, + const Vec3T pa = p - mA, pb = p - mB, pc = p - mC; - const float udist = + const ValueT udist = math::Sign(mBAXNrml.dot(pa)) + math::Sign(mCBXNrml.dot(pb)) + math::Sign(mACXNrml.dot(pc)) < 2 @@ -249,14 +261,14 @@ class OpenTriangularPrismVoxelizer inline bool tileCanFit(const Index& dim) const { - return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); + return mRad >= BaseT::halfWidth() + ValueT(0.5) * (ValueT(dim)-ValueT(1)); } - std::function prismBottomTop = - [this](float& zb, float& zt, const float& x, const float& y) + std::function prismBottomTop = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { - zb = std::numeric_limits::lowest(); - zt = std::numeric_limits::max(); + zb = std::numeric_limits::lowest(); + zt = std::numeric_limits::max(); // TODO with proper book keeping we can know apriori which 2 indexes will set zb & zt // basically figure out a poor man's cylindrical decomposition... @@ -271,12 +283,12 @@ class OpenTriangularPrismVoxelizer template inline void - setPlaneBottomTop(float& zb, float& zt, const float& x, const float& y) const + setPlaneBottomTop(ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) const { if (math::isApproxZero(mFaceNrmls[i].z())) return; - const float z = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; + const ValueT z = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; if (mFaceNrmls[i].z() < 0) { if (zb < z) @@ -289,17 +301,19 @@ class OpenTriangularPrismVoxelizer // world space points and radius inputs // initializes class members in index space + template inline bool - initialize(const Vec3s& pt1, const Vec3s& pt2, const Vec3s& pt3, const float& r) + initialize(const math::Vec3& pt1, const math::Vec3& pt2, + const math::Vec3& pt3, const ScalarType& r) { - const float vx = BaseT::voxelSize(), - hw = BaseT::halfWidth(); + const ValueT vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); - mA = pt1/vx; - mB = pt2/vx; - mC = pt3/vx; + mA = Vec3T(pt1)/vx; + mB = Vec3T(pt2)/vx; + mC = Vec3T(pt3)/vx; - mRad = r/vx; + mRad = ValueT(r)/vx; if (math::isApproxZero(mRad) || mRad < 0) return false; // nothing to voxelize, prism has no volume @@ -317,14 +331,14 @@ class OpenTriangularPrismVoxelizer mCBNorm2 = math::isApproxZero(mCB.lengthSqr()) ? mCB : mCB/mCB.lengthSqr(); mACNorm2 = math::isApproxZero(mAC.lengthSqr()) ? mAC : mAC/mAC.lengthSqr(); - const float len = mTriNrml.length(); + const ValueT len = mTriNrml.length(); if (math::isApproxZero(len)) { return false; // nothing to voxelize, prism has no volume } else { mTriNrml /= len; } - const float hwRad = mRad + hw; + const ValueT hwRad = mRad + hw; mPts = { mA + hwRad * mTriNrml, mB + hwRad * mTriNrml, mC + hwRad * mTriNrml, mA - hwRad * mTriNrml, mB - hwRad * mTriNrml, mC - hwRad * mTriNrml @@ -342,15 +356,15 @@ class OpenTriangularPrismVoxelizer { static const std::vector p_ind = {0, 3, 0, 1, 2}; - mPlaneXCoeffs.assign(5, 0.0f); - mPlaneYCoeffs.assign(5, 0.0f); - mPlaneOffsets.assign(5, 0.0f); + mPlaneXCoeffs.assign(5, ValueT(0)); + mPlaneYCoeffs.assign(5, ValueT(0)); + mPlaneOffsets.assign(5, ValueT(0)); for (Index i = 0; i < 5; ++i) { if (!math::isApproxZero(mFaceNrmls[i].z())) { - const float cx = mFaceNrmls[i].x()/mFaceNrmls[i].z(), - cy = mFaceNrmls[i].y()/mFaceNrmls[i].z(); - const Vec3s p = mPts[p_ind[i]]; + const ValueT cx = mFaceNrmls[i].x()/mFaceNrmls[i].z(), + cy = mFaceNrmls[i].y()/mFaceNrmls[i].z(); + const Vec3T p = mPts[p_ind[i]]; mPlaneXCoeffs[i] = -cx; mPlaneYCoeffs[i] = -cy; mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z(); @@ -366,34 +380,34 @@ class OpenTriangularPrismVoxelizer // ------------ general utilities ------------ template - float - line2D(const float& x) const + ValueT + line2D(const ValueT& x) const { - const float &x1 = mPts[i].x(), &y1 = mPts[i].y(), - &x2 = mPts[j].x(), &y2 = mPts[j].y(); + const ValueT &x1 = mPts[i].x(), &y1 = mPts[i].y(), + &x2 = mPts[j].x(), &y2 = mPts[j].y(); - const float m = (y2-y1)/(x2-x1); + const ValueT m = (y2-y1)/(x2-x1); return y1 + m * (x-x1); } // ------------ private members ------------ - Vec3s mA, mB, mC; - float mRad; + Vec3T mA, mB, mC; + ValueT mRad; - Vec3s mBA, mCB, mAC; - Vec3s mBAXNrml, mCBXNrml, mACXNrml; - Vec3s mBANorm2, mCBNorm2, mACNorm2; + Vec3T mBA, mCB, mAC; + Vec3T mBAXNrml, mCBXNrml, mACXNrml; + Vec3T mBANorm2, mCBNorm2, mACNorm2; - std::vector mPts = std::vector(6); + std::vector mPts = std::vector(6); - Vec3s mTriNrml; - std::vector mFaceNrmls = std::vector(5); + Vec3T mTriNrml; + std::vector mFaceNrmls = std::vector(5); - std::vector mPlaneXCoeffs = std::vector(5), - mPlaneYCoeffs = std::vector(5), - mPlaneOffsets = std::vector(5); + std::vector mPlaneXCoeffs = std::vector(5), + mPlaneYCoeffs = std::vector(5), + mPlaneOffsets = std::vector(5); }; // class OpenTriangularPrismVoxelizer @@ -403,32 +417,33 @@ class OpenTriangularPrismVoxelizer /// The sector is defined by the intersection of two half spaces. /// /// @note @c GridType::ValueType must be a floating-point scalar. -template -class OpenTubeWedgeVoxelizer +template +class OpenCapsuleWedgeVoxelizer : public ConvexVoxelizer< - GridT, - OpenTubeWedgeVoxelizer, + GridType, + OpenCapsuleWedgeVoxelizer, InterruptT> { - using GridPtr = typename GridT::Ptr; - using ValueT = typename GridT::ValueType; - - // ------------ base class members ------------ + using GridPtr = typename GridType::Ptr; using BaseT = ConvexVoxelizer< - GridT, - OpenTubeWedgeVoxelizer, + GridType, + OpenCapsuleWedgeVoxelizer, InterruptT >; using BaseT::mXYData; using BaseT::tileCeil; + using ValueT = typename BaseT::ValueT; + using Vec3T = typename BaseT::Vec3T; + using Vec2T = typename BaseT::Vec2T; + public: friend class ConvexVoxelizer< - GridT, - OpenTubeWedgeVoxelizer, + GridType, + OpenCapsuleWedgeVoxelizer, InterruptT >; @@ -441,7 +456,7 @@ class OpenTubeWedgeVoxelizer /// /// @note The voxel size and half width are determined from the input grid, /// meaning the voxel size and background value need to be set prior to voxelization - OpenTubeWedgeVoxelizer(GridPtr& grid, const bool& threaded = false, + OpenCapsuleWedgeVoxelizer(GridPtr& grid, const bool& threaded = false, InterruptT* interrupter = nullptr) : BaseT(grid, threaded, interrupter) { @@ -457,10 +472,14 @@ class OpenTubeWedgeVoxelizer /// /// @note The normal vectors @f$n @f$ point outward from the open wedge, /// and the clipping half space is defined by the set of points @f$p @f$ that satisfy @f$n . (p - pt1) \leq 0@f$. + template void - operator()(const Vec3s& pt1, const Vec3s& pt2, const float& radius, - const Vec3s& nrml1, const Vec3s& nrml2) + operator()(const math::Vec3& pt1, const math::Vec3& pt2, + const ScalarType& radius, const math::Vec3& nrml1, + const math::Vec3& nrml2) { + static_assert(std::is_floating_point::value); + if (initialize(pt1, pt2, radius, nrml1, nrml2)) BaseT::iterate(); } @@ -471,73 +490,73 @@ class OpenTubeWedgeVoxelizer inline void setXYRangeData(const Index& step = 1) { - const float stepf = float(step); + const ValueT stepv = ValueT(step); // short circuit a vertical cylinder if (mIsVertical) { mXYData.reset(mX1 - mORad, mX1 + mORad, step); - for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepf) + for (ValueT x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); intersectWithXYWedgeLines(); return; } - const float v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); + const ValueT v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); - const float a0 = mX1 - mORad, - a1 = mX1 - v, - a2 = mX1 + v, - a3 = mX2 - v, - a4 = mX2 + v, - a5 = mX2 + mORad; + const ValueT a0 = mX1 - mORad, + a1 = mX1 - v, + a2 = mX1 + v, + a3 = mX2 - v, + a4 = mX2 + v, + a5 = mX2 + mORad; - const float tc0 = tileCeil(a0, step), - tc1 = tileCeil(a1, step), - tc2 = tileCeil(a2, step), - tc3 = tileCeil(a3, step), - tc4 = tileCeil(a4, step); + const ValueT tc0 = tileCeil(a0, step), + tc1 = tileCeil(a1, step), + tc2 = tileCeil(a2, step), + tc3 = tileCeil(a3, step), + tc4 = tileCeil(a4, step); mXYData.reset(a0, a5, step); - for (float x = tc0; x <= a1; x += stepf) + for (ValueT x = tc0; x <= a1; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = tc1; x <= math::Min(a2, a3); x += stepf) + for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv) mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); } else { - for (float x = tc1; x <= math::Min(a2, a3); x += stepf) + for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv) mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); } } if (a2 < a3) { - for (float x = tc2; x <= a3; x += stepf) + for (ValueT x = tc2; x <= a3; x += stepv) mXYData.expandYRange(x, lineBottom(x), lineTop(x)); } else { if (mY2 <= mY1) { - for (float x = tc3; x <= a2; x += stepf) + for (ValueT x = tc3; x <= a2; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); } else { - for (float x = tc3; x <= a2; x += stepf) + for (ValueT x = tc3; x <= a2; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); } } if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) + for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); } else { - for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) + for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv) mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); } } - for (float x = tc4; x <= a5; x += stepf) + for (ValueT x = tc4; x <= a5; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); intersectWithXYStrip(); @@ -551,82 +570,84 @@ class OpenTubeWedgeVoxelizer if (mIsVertical) return; - const Vec3s &pp1 = mPlanePts[0], &pp2 = mPlanePts[1]; - const float &vx = mV.x(), &vy = mV.y(); + const Vec3T &pp1 = mPlanePts[0], &pp2 = mPlanePts[1]; + const ValueT &vx = mV.x(), &vy = mV.y(); - Vec2s n = Vec2s(-vy, vx).unitSafe(); - Vec3s cvec = mORad * Vec3s(-vy, vx, 0.0f).unitSafe(); + Vec2T n = Vec2T(-vy, vx).unitSafe(); + Vec3T cvec = mORad * Vec3T(-vy, vx, ValueT(0)).unitSafe(); - if (math::isApproxZero(vy)) cvec.y() = math::Abs(cvec.y()); - else if (vy > 0.0f) cvec *= -1.0f; + if (math::isApproxZero(vy)) + cvec.y() = math::Abs(cvec.y()); + else if (vy > 0) + cvec *= ValueT(-1); - const Vec3s cpmin(mPt1 - cvec), cpmax(mPt1 + cvec); + const Vec3T cpmin(mPt1 - cvec), cpmax(mPt1 + cvec); if (math::isApproxZero(mXdiff)) { - const float px = mPt1.x(), - xmin = math::Min(px, pp1.x(), pp2.x()), - xmax = math::Max(px, pp1.x(), pp2.x()); + const ValueT px = mPt1.x(), + xmin = math::Min(px, pp1.x(), pp2.x()), + xmax = math::Max(px, pp1.x(), pp2.x()); if (!inWedge(cpmin)) - intersectWithXYHalfSpace(n.x() < 0 ? n : -n, Vec2s(xmin, 0.0f)); + intersectWithXYHalfSpace(n.x() < 0 ? n : -n, Vec2T(xmin, ValueT(0))); if (!inWedge(cpmax)) - intersectWithXYHalfSpace(n.x() > 0 ? n : -n, Vec2s(xmax, 0.0f)); + intersectWithXYHalfSpace(n.x() > 0 ? n : -n, Vec2T(xmax, ValueT(0))); } else { - const float m = mYdiff/mXdiff; - const float y1 = mPt1.y() - m * mPt1.x(), - y2 = pp1.y() - m * pp1.x(), - y3 = pp2.y() - m * pp2.x(); - const float ymin = math::Min(y1, y2, y3), - ymax = math::Max(y1, y2, y3); - - if (!inWedge(vy <= 0.0f ? cpmin : cpmax)) - intersectWithXYHalfSpace(n.y() < 0 ? n : -n, Vec2s(0.0f, ymin)); - - if (!inWedge(vy > 0.0f ? cpmin : cpmax)) - intersectWithXYHalfSpace(n.y() > 0 ? n : -n, Vec2s(0.0f, ymax)); + const ValueT m = mYdiff/mXdiff; + const ValueT y1 = mPt1.y() - m * mPt1.x(), + y2 = pp1.y() - m * pp1.x(), + y3 = pp2.y() - m * pp2.x(); + const ValueT ymin = math::Min(y1, y2, y3), + ymax = math::Max(y1, y2, y3); + + if (!inWedge(vy <= 0 ? cpmin : cpmax)) + intersectWithXYHalfSpace(n.y() < 0 ? n : -n, Vec2T(ValueT(0), ymin)); + + if (!inWedge(vy > 0 ? cpmin : cpmax)) + intersectWithXYHalfSpace(n.y() > 0 ? n : -n, Vec2T(ValueT(0), ymax)); } } inline void intersectWithXYWedgeLines() { - const Vec3s v(mORad * mV.unitSafe()), + const Vec3T v(mORad * mV.unitSafe()), p1(mPt1 - v), p2(mPt2 + v); - const Vec2s p1_2d(p1.x(), p1.y()), p2_2d(p2.x(), p2.y()); + const Vec2T p1_2d(p1.x(), p1.y()), p2_2d(p2.x(), p2.y()); - Vec2s d(-mPlaneNrmls[0].x() - mPlaneNrmls[1].x(), + Vec2T d(-mPlaneNrmls[0].x() - mPlaneNrmls[1].x(), -mPlaneNrmls[0].y() - mPlaneNrmls[1].y()); - Vec2s n0(-mDirVectors[0].y(), mDirVectors[0].x()), + Vec2T n0(-mDirVectors[0].y(), mDirVectors[0].x()), n1(-mDirVectors[1].y(), mDirVectors[1].x()); - if (n0.dot(d) > 0.0f) - n0 *= -1.0f; - if (n1.dot(d) > 0.0f) - n1 *= -1.0f; + if (n0.dot(d) > 0) + n0 *= ValueT(-1); + if (n1.dot(d) > 0) + n1 *= ValueT(-1); if (!math::isApproxZero(n0.lengthSqr())) - intersectWithXYHalfSpace(n0, n0.dot(p2_2d - p1_2d) < 0.0f ? p1_2d : p2_2d); + intersectWithXYHalfSpace(n0, n0.dot(p2_2d - p1_2d) < 0 ? p1_2d : p2_2d); if (!math::isApproxZero(n1.lengthSqr())) - intersectWithXYHalfSpace(n1, n1.dot(p2_2d - p1_2d) < 0.0f ? p1_2d : p2_2d); + intersectWithXYHalfSpace(n1, n1.dot(p2_2d - p1_2d) < 0 ? p1_2d : p2_2d); } inline void - intersectWithXYHalfSpace(const Vec2s& n, const Vec2s& p) + intersectWithXYHalfSpace(const Vec2T& n, const Vec2T& p) { if (mXYData.size() == 0) return; if (math::isApproxZero(n.y())) { - const float px = p.x(); + const ValueT &px = p.x(); if (n.x() < 0) { const Index m = mXYData.size(); for (Index i = 0; i < m; ++i) { - const float x = mXYData.getX(i); + const ValueT x = mXYData.getX(i); if (x < px) mXYData.clearYRange(x); else break; @@ -634,7 +655,7 @@ class OpenTubeWedgeVoxelizer } else { Index i = mXYData.size()-1; while (true) { - const float x = mXYData.getX(i); + const ValueT x = mXYData.getX(i); if (x > px) mXYData.clearYRange(x); else break; @@ -647,13 +668,13 @@ class OpenTubeWedgeVoxelizer const bool set_min = n.y() < 0; const Index m = mXYData.size(); - const float b = -n.x()/n.y(); - const float a = p.y() - b * p.x(); + const ValueT b = -n.x()/n.y(); + const ValueT a = p.y() - b * p.x(); - float x, ymin, ymax; + ValueT x, ymin, ymax; for (Index i = 0; i < m; ++i) { mXYData.XYData(x, ymin, ymax, i); - const float yint = a + b * x; + const ValueT yint = a + b * x; if (ymin <= yint && yint <= ymax) { if (set_min) mXYData.setYMin(x, yint); @@ -669,20 +690,20 @@ class OpenTubeWedgeVoxelizer } // distance in index space - inline float - signedDistance(const Vec3s& p) const + inline ValueT + signedDistance(const Vec3T& p) const { - const Vec3s w = p - mPt1; - const float dot = w.dot(mV); + const Vec3T w = p - mPt1; + const ValueT dot = w.dot(mV); // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr - if (dot <= math::Tolerance::value()) + if (dot <= math::Tolerance::value()) return w.length() - mRad; if (dot >= mVLenSqr) return (p - mPt2).length() - mRad; - const float t = w.dot(mV)/mVLenSqr; + const ValueT t = w.dot(mV)/mVLenSqr; return (w - t * mV).length() - mRad; } @@ -690,11 +711,11 @@ class OpenTubeWedgeVoxelizer inline bool tileCanFit(const Index& dim) const { - return mRad >= BaseT::halfWidth() + 0.5f * (float(dim)-1.0f); + return mRad >= BaseT::halfWidth() + ValueT(0.5) * (ValueT(dim)-ValueT(1)); } - std::function tubeBottomTopVertical = - [this](float& zb, float& zt, const float& x, const float& y) + std::function capsuleBottomTopVertical = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y); zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y); @@ -702,15 +723,15 @@ class OpenTubeWedgeVoxelizer return std::isfinite(zb) && std::isfinite(zt); }; - std::function tubeBottomTop = - [this](float& zb, float& zt, const float& x, const float& y) + std::function capsuleBottomTop = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { - float cylptb, cylptt; + ValueT cylptb, cylptt; if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y)) return false; - const float dotb = (Vec3s(x, y, cylptb) - mPt1).dot(mV); - const float dott = (Vec3s(x, y, cylptt) - mPt1).dot(mV); + const ValueT dotb = (Vec3T(x, y, cylptb) - mPt1).dot(mV); + const ValueT dott = (Vec3T(x, y, cylptt) - mPt1).dot(mV); if (dotb < 0) zb = sphere1Bottom(x, y); @@ -732,21 +753,21 @@ class OpenTubeWedgeVoxelizer intersectWedge<0,1>(zb, zt, x, y); intersectWedge<1,0>(zb, zt, x, y); - return inWedge(x, y, 0.5f*(zb+zt)); + return inWedge(x, y, ValueT(0.5)*(zb+zt)); }; template inline void - intersectWedge(float& zb, float& zt, const float& x, const float& y) + intersectWedge(ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { - const Vec3s& n0 = mPlaneNrmls[i]; + const Vec3T& n0 = mPlaneNrmls[i]; if (math::isApproxZero(n0.z())) return; - const float zp = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; + const ValueT zp = mPlaneXCoeffs[i]*x + mPlaneYCoeffs[i]*y + mPlaneOffsets[i]; - if (zb <= zp && zp <= zt && inHalfSpace(Vec3s(x, y, zp))) { + if (zb <= zp && zp <= zt && inHalfSpace(Vec3T(x, y, zp))) { if (n0.z() < 0) zb = zp; else @@ -755,49 +776,49 @@ class OpenTubeWedgeVoxelizer } inline bool - inWedge(const float& x, const float& y, const float& z) + inWedge(const ValueT& x, const ValueT& y, const ValueT& z) { - return inWedge(Vec3s(x, y, z)); + return inWedge(Vec3T(x, y, z)); } inline bool - inWedge(const Vec3s& pt) + inWedge(const Vec3T& pt) { return inHalfSpace<0>(pt) && inHalfSpace<1>(pt); } template inline bool - inHalfSpace(const Vec3s& pt) + inHalfSpace(const Vec3T& pt) { // allow points within a fuzzy fractional (index space) distance to the halfspace // this ensures the seams between open wedges and open prisms are completely filled in // assumes mPlaneNrmls[i] is a unit vector - static const float VOXFRAC = 0.125f; + static const ValueT VOXFRAC = 0.125; return mPlaneNrmls[i].dot(pt-mPt1) <= VOXFRAC; } // assumes tube is not vertical! inline bool - infiniteCylinderBottomTop(float& cylptb, float& cylptt, - const float& x, const float& y) const + infiniteCylinderBottomTop(ValueT& cylptb, ValueT& cylptt, + const ValueT& x, const ValueT& y) const { - const Vec2s q(x, y); + const Vec2T q(x, y); - const Vec2s qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; + const Vec2T qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; - const float t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; + const ValueT t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; - const Vec3s qproj3D = mPt1 + t * mV; + const Vec3T qproj3D = mPt1 + t * mV; - const float d2 = (q - qproj).lengthSqr(); + const ValueT d2 = (q - qproj).lengthSqr(); // outside of cylinder's 2D projection if (mORad2 < d2) return false; - const float h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); + const ValueT h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); cylptb = qproj3D[2] - h; cylptt = qproj3D[2] + h; @@ -805,85 +826,87 @@ class OpenTubeWedgeVoxelizer return true; } - inline float - lineBottom(const float& x) const + inline ValueT + lineBottom(const ValueT& x) const { return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff; } - inline float - lineTop(const float& x) const + inline ValueT + lineTop(const ValueT& x) const { return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff; } - inline float - circle1Bottom(const float& x) const + inline ValueT + circle1Bottom(const ValueT& x) const { return BaseT::circleBottom(mX1, mY1, mORad, x); } - inline float - circle1Top(const float& x) const + inline ValueT + circle1Top(const ValueT& x) const { return BaseT::circleTop(mX1, mY1, mORad, x); } - inline float - circle2Bottom(const float& x) const + inline ValueT + circle2Bottom(const ValueT& x) const { return BaseT::circleBottom(mX2, mY2, mORad, x); } - inline float - circle2Top(const float& x) const + inline ValueT + circle2Top(const ValueT& x) const { return BaseT::circleTop(mX2, mY2, mORad, x); } - inline float - sphere1Bottom(const float& x, const float& y) const + inline ValueT + sphere1Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y); } - inline float - sphere1Top(const float& x, const float& y) const + inline ValueT + sphere1Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y); } - inline float - sphere2Bottom(const float& x, const float& y) const + inline ValueT + sphere2Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y); } - inline float - sphere2Top(const float& x, const float& y) const + inline ValueT + sphere2Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y); } // world space points and radius inputs // initializes class members in index space + template inline bool - initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r, - const Vec3s& n1, const Vec3s& n2) + initialize(const math::Vec3& pt1, const math::Vec3& pt2, + const ScalarType& r, const math::Vec3& nrml1, + const math::Vec3& nrml2) { - const float vx = BaseT::voxelSize(), - hw = BaseT::halfWidth(); + const ValueT vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); // forces x1 <= x2 if (pt1[0] <= pt2[0]) { - mPt1 = pt1/vx; - mPt2 = pt2/vx; + mPt1 = Vec3T(pt1)/vx; + mPt2 = Vec3T(pt2)/vx; } else { - mPt1 = pt2/vx; - mPt2 = pt1/vx; + mPt1 = Vec3T(pt2)/vx; + mPt2 = Vec3T(pt1)/vx; } - mRad = r/vx; + mRad = ValueT(r)/vx; // tube has no volume if (math::isApproxZero(mRad)) @@ -907,8 +930,8 @@ class OpenTubeWedgeVoxelizer mYdiff = mY2 - mY1; mZdiff = mZ2 - mZ1; - mPt12d = Vec2s(mX1, mY1); - mPt22d = Vec2s(mX2, mY2); + mPt12d = Vec2T(mX1, mY1); + mPt22d = Vec2T(mX2, mY2); mV2d = mPt22d - mPt12d; mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); @@ -916,6 +939,8 @@ class OpenTubeWedgeVoxelizer mIsVertical = math::isApproxZero(mXYNorm); { + const Vec3T n1 = Vec3T(nrml1), n2 = Vec3T(nrml2); + // no direction to form the wedge if (math::isApproxZero(n1.lengthSqr()) || math::isApproxZero(n2.lengthSqr())) return false; @@ -933,26 +958,26 @@ class OpenTubeWedgeVoxelizer if (approxParallel(mPlaneNrmls[0], mPlaneNrmls[1])) { mDirVectors[1] = -mDirVectors[0]; } else { - if (mPlaneNrmls[1].dot(mDirVectors[0]) > 0.0f) - mDirVectors[0] *= -1.0f; - if (mPlaneNrmls[0].dot(mDirVectors[1]) > 0.0f) - mDirVectors[1] *= -1.0f; + if (mPlaneNrmls[1].dot(mDirVectors[0]) > 0) + mDirVectors[0] *= ValueT(-1); + if (mPlaneNrmls[0].dot(mDirVectors[1]) > 0) + mDirVectors[1] *= ValueT(-1); } - mPlanePts[0] = mPt1 + mDirVectors[0] + 0.025f * mPlaneNrmls[0]; - mPlanePts[1] = mPt1 + mDirVectors[1] + 0.025f * mPlaneNrmls[1]; + mPlanePts[0] = mPt1 + mDirVectors[0] + ValueT(0.025) * mPlaneNrmls[0]; + mPlanePts[1] = mPt1 + mDirVectors[1] + ValueT(0.025) * mPlaneNrmls[1]; } { - mPlaneXCoeffs.assign(2, 0.0f); - mPlaneYCoeffs.assign(2, 0.0f); - mPlaneOffsets.assign(2, 0.0f); + mPlaneXCoeffs.assign(2, ValueT(0)); + mPlaneYCoeffs.assign(2, ValueT(0)); + mPlaneOffsets.assign(2, ValueT(0)); for (Index i = 0; i < 2; ++i) { if (!math::isApproxZero(mPlaneNrmls[i].z())) { - const float cx = mPlaneNrmls[i].x()/mPlaneNrmls[i].z(), - cy = mPlaneNrmls[i].y()/mPlaneNrmls[i].z(); - const Vec3s p = mPlanePts[i]; + const ValueT cx = mPlaneNrmls[i].x()/mPlaneNrmls[i].z(), + cy = mPlaneNrmls[i].y()/mPlaneNrmls[i].z(); + const Vec3T p = mPlanePts[i]; mPlaneXCoeffs[i] = -cx; mPlaneYCoeffs[i] = -cy; mPlaneOffsets[i] = p.x()*cx + p.y()*cy + p.z(); @@ -960,19 +985,19 @@ class OpenTubeWedgeVoxelizer } } - BaseT::bottomTop = mIsVertical ? tubeBottomTopVertical : tubeBottomTop; + BaseT::bottomTop = mIsVertical ? capsuleBottomTopVertical : capsuleBottomTop; return true; } inline bool - approxAntiParallel(const Vec3s& n1, const Vec3s& n2) + approxAntiParallel(const Vec3T& n1, const Vec3T& n2) { return approxParallel(n1, -n2); } inline bool - approxParallel(const Vec3s& n1, const Vec3s& n2) + approxParallel(const Vec3T& n1, const Vec3T& n2) { return n1.unitSafe().eq(n2.unitSafe()); } @@ -981,33 +1006,38 @@ class OpenTubeWedgeVoxelizer // tube data -- populated via initialize() - Vec3s mPt1, mPt2, mV; + Vec3T mPt1, mPt2, mV; - Vec2s mPt12d, mPt22d, mV2d; + Vec2T mPt12d, mPt22d, mV2d; - float mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; + ValueT mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; - float mX1, mY1, mZ1, mX2, mY2, mZ2; + ValueT mX1, mY1, mZ1, mX2, mY2, mZ2; bool mIsVertical; - std::vector mPlaneNrmls = std::vector(2), - mDirVectors = std::vector(2), - mPlanePts = std::vector(2); + std::vector mPlaneNrmls = std::vector(2), + mDirVectors = std::vector(2), + mPlanePts = std::vector(2); - std::vector mPlaneXCoeffs = std::vector(2), - mPlaneYCoeffs = std::vector(2), - mPlaneOffsets = std::vector(2); + std::vector mPlaneXCoeffs = std::vector(2), + mPlaneYCoeffs = std::vector(2), + mPlaneOffsets = std::vector(2); -}; // class OpenTubeWedgeVoxelizer +}; // class OpenCapsuleWedgeVoxelizer /// @brief Class representing the connectivity of edges in a triangle mesh, /// where each edge is associated with the cells (triangles) sharing it. /// Provides methods to retrieve adjacent cells, /// vertex coordinates, normals, and other geometric properties. +template class TriangleMeshEdgeConnectivity { + static_assert(std::is_floating_point::value); + + using Vec3T = math::Vec3; + public: /// @brief Constructs the TriangleMeshEdgeConnectivity object with given coordinates and cell data. @@ -1015,7 +1045,8 @@ class TriangleMeshEdgeConnectivity { /// /// @param coords Vector of vertex coordinates. /// @param cells Vector of cell (triangle) indices. - TriangleMeshEdgeConnectivity(const std::vector& coords, const std::vector& cells) + TriangleMeshEdgeConnectivity(const std::vector& coords, + const std::vector& cells) : mCoords(coords), mCells(cells) { const Index n = Index(coords.size()); @@ -1038,7 +1069,7 @@ class TriangleMeshEdgeConnectivity { if (cell[0] >= n || cell[1] >= n || cell[2] >= n) OPENVDB_THROW(ValueError, "out of bounds index"); - const Vec3s &p1 = mCoords[cell[0]], + const Vec3T &p1 = mCoords[cell[0]], &p2 = mCoords[cell[1]], &p3 = mCoords[cell[2]]; @@ -1070,9 +1101,9 @@ class TriangleMeshEdgeConnectivity { /// @brief Retrieves the 3D coordinate at a given index. /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the vertex. - /// @return The 3D coordinate as a constant reference to Vec3s. + /// @return The 3D coordinate as a constant reference to Vec3T. template - inline const Vec3s& + inline const Vec3T& getCoord(const T& i) const { static_assert(std::is_integral::value, "Index must be an integral type"); @@ -1097,9 +1128,9 @@ class TriangleMeshEdgeConnectivity { /// primitive (triangle) at a given cell index. /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the cell (triangle). - /// @return A vector of three Vec3s representing the coordinates of the triangle's vertices. + /// @return A vector of three Vec3T representing the coordinates of the triangle's vertices. template - inline std::vector + inline std::vector getPrimitive(const T& i) const { static_assert(std::is_integral::value, "Index must be an integral type"); @@ -1112,9 +1143,9 @@ class TriangleMeshEdgeConnectivity { /// @brief Retrieves the unit normal vector of a cell (triangle) at a given index. /// @tparam T Any integral type (int, unsigned int, size_t, etc.) /// @param i Index of the cell. - /// @return The normal vector of the triangle as a Vec3s. + /// @return The normal vector of the triangle as a Vec3T. template - inline Vec3s + inline Vec3T getNormal(const T& i) const { static_assert(std::is_integral::value, "Index must be an integral type"); @@ -1155,7 +1186,7 @@ class TriangleMeshEdgeConnectivity { } }; - inline Vec3s + inline Vec3T centroid(Index cellIdx) const { const Vec3I cell = mCells[cellIdx]; @@ -1163,7 +1194,7 @@ class TriangleMeshEdgeConnectivity { } inline bool - onSameHalfPlane(const Vec3s &n, const Vec3s& p0, const Vec3s &p1, const Vec3s &p2) + onSameHalfPlane(const Vec3T &n, const Vec3T& p0, const Vec3T &p1, const Vec3T &p2) { return math::Abs(math::Sign(n.dot(p1-p0)) - math::Sign(n.dot(p2-p0))) != 2; } @@ -1178,13 +1209,14 @@ class TriangleMeshEdgeConnectivity { const Index p1Ind = base_cell[0] + base_cell[1] + base_cell[2] - offset; - const Vec3s &p1 = mCoords[p1Ind], + const Vec3T &p1 = mCoords[p1Ind], &n1 = mNormals[cells[0]]; - const Vec3s p0 = mCoords[edge.mV1]; + const Vec3T p0 = mCoords[edge.mV1]; - Vec3s bi_nrml = n1.cross(p0 - mCoords[edge.mV2]); - if (bi_nrml.dot(p1 - p0) > 0) bi_nrml *= -1.0f; + Vec3T bi_nrml = n1.cross(p0 - mCoords[edge.mV2]); + if (bi_nrml.dot(p1 - p0) > 0) + bi_nrml *= ValueT(-1); auto windingamount = [&](Index cellIdx) { @@ -1193,16 +1225,19 @@ class TriangleMeshEdgeConnectivity { const Vec3I &cell = mCells[cellIdx]; const Index p2Ind = cell[0] + cell[1] + cell[2] - offset; - const Vec3s &p2 = mCoords[p2Ind], + const Vec3T &p2 = mCoords[p2Ind], &n2 = mNormals[cellIdx]; - const float cos_theta = math::Abs(n1.dot(n2)); + const ValueT cos_theta = math::Abs(n1.dot(n2)); const int sgn = math::Sign(n1.dot(p2 - p1)), sgn2 = math::Sign(bi_nrml.dot(p2 - p0)); return sgn != 0 - ? (sgn == 1 ? 1.0f + float(sgn2) * cos_theta : 3.0f - float(sgn2) * cos_theta) - : (onSameHalfPlane(bi_nrml, p0, p1, p2) ? 0.0f : 2.0f); + ? (sgn == 1 + ? ValueT(1) + ValueT(sgn2) * cos_theta + : ValueT(3) - ValueT(sgn2) * cos_theta + ) + : (onSameHalfPlane(bi_nrml, p0, p1, p2) ? ValueT(0) : ValueT(2)); }; std::sort(cells.begin(), cells.end(), [&](const Index& t1, const Index& t2) { @@ -1212,10 +1247,10 @@ class TriangleMeshEdgeConnectivity { // ------------ private members ------------ - const std::vector& mCoords; + const std::vector& mCoords; const std::vector& mCells; - std::vector mNormals; + std::vector mNormals; std::map> mEdgeMap; @@ -1226,19 +1261,26 @@ class TriangleMeshEdgeConnectivity { /// representation of a dilated mesh (surface mesh dilated by a radius in all directions). /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the mesh vertex and radius type +/// and must be a floating-point scalar. +template class DilatedMeshVoxelizer { - using GridPtr = typename GridT::Ptr; - using TreeT = typename GridT::TreeType; + using GridPtr = typename GridType::Ptr; + using TreeT = typename GridType::TreeType; using LeafT = typename TreeT::LeafNodeType; using PartitionerT = tools::PointPartitioner; - using PrismVoxelizer = OpenTriangularPrismVoxelizer; - using WedgeVoxelizer = OpenTubeWedgeVoxelizer; + using PrismVoxelizer = OpenTriangularPrismVoxelizer; + using WedgeVoxelizer = OpenCapsuleWedgeVoxelizer; + + using MeshConnectivity = TriangleMeshEdgeConnectivity; + + using Vec3T = math::Vec3; - using MeshConnectivity = TriangleMeshEdgeConnectivity; + static_assert(std::is_floating_point::value); public: @@ -1248,21 +1290,16 @@ class DilatedMeshVoxelizer { /// @param triangles triangle indices indices in the mesh /// @param radius radius of all tubes in world units /// @param voxelSize voxel size in world units - /// @param background background value in voxel units + /// @param halfWidth half-width in voxel units /// @param interrupter pointer to optional interrupter. Use template /// argument util::NullInterrupter if no interruption is desired. - /// @param grid optional grid to populate into (grid need not be empty). - DilatedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, - float radius, float voxelSize, float background, - InterruptT* interrupter, GridPtr grid = nullptr) + DilatedMeshVoxelizer(const std::vector& vertices, const std::vector& triangles, + ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter) : mMesh(std::make_shared(MeshConnectivity(vertices, triangles))) - , mVox(voxelSize), mBg(background), mRad(radius) + , mVox(voxelSize), mHw(halfWidth), mRad(radius) , mInterrupter(interrupter) { - if (!grid) - initializeGrid(); - else - mGrid = grid; + initializeGrid(); if constexpr (PtPartition) initializePartitioner(); @@ -1272,9 +1309,8 @@ class DilatedMeshVoxelizer { } DilatedMeshVoxelizer(DilatedMeshVoxelizer& other, tbb::split) - : mMesh(other.mMesh) - , mVox(other.mVox), mBg(other.mBg), mRad(other.mRad) - , mInterrupter(other.mInterrupter) + : mMesh(other.mMesh), mVox(other.mVox), mHw(other.mHw) + , mRad(other.mRad), mInterrupter(other.mInterrupter) , mPtPartitioner(other.mPtPartitioner) { initializeGrid(); @@ -1316,9 +1352,9 @@ class DilatedMeshVoxelizer { private: inline bool - affinelyIndependent(const Vec3s& p1, const Vec3s& p2, const Vec3s& p3) const + affinelyIndependent(const Vec3T& p1, const Vec3T& p2, const Vec3T& p3) const { - const Vec3s n = (p2-p1).cross(p3-p1); + const Vec3T n = (p2-p1).cross(p3-p1); return !math::isApproxZero(n.x()) || !math::isApproxZero(n.y()) || !math::isApproxZero(n.z()); @@ -1328,10 +1364,11 @@ class DilatedMeshVoxelizer { voxelizeTriangle(const size_t& i) { const Vec3I &cell = mMesh->getCell(i); - const std::vector pts = mMesh->getPrimitive(i); + const std::vector pts = mMesh->getPrimitive(i); + // degenerate triangle if (!affinelyIndependent(pts[0], pts[1], pts[2])) { - voxelizeTube(pts[0], pts[1], pts[2]); + voxelizeCapsule(pts[0], pts[1], pts[2]); return; } @@ -1339,7 +1376,7 @@ class DilatedMeshVoxelizer { (*mPVoxelizer)(pts[0], pts[1], pts[2], mRad); std::vector cellIds; - Vec3s n1, n2; + Vec3T n1, n2; // wedges for (Index j = 0; j < 3; ++j) { @@ -1352,15 +1389,15 @@ class DilatedMeshVoxelizer { } inline void - voxelizeTube(const Vec3s& p1, const Vec3s& p2, const Vec3s& p3) + voxelizeCapsule(const Vec3T& p1, const Vec3T& p2, const Vec3T& p3) { - lvlset::CapsuleVoxelizer voxelizer(mGrid, false); + lvlset::CapsuleVoxelizer voxelizer(mGrid, false); - float d1 = (p2-p1).lengthSqr(), - d2 = (p3-p2).lengthSqr(), - d3 = (p1-p3).lengthSqr(); + ScalarType d1 = (p2-p1).lengthSqr(), + d2 = (p3-p2).lengthSqr(), + d3 = (p1-p3).lengthSqr(); - float maxd = math::Max(d1, d2, d3); + ScalarType maxd = math::Max(d1, d2, d3); if (maxd == d1) voxelizer(p1, p2, mRad); @@ -1372,7 +1409,7 @@ class DilatedMeshVoxelizer { inline bool findWedgeNormals(const Index& cellIdx, const Index& vIdx, - const std::vector& cellIds, Vec3s& n1, Vec3s& n2) const + const std::vector& cellIds, Vec3T& n1, Vec3T& n2) const { if (cellIds.size() == 1) return findWedgeNormals1(cellIdx, vIdx, n1, n2); @@ -1386,14 +1423,14 @@ class DilatedMeshVoxelizer { inline bool findWedgeNormals1(const Index& cellIdx, const Index& vIdx, - Vec3s& n1, Vec3s& n2) const + Vec3T& n1, Vec3T& n2) const { const Vec3I &cell = mMesh->getCell(cellIdx); - const Vec3s &p1 = mMesh->getCoord(cell[vIdx]), + const Vec3T &p1 = mMesh->getCoord(cell[vIdx]), &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]), &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]); - const Vec3s &n = mMesh->getNormal(cellIdx); + const Vec3T &n = mMesh->getNormal(cellIdx); n1 = n.cross(p2-p1).unitSafe(); if (n1.dot(p3-p1) < 0) n1 *= -1.0f; @@ -1405,19 +1442,19 @@ class DilatedMeshVoxelizer { inline bool findWedgeNormals2(const Index& cellIdx, const Index& vIdx, - const Index& cellIdx2, Vec3s& n1, Vec3s& n2) const + const Index& cellIdx2, Vec3T& n1, Vec3T& n2) const { const Vec3I &cell = mMesh->getCell(cellIdx), &cell2 = mMesh->getCell(cellIdx2); const Index cIdx2 = cell2[0] + cell2[1] + cell2[2] - cell[vIdx] - cell[(vIdx+1) % 3]; - const Vec3s &p1 = mMesh->getCoord(cell[vIdx]), + const Vec3T &p1 = mMesh->getCoord(cell[vIdx]), &p2 = mMesh->getCoord(cell[(vIdx+1) % 3]), &p3 = mMesh->getCoord(cell[(vIdx+2) % 3]), &p4 = mMesh->getCoord(cIdx2); - const Vec3s &nrml1 = mMesh->getNormal(cellIdx), + const Vec3T &nrml1 = mMesh->getNormal(cellIdx), &nrml2 = mMesh->getNormal(cellIdx2); n1 = nrml1.cross(p2-p1).unitSafe(), @@ -1431,7 +1468,7 @@ class DilatedMeshVoxelizer { inline bool findWedgeNormals3(const Index& cellIdx, const Index& vIdx, - const std::vector& cellIds, Vec3s& n1, Vec3s& n2) const + const std::vector& cellIds, Vec3T& n1, Vec3T& n2) const { const Vec3I &cell = mMesh->getCell(cellIdx); @@ -1447,15 +1484,15 @@ class DilatedMeshVoxelizer { cIdx1 = cell1[0] + cell1[1] + cell1[2] - offset, cIdx2 = cell2[0] + cell2[1] + cell2[2] - offset; - const Vec3s &p0 = mMesh->getCoord(cIdx0), + const Vec3T &p0 = mMesh->getCoord(cIdx0), &p1 = mMesh->getCoord(cIdx1), &p2 = mMesh->getCoord(cIdx2); - Vec3s nrml0 = mMesh->getNormal(cellIds[i]), + Vec3T nrml0 = mMesh->getNormal(cellIds[i]), nrml1 = mMesh->getNormal(cellIds[(i+1) % n]); - if (nrml0.dot(p1-p0) > 0) nrml0 *= -1.0f; - if (nrml1.dot(p0-p1) > 0) nrml1 *= -1.0f; + if (nrml0.dot(p1-p0) > 0) nrml0 *= ScalarType(-1); + if (nrml1.dot(p0-p1) > 0) nrml1 *= ScalarType(-1); if (nrml0.dot(p2-p0) > 0 || nrml1.dot(p2-p1) > 0) continue; @@ -1475,15 +1512,15 @@ class DilatedMeshVoxelizer { } inline void - computeCentroids(std::vector& centroids) + computeCentroids(std::vector& centroids) { centroids.resize(mMesh->cellCount()); tbb::parallel_for(tbb::blocked_range(0, centroids.size()), [&](const tbb::blocked_range& r) { for (size_t i = r.begin(); i != r.end(); ++i) { - const std::vector prim = mMesh->getPrimitive(i); - centroids[i] = (prim[0] + prim[1] + prim[2]) / 3.0f; + const std::vector prim = mMesh->getPrimitive(i); + centroids[i] = (prim[0] + prim[1] + prim[2]) / ScalarType(3); } }); } @@ -1491,19 +1528,16 @@ class DilatedMeshVoxelizer { inline void initializeGrid() { - math::Transform transform(*(math::Transform::createLinearTransform(mVox))); - mGrid = GridPtr(new GridT(mBg)); - mGrid->setTransform(transform.copy()); - mGrid->setGridClass(GRID_LEVEL_SET); + mGrid = createLevelSet(mVox, mHw); } inline void initializePartitioner() { - std::vector centroids; + std::vector centroids; computeCentroids(centroids); - lvlset::PointArray points(centroids); + lvlset::PointArray points(centroids); mPtPartitioner = std::make_shared(); mPtPartitioner->construct(points, mGrid->transform()); @@ -1523,7 +1557,9 @@ class DilatedMeshVoxelizer { std::shared_ptr mMesh; - const float mVox, mBg, mRad; + const float mVox, mHw; + + const ScalarType mRad; InterruptT* mInterrupter; @@ -1541,16 +1577,18 @@ class DilatedMeshVoxelizer { // ------------ createLevelSetDilatedMesh ------------- // -template +template typename GridType::Ptr createLevelSetDilatedMesh( - const std::vector& vertices, const std::vector& triangles, - float radius, float voxelSize, float halfWidth, InterruptT* interrupter) + const std::vector>& vertices, const std::vector& triangles, + ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter) { + static_assert(std::is_floating_point::value); + using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; - using Voxelizer = typename lvlset::DilatedMeshVoxelizer; + using Voxelizer = typename lvlset::DilatedMeshVoxelizer; static_assert(std::is_floating_point::value, "createLevelSetDilatedMesh must return a scalar grid"); @@ -1558,7 +1596,7 @@ createLevelSetDilatedMesh( if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - Voxelizer op(vertices, triangles, radius, voxelSize, voxelSize * halfWidth, interrupter); + Voxelizer op(vertices, triangles, radius, voxelSize, halfWidth, interrupter); const tbb::blocked_range triangleRange(0, op.bucketSize()); tbb::parallel_reduce(triangleRange, op); @@ -1569,12 +1607,14 @@ createLevelSetDilatedMesh( return grid; } -template +template typename GridType::Ptr createLevelSetDilatedMesh( - const std::vector& vertices, const std::vector& quads, - float radius, float voxelSize, float halfWidth, InterruptT* interrupter) + const std::vector>& vertices, const std::vector& quads, + ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter) { + static_assert(std::is_floating_point::value); + using ValueT = typename GridType::ValueType; static_assert(std::is_floating_point::value, @@ -1595,16 +1635,18 @@ createLevelSetDilatedMesh( } }); - return createLevelSetDilatedMesh(vertices, triangles, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh( + vertices, triangles, radius, voxelSize, halfWidth, interrupter); } -template +template typename GridType::Ptr -createLevelSetDilatedMesh(const std::vector& vertices, +createLevelSetDilatedMesh(const std::vector>& vertices, const std::vector& triangles, const std::vector& quads, - float radius, float voxelSize, float halfWidth, InterruptT* interrupter) + ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter) { + static_assert(std::is_floating_point::value); + using ValueT = typename GridType::ValueType; static_assert(std::is_floating_point::value, @@ -1614,8 +1656,8 @@ createLevelSetDilatedMesh(const std::vector& vertices, if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); if (quads.empty()) - return createLevelSetDilatedMesh(vertices, triangles, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh( + vertices, triangles, radius, voxelSize, halfWidth, interrupter); const Index64 tn = triangles.size(), qn = quads.size(); const Index64 qn2 = tn + qn; @@ -1637,8 +1679,8 @@ createLevelSetDilatedMesh(const std::vector& vertices, } }); - return createLevelSetDilatedMesh(vertices, tris, radius, - voxelSize, halfWidth, interrupter); + return createLevelSetDilatedMesh( + vertices, tris, radius, voxelSize, halfWidth, interrupter); } diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index 81287ddfdc..ed1aefb10a 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -18,6 +18,7 @@ #include "PointPartitioner.h" #include "Prune.h" +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include namespace openvdb { @@ -47,10 +49,12 @@ namespace tools { /// @param threaded If true multi-threading is enabled (true by default). /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the capsule endpoint and radius type +/// and must be a floating-point scalar. +template typename GridType::Ptr -createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, - float halfWidth = float(LEVEL_SET_HALF_WIDTH), +createLevelSetCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr, bool threaded = true); /// @brief Return a grid of type @c GridType containing a narrow-band level set @@ -64,10 +68,13 @@ createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float vo /// @param threaded If true multi-threading is enabled (true by default). /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the capsule endpoint and radius type +/// and must be a floating-point scalar. +template typename GridType::Ptr -createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, - float halfWidth = float(LEVEL_SET_HALF_WIDTH), bool threaded = true); +createLevelSetCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), + bool threaded = true); /// @brief Return a grid of type @c GridType containing a narrow-band level set @@ -84,9 +91,12 @@ createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float vo /// @param threaded If true multi-threading is enabled (true by default). /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the tapered capsule endpoint and radius type +/// and must be a floating-point scalar. +template typename GridType::Ptr -createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, +createLevelSetTaperedCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr, bool threaded = true); @@ -103,10 +113,13 @@ createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, /// @param threaded If true multi-threading is enabled (true by default). /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the tapered capsule endpoint and radius type +/// and must be a floating-point scalar. +template typename GridType::Ptr -createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, - float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), bool threaded = true); +createLevelSetTaperedCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius1, ScalarType radius2, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), bool threaded = true); /// @brief Different policies when creating a tube complex with varying radii /// @details @@ -137,11 +150,13 @@ enum TubeRadiiPolicy { TUBE_AUTOMATIC = 0, TUBE_VERTEX_RADII, TUBE_SEGMENT_RADII /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. -template +/// @note @c ScalarType represents the capsule complex vertex and radius type +/// and must be a floating-point scalar. +template typename GridType::Ptr -createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, - float radius, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), - InterruptT* interrupter = nullptr); +createLevelSetTubeComplex(const std::vector>& vertices, + const std::vector& segments, ScalarType radius, float voxelSize, + float halfWidth = float(LEVEL_SET_HALF_WIDTH), InterruptT* interrupter = nullptr); /// @brief Return a grid of type @c GridType containing a narrow-band level set /// representation of a tube complex (a collection of tubes defined by endpoint coordinates, segment indices, and radii). @@ -155,11 +170,14 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< /// @param interrupter Interrupter adhering to the util::NullInterrupter interface. /// /// @note @c GridType::ValueType must be a floating-point scalar. -/// @note The automatic @c TubeRadiiPolicy chooses the valid per-segment or per-vertex policy, defaulting to per-vertex if both are valid. -template +/// @note @c ScalarType represents the capsule complex vertex and radius type +/// and must be a floating-point scalar. +/// @note The automatic @c TubeRadiiPolicy chooses the valid per-segment or per-vertex policy, +/// defaulting to per-vertex if both are valid. +template typename GridType::Ptr -createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, - const std::vector& radii, float voxelSize, +createLevelSetTubeComplex(const std::vector>& vertices, + const std::vector& segments, const std::vector& radii, float voxelSize, float halfWidth = float(LEVEL_SET_HALF_WIDTH), TubeRadiiPolicy radii_policy = TUBE_AUTOMATIC, InterruptT* interrupter = nullptr); @@ -181,9 +199,6 @@ class CapsuleVoxelizer InterruptT> { using GridPtr = typename GridType::Ptr; - using ValueT = typename GridType::ValueType; - - // ------------ base class members ------------ using BaseT = ConvexVoxelizer< GridType, @@ -194,6 +209,10 @@ class CapsuleVoxelizer using BaseT::mXYData; using BaseT::tileCeil; + using ValueT = typename BaseT::ValueT; + using Vec3T = typename BaseT::Vec3T; + using Vec2T = typename BaseT::Vec2T; + public: friend class ConvexVoxelizer< @@ -222,9 +241,13 @@ class CapsuleVoxelizer /// @param pt1 first endpoint of the capsule in world units /// @param pt2 second endpoint of the capsule in world units /// @param radius radius of the capsule in world units + template void - operator()(const Vec3s& pt1, const Vec3s& pt2, const float& r) + operator()(const math::Vec3& pt1, + const math::Vec3& pt2, const ScalarType& r) { + static_assert(std::is_floating_point::value); + initialize(pt1, pt2, r); BaseT::iterate(); @@ -294,91 +317,91 @@ class CapsuleVoxelizer inline void setXYRangeData(const Index& step = 1) { - const float stepf = float(step); + const ValueT stepv = ValueT(step); // short circuit a vertical cylinder if (mIsVertical) { mXYData.reset(mX1 - mORad, mX1 + mORad, step); - for (float x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepf) + for (ValueT x = tileCeil(mX1 - mORad, step); x <= mX1 + mORad; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); return; } - const float v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); + const ValueT v = math::Min(mORad, mORad * math::Abs(mYdiff)/mXYNorm); - const float a0 = mX1 - mORad, - a1 = mX1 - v, - a2 = mX1 + v, - a3 = mX2 - v, - a4 = mX2 + v, - a5 = mX2 + mORad; + const ValueT a0 = mX1 - mORad, + a1 = mX1 - v, + a2 = mX1 + v, + a3 = mX2 - v, + a4 = mX2 + v, + a5 = mX2 + mORad; - const float tc0 = tileCeil(a0, step), - tc1 = tileCeil(a1, step), - tc2 = tileCeil(a2, step), - tc3 = tileCeil(a3, step), - tc4 = tileCeil(a4, step); + const ValueT tc0 = tileCeil(a0, step), + tc1 = tileCeil(a1, step), + tc2 = tileCeil(a2, step), + tc3 = tileCeil(a3, step), + tc4 = tileCeil(a4, step); mXYData.reset(a0, a5, step); - for (float x = tc0; x <= a1; x += stepf) + for (ValueT x = tc0; x <= a1; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = tc1; x <= math::Min(a2, a3); x += stepf) + for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv) mXYData.expandYRange(x, lineBottom(x), circle1Top(x)); } else { - for (float x = tc1; x <= math::Min(a2, a3); x += stepf) + for (ValueT x = tc1; x <= math::Min(a2, a3); x += stepv) mXYData.expandYRange(x, circle1Bottom(x), lineTop(x)); } } if (a2 < a3) { - for (float x = tc2; x <= a3; x += stepf) + for (ValueT x = tc2; x <= a3; x += stepv) mXYData.expandYRange(x, lineBottom(x), lineTop(x)); } else { if (mY2 <= mY1) { - for (float x = tc3; x <= a2; x += stepf) + for (ValueT x = tc3; x <= a2; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), circle1Top(x)); } else { - for (float x = tc3; x <= a2; x += stepf) + for (ValueT x = tc3; x <= a2; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle2Top(x)); } } if (!math::isApproxZero(mXdiff)) { if (mY1 > mY2) { - for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) + for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), lineTop(x)); } else { - for (float x = math::Max(tc2, tc3); x <= a4; x += stepf) + for (ValueT x = math::Max(tc2, tc3); x <= a4; x += stepv) mXYData.expandYRange(x, lineBottom(x), circle2Top(x)); } } - for (float x = tc4; x <= a5; x += stepf) + for (ValueT x = tc4; x <= a5; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); mXYData.trim(); } // distance in index space - inline float - signedDistance(const Vec3s& p) const + inline ValueT + signedDistance(const Vec3T& p) const { - const Vec3s w = p - mPt1; - const float dot = w.dot(mV); + const Vec3T w = p - mPt1; + const ValueT dot = w.dot(mV); // carefully short circuit with a fuzzy tolerance, which avoids division by small mVLenSqr - if (dot <= math::Tolerance::value()) + if (dot <= math::Tolerance::value()) return w.length() - mRad; if (dot >= mVLenSqr) return (p - mPt2).length() - mRad; - const float t = w.dot(mV)/mVLenSqr; + const ValueT t = w.dot(mV)/mVLenSqr; return (w - t * mV).length() - mRad; } @@ -386,14 +409,14 @@ class CapsuleVoxelizer inline bool tileCanFit(const Index& dim) const { - return mRad >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); + return mRad >= BaseT::halfWidth() + ValueT(0.70711) * (ValueT(dim)-ValueT(1)); } // vertical capsule // for a given x,y pair, find the z-range of a tube // z-range is bottom sphere cap to the top sphere cap in vertical case - std::function capsuleBottomTopVertical = - [this](float& zb, float& zt, const float& x, const float& y) + std::function capsuleBottomTopVertical = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { zb = BaseT::sphereBottom(mX1, mY1, math::Min(mZ1, mZ2), mORad, x, y); zt = BaseT::sphereTop(mX2, mY2, math::Max(mZ1, mZ2), mORad, x, y); @@ -405,15 +428,15 @@ class CapsuleVoxelizer // for a given x,y pair, find the z-range of a tube // first find the z-range as if its an infinite cylinder // then for each z-range endpoint, determine if it should be on a sphere cap - std::function capsuleBottomTop = - [this](float& zb, float& zt, const float& x, const float& y) + std::function capsuleBottomTop = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { - float cylptb, cylptt; + ValueT cylptb, cylptt; if (!infiniteCylinderBottomTop(cylptb, cylptt, x, y)) return false; - const float dotb = (Vec3s(x, y, cylptb) - mPt1).dot(mV); - const float dott = (Vec3s(x, y, cylptt) - mPt1).dot(mV); + const ValueT dotb = (Vec3T(x, y, cylptb) - mPt1).dot(mV); + const ValueT dott = (Vec3T(x, y, cylptt) - mPt1).dot(mV); if (dotb < 0) zb = sphere1Bottom(x, y); @@ -434,23 +457,23 @@ class CapsuleVoxelizer // assumes capsule is not vertical! inline bool - infiniteCylinderBottomTop(float& cylptb, float& cylptt, const float& x, const float& y) const + infiniteCylinderBottomTop(ValueT& cylptb, ValueT& cylptt, const ValueT& x, const ValueT& y) const { - const Vec2s q(x, y); + const Vec2T q(x, y); - const Vec2s qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; + const Vec2T qproj = mPt12d + mV2d*((q - mPt12d).dot(mV2d))/mXYNorm2; - const float t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; + const ValueT t = mX1 != mX2 ? (qproj[0] - mX1)/mXdiff : (qproj[1] - mY1)/mYdiff; - const Vec3s qproj3D = mPt1 + t * mV; + const Vec3T qproj3D = mPt1 + t * mV; - const float d2 = (q - qproj).lengthSqr(); + const ValueT d2 = (q - qproj).lengthSqr(); // outside of cylinder's 2D projection if (mORad2 < d2) return false; - const float h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); + const ValueT h = math::Sqrt((mORad2 - d2) * mVLenSqr/mXYNorm2); cylptb = qproj3D[2] - h; cylptt = qproj3D[2] + h; @@ -458,83 +481,85 @@ class CapsuleVoxelizer return true; } - inline float - lineBottom(const float& x) const + inline ValueT + lineBottom(const ValueT& x) const { return mY1 + (mYdiff*(x-mX1) - mORad * mXYNorm)/mXdiff; } - inline float - lineTop(const float& x) const + inline ValueT + lineTop(const ValueT& x) const { return mY1 + (mYdiff*(x-mX1) + mORad * mXYNorm)/mXdiff; } - inline float - circle1Bottom(const float& x) const + inline ValueT + circle1Bottom(const ValueT& x) const { return BaseT::circleBottom(mX1, mY1, mORad, x); } - inline float - circle1Top(const float& x) const + inline ValueT + circle1Top(const ValueT& x) const { return BaseT::circleTop(mX1, mY1, mORad, x); } - inline float - circle2Bottom(const float& x) const + inline ValueT + circle2Bottom(const ValueT& x) const { return BaseT::circleBottom(mX2, mY2, mORad, x); } - inline float - circle2Top(const float& x) const + inline ValueT + circle2Top(const ValueT& x) const { return BaseT::circleTop(mX2, mY2, mORad, x); } - inline float - sphere1Bottom(const float& x, const float& y) const + inline ValueT + sphere1Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX1, mY1, mZ1, mORad, x, y); } - inline float - sphere1Top(const float& x, const float& y) const + inline ValueT + sphere1Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX1, mY1, mZ1, mORad, x, y); } - inline float - sphere2Bottom(const float& x, const float& y) const + inline ValueT + sphere2Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX2, mY2, mZ2, mORad, x, y); } - inline float - sphere2Top(const float& x, const float& y) const + inline ValueT + sphere2Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX2, mY2, mZ2, mORad, x, y); } // world space points and radius inputs // initializes class members in index space + template inline void - initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r) + initialize(const math::Vec3& pt1, + const math::Vec3& pt2, const ScalarType& r) { - const float vx = BaseT::voxelSize(), - hw = BaseT::halfWidth(); + const ValueT vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); if (pt1[0] <= pt2[0]) { - mPt1 = pt1/vx; - mPt2 = pt2/vx; + mPt1 = Vec3T(pt1)/vx; + mPt2 = Vec3T(pt2)/vx; } else { - mPt1 = pt2/vx; - mPt2 = pt1/vx; + mPt1 = Vec3T(pt2)/vx; + mPt2 = Vec3T(pt1)/vx; } - mRad = r/vx; + mRad = ValueT(r)/vx; // padded radius used to populate the outer halfwidth of the sdf mORad = mRad + hw; @@ -550,8 +575,8 @@ class CapsuleVoxelizer mYdiff = mY2 - mY1; mZdiff = mZ2 - mZ1; - mPt12d = Vec2s(mX1, mY1); - mPt22d = Vec2s(mX2, mY2); + mPt12d = Vec2T(mX1, mY1); + mPt22d = Vec2T(mX2, mY2); mV2d = mPt22d - mPt12d; mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); @@ -565,13 +590,13 @@ class CapsuleVoxelizer // tube data -- populated via initialize() - Vec3s mPt1, mPt2, mV; + Vec3T mPt1, mPt2, mV; - Vec2s mPt12d, mPt22d, mV2d; + Vec2T mPt12d, mPt22d, mV2d; - float mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; + ValueT mORad, mORad2, mRad, mVLenSqr, mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2; - float mX1, mY1, mZ1, mX2, mY2, mZ2; + ValueT mX1, mY1, mZ1, mX2, mY2, mZ2; bool mIsVertical; @@ -590,9 +615,6 @@ class TaperedCapsuleVoxelizer InterruptT> { using GridPtr = typename GridType::Ptr; - using ValueT = typename GridType::ValueType; - - // ------------ base class members ------------ using BaseT = ConvexVoxelizer< GridType, @@ -603,6 +625,10 @@ class TaperedCapsuleVoxelizer using BaseT::mXYData; using BaseT::tileCeil; + using ValueT = typename BaseT::ValueT; + using Vec3T = typename BaseT::Vec3T; + using Vec2T = typename BaseT::Vec2T; + public: friend class ConvexVoxelizer< @@ -632,9 +658,13 @@ class TaperedCapsuleVoxelizer /// @param pt2 second endpoint of the tapered capsule in world units /// @param radius1 radius of the tapered capsule at @c pt1 in world units /// @param radius2 radius of the tapered capsule at @c pt2 in world units + template void - operator()(const Vec3s& pt1, const Vec3s& pt2, const float& radius1, const float& radius2) + operator()(const math::Vec3& pt1, const math::Vec3& pt2, + const ScalarType& radius1, const ScalarType& radius2) { + static_assert(std::is_floating_point::value); + // fail on degenerate inputs for now // ball @@ -643,7 +673,7 @@ class TaperedCapsuleVoxelizer "The tapered capsule is degenerate, in this case it is a ball. Consider using the CapsuleVoxelizer class instead."); } - // tube + // capsule if (math::Abs(radius1 - radius2) < 0.001f*BaseT::voxelSize()) { OPENVDB_THROW(RuntimeError, "The tapered capsule is degenerate, in this case it is a capsule. Consider using the CapsuleVoxelizer class instead."); @@ -659,17 +689,17 @@ class TaperedCapsuleVoxelizer inline void setXYRangeData(const Index& step = 1) { - const float stepf = float(step); + const ValueT stepv = ValueT(step); // short circuit when one circle is in the other if (mXYNorm2 <= mRdiff2) { if (mX1 - mORad1 <= mX2 - mORad2) { mXYData.reset(mX1 - mORad1, mX1 + mORad1, step); - for (float x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += stepf) + for (ValueT x = tileCeil(mX1 - mORad1, step); x <= mX1 + mORad1; x += stepv) mXYData.expandYRange(x, circle1Bottom(x), circle1Top(x)); } else { mXYData.reset(mX2 - mORad2, mX2 + mORad2, step); - for (float x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += stepf) + for (ValueT x = tileCeil(mX2 - mORad2, step); x <= mX2 + mORad2; x += stepv) mXYData.expandYRange(x, circle2Bottom(x), circle2Top(x)); } return; @@ -681,15 +711,15 @@ class TaperedCapsuleVoxelizer step ); - Vec2s p1t, p2t, p1b, p2b; + Vec2T p1t, p2t, p1b, p2b; const bool success = pullyPoints(p1t, p2t, p1b, p2b); if (success) { - setLineXYData(p1t, p2t, stepf); - setLineXYData(p1b, p2b, stepf); + setLineXYData(p1t, p2t, stepv); + setLineXYData(p1b, p2b, stepv); - setCircleXYData(p1t, p1b, stepf, true); // mPt1 - setCircleXYData(p2t, p2b, stepf, false); // mPt2 + setCircleXYData(p1t, p1b, stepv, true); // mPt1 + setCircleXYData(p2t, p2b, stepv, false); // mPt2 } mXYData.trim(); @@ -697,17 +727,17 @@ class TaperedCapsuleVoxelizer // https://en.wikipedia.org/wiki/Belt_problem#Pulley_problem inline bool - pullyPoints(Vec2s& p1t, Vec2s& p2t, Vec2s& p1b, Vec2s& p2b) const + pullyPoints(Vec2T& p1t, Vec2T& p2t, Vec2T& p1b, Vec2T& p2b) const { - const float diff = mXYNorm2 - mRdiff2; - if (diff < 0.0f) + const ValueT diff = mXYNorm2 - mRdiff2; + if (diff < 0) return false; - const float alpha = std::atan2(mYdiff, mXdiff), - theta = std::atan2(math::Sqrt(diff), mRdiff); + const ValueT alpha = std::atan2(mYdiff, mXdiff), + theta = std::atan2(math::Sqrt(diff), mRdiff); - const float sin1 = math::Sin(theta + alpha), sin2 = math::Sin(theta - alpha), - cos1 = math::Cos(theta + alpha), cos2 = math::Cos(theta - alpha); + const ValueT sin1 = math::Sin(theta + alpha), sin2 = math::Sin(theta - alpha), + cos1 = math::Cos(theta + alpha), cos2 = math::Cos(theta - alpha); p1t.x() = mX1 + mORad1*cos1; p1t.y() = mY1 + mORad1*sin1; p2t.x() = mX2 + mORad2*cos1; p2t.y() = mY2 + mORad2*sin1; @@ -718,38 +748,38 @@ class TaperedCapsuleVoxelizer } inline void - setLineXYData(const Vec2s& q1, const Vec2s& q2, const float& step) + setLineXYData(const Vec2T& q1, const Vec2T& q2, const ValueT& step) { - if (math::Abs(q1.x() - q2.x()) < math::Tolerance::value()) { - float x = tileCeil(q1.x(), step); + if (math::Abs(q1.x() - q2.x()) < math::Tolerance::value()) { + ValueT x = tileCeil(q1.x(), step); if (q1.x() == x) { mXYData.expandYRange(x, q1.y()); mXYData.expandYRange(x, q2.y()); } } else { const bool q1_left = q1.x() < q2.x(); - const float &x1 = q1_left ? q1.x() : q2.x(), - &y1 = q1_left ? q1.y() : q2.y(), - &x2 = q1_left ? q2.x() : q1.x(), - &y2 = q1_left ? q2.y() : q1.y(); - - float m = (y2 - y1)/(x2 - x1), - x = tileCeil(x1, step), - y = y1 + m * (x-x1), - delta = m * step; + const ValueT &x1 = q1_left ? q1.x() : q2.x(), + &y1 = q1_left ? q1.y() : q2.y(), + &x2 = q1_left ? q2.x() : q1.x(), + &y2 = q1_left ? q2.y() : q1.y(); + + ValueT m = (y2 - y1)/(x2 - x1), + x = tileCeil(x1, step), + y = y1 + m * (x-x1), + delta = m * step; for (; x <= x2; x += step, y += delta) mXYData.expandYRange(x, y); } } inline void - setCircleXYData(const Vec2s& q1, const Vec2s& q2, - const float& step, const bool is_pt1) + setCircleXYData(const Vec2T& q1, const Vec2T& q2, + const ValueT& step, const bool is_pt1) { - const Vec3s &p1 = is_pt1 ? mPt1 : mPt2; - const float &r1 = is_pt1 ? mORad1 : mORad2; + const Vec3T &p1 = is_pt1 ? mPt1 : mPt2; + const ValueT &r1 = is_pt1 ? mORad1 : mORad2; - const std::vector xs = { + const std::vector xs = { tileCeil(p1.x() - r1, step), tileCeil(math::Min(q1.x(), q2.x()), step), tileCeil(math::Max(q1.x(), q2.x()), step), @@ -763,40 +793,40 @@ class TaperedCapsuleVoxelizer } inline void - setCircleHiXYData(const float& x1, const float& x2, - const float& step, const bool& is_pt1) + setCircleHiXYData(const ValueT& x1, const ValueT& x2, + const ValueT& step, const bool& is_pt1) { - const float x_test = static_cast(math::Floor(0.5f*(x1+x2))); + const ValueT x_test = static_cast(math::Floor(ValueT(0.5)*(x1+x2))); if (is_pt1) { // if |x2-x1| is small, our test point might be too close to the pulley point if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle1Top(x_test)) { - for (float x = x1; x < x2; x += step) + for (ValueT x = x1; x < x2; x += step) mXYData.expandYMax(x, circle1Top(x)); } } else { if (math::Abs(x2-x1) < 5 || mXYData.getYMax(x_test) <= circle2Top(x_test)) { - for (float x = x1; x < x2; x += step) + for (ValueT x = x1; x < x2; x += step) mXYData.expandYMax(x, circle2Top(x)); } } } inline void - setCircleLoXYData(const float& x1, const float& x2, - const float& step, const bool& is_pt1) + setCircleLoXYData(const ValueT& x1, const ValueT& x2, + const ValueT& step, const bool& is_pt1) { - const float x_test = static_cast(math::Floor(0.5f*(x1+x2))); + const ValueT x_test = static_cast(math::Floor(ValueT(0.5)*(x1+x2))); if (is_pt1) { // if |x2-x1| is small, our test point might be too close to the pulley point if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle1Bottom(x_test)) { - for (float x = x1; x < x2; x += step) + for (ValueT x = x1; x < x2; x += step) mXYData.expandYMin(x, circle1Bottom(x)); } } else { if (math::Abs(x2-x1) < 5 || mXYData.getYMin(x_test) >= circle2Bottom(x_test)) { - for (float x = x1; x < x2; x += step) + for (ValueT x = x1; x < x2; x += step) mXYData.expandYMin(x, circle2Bottom(x)); } } @@ -804,22 +834,22 @@ class TaperedCapsuleVoxelizer // Round Cone: https://iquilezles.org/articles/distfunctions/ // distance in index space - inline float - signedDistance(const Vec3s& p) const + inline ValueT + signedDistance(const Vec3T& p) const { - const Vec3s w = p - mPt1; - const float y = w.dot(mV), - z = y - mVLenSqr, - x2 = (w*mVLenSqr - mV*y).lengthSqr(), - y2 = y*y*mVLenSqr, - z2 = z*z*mVLenSqr, - k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive + const Vec3T w = p - mPt1; + const ValueT y = w.dot(mV), + z = y - mVLenSqr, + x2 = (w*mVLenSqr - mV*y).lengthSqr(), + y2 = y*y*mVLenSqr, + z2 = z*z*mVLenSqr, + k = mRdiff2*x2; // should multiply by sgn(mRdiff), but it's always positive - if (float(math::Sign(z))*mA2*z2 >= k) - return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2; + if (ValueT(math::Sign(z))*mA2*z2 >= k) + return math::Sqrt(x2 + z2)*mInvVLenSqr - mRad2; - if (float(math::Sign(y))*mA2*y2 <= k) - return math::Sqrt(x2 + y2)*mInvVLenSqr - mRad1; + if (ValueT(math::Sign(y))*mA2*y2 <= k) + return math::Sqrt(x2 + y2)*mInvVLenSqr - mRad1; return (math::Sqrt(x2*mA2*mInvVLenSqr) + y*mRdiff)*mInvVLenSqr - mRad1; } @@ -827,13 +857,14 @@ class TaperedCapsuleVoxelizer inline bool tileCanFit(const Index& dim) const { - return math::Max(mRad1, mRad2) >= BaseT::halfWidth() + 0.70711f * (float(dim)-1.0f); + // we know mRad1 >= mRad2 + return mRad1 >= BaseT::halfWidth() + ValueT(0.70711) * (ValueT(dim)-ValueT(1)); } - std::function TaperedCapsuleBottomTop = - [this](float& zb, float& zt, const float& x, const float& y) + std::function taperedCapsuleBottomTop = + [this](ValueT& zb, ValueT& zt, const ValueT& x, const ValueT& y) { - const Vec2s q(x, y); + const Vec2T q(x, y); const bool in_ball1 = (q - mPt12d).lengthSqr() <= mORad1Sqr, in_ball2 = (q - mPt22d).lengthSqr() <= mORad2Sqr; @@ -845,8 +876,8 @@ class TaperedCapsuleVoxelizer if (in_ball2) { if (in_ball1) { - const float zt2 = sphere2Top(x, y), - zb2 = 2.0f*mZ2 - zt2; + const ValueT zt2 = sphere2Top(x, y), + zb2 = 2.0f*mZ2 - zt2; zt = math::Max(zt, zt2); zb = math::Min(zb, zb2); @@ -858,17 +889,17 @@ class TaperedCapsuleVoxelizer // attempt to short circuit when top and bottom hits are on sphere caps if (in_ball1 || in_ball2) { - const float ht = float(mConeD.dot(Vec3s(x,y,zt) - mConeV)); + const double ht = mConeD.dot(Vec3d(x,y,zt) - mConeV); // top point is in one of the half spaces pointing away from the cone if (mH1 > ht || ht > mH2) { - const float hb = float(mConeD.dot(Vec3s(x,y,zb) - mConeV)); + const double hb = mConeD.dot(Vec3d(x,y,zb) - mConeV); // bottom point is in one of the half spaces pointing away from the cone if (mH1 > hb || hb > mH2) return true; } } - float conezb = 0.0f, conezt = 0.0f; + ValueT conezb = 0.0f, conezt = 0.0f; int cint_cnt; openConeFrustumBottomTop(conezb, conezt, cint_cnt, x, y); @@ -905,12 +936,13 @@ class TaperedCapsuleVoxelizer }; // https://www.geometrictools.com/Documentation/IntersectionLineCone.pdf + // works in double precision in case the cone tapers very slowly (r1 ~ r2) inline void - openConeFrustumBottomTop(float& conezb, float& conezt, int& cint_cnt, - const float& x, const float& y) const + openConeFrustumBottomTop(ValueT& conezb, ValueT& conezt, int& cint_cnt, + const ValueT& x, const ValueT& y) const { cint_cnt = 0; - const Vec3d p(x, y, mRayZ); + const Vec3d p(double(x), double(y), mRayZ); const Vec3d diff = p - mConeV; const double ddotdiff = mConeD.dot(diff); @@ -925,16 +957,16 @@ class TaperedCapsuleVoxelizer const double t1 = mC2Inv*(-c1 + sqrt); if (validFrustumRange(t1, ddotdiff)) { cint_cnt++; - conezb = float(mRayZ - t1); + conezb = ValueT(mRayZ - t1); } const double t2 = mC2Inv*(-c1 - sqrt); if (validFrustumRange(t2, ddotdiff)) { cint_cnt++; if (cint_cnt == 2 && t1 > t2) - conezt = float(mRayZ - t2); + conezt = ValueT(mRayZ - t2); else { conezt = conezb; - conezb = float(mRayZ - t2); + conezb = ValueT(mRayZ - t2); } } } @@ -942,7 +974,7 @@ class TaperedCapsuleVoxelizer const double t = -c0/(2.0f*c1); if (validFrustumRange(t, ddotdiff)) { cint_cnt = 1; - conezb = float(mRayZ - t); + conezb = ValueT(mRayZ - t); } } @@ -957,73 +989,75 @@ class TaperedCapsuleVoxelizer return mH1 <= h && h <= mH2; } - inline float - circle1Bottom(const float& x) const + inline ValueT + circle1Bottom(const ValueT& x) const { return BaseT::circleBottom(mX1, mY1, mORad1, x); } - inline float - circle1Top(const float& x) const + inline ValueT + circle1Top(const ValueT& x) const { return BaseT::circleTop(mX1, mY1, mORad1, x); } - inline float - circle2Bottom(const float& x) const + inline ValueT + circle2Bottom(const ValueT& x) const { return BaseT::circleBottom(mX2, mY2, mORad2, x); } - inline float - circle2Top(const float& x) const + inline ValueT + circle2Top(const ValueT& x) const { return BaseT::circleTop(mX2, mY2, mORad2, x); } - inline float - sphere1Bottom(const float& x, const float& y) const + inline ValueT + sphere1Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX1, mY1, mZ1, mORad1, x, y); } - inline float - sphere1Top(const float& x, const float& y) const + inline ValueT + sphere1Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX1, mY1, mZ1, mORad1, x, y); } - inline float - sphere2Bottom(const float& x, const float& y) const + inline ValueT + sphere2Bottom(const ValueT& x, const ValueT& y) const { return BaseT::sphereBottom(mX2, mY2, mZ2, mORad2, x, y); } - inline float - sphere2Top(const float& x, const float& y) const + inline ValueT + sphere2Top(const ValueT& x, const ValueT& y) const { return BaseT::sphereTop(mX2, mY2, mZ2, mORad2, x, y); } // world space points and radius inputs // initializes class members in index space + template inline void - initialize(const Vec3s& pt1, const Vec3s& pt2, const float& r1, const float& r2) + initialize(const math::Vec3& pt1, const math::Vec3& pt2, + const ScalarType& r1, const ScalarType& r2) { - const float vx = BaseT::voxelSize(), - hw = BaseT::halfWidth(); + const ValueT vx = BaseT::voxelSize(), + hw = BaseT::halfWidth(); // enforce mRad1 > mRad2 if (r2 <= r1) { - mPt1 = pt1/vx; - mPt2 = pt2/vx; - mRad1 = r1/vx; - mRad2 = r2/vx; + mPt1 = Vec3T(pt1)/vx; + mPt2 = Vec3T(pt2)/vx; + mRad1 = ValueT(r1)/vx; + mRad2 = ValueT(r2)/vx; } else { - mPt1 = pt2/vx; - mPt2 = pt1/vx; - mRad1 = r2/vx; - mRad2 = r1/vx; + mPt1 = Vec3T(pt2)/vx; + mPt2 = Vec3T(pt1)/vx; + mRad1 = ValueT(r2)/vx; + mRad2 = ValueT(r1)/vx; } // padded radii used to populate the outer halfwidth of the sdf @@ -1034,7 +1068,7 @@ class TaperedCapsuleVoxelizer mV = mPt2 - mPt1; mVLenSqr = mV.lengthSqr(); - mInvVLenSqr = mVLenSqr != 0.0f ? 1.0f/mVLenSqr : 1.0f; + mInvVLenSqr = mVLenSqr != ValueT(0) ? ValueT(1)/mVLenSqr : ValueT(1); mX1 = mPt1[0]; mY1 = mPt1[1]; mZ1 = mPt1[2]; mX2 = mPt2[0]; mY2 = mPt2[1]; mZ2 = mPt2[2]; @@ -1043,13 +1077,13 @@ class TaperedCapsuleVoxelizer mYdiff = mY2 - mY1; mZdiff = mZ2 - mZ1; - mPt12d = Vec2s(mX1, mY1); - mPt22d = Vec2s(mX2, mY2); + mPt12d = Vec2T(mX1, mY1); + mPt22d = Vec2T(mX2, mY2); mV2d = mPt22d - mPt12d; mXYNorm2 = math::Pow2(mXdiff) + math::Pow2(mYdiff); mXYNorm = math::Sqrt(mXYNorm2); - mIXYNorm2 = mXYNorm2 != 0.0f ? 1.0f/mXYNorm2 : 1.0f; + mIXYNorm2 = mXYNorm2 != ValueT(0) ? ValueT(1)/mXYNorm2 : ValueT(1); // mRdiff is non negative mRdiff = mRad1 - mRad2; @@ -1060,6 +1094,7 @@ class TaperedCapsuleVoxelizer // alpha is solid angle of cone // r1 != r2, since the object is not a capsule // P > abs(r1-r2), since one ball is not contained in the other + // we work in double precision in case csc is large const double P = mV.length(), csc = P/mRdiff, // csc(alpha/2) sin = mRdiff/P; // sin(alpha/2) @@ -1074,25 +1109,28 @@ class TaperedCapsuleVoxelizer mC2 = math::Pow2(mConeD.z()) - mGamma; mC2Inv = mC2 != 0.0 ? 1.0/mC2 : 1.0; - BaseT::bottomTop = TaperedCapsuleBottomTop; + BaseT::bottomTop = taperedCapsuleBottomTop; } // ------------ private members ------------ // tapered capsule data -- populated via initialize() - Vec3s mPt1, mPt2, mV; + Vec3T mPt1, mPt2, mV; - Vec3d mConeV, mConeD; + Vec2T mPt12d, mPt22d, mV2d; - Vec2s mPt12d, mPt22d, mV2d; + ValueT mORad1, mORad2, mORad1Sqr, mORad2Sqr, mRad1, mRad2, mVLenSqr, mInvVLenSqr, + mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2, mIXYNorm2, mRdiff, mRdiff2, mA2; - float mORad1, mORad2, mORad1Sqr, mORad2Sqr, mRad1, mRad2, mVLenSqr, mInvVLenSqr, - mXdiff, mYdiff, mZdiff, mXYNorm, mXYNorm2, mIXYNorm2, mRdiff, mRdiff2, mA2; + ValueT mX1, mY1, mZ1, mX2, mY2, mZ2; - double mRayZ, mGamma, mC2, mC2Inv, mH1, mH2; + // some members are stored explicitly as double because when + // the cone tapers very slowly (r1 ~ r2), then csc(cone_angle) can be very large - float mX1, mY1, mZ1, mX2, mY2, mZ2; + Vec3d mConeV, mConeD; + + double mRayZ, mGamma, mC2, mC2Inv, mH1, mH2; }; // class TaperedCapsuleVoxelizer @@ -1101,8 +1139,11 @@ class TaperedCapsuleVoxelizer /// representation of a tube complex. /// /// @note @c GridType::ValueType must be a floating-point scalar. +/// @note @c ScalarType represents the capsule complex vertex and radius type +/// and must be a floating-point scalar. /// @note Setting @c PerSegmentRadii to @c true gives a complex of capsules and a complex of tapered capsules otherwise. -template +template class TubeComplexVoxelizer { using GridPtr = typename GridType::Ptr; @@ -1111,6 +1152,10 @@ class TubeComplexVoxelizer { using PartitionerT = tools::PointPartitioner; + using Vec3T = math::Vec3; + + static_assert(std::is_floating_point::value); + public: /// @brief Constructor for constant radius @@ -1122,9 +1167,9 @@ class TubeComplexVoxelizer { /// @param background background value in voxel units /// @param interrupter pointer to optional interrupter. Use template /// argument util::NullInterrupter if no interruption is desired. - TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, - float radius, float voxelSize, float background, InterruptT* interrupter) - : mVox(voxelSize), mBg(background), mRad(radius) + TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, + ScalarType radius, float voxelSize, float halfWidth, InterruptT* interrupter) + : mVox(voxelSize), mHw(halfWidth), mRad(radius) , mCoords(vertices), mCells(segments), mRadii(getEmptyVector()) , mInterrupter(interrupter) { @@ -1138,17 +1183,17 @@ class TubeComplexVoxelizer { /// @param segments segment indices in the tube complex /// @param radii radii specification for all tubes in world units /// @param voxelSize voxel size in world units - /// @param background background value in voxel units + /// @param halfWidth half-width value in voxel units /// @param interrupter pointer to optional interrupter. Use template /// argument util::NullInterrupter if no interruption is desired. /// /// @note If @c PerSegmentRadii is set to @c true then @c segments and @c radii must have /// the same size. If @c PerSegmentRadii is set to @c false then @c vertices and @c radii /// must have the same size. - TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, - const std::vector& radii, float voxelSize, float background, - InterruptT* interrupter) - : mVox(voxelSize), mBg(background), mRad(0.0) + TubeComplexVoxelizer(const std::vector& vertices, const std::vector& segments, + const std::vector& radii, float voxelSize, float halfWidth, + InterruptT* interrupter) + : mVox(voxelSize), mHw(halfWidth), mRad(0.0) , mCoords(vertices), mCells(segments), mRadii(radii) , mInterrupter(interrupter) { @@ -1167,7 +1212,7 @@ class TubeComplexVoxelizer { } TubeComplexVoxelizer(TubeComplexVoxelizer& other, tbb::split) - : mVox(other.mVox), mBg(other.mBg), mRad(other.mRad) + : mVox(other.mVox), mHw(other.mHw), mRad(other.mRad) , mCoords(other.mCoords), mCells(other.mCells), mRadii(other.mRadii) , mPtPartitioner(other.mPtPartitioner), mInterrupter(other.mInterrupter) { @@ -1258,7 +1303,7 @@ class TubeComplexVoxelizer { inline void perVertexRadiusVoxelize(const tbb::blocked_range& rng) { - TaperedCapsuleVoxelizer rc_voxelizer(mGrid, false); + TaperedCapsuleVoxelizer tc_voxelizer(mGrid, false); CapsuleVoxelizer c_voxelizer(mGrid, false); @@ -1268,25 +1313,25 @@ class TubeComplexVoxelizer { const Vec2I& cell = mCells[k]; const Index32 &i = cell.x(), &j = cell.y(); - const Vec3s &pt1 = mCoords[i], &pt2 = mCoords[j]; - const float &r1 = mRadii[i], &r2 = mRadii[j]; + const Vec3T &pt1 = mCoords[i], &pt2 = mCoords[j]; + const ScalarType &r1 = mRadii[i], &r2 = mRadii[j]; if ((pt1 - pt2).lengthSqr() <= math::Pow2(r1-r2)) { // ball if (r1 >= r2) c_voxelizer(pt1, pt1, r1); else c_voxelizer(pt2, pt2, r2); - } else if (math::Abs(r1-r2) < 0.001f*mVox) { // tube + } else if (math::Abs(r1-r2) < 0.001*mVox) { // capsule c_voxelizer(pt1, pt2, r1); } else { - rc_voxelizer(pt1, pt2, r1, r2); + tc_voxelizer(pt1, pt2, r1, r2); } } } } inline void - computeCentroids(std::vector& centroids) + computeCentroids(std::vector& centroids) { const Index n = Index(mCoords.size()); @@ -1300,7 +1345,7 @@ class TubeComplexVoxelizer { if (cell[0] >= n || cell[1] >= n) OPENVDB_THROW(ValueError, "out of bounds index"); - centroids[i] = 0.5f * (mCoords[cell[0]] + mCoords[cell[1]]); + centroids[i] = ScalarType(0.5) * (mCoords[cell[0]] + mCoords[cell[1]]); } }); } @@ -1308,19 +1353,16 @@ class TubeComplexVoxelizer { inline void initializeGrid() { - math::Transform transform(*(math::Transform::createLinearTransform(mVox))); - mGrid = GridPtr(new GridType(mBg)); - mGrid->setTransform(transform.copy()); - mGrid->setGridClass(GRID_LEVEL_SET); + mGrid = createLevelSet(mVox, mHw); } inline void initializePartitioner() { - std::vector centroids; + std::vector centroids; computeCentroids(centroids); - lvlset::PointArray points(centroids); + lvlset::PointArray points(centroids); mPtPartitioner = std::make_shared(); mPtPartitioner->construct(points, mGrid->transform()); @@ -1336,19 +1378,21 @@ class TubeComplexVoxelizer { return true; } - static const std::vector& + static const std::vector& getEmptyVector() { - static const std::vector empty; + static const std::vector empty; return empty; } // ------------ private members ------------ - const float mVox, mBg, mRad; + const float mVox, mHw; + + const ScalarType mRad; - const std::vector& mCoords; + const std::vector& mCoords; const std::vector& mCells; - const std::vector& mRadii; + const std::vector& mRadii; std::shared_ptr mPtPartitioner; @@ -1365,15 +1409,18 @@ class TubeComplexVoxelizer { // constant radius -template +template typename GridType::Ptr -createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, - float radius, float voxelSize, float halfWidth, InterruptT* interrupter) +createLevelSetTubeComplex(const std::vector>& vertices, + const std::vector& segments, ScalarType radius, + float voxelSize, float halfWidth, InterruptT* interrupter) { + static_assert(std::is_floating_point::value); + using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; - using ComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + using ComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; static_assert(std::is_floating_point::value, "createLevelSetTubeComplex must return a scalar grid"); @@ -1381,8 +1428,7 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - const float background = voxelSize * halfWidth; - ComplexVoxelizer op(vertices, segments, radius, voxelSize, background, interrupter); + ComplexVoxelizer op(vertices, segments, radius, voxelSize, halfWidth, interrupter); const tbb::blocked_range segmentRange(0, op.bucketSize()); tbb::parallel_reduce(segmentRange, op); @@ -1395,17 +1441,19 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< // varying radii -template +template typename GridType::Ptr -createLevelSetTubeComplex(const std::vector& vertices, const std::vector& segments, - const std::vector& radii, float voxelSize, float halfWidth, - TubeRadiiPolicy radii_policy, InterruptT* interrupter) +createLevelSetTubeComplex(const std::vector>& vertices, + const std::vector& segments, const std::vector& radii, + float voxelSize, float halfWidth, TubeRadiiPolicy radii_policy, InterruptT* interrupter) { + static_assert(std::is_floating_point::value); + using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; - using CapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; - using TaperedCapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + using CapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; + using TaperedCapsuleComplexVoxelizer = typename lvlset::TubeComplexVoxelizer; static_assert(std::is_floating_point::value, "createLevelSetTubeComplex must return a scalar grid"); @@ -1436,12 +1484,11 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< OPENVDB_THROW(ValueError, "Invalid tube radii policy."); } - const float background = voxelSize * halfWidth; GridPtr tubegrid; if (vertices.size() == radii.size()) { TaperedCapsuleComplexVoxelizer op(vertices, segments, radii, - voxelSize, background, interrupter); + voxelSize, halfWidth, interrupter); const tbb::blocked_range segmentRange(0, op.bucketSize()); tbb::parallel_reduce(segmentRange, op); @@ -1449,7 +1496,7 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< tubegrid = op.getGrid(); } else { CapsuleComplexVoxelizer op(vertices, segments, radii, - voxelSize, background, interrupter); + voxelSize, halfWidth, interrupter); const tbb::blocked_range segmentRange(0, op.bucketSize()); tbb::parallel_reduce(segmentRange, op); @@ -1465,11 +1512,14 @@ createLevelSetTubeComplex(const std::vector& vertices, const std::vector< // ------------ createLevelSetCapsule ------------- // -template +template typename GridType::Ptr -createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, - float halfWidth, InterruptT* interrupter, bool threaded) +createLevelSetCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius, float voxelSize, float halfWidth, + InterruptT* interrupter, bool threaded) { + static_assert(std::is_floating_point::value); + using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; @@ -1481,12 +1531,7 @@ createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float vo if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - const ValueT background = voxelSize * halfWidth; - math::Transform transform(*(math::Transform::createLinearTransform(voxelSize))); - - GridPtr grid(new GridType(background)); - grid->setTransform(transform.copy()); - grid->setGridClass(GRID_LEVEL_SET); + GridPtr grid = createLevelSet(voxelSize, halfWidth); CapsuleVoxelizer voxelizer(grid, threaded, interrupter); voxelizer(pt1, pt2, radius); @@ -1494,23 +1539,26 @@ createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float vo return grid; } -template +template typename GridType::Ptr -createLevelSetCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius, float voxelSize, - float halfWidth, bool threaded) +createLevelSetCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius, float voxelSize, float halfWidth, bool threaded) { - return createLevelSetCapsule( + return createLevelSetCapsule( pt1, pt2, radius, voxelSize, halfWidth, nullptr, threaded); } // ------------ createLevelSetTaperedCapsule ------------- // -template +template typename GridType::Ptr -createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, +createLevelSetTaperedCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth, InterruptT* interrupter, bool threaded) { + static_assert(std::is_floating_point::value); + using GridPtr = typename GridType::Ptr; using ValueT = typename GridType::ValueType; @@ -1523,12 +1571,7 @@ createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - const ValueT background = voxelSize * halfWidth; - math::Transform transform(*(math::Transform::createLinearTransform(voxelSize))); - - GridPtr grid(new GridType(background)); - grid->setTransform(transform.copy()); - grid->setGridClass(GRID_LEVEL_SET); + GridPtr grid = createLevelSet(voxelSize, halfWidth); if ((pt1 - pt2).lengthSqr() <= math::Pow2(radius1 - radius2)) { // ball @@ -1538,7 +1581,7 @@ createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, else voxelizer(pt2, pt2, radius2); - } else if (math::Abs(radius1 - radius2) < 0.001f*voxelSize) { // tube + } else if (math::Abs(radius1 - radius2) < 0.001*voxelSize) { // capsule CapsuleVoxelizer voxelizer(grid, threaded, interrupter); voxelizer(pt1, pt2, radius1); @@ -1552,12 +1595,12 @@ createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, return grid; } -template +template typename GridType::Ptr -createLevelSetTaperedCapsule(const Vec3s& pt1, const Vec3s& pt2, float radius1, float radius2, - float voxelSize, float halfWidth, bool threaded) +createLevelSetTaperedCapsule(const math::Vec3& pt1, const math::Vec3& pt2, + ScalarType radius1, ScalarType radius2, float voxelSize, float halfWidth, bool threaded) { - return createLevelSetTaperedCapsule( + return createLevelSetTaperedCapsule( pt1, pt2, radius1, radius2, voxelSize, halfWidth, nullptr, threaded); } From d48b5cf6ea4fb19b6dae64bccea2961bb68f19fd Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Thu, 31 Oct 2024 22:18:43 -0400 Subject: [PATCH 15/19] Update ConvexVoxelizer.h Store reference to grid in ConvexVoxelizer instead of the tree. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/ConvexVoxelizer.h | 34 ++++++++++++------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/openvdb/openvdb/tools/ConvexVoxelizer.h b/openvdb/openvdb/tools/ConvexVoxelizer.h index 683b856d05..d39c6a4ea2 100644 --- a/openvdb/openvdb/tools/ConvexVoxelizer.h +++ b/openvdb/openvdb/tools/ConvexVoxelizer.h @@ -199,10 +199,8 @@ class ConvexVoxelizer /// @note The voxel size and half width are determined from the input grid, /// meaning the voxel size and background value need to be set prior to voxelization ConvexVoxelizer(GridPtr& grid, const bool& threaded = false, InterruptType* interrupter = nullptr) - : mTree(grid->tree()) + : mGrid(grid) , mVox(ValueT((grid->voxelSize())[0])) - , mBgF(ValueT(grid->background())) - , mNegBgF(ValueT(-(grid->background()))) , mHw(ValueT(grid->background()/(grid->voxelSize())[0])) , mBg(grid->background()) , mNegBg(-(grid->background())) @@ -259,7 +257,7 @@ class ConvexVoxelizer return; if (!mSerial) - tools::signedFloodFill(mTree); + tools::signedFloodFill(getTree()); } // ------------ derived classes need to implement these ------------ @@ -817,7 +815,7 @@ class ConvexVoxelizer const Index n = mXYData.size(); if (mSerial) { - CacheLastLeafAccessor acc(mTree); + CacheLastLeafAccessor acc(getTree()); for (Index i = 0; i < n; ++i) { if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) return; @@ -825,7 +823,7 @@ class ConvexVoxelizer iterateYZ(i, acc); } } else { - tbb::enumerable_thread_specific pool(mTree); + tbb::enumerable_thread_specific pool(getTree()); auto kernel = [&](const tbb::blocked_range& rng) { TreeT &tree = pool.local(); @@ -853,7 +851,7 @@ class ConvexVoxelizer void operator()(RangeT &r) { for (auto i=r.begin(); i!=r.end(); ++i) this->merge(*i);} void join(Op &other) { this->merge(*(other.mTree)); } void merge(TreeT &tree) { mTree->merge(tree, MERGE_ACTIVE_STATES); } - } op( mTree ); + } op( getTree() ); tbb::parallel_reduce(RangeT(pool.begin(), pool.end(), 4), op); } } @@ -891,7 +889,7 @@ class ConvexVoxelizer ijk[2] = Int32(z); const ValueT val = acc.template getValue<1>(ijk); - if (val == mNegBgF) { + if (val == mNegBg) { if constexpr (LeapFrog) acc.template leapUp(ijk, z); continue; } @@ -899,7 +897,7 @@ class ConvexVoxelizer p[2] = z; const ValueT dist = mVox * invokeSignedDistance(p); - if (dist <= mNegBgF) { + if (dist <= mNegBg) { acc.template setValueOff<1,false>(ijk, mNegBg); } else if (dist < val) { acc.template setValueOn<1,false>(ijk, dist); @@ -942,11 +940,11 @@ class ConvexVoxelizer p[2] = z; const ValueT dist = mVox * invokeSignedDistance(p); - if (dist <= mNegBgF) { + if (dist <= mNegBg) { early_break = true; z_stop = z; break; - } else if (dist < mBgF) { + } else if (dist < mBg) { acc.template setValueOn<1>(ijk, dist); } else { // dist >= mBg acc.template checkReset<1>(ijk); @@ -960,9 +958,9 @@ class ConvexVoxelizer p[2] = z; const ValueT dist = mVox * invokeSignedDistance(p); - if (dist <= mNegBgF) { + if (dist <= mNegBg) { break; - } else if (dist < mBgF) { + } else if (dist < mBg) { acc.template setValueOn<-1>(ijk, dist); } else { // dist >= mBg acc.template checkReset<-1>(ijk); @@ -976,7 +974,7 @@ class ConvexVoxelizer void tileIterateXYZ() { - AccessorT acc(mTree); + AccessorT acc(getTree()); for (Index i = 0; i < mXYData.size(); ++i) { if (mInterrupter && !(i & ((1 << 7) - 1)) && !checkInterrupter()) return; @@ -1159,6 +1157,8 @@ class ConvexVoxelizer return true; } + inline TreeT& getTree() { return mGrid->tree(); } + // ------------ private nested classes ------------ /// @brief A class that caches access to the last leaf node. @@ -1338,13 +1338,11 @@ class ConvexVoxelizer // ------------ private members ------------ - // grid & tree data - - TreeT &mTree; + GridPtr mGrid; const std::vector mTileSizes = treeTileSizes(); - const ValueT mVox, mBgF, mNegBgF, mHw, mBg, mNegBg; + const ValueT mVox, mHw, mBg, mNegBg; // misc From 397b003606533423476fd200e0267b3ccc1acb4d Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:02:55 -0500 Subject: [PATCH 16/19] TestLevelSetTubes.cc Adds test file for testing tubes. * to test capsule and tapered capsule area, volume, gauss&mean curvature analytically derived formulas * tests for capsules Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/unittest/CMakeLists.txt | 1 + openvdb/openvdb/unittest/TestLevelSetTubes.cc | 174 ++++++++++++++++++ openvdb/openvdb/unittest/TestTools.cc | 10 - 3 files changed, 175 insertions(+), 10 deletions(-) create mode 100644 openvdb/openvdb/unittest/TestLevelSetTubes.cc diff --git a/openvdb/openvdb/unittest/CMakeLists.txt b/openvdb/openvdb/unittest/CMakeLists.txt index b1e04ebcfe..f42019cdd2 100644 --- a/openvdb/openvdb/unittest/CMakeLists.txt +++ b/openvdb/openvdb/unittest/CMakeLists.txt @@ -121,6 +121,7 @@ else() TestLeafOrigin.cc TestLevelSetFilter.cc TestLevelSetRayIntersector.cc + TestLevelSetTubes.cc TestLevelSetUtil.cc TestLinearInterp.cc TestMaps.cc diff --git a/openvdb/openvdb/unittest/TestLevelSetTubes.cc b/openvdb/openvdb/unittest/TestLevelSetTubes.cc new file mode 100644 index 0000000000..2b0d45beb2 --- /dev/null +++ b/openvdb/openvdb/unittest/TestLevelSetTubes.cc @@ -0,0 +1,174 @@ +// Copyright Contributors to the OpenVDB Project +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include +#include +#include + +#include + +class TestLevelSetTubes: public ::testing::Test +{ +public: + void SetUp() override { openvdb::initialize(); } + void TearDown() override { openvdb::uninitialize(); } +}; + +namespace { + +template +void +testCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, const float& r, + typename GridT::Ptr& grid, float error = 0.01f) +{ + using namespace openvdb; + + const float pi = openvdb::math::pi(); + const float l = (p2-p1).length(); + + const float area = 2.0f*l*pi*r + 4.0f*pi*math::Pow2(r), + volume = l*pi*math::Pow2(r) + 4.0f*pi*math::Pow(r,3)/3.0f, + totGaussCurv = 4.0f*pi, // Gauss-Bonnet + totMeanCurv = pi*l + 4.0f*pi*r; + + tools::LevelSetMeasure m(*grid); + + EXPECT_NEAR(m.area(true), area, area*error); + EXPECT_NEAR(m.volume(true), volume, volume*error); + EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, totGaussCurv*error); + EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); +} + +template +void +testTaperedCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, + const float& r1, const float& r2, + typename GridT::Ptr& grid, float error = 0.01f) +{ + using namespace openvdb; + + const float pi = openvdb::math::pi(); + const float l = (p2-p1).length(); + + const float l2 = math::Pow2(l), r12 = math::Pow2(r1), + r22 = math::Pow2(r2), rdiff2 = math::Pow2(r1-r2); + + const float area = pi/l * (l + r1 + r2) * (rdiff2 + l*(r1 + r2)), + volume = pi/(3.0f*l) * ((l2 + rdiff2)*(r12 + r1*r2 + r22) + 2.0f*l*(r1*r12 + r2*r22)), + totGaussCurv = 4.0f*pi, // Gauss-Bonnet + totMeanCurv = pi/l * (math::Pow2(l) + math::Pow2(r1 - r2) + 2.0f*l*(r1 + r2)); + + tools::LevelSetMeasure m(*grid); + + EXPECT_NEAR(m.area(true), area, area*error); + EXPECT_NEAR(m.volume(true), volume, volume*error); + EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, totGaussCurv*error); + EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); +} + +} + + +TEST_F(TestLevelSetTubes, testCapsule) +{ + using namespace openvdb; + + {// generic tests + + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f); + const float r = 4.3f; + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + {// test measures + + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f), + p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); + const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; + + const float voxelSize = 0.1f; + + FloatGrid::Ptr ls1 = tools::createLevelSetCapsule(p1, p2, r1, voxelSize); + FloatGrid::Ptr ls2 = tools::createLevelSetCapsule(p1, p3, r2, voxelSize); + FloatGrid::Ptr ls3 = tools::createLevelSetCapsule(p3, p2, r3, voxelSize); + FloatGrid::Ptr ls4 = tools::createLevelSetCapsule(p2, p4, r4, voxelSize); + + testCapsuleMeasures(p1, p2, r1, ls1); + testCapsuleMeasures(p1, p3, r2, ls2); + testCapsuleMeasures(p3, p2, r3, ls3); + testCapsuleMeasures(p2, p4, r4, ls4); + } + + {// degenerate capsule: sphere + + const Vec3s p1(4.3f, 7.9f, -4.8f), p2(4.3f, 7.9f, -4.8f); + const float r = 4.3f; + + const float voxelSize = 0.1f; + + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + testCapsuleMeasures(p1, p2, r, ls); + } + + {// degenerate capsule: line + + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(1.0f, 0.0f, 0.0f); + const float r = 0.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() == 117 /* 90 + 27 */); + EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + {// degenerate capsule: point + + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); + const float r = 0.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() == 27 /* 3x3x3 grid */); + EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + {// degenerate capsule: negative radius --> empty grid + + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); + const float r = -10.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() == 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + +}// testCapsule diff --git a/openvdb/openvdb/unittest/TestTools.cc b/openvdb/openvdb/unittest/TestTools.cc index 1cbf2c2bd5..9ebe540b96 100644 --- a/openvdb/openvdb/unittest/TestTools.cc +++ b/openvdb/openvdb/unittest/TestTools.cc @@ -293,16 +293,6 @@ TEST_F(TestTools, testLevelSetTubes) int(p1[1]/voxelSize), int(p1[2]/voxelSize));// inside - {// test capsule - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r1, voxelSize, width); - - EXPECT_TRUE(ls->activeVoxelCount() > 0); - EXPECT_TRUE(ls->tree().isValueOff(ijk)); - EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); - EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); - EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); - EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); - } {// test tapered capsule FloatGrid::Ptr ls = tools::createLevelSetTaperedCapsule( p1, p2, r1, r2, voxelSize, width); From 3ea8d7306457694b791c23d1067dda692223a4cd Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Tue, 5 Nov 2024 10:09:57 -0500 Subject: [PATCH 17/19] Tests for tapered capsules Tests for tapered capsules. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/unittest/TestLevelSetTubes.cc | 258 +++++++++++++++--- openvdb/openvdb/unittest/TestTools.cc | 11 - 2 files changed, 221 insertions(+), 48 deletions(-) diff --git a/openvdb/openvdb/unittest/TestLevelSetTubes.cc b/openvdb/openvdb/unittest/TestLevelSetTubes.cc index 2b0d45beb2..e2ee7a36c6 100644 --- a/openvdb/openvdb/unittest/TestLevelSetTubes.cc +++ b/openvdb/openvdb/unittest/TestLevelSetTubes.cc @@ -26,8 +26,8 @@ testCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, const fl { using namespace openvdb; - const float pi = openvdb::math::pi(); - const float l = (p2-p1).length(); + const float pi = math::pi(), + l = (p2-p1).length(); const float area = 2.0f*l*pi*r + 4.0f*pi*math::Pow2(r), volume = l*pi*math::Pow2(r) + 4.0f*pi*math::Pow(r,3)/3.0f, @@ -45,28 +45,43 @@ testCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, const fl template void testTaperedCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, - const float& r1, const float& r2, - typename GridT::Ptr& grid, float error = 0.01f) + const float& r1, const float& r2, typename GridT::Ptr& grid, + float error = 0.01f, bool test_gauss_curvature = true) { using namespace openvdb; - const float pi = openvdb::math::pi(); - const float l = (p2-p1).length(); + const float pi = math::pi(), epsilon = math::Tolerance().value(), + l = (p2-p1).length(), R = math::Max(r1, r2); const float l2 = math::Pow2(l), r12 = math::Pow2(r1), r22 = math::Pow2(r2), rdiff2 = math::Pow2(r1-r2); - const float area = pi/l * (l + r1 + r2) * (rdiff2 + l*(r1 + r2)), - volume = pi/(3.0f*l) * ((l2 + rdiff2)*(r12 + r1*r2 + r22) + 2.0f*l*(r1*r12 + r2*r22)), - totGaussCurv = 4.0f*pi, // Gauss-Bonnet - totMeanCurv = pi/l * (math::Pow2(l) + math::Pow2(r1 - r2) + 2.0f*l*(r1 + r2)); + float area, volume, totGaussCurv, totMeanCurv; + + if (l <= math::Max(epsilon, R)) { + // degenerate case: sphere + area = 4.0f*pi*math::Pow2(R), + volume = 4.0f/3.0f*pi*math::Pow3(R), + totGaussCurv = 4.0f*pi, // Gauss-Bonnet + totMeanCurv = 4.0f*pi*R; + } else { + // formulas assume non-degenerate object, i.e. l > 0 && r1 > 0 && r2 > 0 + area = pi/l * (l + r1 + r2) * (rdiff2 + l*(r1 + r2)), + volume = pi/(3.0f*l) * ((l2 + rdiff2)*(r12 + r1*r2 + r22) + 2.0f*l*(r1*r12 + r2*r22)), + totGaussCurv = 4.0f*pi, // Gauss-Bonnet + totMeanCurv = pi/l * (math::Pow2(l) + math::Pow2(r1 - r2) + 2.0f*l*(r1 + r2)); + } tools::LevelSetMeasure m(*grid); - EXPECT_NEAR(m.area(true), area, area*error); - EXPECT_NEAR(m.volume(true), volume, volume*error); - EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, totGaussCurv*error); - EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); + EXPECT_NEAR(m.area(true), area, area*error); + EXPECT_NEAR(m.volume(true), volume, volume*error); + EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); + + // objects with sharp corners or edges tend to give answers far from the analytical solution + // in cases like these we can toggle this test off + if (test_gauss_curvature) + EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, totGaussCurv*error); } } @@ -76,8 +91,11 @@ TEST_F(TestLevelSetTubes, testCapsule) { using namespace openvdb; - {// generic tests + using GridT = FloatGrid; + using GridPtr = typename GridT::Ptr; + // generic tests + { const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f); const float r = 4.3f; @@ -86,7 +104,7 @@ TEST_F(TestLevelSetTubes, testCapsule) int(p1[1]/voxelSize), int(p1[2]/voxelSize));// inside - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + GridPtr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() > 0); EXPECT_TRUE(ls->tree().isValueOff(ijk)); @@ -96,76 +114,242 @@ TEST_F(TestLevelSetTubes, testCapsule) EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// test measures - + // test measures + { const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f), p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; const float voxelSize = 0.1f; - FloatGrid::Ptr ls1 = tools::createLevelSetCapsule(p1, p2, r1, voxelSize); - FloatGrid::Ptr ls2 = tools::createLevelSetCapsule(p1, p3, r2, voxelSize); - FloatGrid::Ptr ls3 = tools::createLevelSetCapsule(p3, p2, r3, voxelSize); - FloatGrid::Ptr ls4 = tools::createLevelSetCapsule(p2, p4, r4, voxelSize); + GridPtr ls1 = tools::createLevelSetCapsule(p1, p2, r1, voxelSize); + GridPtr ls2 = tools::createLevelSetCapsule(p1, p3, r2, voxelSize); + GridPtr ls3 = tools::createLevelSetCapsule(p3, p2, r3, voxelSize); + GridPtr ls4 = tools::createLevelSetCapsule(p2, p4, r4, voxelSize); + + testCapsuleMeasures(p1, p2, r1, ls1); + testCapsuleMeasures(p1, p3, r2, ls2); + testCapsuleMeasures(p3, p2, r3, ls3); + testCapsuleMeasures(p2, p4, r4, ls4); + } + + // degenerate case: sphere + { + const Vec3s p1(4.3f, 7.9f, -4.8f), p2(4.3f, 7.9f, -4.8f); + const float r = 4.3f; + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + testCapsuleMeasures(p1, p2, r, ls); + } + + // degenerate case: line + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(1.0f, 0.0f, 0.0f); + const float r = 0.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + GridPtr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() == 117 /* 90 + 27 */); + EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + // degenerate case: point + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); + const float r = 0.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + GridPtr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() == 27 /* 3x3x3 grid */); + EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + // degenerate case: negative radius --> empty grid + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); + const float r = -10.0f; + + const float voxelSize = 0.1f, width = 2.0f; + + GridPtr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); - testCapsuleMeasures(p1, p2, r1, ls1); - testCapsuleMeasures(p1, p3, r2, ls2); - testCapsuleMeasures(p3, p2, r3, ls3); - testCapsuleMeasures(p2, p4, r4, ls4); + EXPECT_TRUE(ls->activeVoxelCount() == 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// degenerate capsule: sphere +}// testCapsule +TEST_F(TestLevelSetTubes, testTaperedCapsule) +{ + using namespace openvdb; + + using GridT = FloatGrid; + using GridPtr = typename GridT::Ptr; + + // generic tests + { + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f); + const float r1 = 4.3f, r2 = 1.2f; + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + // test measures + { + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f), + p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); + const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; + + const float voxelSize = 0.1f; + + GridPtr ls1 = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize); + GridPtr ls2 = tools::createLevelSetTaperedCapsule(p1, p3, r2, r3, voxelSize); + GridPtr ls3 = tools::createLevelSetTaperedCapsule(p3, p2, r3, r4, voxelSize); + GridPtr ls4 = tools::createLevelSetTaperedCapsule(p2, p4, r4, r3, voxelSize); + + testTaperedCapsuleMeasures(p1, p2, r1, r2, ls1); + testTaperedCapsuleMeasures(p1, p3, r2, r3, ls2); + testTaperedCapsuleMeasures(p3, p2, r3, r4, ls3); + testTaperedCapsuleMeasures(p2, p4, r4, r3, ls4); + } + + // degenerate case: capsule + { const Vec3s p1(4.3f, 7.9f, -4.8f), p2(4.3f, 7.9f, -4.8f); const float r = 4.3f; const float voxelSize = 0.1f; - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize); + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r, r, voxelSize); EXPECT_TRUE(ls->activeVoxelCount() > 0); EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); - testCapsuleMeasures(p1, p2, r, ls); + testTaperedCapsuleMeasures(p1, p2, r, r, ls); } - {// degenerate capsule: line + // degenerate case: sphere by equivalent endpoints + { + const Vec3s p1(4.3f, 7.9f, -4.8f), p2(4.3f, 7.9f, -4.8f); + const float r1 = 4.3f, r2 = 1.2f; + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + testTaperedCapsuleMeasures(p1, p2, r1, r2, ls); + } + // degenerate case: sphere by nested spheres + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(1.0f, 0.0f, 0.0f); + const float r1 = 1.5f, r2 = 0.5f; + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + testTaperedCapsuleMeasures(p1, p2, r1, r2, ls); + } + + // degenerate case: cone with sphere cap (tear drop) + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(4.0f, 0.0f, 0.0f); + const float r1 = 1.0f, r2 = 0.0f; + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + // don't test total Gaussian curvature + testTaperedCapsuleMeasures(p1, p2, r1, r2, ls, 0.05f, false); + } + + // degenerate case: cone with sphere cap (tear drop) with tip between endpoints + { + const Vec3s p1(0.0f, 0.0f, 0.0f), p2(4.0f, 0.0f, 0.0f), p2_equiv(2.0f, 0.0f, 0.0f); + const float r1 = 1.0f, r2 = -1.0f, r2_equiv = 0.0f; + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r1, r2, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + // don't test total Gaussian curvature + testTaperedCapsuleMeasures(p1, p2_equiv, r1, r2_equiv, ls, 0.05f, false); + } + + // degenerate case: line + { const Vec3s p1(0.0f, 0.0f, 0.0f), p2(1.0f, 0.0f, 0.0f); const float r = 0.0f; const float voxelSize = 0.1f, width = 2.0f; - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() == 117 /* 90 + 27 */); EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// degenerate capsule: point - + // degenerate case: point + { const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); const float r = 0.0f; const float voxelSize = 0.1f, width = 2.0f; - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() == 27 /* 3x3x3 grid */); EXPECT_TRUE(tools::minMax(ls->tree()).min() >= 0.0f); // no zero crossings EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } - {// degenerate capsule: negative radius --> empty grid - + // degenerate case: negative radius --> empty grid + { const Vec3s p1(0.0f, 0.0f, 0.0f), p2(0.0f, 0.0f, 0.0f); const float r = -10.0f; const float voxelSize = 0.1f, width = 2.0f; - FloatGrid::Ptr ls = tools::createLevelSetCapsule(p1, p2, r, voxelSize, width); + GridPtr ls = tools::createLevelSetTaperedCapsule(p1, p2, r, r, voxelSize, width); EXPECT_TRUE(ls->activeVoxelCount() == 0); EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); diff --git a/openvdb/openvdb/unittest/TestTools.cc b/openvdb/openvdb/unittest/TestTools.cc index 9ebe540b96..ae66a83719 100644 --- a/openvdb/openvdb/unittest/TestTools.cc +++ b/openvdb/openvdb/unittest/TestTools.cc @@ -293,17 +293,6 @@ TEST_F(TestTools, testLevelSetTubes) int(p1[1]/voxelSize), int(p1[2]/voxelSize));// inside - {// test tapered capsule - FloatGrid::Ptr ls = tools::createLevelSetTaperedCapsule( - p1, p2, r1, r2, voxelSize, width); - - EXPECT_TRUE(ls->activeVoxelCount() > 0); - EXPECT_TRUE(ls->tree().isValueOff(ijk)); - EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); - EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); - EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); - EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); - } {// test tube complex with constant radius FloatGrid::Ptr ls = tools::createLevelSetTubeComplex( vertices, segments, r3, voxelSize, width); From 7f06613f810b70e049a8cae94acbcfecb0de9532 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:02:53 -0500 Subject: [PATCH 18/19] Tests for tube complex Tests for tube complex. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/tools/LevelSetTubes.h | 56 ++-- openvdb/openvdb/unittest/TestLevelSetTubes.cc | 267 +++++++++++++++++- openvdb/openvdb/unittest/TestTools.cc | 42 --- 3 files changed, 293 insertions(+), 72 deletions(-) diff --git a/openvdb/openvdb/tools/LevelSetTubes.h b/openvdb/openvdb/tools/LevelSetTubes.h index ed1aefb10a..d088508285 100644 --- a/openvdb/openvdb/tools/LevelSetTubes.h +++ b/openvdb/openvdb/tools/LevelSetTubes.h @@ -1461,49 +1461,51 @@ createLevelSetTubeComplex(const std::vector>& vertices, if (voxelSize <= 0) OPENVDB_THROW(ValueError, "voxel size must be positive"); if (halfWidth <= 0) OPENVDB_THROW(ValueError, "half-width must be positive"); - switch(radii_policy) { - case TUBE_AUTOMATIC : { - if (vertices.size() != radii.size() && segments.size() != radii.size()) - OPENVDB_THROW(ValueError, - "createLevelSetTubeComplex requires equal number of vertices and radii, or segments and radii, with automatic radii policy."); - break; - } + if (radii_policy == TUBE_AUTOMATIC) { + if (vertices.size() != radii.size() && segments.size() != radii.size()) + OPENVDB_THROW(ValueError, + "createLevelSetTubeComplex requires equal number of vertices and radii, or segments and radii, with automatic radii policy."); + } + + TubeRadiiPolicy resolved_radii_policy = radii_policy == TUBE_AUTOMATIC + ? (vertices.size() == radii.size() ? TUBE_VERTEX_RADII : TUBE_SEGMENT_RADII) + : radii_policy; + + GridPtr tubegrid; + + switch(resolved_radii_policy) { case TUBE_VERTEX_RADII : { if (vertices.size() != radii.size()) OPENVDB_THROW(ValueError, "createLevelSetTubeComplex requires equal number of vertices and radii with per-vertex radii policy."); + + TaperedCapsuleComplexVoxelizer op(vertices, segments, radii, + voxelSize, halfWidth, interrupter); + + const tbb::blocked_range segmentRange(0, op.bucketSize()); + tbb::parallel_reduce(segmentRange, op); + + tubegrid = op.getGrid(); break; } case TUBE_SEGMENT_RADII : { if (segments.size() != radii.size()) OPENVDB_THROW(ValueError, "createLevelSetTubeComplex requires equal number of segments and radii with per-segment radii policy."); + + CapsuleComplexVoxelizer op(vertices, segments, radii, + voxelSize, halfWidth, interrupter); + + const tbb::blocked_range segmentRange(0, op.bucketSize()); + tbb::parallel_reduce(segmentRange, op); + + tubegrid = op.getGrid(); break; } default: OPENVDB_THROW(ValueError, "Invalid tube radii policy."); } - GridPtr tubegrid; - - if (vertices.size() == radii.size()) { - TaperedCapsuleComplexVoxelizer op(vertices, segments, radii, - voxelSize, halfWidth, interrupter); - - const tbb::blocked_range segmentRange(0, op.bucketSize()); - tbb::parallel_reduce(segmentRange, op); - - tubegrid = op.getGrid(); - } else { - CapsuleComplexVoxelizer op(vertices, segments, radii, - voxelSize, halfWidth, interrupter); - - const tbb::blocked_range segmentRange(0, op.bucketSize()); - tbb::parallel_reduce(segmentRange, op); - - tubegrid = op.getGrid(); - } - tools::pruneLevelSet(tubegrid->tree()); return tubegrid; diff --git a/openvdb/openvdb/unittest/TestLevelSetTubes.cc b/openvdb/openvdb/unittest/TestLevelSetTubes.cc index e2ee7a36c6..ddd5a94e39 100644 --- a/openvdb/openvdb/unittest/TestLevelSetTubes.cc +++ b/openvdb/openvdb/unittest/TestLevelSetTubes.cc @@ -5,8 +5,9 @@ #include #include -#include +#include #include +#include #include @@ -297,7 +298,7 @@ TEST_F(TestLevelSetTubes, testTaperedCapsule) // don't test total Gaussian curvature testTaperedCapsuleMeasures(p1, p2, r1, r2, ls, 0.05f, false); } - + // degenerate case: cone with sphere cap (tear drop) with tip between endpoints { const Vec3s p1(0.0f, 0.0f, 0.0f), p2(4.0f, 0.0f, 0.0f), p2_equiv(2.0f, 0.0f, 0.0f); @@ -355,4 +356,264 @@ TEST_F(TestLevelSetTubes, testTaperedCapsule) EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); } -}// testCapsule +}// testTaperedCapsule + +TEST_F(TestLevelSetTubes, testTubeComplexConstantRadius) +{ + using namespace openvdb; + + using GridT = FloatGrid; + using GridPtr = typename GridT::Ptr; + + // generic tests + { + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f), + p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); + const float r = 2.1f; + + const std::vector vertices({p1, p2, p3, p4}); + const std::vector segments({Vec2I(0, 1), Vec2I(0, 2), Vec2I(0, 3)}); + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, r, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + // test measures of complex that forms a single straight capsule + { + const std::vector vertices({Vec3s(0.0f, 0.0f, 0.0f), Vec3s(1.0f, 0.0f, 0.0f), + Vec3s(1.5f, 0.0f, 0.0f), Vec3s(3.0f, 0.0f, 0.0f)}); + const std::vector segments({Vec2I(0, 1), Vec2I(1, 2), Vec2I(2, 3)}); + + const float r = 0.6f, voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, r, voxelSize); + + // this complex is equivalent to a capsule from first vertex to last vertex with radius r + testCapsuleMeasures(vertices[0], vertices[3], r, ls); + } + + // test measures of an approximate a torus + { + const float pi = math::pi(), error = 0.05f, + a = 1.2f, c = 3.0f, voxelSize = 0.1f; + + const Index32 n = 128; + std::vector vertices; + std::vector segments; + const float delta = 2.0f*pi/static_cast(n); + + float theta = 0.0f; + for (Index32 i = 0; i < n; ++i, theta += delta) { + vertices.push_back(Vec3s(c*math::Cos(theta), c*math::Sin(theta), 0.0f)); + segments.push_back(Vec2I(i, (i+1) % n)); + } + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, a, voxelSize); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + const float area = 4.0f*math::Pow2(pi)*a*c, + volume = 2.0f*math::Pow2(pi)*math::Pow2(a)*c, + totGaussCurv = 0.0f, // Gauss-Bonnet + totMeanCurv = 2.0f*c*math::Pow2(pi); + + tools::LevelSetMeasure m(*ls); + + EXPECT_NEAR(m.area(true), area, area*error); + EXPECT_NEAR(m.volume(true), volume, volume*error); + EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, 0.1f); + EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); + } + + // genus of a cube wireframe + { + using FilterT = tools::LevelSetFilter; + + const std::vector vertices({ + Vec3s(-0.5, 0.5, -0.5f), Vec3s(0.5, 0.5, -0.5f), Vec3s(0.5, -0.5, -0.5f), + Vec3s(-0.5, -0.5, -0.5f), Vec3s(-0.5, 0.5, 0.5f), Vec3s(0.5, 0.5, 0.5f), + Vec3s(0.5, -0.5, 0.5f), Vec3s(-0.5, -0.5, 0.5f) + }); + + const std::vector segments({ + Vec2I(0, 1), Vec2I(0, 3), Vec2I(0, 4), Vec2I(1, 2), Vec2I(1, 5), Vec2I(2, 3), + Vec2I(2, 6), Vec2I(3, 7), Vec2I(4, 5), Vec2I(4, 7), Vec2I(5, 6), Vec2I(6, 7) + }); + + const float r = 0.1f, voxelSize = 0.025f; + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, r, voxelSize); + + // smooth slightly to ensure numerical integration of the genus is correct + FilterT filter(*ls); + for (Index i = 0; i < 5; ++i) + filter.meanCurvature(); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + EXPECT_EQ(tools::levelSetGenus(*ls), int(5)); + } + +}// testTubeComplexConstantRadius + +TEST_F(TestLevelSetTubes, testTubeComplexVariableRadius) +{ + using namespace openvdb; + + using GridT = FloatGrid; + using GridPtr = typename GridT::Ptr; + + // generic tests + { + const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f), + p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); + const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; + + const std::vector vertices({p1, p2, p3, p4}); + const std::vector segments({Vec2I(0, 1), Vec2I(0, 2), Vec2I(0, 3)}); + const std::vector radii({r1, r2, r3, r4}); + + const float voxelSize = 0.1f, width = 3.25f; + const Coord ijk(int(p1[0]/voxelSize), + int(p1[1]/voxelSize), + int(p1[2]/voxelSize));// inside + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, radii, voxelSize, width); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_TRUE(ls->tree().isValueOff(ijk)); + EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); + EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); + EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + } + + // test measures of complex that forms a single straight tapered capsule + { + const std::vector vertices({Vec3s(0.0f, 0.0f, 0.0f), Vec3s(1.0f, 0.0f, 0.0f), + Vec3s(1.5f, 0.0f, 0.0f), Vec3s(3.0f, 0.0f, 0.0f)}); + const std::vector segments({Vec2I(0, 1), Vec2I(1, 2), Vec2I(2, 3)}); + const std::vector radii({0.3f, 0.4f, 0.45f, 0.6f}); + + const float voxelSize = 0.1f; + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, radii, voxelSize); + + // this complex is equivalent to a tapered capsule from first vertex to last vertex + testTaperedCapsuleMeasures(vertices[0], vertices[3], radii[0], radii[3], ls, 0.02f); + } + + // test genus of an approximate a torus with per-vertex radii + { + using FilterT = tools::LevelSetFilter; + + const float pi = math::pi(), error = 0.05f, + a = 1.2f, c = 3.0f, voxelSize = 0.1f, width = 3.0f; + + const Index32 n = 128; + std::vector vertices; + std::vector segments; + std::vector radii; + const float delta = 2.0f*pi/static_cast(n); + + float theta = 0.0f; + for (Index32 i = 0; i < n; ++i, theta += delta) { + vertices.push_back(Vec3s(c*math::Cos(theta), c*math::Sin(theta), 0.0f)); + segments.push_back(Vec2I(i, (i+1) % n)); + radii.push_back(a + 0.15f*a*math::Sin(5.0f*theta)); + } + + GridPtr ls = tools::createLevelSetTubeComplex( + vertices, segments, radii, voxelSize, width, tools::TUBE_VERTEX_RADII); + + // smooth slightly to ensure numerical integration of the genus is correct + FilterT filter(*ls); + for (Index i = 0; i < 5; ++i) + filter.meanCurvature(); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + EXPECT_EQ(tools::levelSetGenus(*ls), int(1)); + } + + // test genus of an approximate a torus with per-segment radii + { + using FilterT = tools::LevelSetFilter; + + const float pi = math::pi(), error = 0.05f, + a = 1.2f, c = 3.0f, voxelSize = 0.1f, width = 3.0f; + + const Index32 n = 10; + std::vector vertices; + std::vector segments; + std::vector radii; + const float delta = 2.0f*pi/static_cast(n); + + float theta = 0.0f; + for (Index32 i = 0; i < n; ++i, theta += delta) { + vertices.push_back(Vec3s(c*math::Cos(theta), c*math::Sin(theta), 0.0f)); + segments.push_back(Vec2I(i, (i+1) % n)); + radii.push_back(0.7f*a + 0.3f*a*math::Sin(9.0f*theta)); + } + + GridPtr ls = tools::createLevelSetTubeComplex( + vertices, segments, radii, voxelSize, width, tools::TUBE_SEGMENT_RADII); + + // smooth slightly to ensure numerical integration of the genus is correct + FilterT filter(*ls); + for (Index i = 0; i < 5; ++i) + filter.meanCurvature(); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + EXPECT_EQ(tools::levelSetGenus(*ls), int(1)); + } + + // genus of a cube wireframe + { + using FilterT = tools::LevelSetFilter; + + const std::vector vertices({ + Vec3s(-0.5, 0.5, -0.5f), Vec3s(0.5, 0.5, -0.5f), Vec3s(0.5, -0.5, -0.5f), + Vec3s(-0.5, -0.5, -0.5f), Vec3s(-0.5, 0.5, 0.5f), Vec3s(0.5, 0.5, 0.5f), + Vec3s(0.5, -0.5, 0.5f), Vec3s(-0.5, -0.5, 0.5f) + }); + + const std::vector segments({ + Vec2I(0, 1), Vec2I(0, 3), Vec2I(0, 4), Vec2I(1, 2), Vec2I(1, 5), Vec2I(2, 3), + Vec2I(2, 6), Vec2I(3, 7), Vec2I(4, 5), Vec2I(4, 7), Vec2I(5, 6), Vec2I(6, 7) + }); + + const std::vector radii({0.1f, 0.15f, 0.2f, 0.175f, 0.08f, 0.11f, 0.3f, 0.09f}); + + const float voxelSize = 0.025f; + + GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, radii, voxelSize); + + // smooth slightly to ensure numerical integration of the genus is correct + FilterT filter(*ls); + for (Index i = 0; i < 5; ++i) + filter.meanCurvature(); + + EXPECT_TRUE(ls->activeVoxelCount() > 0); + EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); + + EXPECT_EQ(tools::levelSetGenus(*ls), int(5)); + } + +}// testTubeComplexConstantRadius diff --git a/openvdb/openvdb/unittest/TestTools.cc b/openvdb/openvdb/unittest/TestTools.cc index ae66a83719..24842be73b 100644 --- a/openvdb/openvdb/unittest/TestTools.cc +++ b/openvdb/openvdb/unittest/TestTools.cc @@ -276,48 +276,6 @@ TEST_F(TestTools, testLevelSetDilatedMesh) }// testLevelSetDilatedMesh -TEST_F(TestTools, testLevelSetTubes) -{ - using namespace openvdb; - - const float r1 = 4.3f, r2 = 1.2f, r3 = 2.1f, r4 = 3.6f; - const Vec3s p1(15.8f, 13.2f, 16.7f), p2(4.3f, 7.9f, -4.8f); - const Vec3s p3(-3.0f, -7.4f, 8.9f), p4(-2.7f, 8.9f, 30.4f); - - const std::vector vertices({p1, p2, p3, p4}); - const std::vector segments({Vec2I(0, 1), Vec2I(0, 2), Vec2I(0, 3)}); - const std::vector radii({r1, r2, r3, r4}); - - const float voxelSize = 0.1f, width = 3.25f; - const Coord ijk(int(p1[0]/voxelSize), - int(p1[1]/voxelSize), - int(p1[2]/voxelSize));// inside - - {// test tube complex with constant radius - FloatGrid::Ptr ls = tools::createLevelSetTubeComplex( - vertices, segments, r3, voxelSize, width); - - EXPECT_TRUE(ls->activeVoxelCount() > 0); - EXPECT_TRUE(ls->tree().isValueOff(ijk)); - EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); - EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); - EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); - EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); - } - {// test tube complex with variable radius - FloatGrid::Ptr ls = tools::createLevelSetTubeComplex( - vertices, segments, radii, voxelSize, width); - - EXPECT_TRUE(ls->activeVoxelCount() > 0); - EXPECT_TRUE(ls->tree().isValueOff(ijk)); - EXPECT_NEAR(-ls->background(), ls->tree().getValue(ijk), 1e-6); - EXPECT_NEAR(voxelSize*width, ls->background(), 1e-6); - EXPECT_NEAR(ls->background(),ls->tree().getValue(Coord(0)), 1e-6); - EXPECT_EQ(int(GRID_LEVEL_SET), int(ls->getGridClass())); - } - -}// testLevelSetTubes - TEST_F(TestTools, testLevelSetAdvect) { // Uncomment sections below to run this (time-consuming) test From a43589d6d16146b49653c2182a39a2a70ba814a1 Mon Sep 17 00:00:00 2001 From: ghurstunither <62885595+ghurstunither@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:12:38 -0500 Subject: [PATCH 19/19] Update TestLevelSetTubes.cc Fix some linux build failures. Signed-off-by: ghurstunither <62885595+ghurstunither@users.noreply.github.com> --- openvdb/openvdb/unittest/TestLevelSetTubes.cc | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/openvdb/openvdb/unittest/TestLevelSetTubes.cc b/openvdb/openvdb/unittest/TestLevelSetTubes.cc index ddd5a94e39..9ba9bf23bf 100644 --- a/openvdb/openvdb/unittest/TestLevelSetTubes.cc +++ b/openvdb/openvdb/unittest/TestLevelSetTubes.cc @@ -80,9 +80,10 @@ testTaperedCapsuleMeasures(const openvdb::Vec3s& p1, const openvdb::Vec3s& p2, EXPECT_NEAR(m.totMeanCurvature(true), totMeanCurv, totMeanCurv*error); // objects with sharp corners or edges tend to give answers far from the analytical solution - // in cases like these we can toggle this test off - if (test_gauss_curvature) + // in cases like these, e.g. cone-tip, we can toggle this test off + if (test_gauss_curvature) { EXPECT_NEAR(m.totGaussianCurvature(true), totGaussCurv, totGaussCurv*error); + } } } @@ -299,7 +300,7 @@ TEST_F(TestLevelSetTubes, testTaperedCapsule) testTaperedCapsuleMeasures(p1, p2, r1, r2, ls, 0.05f, false); } - // degenerate case: cone with sphere cap (tear drop) with tip between endpoints + // degenerate case: cone with sphere cap (tear drop) and tip between endpoints { const Vec3s p1(0.0f, 0.0f, 0.0f), p2(4.0f, 0.0f, 0.0f), p2_equiv(2.0f, 0.0f, 0.0f); const float r1 = 1.0f, r2 = -1.0f, r2_equiv = 0.0f; @@ -456,7 +457,7 @@ TEST_F(TestLevelSetTubes, testTubeComplexConstantRadius) GridPtr ls = tools::createLevelSetTubeComplex(vertices, segments, r, voxelSize); - // smooth slightly to ensure numerical integration of the genus is correct + // smooth slightly to ensure numerical integration when determining the genus is correct FilterT filter(*ls); for (Index i = 0; i < 5; ++i) filter.meanCurvature(); @@ -516,11 +517,11 @@ TEST_F(TestLevelSetTubes, testTubeComplexVariableRadius) testTaperedCapsuleMeasures(vertices[0], vertices[3], radii[0], radii[3], ls, 0.02f); } - // test genus of an approximate a torus with per-vertex radii + // test genus of a torus with per-vertex radii { using FilterT = tools::LevelSetFilter; - const float pi = math::pi(), error = 0.05f, + const float pi = math::pi(), a = 1.2f, c = 3.0f, voxelSize = 0.1f, width = 3.0f; const Index32 n = 128; @@ -550,11 +551,11 @@ TEST_F(TestLevelSetTubes, testTubeComplexVariableRadius) EXPECT_EQ(tools::levelSetGenus(*ls), int(1)); } - // test genus of an approximate a torus with per-segment radii + // test genus of a torus with per-segment radii { using FilterT = tools::LevelSetFilter; - const float pi = math::pi(), error = 0.05f, + const float pi = math::pi(), a = 1.2f, c = 3.0f, voxelSize = 0.1f, width = 3.0f; const Index32 n = 10;