1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3
4 /// @file SOP_OpenVDB_Clip.cc
5 ///
6 /// @author FX R&D OpenVDB team
7 ///
8 /// @brief Clip grids
9
10 #include <houdini_utils/ParmFactory.h>
11 #include <openvdb_houdini/GeometryUtil.h> // for drawFrustum(), frustumTransformFromCamera()
12 #include <openvdb_houdini/Utils.h>
13 #include <openvdb_houdini/SOP_NodeVDB.h>
14 #include <openvdb/tools/Clip.h> // for tools::clip()
15 #include <openvdb/tools/LevelSetUtil.h> // for tools::sdfInteriorMask()
16 #include <openvdb/tools/Mask.h> // for tools::interiorMask()
17 #include <openvdb/tools/Morphology.h> // for tools::dilateActiveValues(), tools::erodeActiveValues()
18 #include <openvdb/points/PointDataGrid.h>
19 #include <OBJ/OBJ_Camera.h>
20 #include <cmath> // for std::abs(), std::round()
21 #include <exception>
22 #include <string>
23
24
25
26 namespace hvdb = openvdb_houdini;
27 namespace hutil = houdini_utils;
28
29
30 class SOP_OpenVDB_Clip: public hvdb::SOP_NodeVDB
31 {
32 public:
33 SOP_OpenVDB_Clip(OP_Network*, const char* name, OP_Operator*);
~SOP_OpenVDB_Clip()34 ~SOP_OpenVDB_Clip() override {}
35
36 static OP_Node* factory(OP_Network*, const char* name, OP_Operator*);
37
isRefInput(unsigned input) const38 int isRefInput(unsigned input) const override { return (input == 1); }
39
40 class Cache: public SOP_VDBCacheOptions
41 {
42 public:
frustum() const43 openvdb::math::Transform::Ptr frustum() const { return mFrustum; }
44 protected:
45 OP_ERROR cookVDBSop(OP_Context&) override;
46 private:
47 void getFrustum(OP_Context&);
48
49 openvdb::math::Transform::Ptr mFrustum;
50 }; // class Cache
51
52 protected:
53 void resolveObsoleteParms(PRM_ParmList*) override;
54 bool updateParmsFlags() override;
55 OP_ERROR cookMyGuide1(OP_Context&) override;
56 };
57
58
59 ////////////////////////////////////////
60
61
62 void
newSopOperator(OP_OperatorTable * table)63 newSopOperator(OP_OperatorTable* table)
64 {
65 if (table == nullptr) return;
66
67 hutil::ParmList parms;
68
69 parms.add(hutil::ParmFactory(PRM_STRING, "group", "Group")
70 .setChoiceList(&hutil::PrimGroupMenuInput1)
71 .setTooltip("Specify a subset of VDBs from the first input to be clipped.")
72 .setDocumentation(
73 "A subset of VDBs from the first input to be clipped"
74 " (see [specifying volumes|/model/volumes#group])"));
75
76 parms.add(hutil::ParmFactory(PRM_TOGGLE, "inside", "Keep Inside")
77 .setDefault(PRMoneDefaults)
78 .setTooltip(
79 "If enabled, keep voxels that lie inside the clipping region.\n"
80 "If disabled, keep voxels that lie outside the clipping region.")
81 .setDocumentation(
82 "If enabled, keep voxels that lie inside the clipping region,"
83 " otherwise keep voxels that lie outside the clipping region."));
84
85 parms.add(hutil::ParmFactory(PRM_STRING, "clipper", "Clip To")
86 .setChoiceListItems(PRM_CHOICELIST_SINGLE, {
87 "camera", "Camera",
88 "geometry", "Geometry",
89 "mask", "Mask VDB"
90 })
91 .setDefault("geometry")
92 .setTooltip("Specify how the clipping region should be defined.")
93 .setDocumentation("\
94 How to define the clipping region\n\
95 \n\
96 Camera:\n\
97 Use a camera frustum as the clipping region.\n\
98 Geometry:\n\
99 Use the bounding box of geometry from the second input as the clipping region.\n\
100 Mask VDB:\n\
101 Use the active voxels of a VDB volume from the second input as a clipping mask.\n"));
102
103 parms.add(hutil::ParmFactory(PRM_STRING, "mask", "Mask VDB")
104 .setChoiceList(&hutil::PrimGroupMenuInput2)
105 .setTooltip("Specify a VDB whose active voxels are to be used as a clipping mask.")
106 .setDocumentation(
107 "A VDB from the second input whose active voxels are to be used as a clipping mask"
108 " (see [specifying volumes|/model/volumes#group])"));
109
110 parms.add(hutil::ParmFactory(PRM_STRING, "camera", "Camera")
111 .setTypeExtended(PRM_TYPE_DYNAMIC_PATH)
112 .setSpareData(&PRM_SpareData::objCameraPath)
113 .setTooltip("Specify the path to a reference camera")
114 .setDocumentation(
115 "The path to the camera whose frustum is to be used as a clipping region"
116 " (e.g., `/obj/cam1`)"));
117
118 parms.add(hutil::ParmFactory(PRM_TOGGLE, "setnear", "")
119 .setDefault(PRMzeroDefaults)
120 .setTypeExtended(PRM_TYPE_TOGGLE_JOIN)
121 .setTooltip("If enabled, override the camera's near clipping plane."));
122
123 parms.add(hutil::ParmFactory(PRM_FLT_E, "near", "Near Clipping")
124 .setDefault(0.001)
125 .setTooltip("The position of the near clipping plane")
126 .setDocumentation(
127 "The position of the near clipping plane\n\n"
128 "If enabled, this setting overrides the camera's clipping plane."));
129
130 parms.add(hutil::ParmFactory(PRM_TOGGLE, "setfar", "")
131 .setDefault(PRMzeroDefaults)
132 .setTypeExtended(PRM_TYPE_TOGGLE_JOIN)
133 .setTooltip("If enabled, override the camera's far clipping plane."));
134
135 parms.add(hutil::ParmFactory(PRM_FLT_E, "far", "Far Clipping")
136 .setDefault(10000)
137 .setTooltip("The position of the far clipping plane")
138 .setDocumentation(
139 "The position of the far clipping plane\n\n"
140 "If enabled, this setting overrides the camera's clipping plane."));
141
142 parms.add(hutil::ParmFactory(PRM_TOGGLE, "setpadding", "")
143 .setDefault(PRMzeroDefaults)
144 .setTypeExtended(PRM_TYPE_TOGGLE_JOIN)
145 .setTooltip("If enabled, expand or shrink the clipping region."));
146
147 parms.add(hutil::ParmFactory(PRM_FLT_E, "padding", "Padding")
148 .setVectorSize(3)
149 .setDefault(PRMzeroDefaults)
150 .setTooltip("Padding in world units to be added to the clipping region")
151 .setDocumentation(
152 "Padding in world units to be added to the clipping region\n\n"
153 "Negative values shrink the clipping region.\n\n"
154 "Nonuniform padding is not supported when clipping to a VDB volume.\n"
155 "The mask volume will be dilated or eroded uniformly"
156 " by the _x_-axis padding value."));
157
158
159 // Obsolete parameters
160 hutil::ParmList obsoleteParms;
161 obsoleteParms.add(hutil::ParmFactory(PRM_TOGGLE, "usemask", "").setDefault(PRMzeroDefaults));
162
163
164 hvdb::OpenVDBOpFactory("VDB Clip", SOP_OpenVDB_Clip::factory, parms, *table)
165 .addInput("VDBs")
166 .addOptionalInput("Mask VDB or bounding geometry")
167 .setObsoleteParms(obsoleteParms)
168 .setVerb(SOP_NodeVerb::COOK_INPLACE, []() { return new SOP_OpenVDB_Clip::Cache; })
169 .setDocumentation("\
170 #icon: COMMON/openvdb\n\
171 #tags: vdb\n\
172 \n\
173 \"\"\"Clip VDB volumes using a camera frustum, a bounding box, or another VDB as a mask.\"\"\"\n\
174 \n\
175 @overview\n\
176 \n\
177 This node clips VDB volumes, that is, it removes voxels that lie outside\n\
178 (or, optionally, inside) a given region by deactivating them and setting them\n\
179 to the background value.\n\
180 The clipping region may be one of the following:\n\
181 * the frustum of a camera\n\
182 * the bounding box of reference geometry\n\
183 * the active voxels of another VDB.\n\
184 \n\
185 When the clipping region is defined by a VDB, the operation\n\
186 is similar to [activity intersection|Node:sop/DW_OpenVDBCombine],\n\
187 except that clipped voxels are not only deactivated but also set\n\
188 to the background value.\n\
189 \n\
190 @related\n\
191 \n\
192 - [OpenVDB Combine|Node:sop/DW_OpenVDBCombine]\n\
193 - [OpenVDB Occlusion Mask|Node:sop/DW_OpenVDBOcclusionMask]\n\
194 - [Node:sop/vdbactivate]\n\
195 \n\
196 @examples\n\
197 \n\
198 See [openvdb.org|http://www.openvdb.org/download/] for source code\n\
199 and usage examples.\n");
200 }
201
202
203 void
resolveObsoleteParms(PRM_ParmList * obsoleteParms)204 SOP_OpenVDB_Clip::resolveObsoleteParms(PRM_ParmList* obsoleteParms)
205 {
206 if (!obsoleteParms) return;
207
208 auto* parm = obsoleteParms->getParmPtr("usemask");
209 if (parm && !parm->isFactoryDefault()) { // factory default was Off
210 setString("clipper", CH_STRING_LITERAL, "mask", 0, 0.0);
211 }
212
213 // Delegate to the base class.
214 hvdb::SOP_NodeVDB::resolveObsoleteParms(obsoleteParms);
215 }
216
217
218 bool
updateParmsFlags()219 SOP_OpenVDB_Clip::updateParmsFlags()
220 {
221 bool changed = false;
222
223 UT_String clipper;
224 evalString(clipper, "clipper", 0, 0.0);
225
226 const bool clipToCamera = (clipper == "camera");
227
228 changed |= enableParm("mask", clipper == "mask");
229 changed |= enableParm("camera", clipToCamera);
230 changed |= enableParm("setnear", clipToCamera);
231 changed |= enableParm("near", clipToCamera && evalInt("setnear", 0, 0.0));
232 changed |= enableParm("setfar", clipToCamera);
233 changed |= enableParm("far", clipToCamera && evalInt("setfar", 0, 0.0));
234 changed |= enableParm("padding", 0 != evalInt("setpadding", 0, 0.0));
235
236 changed |= setVisibleState("mask", clipper == "mask");
237 changed |= setVisibleState("camera", clipToCamera);
238 changed |= setVisibleState("setnear", clipToCamera);
239 changed |= setVisibleState("near", clipToCamera);
240 changed |= setVisibleState("setfar", clipToCamera);
241 changed |= setVisibleState("far", clipToCamera);
242
243 return changed;
244 }
245
246
247 ////////////////////////////////////////
248
249
250 OP_Node*
factory(OP_Network * net,const char * name,OP_Operator * op)251 SOP_OpenVDB_Clip::factory(OP_Network* net,
252 const char* name, OP_Operator* op)
253 {
254 return new SOP_OpenVDB_Clip(net, name, op);
255 }
256
257
SOP_OpenVDB_Clip(OP_Network * net,const char * name,OP_Operator * op)258 SOP_OpenVDB_Clip::SOP_OpenVDB_Clip(OP_Network* net,
259 const char* name, OP_Operator* op):
260 hvdb::SOP_NodeVDB(net, name, op)
261 {
262 }
263
264
265 ////////////////////////////////////////
266
267
268 namespace {
269
270 // Functor to convert a mask grid of arbitrary type to a BoolGrid
271 // and to dilate or erode it
272 struct DilatedMaskOp
273 {
DilatedMaskOp__anone226ebb30211::DilatedMaskOp274 DilatedMaskOp(int dilation_): dilation{dilation_} {}
275
276 template<typename GridType>
operator ()__anone226ebb30211::DilatedMaskOp277 void operator()(const GridType& grid)
278 {
279 if (dilation == 0) return;
280
281 maskGrid = openvdb::BoolGrid::create();
282 maskGrid->setTransform(grid.transform().copy());
283 maskGrid->topologyUnion(grid);
284
285 UT_AutoInterrupt progress{
286 ((dilation > 0 ? "Dilating" : "Eroding") + std::string{" VDB mask"}).c_str()};
287
288 int numIterations = std::abs(dilation);
289
290 const int kNumIterationsPerPass = 4;
291 const int numPasses = numIterations / kNumIterationsPerPass;
292
293 auto morphologyOp = [&](int iterations) {
294 if (dilation > 0) {
295 openvdb::tools::dilateActiveValues(maskGrid->tree(), iterations);
296 } else {
297 openvdb::tools::erodeActiveValues(maskGrid->tree(), iterations);
298 }
299 };
300
301 // Since large dilations and erosions can be expensive, apply them
302 // in multiple passes and check for interrupts.
303 for (int pass = 0; pass < numPasses; ++pass, numIterations -= kNumIterationsPerPass) {
304 const bool interrupt = progress.wasInterrupted(
305 /*pct=*/int((100.0 * pass * kNumIterationsPerPass) / std::abs(dilation)));
306 if (interrupt) {
307 maskGrid.reset();
308 throw std::runtime_error{"interrupted"};
309 }
310 morphologyOp(kNumIterationsPerPass);
311 }
312 if (numIterations > 0) {
313 morphologyOp(numIterations);
314 }
315 }
316
317 int dilation = 0; // positive = dilation, negative = erosion
318 openvdb::BoolGrid::Ptr maskGrid;
319 };
320
321
322 struct LevelSetMaskOp
323 {
324 template<typename GridType>
operator ()__anone226ebb30211::LevelSetMaskOp325 void operator()(const GridType& grid)
326 {
327 outputGrid = openvdb::tools::sdfInteriorMask(grid);
328 }
329
330 hvdb::GridPtr outputGrid;
331 };
332
333
334 struct BBoxClipOp
335 {
BBoxClipOp__anone226ebb30211::BBoxClipOp336 BBoxClipOp(const openvdb::BBoxd& bbox_, bool inside_ = true):
337 bbox(bbox_), inside(inside_)
338 {}
339
340 template<typename GridType>
operator ()__anone226ebb30211::BBoxClipOp341 void operator()(const GridType& grid)
342 {
343 outputGrid = openvdb::tools::clip(grid, bbox, inside);
344 }
345
346 openvdb::BBoxd bbox;
347 hvdb::GridPtr outputGrid;
348 bool inside = true;
349 };
350
351
352 struct FrustumClipOp
353 {
FrustumClipOp__anone226ebb30211::FrustumClipOp354 FrustumClipOp(const openvdb::math::Transform::Ptr& frustum_, bool inside_ = true):
355 frustum(frustum_), inside(inside_)
356 {}
357
358 template<typename GridType>
operator ()__anone226ebb30211::FrustumClipOp359 void operator()(const GridType& grid)
360 {
361 openvdb::math::NonlinearFrustumMap::ConstPtr mapPtr;
362 if (frustum) mapPtr = frustum->constMap<openvdb::math::NonlinearFrustumMap>();
363 if (mapPtr) {
364 outputGrid = openvdb::tools::clip(grid, *mapPtr, inside);
365 }
366 }
367
368 const openvdb::math::Transform::ConstPtr frustum;
369 const bool inside = true;
370 hvdb::GridPtr outputGrid;
371 };
372
373
374 template<typename GridType>
375 struct MaskClipDispatchOp
376 {
MaskClipDispatchOp__anone226ebb30211::MaskClipDispatchOp377 MaskClipDispatchOp(const GridType& grid_, bool inside_ = true):
378 grid(&grid_), inside(inside_)
379 {}
380
381 template<typename MaskGridType>
operator ()__anone226ebb30211::MaskClipDispatchOp382 void operator()(const MaskGridType& mask)
383 {
384 outputGrid.reset();
385 if (grid) outputGrid = openvdb::tools::clip(*grid, mask, inside);
386 }
387
388 const GridType* grid;
389 hvdb::GridPtr outputGrid;
390 bool inside = true;
391 };
392
393
394 struct MaskClipOp
395 {
MaskClipOp__anone226ebb30211::MaskClipOp396 MaskClipOp(hvdb::GridCPtr mask_, bool inside_ = true):
397 mask(mask_), inside(inside_)
398 {}
399
400 template<typename GridType>
operator ()__anone226ebb30211::MaskClipOp401 void operator()(const GridType& grid)
402 {
403 outputGrid.reset();
404 if (mask) {
405 // Dispatch on the mask grid type, now that the source grid type is resolved.
406 MaskClipDispatchOp<GridType> op(grid, inside);
407 if (mask->apply<hvdb::AllGridTypes>(op)) {
408 outputGrid = op.outputGrid;
409 }
410 }
411 }
412
413 hvdb::GridCPtr mask;
414 hvdb::GridPtr outputGrid;
415 bool inside = true;
416 };
417
418 } // unnamed namespace
419
420
421 ////////////////////////////////////////
422
423
424 /// Get the selected camera's frustum transform.
425 void
getFrustum(OP_Context & context)426 SOP_OpenVDB_Clip::Cache::getFrustum(OP_Context& context)
427 {
428 mFrustum.reset();
429
430 const auto time = context.getTime();
431
432 UT_String cameraPath;
433 evalString(cameraPath, "camera", 0, time);
434 if (!cameraPath.isstring()) {
435 throw std::runtime_error{"no camera path was specified"};
436 }
437
438 OBJ_Camera* camera = nullptr;
439 if (auto* obj = cookparms()->getCwd()->findOBJNode(cameraPath)) {
440 camera = obj->castToOBJCamera();
441 }
442 OP_Node* self = cookparms()->getCwd();
443
444 if (!camera) {
445 throw std::runtime_error{"camera \"" + cameraPath.toStdString() + "\" was not found"};
446 }
447 self->addExtraInput(camera, OP_INTEREST_DATA);
448
449 OBJ_CameraParms cameraParms;
450 camera->getCameraParms(cameraParms, time);
451 if (cameraParms.projection != OBJ_PROJ_PERSPECTIVE) {
452 throw std::runtime_error{cameraPath.toStdString() + " is not a perspective camera"};
453 /// @todo support ortho and other cameras?
454 }
455
456 const bool pad = (0 != evalInt("setpadding", 0, time));
457 const auto padding = pad ? evalVec3f("padding", time) : openvdb::Vec3f{0};
458
459 const float nearPlane = (evalInt("setnear", 0, time)
460 ? static_cast<float>(evalFloat("near", 0, time))
461 : static_cast<float>(camera->getNEAR(time))) - padding[2];
462 const float farPlane = (evalInt("setfar", 0, time)
463 ? static_cast<float>(evalFloat("far", 0, time))
464 : static_cast<float>(camera->getFAR(time))) + padding[2];
465
466 mFrustum = hvdb::frustumTransformFromCamera(*self, context, *camera,
467 /*offset=*/0.f, nearPlane, farPlane, /*voxelDepth=*/1.f, /*voxelCountX=*/100);
468
469 if (!mFrustum || !mFrustum->constMap<openvdb::math::NonlinearFrustumMap>()) {
470 throw std::runtime_error{
471 "failed to compute frustum bounds for camera " + cameraPath.toStdString()};
472 }
473
474 if (pad) {
475 const auto extents =
476 mFrustum->constMap<openvdb::math::NonlinearFrustumMap>()->getBBox().extents();
477 mFrustum->preScale(openvdb::Vec3d{
478 (extents[0] + 2 * padding[0]) / extents[0],
479 (extents[1] + 2 * padding[1]) / extents[1],
480 1.0});
481 }
482 }
483
484
485 ////////////////////////////////////////
486
487
488 OP_ERROR
cookMyGuide1(OP_Context &)489 SOP_OpenVDB_Clip::cookMyGuide1(OP_Context&)
490 {
491 myGuide1->clearAndDestroy();
492
493 openvdb::math::Transform::ConstPtr frustum;
494 // Attempt to extract the frustum from our cache.
495 if (auto* cache = dynamic_cast<SOP_OpenVDB_Clip::Cache*>(myNodeVerbCache)) {
496 frustum = cache->frustum();
497 }
498
499 if (frustum) {
500 const UT_Vector3 color{0.9f, 0.0f, 0.0f};
501 hvdb::drawFrustum(*myGuide1, *frustum, &color,
502 /*tickColor=*/nullptr, /*shaded=*/false, /*ticks=*/false);
503 }
504 return error();
505 }
506
507
508 OP_ERROR
cookVDBSop(OP_Context & context)509 SOP_OpenVDB_Clip::Cache::cookVDBSop(OP_Context& context)
510 {
511 try {
512 const fpreal time = context.getTime();
513
514 UT_AutoInterrupt progress{"Clipping VDBs"};
515
516 const GU_Detail* maskGeo = inputGeo(1);
517
518 UT_String clipper;
519 evalString(clipper, "clipper", 0, time);
520
521 const bool
522 useCamera = (clipper == "camera"),
523 useMask = (clipper == "mask"),
524 inside = evalInt("inside", 0, time),
525 pad = evalInt("setpadding", 0, time);
526
527 const auto padding = pad ? evalVec3f("padding", time) : openvdb::Vec3f{0};
528
529 mFrustum.reset();
530
531 openvdb::BBoxd clipBox;
532 hvdb::GridCPtr maskGrid;
533
534 if (useCamera) {
535 getFrustum(context);
536 } else if (maskGeo) {
537 if (useMask) {
538 const GA_PrimitiveGroup* maskGroup = parsePrimitiveGroups(
539 evalStdString("mask", time).c_str(), GroupCreator{maskGeo});
540 hvdb::VdbPrimCIterator maskIt{maskGeo, maskGroup};
541 if (maskIt) {
542 if (maskIt->getConstGrid().getGridClass() == openvdb::GRID_LEVEL_SET) {
543 // If the mask grid is a level set, extract an interior mask from it.
544 LevelSetMaskOp op;
545 hvdb::GEOvdbApply<hvdb::NumericGridTypes>(**maskIt, op);
546 maskGrid = op.outputGrid;
547 } else {
548 maskGrid = maskIt->getConstGridPtr();
549 }
550 }
551 if (!maskGrid) {
552 addError(SOP_MESSAGE, "mask VDB not found");
553 return error();
554 }
555 if (pad) {
556 // If padding is enabled and nonzero, dilate or erode the mask grid.
557 const auto paddingInVoxels = padding / maskGrid->voxelSize();
558 if (!openvdb::math::isApproxEqual(paddingInVoxels[0], paddingInVoxels[1])
559 || !openvdb::math::isApproxEqual(paddingInVoxels[1], paddingInVoxels[2]))
560 {
561 addWarning(SOP_MESSAGE,
562 "nonuniform padding is not supported for mask clipping");
563 }
564 if (const int dilation = int(std::round(paddingInVoxels[0]))) {
565 DilatedMaskOp op{dilation};
566 maskGrid->apply<hvdb::AllGridTypes>(op);
567 if (op.maskGrid) maskGrid = op.maskGrid;
568 }
569 }
570 } else {
571 UT_BoundingBox box;
572 maskGeo->getBBox(&box);
573
574 clipBox.min()[0] = box.xmin();
575 clipBox.min()[1] = box.ymin();
576 clipBox.min()[2] = box.zmin();
577 clipBox.max()[0] = box.xmax();
578 clipBox.max()[1] = box.ymax();
579 clipBox.max()[2] = box.zmax();
580 if (pad) {
581 clipBox.min() -= padding;
582 clipBox.max() += padding;
583 }
584 }
585 } else {
586 addError(SOP_MESSAGE, "Not enough sources specified.");
587 return error();
588 }
589
590 // Get the group of grids to process.
591 const GA_PrimitiveGroup* group = matchGroup(*gdp, evalStdString("group", time));
592
593 int numLevelSets = 0;
594 for (hvdb::VdbPrimIterator it{gdp, group}; it; ++it) {
595 if (progress.wasInterrupted()) { throw std::runtime_error{"interrupted"}; }
596
597 const auto& inGrid = it->getConstGrid();
598
599 hvdb::GridPtr outGrid;
600
601 if (inGrid.getGridClass() == openvdb::GRID_LEVEL_SET) {
602 ++numLevelSets;
603 }
604
605 progress.getInterrupt()->setAppTitle(
606 ("Clipping VDB " + it.getPrimitiveIndexAndName().toStdString()).c_str());
607
608 if (maskGrid) {
609 MaskClipOp op{maskGrid, inside};
610 if (hvdb::GEOvdbApply<hvdb::VolumeGridTypes>(**it, op)) { // all Houdini-supported volume grid types
611 outGrid = op.outputGrid;
612 } else if (inGrid.isType<openvdb::points::PointDataGrid>()) {
613 addWarning(SOP_MESSAGE,
614 "only bounding box clipping is currently supported for point data grids");
615 }
616 } else if (useCamera) {
617 FrustumClipOp op{mFrustum, inside};
618 if (hvdb::GEOvdbApply<hvdb::VolumeGridTypes>(**it, op)) { // all Houdini-supported volume grid types
619 outGrid = op.outputGrid;
620 } else if (inGrid.isType<openvdb::points::PointDataGrid>()) {
621 addWarning(SOP_MESSAGE,
622 "only bounding box clipping is currently supported for point data grids");
623 }
624 } else {
625 BBoxClipOp op{clipBox, inside};
626 if (hvdb::GEOvdbApply<hvdb::VolumeGridTypes>(**it, op)) { // all Houdini-supported volume grid types
627 outGrid = op.outputGrid;
628 } else if (inGrid.isType<openvdb::points::PointDataGrid>()) {
629 if (inside) {
630 outGrid = inGrid.deepCopyGrid();
631 outGrid->clipGrid(clipBox);
632 } else {
633 addWarning(SOP_MESSAGE,
634 "only Keep Inside mode is currently supported for point data grids");
635 }
636 }
637 }
638
639 // Replace the original VDB primitive with a new primitive that contains
640 // the output grid and has the same attributes and group membership.
641 hvdb::replaceVdbPrimitive(*gdp, outGrid, **it, true);
642 }
643
644 if (numLevelSets > 0) {
645 if (numLevelSets == 1) {
646 addWarning(SOP_MESSAGE, "a level set grid was clipped;"
647 " the resulting grid might not be a valid level set");
648 } else {
649 addWarning(SOP_MESSAGE, "some level sets were clipped;"
650 " the resulting grids might not be valid level sets");
651 }
652 }
653
654 } catch (std::exception& e) {
655 addError(SOP_MESSAGE, e.what());
656 }
657
658 return error();
659 }
660