Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhauled OpenVDB Clip SOP camera frustum padding + misc QOL changes #1818

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions openvdb/openvdb/tools/Clip.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,16 @@ clip(const GridType& grid, const BBoxd& bbox, bool keepInterior = true);
/// @param grid the grid to be clipped
/// @param frustum a frustum map
/// @param keepInterior if true, discard voxels that lie outside the frustum;
/// if false, discard voxels that lie inside the frustum
/// if false, discard voxels that lie inside the frustum
/// @param padding padding added to the frustum's X & Y extents in NDC space. Given as
/// (-X,-Y,+X,+Y); added to the left, bottom, right, top sides of frustum
/// @warning Clipping a level set will likely produce a grid that is
/// no longer a valid level set.
template<typename GridType>
typename GridType::Ptr
clip(const GridType& grid, const math::NonlinearFrustumMap& frustum, bool keepInterior = true);
clip(
const GridType& grid, const math::NonlinearFrustumMap& frustum,
bool keepInterior = true, const Vec4d& padding = Vec4d::zero());

/// @brief Clip a grid against the active voxels of another grid
/// and return a new grid containing the result.
Expand Down Expand Up @@ -401,14 +405,20 @@ clip(const SrcGridType& srcGrid, const Grid<ClipTreeType>& clipGrid, bool keepIn
/// @private
template<typename GridType>
typename GridType::Ptr
clip(const GridType& inGrid, const math::NonlinearFrustumMap& frustumMap, bool keepInterior)
clip(const GridType& inGrid, const math::NonlinearFrustumMap& frustumMap,
bool keepInterior, const Vec4d& padding)
{
using ValueT = typename GridType::ValueType;
using TreeT = typename GridType::TreeType;
using LeafT = typename TreeT::LeafNodeType;

const auto& gridXform = inGrid.transform();
const auto frustumIndexBBox = frustumMap.getBBox();
BBoxd frustumIndexBBox = frustumMap.getBBox();
if (!padding.isZero()) {
const Vec2d extentsXY(frustumIndexBBox.extents().asPointer());
frustumIndexBBox.min() -= Vec3d(padding[0]*extentsXY.x(), padding[1]*extentsXY.y(), 0.0);
frustumIndexBBox.max() += Vec3d(padding[2]*extentsXY.x(), padding[3]*extentsXY.y(), 0.0);
}

// Return true if index-space point (i,j,k) lies inside the frustum.
auto frustumContainsCoord = [&](const Coord& ijk) -> bool {
Expand Down Expand Up @@ -582,7 +592,7 @@ OPENVDB_VOLUME_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION

#define _FUNCTION(TreeT) \
Grid<TreeT>::Ptr clip(const Grid<TreeT>&, const math::NonlinearFrustumMap&, bool)
Grid<TreeT>::Ptr clip(const Grid<TreeT>&, const math::NonlinearFrustumMap&, bool, const Vec4d&)
OPENVDB_ALL_TREE_INSTANTIATE(_FUNCTION)
#undef _FUNCTION

Expand Down
9 changes: 7 additions & 2 deletions openvdb_houdini/openvdb_houdini/GeometryUtil.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ void
drawFrustum(
GU_Detail& geo, const openvdb::math::Transform& transform,
const UT_Vector3* boxColor, const UT_Vector3* tickColor,
bool shaded, bool drawTicks)
bool shaded, bool drawTicks, const openvdb::Vec4d& padding)
{
if (transform.mapType() != openvdb::math::NonlinearFrustumMap::mapType()) {
return;
}

const openvdb::math::NonlinearFrustumMap& frustum =
*transform.map<openvdb::math::NonlinearFrustumMap>();
const openvdb::BBoxd bbox = frustum.getBBox();
openvdb::BBoxd bbox = frustum.getBBox();
if (!padding.isZero()) {
const openvdb::Vec2d extentsXY(bbox.extents().asPointer());
bbox.min() -= openvdb::Vec3d(padding[0]*extentsXY.x(), padding[1]*extentsXY.y(), 0.0);
bbox.max() += openvdb::Vec3d(padding[2]*extentsXY.x(), padding[3]*extentsXY.y(), 0.0);
}

UT_Vector3 corners[8];

Expand Down
4 changes: 2 additions & 2 deletions openvdb_houdini/openvdb_houdini/GeometryUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class Interrupter;
OPENVDB_HOUDINI_API
void
drawFrustum(GU_Detail&, const openvdb::math::Transform&,
const UT_Vector3* boxColor, const UT_Vector3* tickColor,
bool shaded, bool drawTicks = true);
const UT_Vector3* boxColor, const UT_Vector3* tickColor, bool shaded,
bool drawTicks = true, const openvdb::Vec4d& padding = openvdb::Vec4d::zero());


/// Construct a frustum transform from a Houdini camera.
Expand Down
165 changes: 120 additions & 45 deletions openvdb_houdini/openvdb_houdini/SOP_OpenVDB_Clip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,14 @@ class SOP_OpenVDB_Clip: public hvdb::SOP_NodeVDB
{
public:
openvdb::math::Transform::Ptr frustum() const { return mFrustum; }
openvdb::math::Vec4d padding() const { return mPadding; }
protected:
OP_ERROR cookVDBSop(OP_Context&) override;
private:
void getFrustum(OP_Context&);

openvdb::math::Transform::Ptr mFrustum;
openvdb::math::Vec4d mPadding = openvdb::Vec4d::zero();
}; // class Cache

protected:
Expand Down Expand Up @@ -82,23 +84,36 @@ newSopOperator(OP_OperatorTable* table)
"If enabled, keep voxels that lie inside the clipping region,"
" otherwise keep voxels that lie outside the clipping region."));

parms.add(hutil::ParmFactory(PRM_STRING, "clipper", "Clip To")
parms.add(hutil::ParmFactory(PRM_STRING | PRM_TYPE_JOIN_NEXT, "clipper", "Clip To")
.setChoiceListItems(PRM_CHOICELIST_SINGLE, {
"camera", "Camera",
"geometry", "Geometry Bounding Box",
"geometry", "Geometry",
"mask", "Mask VDB"
})
.setDefault("geometry")
.setTooltip("Specify how the clipping region should be defined.")
.setDocumentation("\
How to define the clipping region\n\
\n\
Camera:\n\
Use a camera frustum as the clipping region.\n\
Geometry:\n\
Use the bounding box of geometry from the second input as the clipping region.\n\
Mask VDB:\n\
Use the active voxels of a VDB volume from the second input as a clipping mask.\n"));
.setDocumentation("How to define the clipping region\n\n"
"Camera:\n"
" Use a camera frustum as the clipping region.\n"
"Geometry:\n"
" Use the bounding box of geometry from the second input as the clipping region.\n"
"Mask VDB:\n"
" Use the active voxels of a VDB volume from the second input as a clipping mask.\n"));

parms.add(hutil::ParmFactory(PRM_TOGGLE_E, "legacycamclip", "Legacy")
.setDefault(PRMzeroDefaults)
.setInvisible()
.setTooltip("Enables legacy camera clipping implementation and controls")
.setDocumentation(
"Enables legacy camera clipping implementation and controls.\n\n"

"Implementation used prior to (OpenVDB version - / Houdini -). Overestimates "
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've bookmarked the doc-string here so that version numbers can be added when they are known later.

"camera frustum height when the frame's height is not divisible by its width. Padding "
"is specified with separate X, Y, Z values. The X & Y padding values represent "
"a percentage of the original frame *width*, which is then added to both the sides of "
"the frame, expanding it uniformly. _Y_ padding is not scaled by aspect ratio.\n\n"
"The Z padding value translates the near and far planes further `(Z > 0)` or closer "
"`(Z < 0)` together by <<Z>> world-units. Extreme values can produce an invalid frustum."));

parms.add(hutil::ParmFactory(PRM_STRING, "mask", "Mask VDB")
.setChoiceList(&hutil::PrimGroupMenuInput2)
Expand Down Expand Up @@ -139,22 +154,49 @@ Mask VDB:\n\
"The position of the far clipping plane\n\n"
"If enabled, this setting overrides the camera's clipping plane."));

parms.add(hutil::ParmFactory(PRM_TOGGLE, "setpadding", "")
parms.add(hutil::ParmFactory(PRM_TOGGLE | PRM_TYPE_JOIN_NEXT, "setpadding", "Padding")
.setDefault(PRMzeroDefaults)
.setTypeExtended(PRM_TYPE_TOGGLE_JOIN)
.setTooltip("If enabled, expand or shrink the clipping region."));

parms.add(hutil::ParmFactory(PRM_FLT_E, "padding", "Padding")
parms.add(hutil::ParmFactory(PRM_FLT_J, "padding", "")
.setVectorSize(3)
.setDefault(PRMzeroDefaults)
.setTooltip("Padding in world units to be added to the clipping region")
.setTooltip("Padding in world units to be added to the clipping region or mask")
.setDocumentation(
"Padding in world units to be added to the clipping region\n\n"
"Negative values shrink the clipping region.\n\n"
"Nonuniform padding is not supported when clipping to a VDB volume.\n"
"The mask volume will be dilated or eroded uniformly"
" by the _x_-axis padding value."));

"Padding to be added to the clipping region or mask. Negative values shrink the "
"clipping region.\n\n"

"When in geometry mode, padding is measured in world units and is applied to the "
"input geometry's axis-aligned bounding box.\n\n"

"When in mask mode, the padding is the approximate world-space distance by which "
"to dilate or erode the mask. The padding value is rounded to a voxel-count, by which "
"the mask is then dilated/eroded. Mask padding is uniform, so only the <<X>> padding "
"value is utilized."));

const static PRM_Default winPaddingDefaults[4] = {
PRMzeroDefaults[0],
PRM_Default(0.0f, "ch('padwinx1')"),
PRMzeroDefaults[0],
PRM_Default(0.0f, "ch('padwiny1')")
};
parms.add(hutil::ParmFactory(PRM_FLT_J, "padwinx", "(Left, Right)")
.setVectorSize(2)
.setDefault(winPaddingDefaults)
.setTooltip("Padding to be added to the left and right of the camera frame.")
.setDocumentation(
"Padding to be added to the left and right of the camera frame.\n\n"
"The camera frame uses normalized device coordinates (NDC), where the width of the "
"frame is always `1.0`. Likewise a value of `(.5, .5)` will double the frame's width."));

parms.add(hutil::ParmFactory(PRM_FLT_J, "padwiny", "(Bottom, Top)")
.setVectorSize(2)
.setDefault(winPaddingDefaults+2)
.setTooltip("Padding to be added to the bottom and top of the frame.")
.setDocumentation(
"Padding to be added to the bottom and top of the camera frame.\n\n"
"The camera frame uses normalized device coordinates (NDC), where the height of the "
"frame is always `1.0`. Likewise a value of `(.5, .5)` will double the frame's height."));

// Obsolete parameters
hutil::ParmList obsoleteParms;
Expand Down Expand Up @@ -220,20 +262,39 @@ SOP_OpenVDB_Clip::updateParmsFlags()
{
bool changed = false;

UT_String clipper;
evalString(clipper, "clipper", 0, 0.0);
const fpreal time = CHgetEvalTime();
UT_String clipper; evalString(clipper, "clipper", 0, time);
const bool pad = evalInt("setpadding", 0, time) != 0;
const bool useLegacyPadding = evalInt("legacycamclip", 0, 0.0f);

const bool clipToCamera = (clipper == "camera");
const bool clipToCamera = clipper == "camera";
const bool clipToMask = clipper == "mask";

changed |= enableParm("mask", clipper == "mask");
changed |= enableParm("mask", clipToMask);
changed |= enableParm("camera", clipToCamera);
changed |= enableParm("setnear", clipToCamera);
changed |= enableParm("near", clipToCamera && evalInt("setnear", 0, 0.0));
changed |= enableParm("near", clipToCamera && evalInt("setnear", 0, time));
changed |= enableParm("setfar", clipToCamera);
changed |= enableParm("far", clipToCamera && evalInt("setfar", 0, 0.0));
changed |= enableParm("padding", 0 != evalInt("setpadding", 0, 0.0));
changed |= enableParm("far", clipToCamera && evalInt("setfar", 0, time));

changed |= setVisibleState("mask", clipper == "mask");
changed |= enableParm("legacycamclip", clipToCamera);
changed |= setVisibleState("legacycamclip", clipToCamera);

if (pad && clipToMask) {
// Disable all but the X component of padding Vec3 value
changed |= enableParm("padding", false);
changed |= enableParm("padding", true, 0);
} else {
changed |= enableParm("padding", pad && (!clipToCamera || useLegacyPadding));
}

changed |= setVisibleState("padding", (!clipToCamera || useLegacyPadding));
changed |= setVisibleState("padwinx", clipToCamera && !useLegacyPadding);
changed |= setVisibleState("padwiny", clipToCamera && !useLegacyPadding);
changed |= enableParm("padwinx", pad && clipToCamera && !useLegacyPadding);
changed |= enableParm("padwiny", pad && clipToCamera && !useLegacyPadding);

changed |= setVisibleState("mask", clipToMask);
changed |= setVisibleState("camera", clipToCamera);
changed |= setVisibleState("setnear", clipToCamera);
changed |= setVisibleState("near", clipToCamera);
Expand Down Expand Up @@ -351,8 +412,8 @@ struct BBoxClipOp

struct FrustumClipOp
{
FrustumClipOp(const openvdb::math::Transform::Ptr& frustum_, bool inside_ = true):
frustum(frustum_), inside(inside_)
FrustumClipOp(const openvdb::math::Transform::Ptr& frustum_, bool inside_ = true, const openvdb::Vec4d& padding_ = openvdb::Vec4d(0.0)):
frustum(frustum_), inside(inside_), padding(padding_)
{}

template<typename GridType>
Expand All @@ -361,13 +422,14 @@ struct FrustumClipOp
openvdb::math::NonlinearFrustumMap::ConstPtr mapPtr;
if (frustum) mapPtr = frustum->constMap<openvdb::math::NonlinearFrustumMap>();
if (mapPtr) {
outputGrid = openvdb::tools::clip(grid, *mapPtr, inside);
outputGrid = openvdb::tools::clip(grid, *mapPtr, inside, padding);
}
}

const openvdb::math::Transform::ConstPtr frustum;
const bool inside = true;
hvdb::GridPtr outputGrid;
const openvdb::Vec4d padding;
};


Expand Down Expand Up @@ -426,6 +488,7 @@ void
SOP_OpenVDB_Clip::Cache::getFrustum(OP_Context& context)
{
mFrustum.reset();
mPadding.setZero();

const auto time = context.getTime();

Expand Down Expand Up @@ -454,24 +517,36 @@ SOP_OpenVDB_Clip::Cache::getFrustum(OP_Context& context)
}

const bool pad = (0 != evalInt("setpadding", 0, time));
const auto padding = pad ? evalVec3f("padding", time) : openvdb::Vec3f{0};
const bool useLegacyPadding = evalInt("legacycamclip", 0, 0.0f);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Legacy clipping toggle is non-animatable, so evaluating at t = 0 is correct.

const openvdb::Vec3f padding = pad ? evalVec3f("padding", time) : openvdb::Vec3f{0.0};

const float nearPlane = (evalInt("setnear", 0, time)
float nearPlane = (evalInt("setnear", 0, time)
? static_cast<float>(evalFloat("near", 0, time))
: static_cast<float>(camera->getNEAR(time))) - padding[2];
const float farPlane = (evalInt("setfar", 0, time)
: static_cast<float>(camera->getNEAR(time)));
float farPlane = (evalInt("setfar", 0, time)
? static_cast<float>(evalFloat("far", 0, time))
: static_cast<float>(camera->getFAR(time))) + padding[2];
: static_cast<float>(camera->getFAR(time)));

if (useLegacyPadding) {
nearPlane -= padding[2];
farPlane += padding[2];
}

const int voxelCountX = !useLegacyPadding ? std::ceil(camera->RESX(time)) : 100;

mFrustum = hvdb::frustumTransformFromCamera(*self, context, *camera,
/*offset=*/0.f, nearPlane, farPlane, /*voxelDepth=*/1.f, /*voxelCountX=*/100);
/*offset=*/0.f, nearPlane, farPlane, /*voxelDepth=*/1.f, voxelCountX);

if (!mFrustum || !mFrustum->constMap<openvdb::math::NonlinearFrustumMap>()) {
throw std::runtime_error{
"failed to compute frustum bounds for camera " + cameraPath.toStdString()};
}

if (pad) {
if (pad && !useLegacyPadding) {
const openvdb::Vec2d padX = evalVec2R("padwinx", time);
const openvdb::Vec2d padY = evalVec2R("padwiny", time);
mPadding = openvdb::Vec4d(padX[0], padY[0], padX[1], padY[1]);
} else if (pad) {
const auto extents =
mFrustum->constMap<openvdb::math::NonlinearFrustumMap>()->getBBox().extents();
mFrustum->preScale(openvdb::Vec3d{
Expand All @@ -491,15 +566,17 @@ SOP_OpenVDB_Clip::cookMyGuide1(OP_Context&)
myGuide1->clearAndDestroy();

openvdb::math::Transform::ConstPtr frustum;
openvdb::math::Vec4d padding;
// Attempt to extract the frustum from our cache.
if (auto* cache = dynamic_cast<SOP_OpenVDB_Clip::Cache*>(myNodeVerbCache)) {
frustum = cache->frustum();
padding = cache->padding();
}

if (frustum) {
const UT_Vector3 color{0.9f, 0.0f, 0.0f};
hvdb::drawFrustum(*myGuide1, *frustum, &color,
/*tickColor=*/nullptr, /*shaded=*/false, /*ticks=*/false);
/*tickColor=*/nullptr, /*shaded=*/false, /*ticks=*/false, /*padding=*/padding);
}
return error();
}
Expand All @@ -526,8 +603,6 @@ SOP_OpenVDB_Clip::Cache::cookVDBSop(OP_Context& context)

const auto padding = pad ? evalVec3f("padding", time) : openvdb::Vec3f{0};

mFrustum.reset();

openvdb::BBoxd clipBox;
hvdb::GridCPtr maskGrid;

Expand Down Expand Up @@ -555,8 +630,8 @@ SOP_OpenVDB_Clip::Cache::cookVDBSop(OP_Context& context)
if (pad) {
// If padding is enabled and nonzero, dilate or erode the mask grid.
const auto paddingInVoxels = padding / maskGrid->voxelSize();
if (!openvdb::math::isApproxEqual(paddingInVoxels[0], paddingInVoxels[1])
|| !openvdb::math::isApproxEqual(paddingInVoxels[1], paddingInVoxels[2]))

if (!openvdb::math::isApproxZero(padding[1]) || !openvdb::math::isApproxZero(padding[2]))
{
addWarning(SOP_MESSAGE,
"nonuniform padding is not supported for mask clipping");
Expand Down Expand Up @@ -614,7 +689,7 @@ SOP_OpenVDB_Clip::Cache::cookVDBSop(OP_Context& context)
"only bounding box clipping is currently supported for point data grids");
}
} else if (useCamera) {
FrustumClipOp op{mFrustum, inside};
FrustumClipOp op{mFrustum, inside, mPadding};
if (hvdb::GEOvdbApply<hvdb::VolumeGridTypes>(**it, op)) { // all Houdini-supported volume grid types
outGrid = op.outputGrid;
} else if (inGrid.isType<openvdb::points::PointDataGrid>()) {
Expand Down
Loading