1 // Copyright Contributors to the OpenVDB Project 2 // SPDX-License-Identifier: MPL-2.0 3 4 /// @author Nick Avramoussis, Francisco Gochez, Dan Bailey 5 /// 6 /// @file points/PointSample.h 7 /// 8 /// @brief Sample a VDB Grid onto a VDB Points attribute 9 10 #ifndef OPENVDB_POINTS_POINT_SAMPLE_HAS_BEEN_INCLUDED 11 #define OPENVDB_POINTS_POINT_SAMPLE_HAS_BEEN_INCLUDED 12 13 #include "openvdb/util/NullInterrupter.h" 14 #include "openvdb/thread/Threading.h" 15 #include "openvdb/tools/Interpolation.h" 16 17 #include "PointDataGrid.h" 18 #include "PointAttribute.h" 19 20 #include <sstream> 21 #include <type_traits> 22 23 namespace openvdb { 24 OPENVDB_USE_VERSION_NAMESPACE 25 namespace OPENVDB_VERSION_NAME { 26 namespace points { 27 28 29 /// @brief Performs closest point sampling from a VDB grid onto a VDB Points attribute 30 /// @param points the PointDataGrid whose points will be sampled on to 31 /// @param sourceGrid VDB grid which will be sampled 32 /// @param targetAttribute a target attribute on the points which will hold samples. This 33 /// attribute will be created with the source grid type if it does 34 /// not exist, and with the source grid name if the name is empty 35 /// @param filter an optional index filter 36 /// @param interrupter an optional interrupter 37 /// @note The target attribute may exist provided it can be cast to the SourceGridT ValueType 38 template<typename PointDataGridT, typename SourceGridT, 39 typename FilterT = NullFilter, typename InterrupterT = util::NullInterrupter> 40 inline void pointSample(PointDataGridT& points, 41 const SourceGridT& sourceGrid, 42 const Name& targetAttribute = "", 43 const FilterT& filter = NullFilter(), 44 InterrupterT* const interrupter = nullptr); 45 46 47 /// @brief Performs tri-linear sampling from a VDB grid onto a VDB Points attribute 48 /// @param points the PointDataGrid whose points will be sampled on to 49 /// @param sourceGrid VDB grid which will be sampled 50 /// @param targetAttribute a target attribute on the points which will hold samples. This 51 /// attribute will be created with the source grid type if it does 52 /// not exist, and with the source grid name if the name is empty 53 /// @param filter an optional index filter 54 /// @param interrupter an optional interrupter 55 /// @note The target attribute may exist provided it can be cast to the SourceGridT ValueType 56 template<typename PointDataGridT, typename SourceGridT, 57 typename FilterT = NullFilter, typename InterrupterT = util::NullInterrupter> 58 inline void boxSample( PointDataGridT& points, 59 const SourceGridT& sourceGrid, 60 const Name& targetAttribute = "", 61 const FilterT& filter = NullFilter(), 62 InterrupterT* const interrupter = nullptr); 63 64 65 /// @brief Performs tri-quadratic sampling from a VDB grid onto a VDB Points attribute 66 /// @param points the PointDataGrid whose points will be sampled on to 67 /// @param sourceGrid VDB grid which will be sampled 68 /// @param targetAttribute a target attribute on the points which will hold samples. This 69 /// attribute will be created with the source grid type if it does 70 /// not exist, and with the source grid name if the name is empty 71 /// @param filter an optional index filter 72 /// @param interrupter an optional interrupter 73 /// @note The target attribute may exist provided it can be cast to the SourceGridT ValueType 74 template<typename PointDataGridT, typename SourceGridT, 75 typename FilterT = NullFilter, typename InterrupterT = util::NullInterrupter> 76 inline void quadraticSample(PointDataGridT& points, 77 const SourceGridT& sourceGrid, 78 const Name& targetAttribute = "", 79 const FilterT& filter = NullFilter(), 80 InterrupterT* const interrupter = nullptr); 81 82 83 // This struct samples the source grid accessor using the world-space position supplied, 84 // with SamplerT providing the sampling scheme. In the case where ValueT does not match 85 // the value type of the source grid, the sample() method will also convert the sampled 86 // value into a ValueT value, using round-to-nearest for float-to-integer conversion. 87 struct SampleWithRounding 88 { 89 template<typename ValueT, typename SamplerT, typename AccessorT> 90 inline ValueT sample(const AccessorT& accessor, const Vec3d& position) const; 91 }; 92 93 94 // A dummy struct that is used to mean that the sampled attribute should either match the type 95 // of the existing attribute or the type of the source grid (if the attribute doesn't exist yet) 96 struct DummySampleType { }; 97 98 99 /// @brief Performs sampling and conversion from a VDB grid onto a VDB Points attribute 100 /// @param order the sampling order - 0 = closest-point, 1 = trilinear, 2 = triquadratic 101 /// @param points the PointDataGrid whose points will be sampled on to 102 /// @param sourceGrid VDB grid which will be sampled 103 /// @param targetAttribute a target attribute on the points which will hold samples. This 104 /// attribute will be created with the source grid type if it does 105 /// not exist, and with the source grid name if the name is empty 106 /// @param filter an optional index filter 107 /// @param sampler handles sampling and conversion into the target attribute type, 108 /// which by default this uses the SampleWithRounding struct. 109 /// @param interrupter an optional interrupter 110 /// @param threaded enable or disable threading (threading is enabled by default) 111 /// @note The target attribute may exist provided it can be cast to the SourceGridT ValueType 112 template<typename PointDataGridT, typename SourceGridT, typename TargetValueT = DummySampleType, 113 typename SamplerT = SampleWithRounding, typename FilterT = NullFilter, 114 typename InterrupterT = util::NullInterrupter> 115 inline void sampleGrid( size_t order, 116 PointDataGridT& points, 117 const SourceGridT& sourceGrid, 118 const Name& targetAttribute, 119 const FilterT& filter = NullFilter(), 120 const SamplerT& sampler = SampleWithRounding(), 121 InterrupterT* const interrupter = nullptr, 122 const bool threaded = true); 123 124 125 /////////////////////////////////////////////////// 126 127 /// @cond OPENVDB_DOCS_INTERNAL 128 129 namespace point_sample_internal { 130 131 132 template<typename FromType, typename ToType> 133 struct CompatibleTypes { enum { value = std::is_constructible<ToType, FromType>::value }; }; 134 135 // Specializations for types that can be converted from source grid to target attribute 136 template<typename T> struct CompatibleTypes< 137 T, T> { enum { value = true }; }; 138 template<typename T> struct CompatibleTypes< 139 T, math::Vec2<T>> { enum { value = true }; }; 140 template<typename T> struct CompatibleTypes< 141 T, math::Vec3<T>> { enum { value = true }; }; 142 template<typename T> struct CompatibleTypes< 143 T, math::Vec4<T>> { enum { value = true }; }; 144 template<typename T> struct CompatibleTypes< 145 math::Vec2<T>, math::Vec2<T>> { enum { value = true }; }; 146 template<typename T> struct CompatibleTypes< 147 math::Vec3<T>, math::Vec3<T>> { enum { value = true }; }; 148 template<typename T> struct CompatibleTypes< 149 math::Vec4<T>, math::Vec4<T>> { enum { value = true }; }; 150 template<typename T0, typename T1> struct CompatibleTypes< 151 math::Vec2<T0>, math::Vec2<T1>> { enum { value = CompatibleTypes<T0, T1>::value }; }; 152 template<typename T0, typename T1> struct CompatibleTypes< 153 math::Vec3<T0>, math::Vec3<T1>> { enum { value = CompatibleTypes<T0, T1>::value }; }; 154 template<typename T0, typename T1> struct CompatibleTypes< 155 math::Vec4<T0>, math::Vec4<T1>> { enum { value = CompatibleTypes<T0, T1>::value }; }; 156 template<typename T> struct CompatibleTypes< 157 ValueMask, T> { enum { value = CompatibleTypes<bool, T>::value }; }; 158 159 160 // Ability to access the Order and Staggered template parameter from tools::Sampler<Order, Staggered> 161 template <typename T> struct SamplerTraits { 162 static const size_t Order = 0; 163 static const bool Staggered = false; 164 }; 165 template <size_t T0, bool T1> struct SamplerTraits<tools::Sampler<T0, T1>> { 166 static const size_t Order = T0; 167 static const bool Staggered = T1; 168 }; 169 170 171 // default sampling is incompatible, so throw an error 172 template <typename ValueT, typename SamplerT, typename AccessorT, bool Round, bool Compatible = false> 173 struct SampleWithRoundingOp 174 { 175 static inline void sample(ValueT&, const AccessorT&, const Vec3d&) 176 { 177 std::ostringstream ostr; 178 ostr << "Cannot sample a " << typeNameAsString<typename AccessorT::ValueType>() 179 << " grid on to a " << typeNameAsString<ValueT>() << " attribute"; 180 OPENVDB_THROW(TypeError, ostr.str()); 181 } 182 }; 183 // partial specialization to handle sampling and rounding of compatible conversion 184 template <typename ValueT, typename SamplerT, typename AccessorT> 185 struct SampleWithRoundingOp<ValueT, SamplerT, AccessorT, /*Round=*/true, /*Compatible=*/true> 186 { 187 static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) 188 { 189 value = ValueT(math::Round(SamplerT::sample(accessor, position))); 190 } 191 }; 192 // partial specialization to handle sampling and simple casting of compatible conversion 193 template <typename ValueT, typename SamplerT, typename AccessorT> 194 struct SampleWithRoundingOp<ValueT, SamplerT, AccessorT, /*Round=*/false, /*Compatible=*/true> 195 { 196 static inline void sample(ValueT& value, const AccessorT& accessor, const Vec3d& position) 197 { 198 value = ValueT(SamplerT::sample(accessor, position)); 199 } 200 }; 201 202 203 template <typename PointDataGridT, typename SamplerT, typename FilterT, typename InterrupterT> 204 class PointDataSampler 205 { 206 public: 207 PointDataSampler(size_t order, 208 PointDataGridT& points, 209 const SamplerT& sampler, 210 const FilterT& filter, 211 InterrupterT* const interrupter, 212 const bool threaded) 213 : mOrder(order) 214 , mPoints(points) 215 , mSampler(sampler) 216 , mFilter(filter) 217 , mInterrupter(interrupter) 218 , mThreaded(threaded) { } 219 220 private: 221 // No-op transformation 222 struct AlignedTransform 223 { 224 inline Vec3d transform(const Vec3d& position) const { return position; } 225 }; // struct AlignedTransform 226 227 // Re-sample world-space position from source to target transforms 228 struct NonAlignedTransform 229 { 230 NonAlignedTransform(const math::Transform& source, const math::Transform& target) 231 : mSource(source) 232 , mTarget(target) { } 233 234 inline Vec3d transform(const Vec3d& position) const 235 { 236 return mSource.worldToIndex(mTarget.indexToWorld(position)); 237 } 238 239 private: 240 const math::Transform& mSource; 241 const math::Transform& mTarget; 242 }; // struct NonAlignedTransform 243 244 // A simple convenience wrapper that contains the source grid accessor and the sampler 245 template <typename ValueT, typename SourceGridT, typename GridSamplerT> 246 struct SamplerWrapper 247 { 248 using ValueType = ValueT; 249 using SourceValueType = typename SourceGridT::ValueType; 250 using SourceAccessorT = typename SourceGridT::ConstAccessor; 251 252 // can only sample from a bool or mask grid using a PointSampler 253 static const bool SourceIsBool = std::is_same<SourceValueType, bool>::value || 254 std::is_same<SourceValueType, ValueMask>::value; 255 static const bool OrderIsZero = SamplerTraits<GridSamplerT>::Order == 0; 256 static const bool IsValid = !SourceIsBool || OrderIsZero; 257 258 SamplerWrapper(const SourceGridT& sourceGrid, const SamplerT& sampler) 259 : mAccessor(sourceGrid.getConstAccessor()) 260 , mSampler(sampler) { } 261 262 // note that creating a new accessor from the underlying tree is faster than 263 // copying an existing accessor 264 SamplerWrapper(const SamplerWrapper& other) 265 : mAccessor(other.mAccessor.tree()) 266 , mSampler(other.mSampler) { } 267 268 template <bool IsValidT = IsValid> 269 inline typename std::enable_if<IsValidT, ValueT>::type 270 sample(const Vec3d& position) const { 271 return mSampler.template sample<ValueT, GridSamplerT, SourceAccessorT>( 272 mAccessor, position); 273 } 274 275 template <bool IsValidT = IsValid> 276 inline typename std::enable_if<!IsValidT, ValueT>::type 277 sample(const Vec3d& /*position*/) const { 278 OPENVDB_THROW(RuntimeError, "Cannot sample bool grid with BoxSampler or QuadraticSampler."); 279 } 280 281 private: 282 SourceAccessorT mAccessor; 283 const SamplerT& mSampler; 284 }; // struct SamplerWrapper 285 286 template <typename SamplerWrapperT, typename TransformerT> 287 inline void doSample(const SamplerWrapperT& sampleWrapper, const Index targetIndex, 288 const TransformerT& transformer) 289 { 290 using PointDataTreeT = typename PointDataGridT::TreeType; 291 using LeafT = typename PointDataTreeT::LeafNodeType; 292 using LeafManagerT = typename tree::LeafManager<PointDataTreeT>; 293 294 const auto& filter(mFilter); 295 const auto& interrupter(mInterrupter); 296 297 auto sampleLambda = [targetIndex, &sampleWrapper, &transformer, &filter, &interrupter]( 298 LeafT& leaf, size_t /*idx*/) 299 { 300 using TargetHandleT = AttributeWriteHandle<typename SamplerWrapperT::ValueType>; 301 302 if (util::wasInterrupted(interrupter)) { 303 thread::cancelGroupExecution(); 304 return; 305 } 306 307 SamplerWrapperT newSampleWrapper(sampleWrapper); 308 auto positionHandle = AttributeHandle<Vec3f>::create(leaf.constAttributeArray("P")); 309 auto targetHandle = TargetHandleT::create(leaf.attributeArray(targetIndex)); 310 for (auto iter = leaf.beginIndexOn(filter); iter; ++iter) { 311 const Vec3d position = transformer.transform( 312 positionHandle->get(*iter) + iter.getCoord().asVec3d()); 313 targetHandle->set(*iter, newSampleWrapper.sample(position)); 314 } 315 }; 316 317 LeafManagerT leafManager(mPoints.tree()); 318 319 if (mInterrupter) mInterrupter->start(); 320 321 leafManager.foreach(sampleLambda, mThreaded); 322 323 if (mInterrupter) mInterrupter->end(); 324 } 325 326 template <typename SourceGridT, typename SamplerWrapperT> 327 inline void resolveTransform(const SourceGridT& sourceGrid, const SamplerWrapperT& sampleWrapper, 328 const Index targetIndex) 329 { 330 const auto& sourceTransform = sourceGrid.constTransform(); 331 const auto& pointsTransform = mPoints.constTransform(); 332 333 if (sourceTransform == pointsTransform) { 334 AlignedTransform transformer; 335 doSample(sampleWrapper, targetIndex, transformer); 336 } else { 337 NonAlignedTransform transformer(sourceTransform, pointsTransform); 338 doSample(sampleWrapper, targetIndex, transformer); 339 } 340 } 341 342 template <typename SourceGridT, typename TargetValueT, size_t Order> 343 inline void resolveStaggered(const SourceGridT& sourceGrid, const Index targetIndex) 344 { 345 using SamplerWrapperT = SamplerWrapper<TargetValueT, SourceGridT, tools::Sampler<Order, false>>; 346 using StaggeredSamplerWrapperT = SamplerWrapper<TargetValueT, SourceGridT, tools::Sampler<Order, true>>; 347 348 using SourceValueType = typename SourceGridT::ValueType; 349 if (VecTraits<SourceValueType>::Size == 3 && sourceGrid.getGridClass() == GRID_STAGGERED) { 350 StaggeredSamplerWrapperT sampleWrapper(sourceGrid, mSampler); 351 resolveTransform(sourceGrid, sampleWrapper, targetIndex); 352 } else { 353 SamplerWrapperT sampleWrapper(sourceGrid, mSampler); 354 resolveTransform(sourceGrid, sampleWrapper, targetIndex); 355 } 356 } 357 358 public: 359 template <typename SourceGridT, typename TargetValueT = typename SourceGridT::ValueType> 360 inline void sample(const SourceGridT& sourceGrid, Index targetIndex) 361 { 362 using SourceValueType = typename SourceGridT::ValueType; 363 static const bool SourceIsMask = std::is_same<SourceValueType, bool>::value || 364 std::is_same<SourceValueType, ValueMask>::value; 365 366 if (SourceIsMask || mOrder == 0) { 367 resolveStaggered<SourceGridT, TargetValueT, 0>(sourceGrid, targetIndex); 368 } else if (mOrder == 1) { 369 resolveStaggered<SourceGridT, TargetValueT, 1>(sourceGrid, targetIndex); 370 } else if (mOrder == 2) { 371 resolveStaggered<SourceGridT, TargetValueT, 2>(sourceGrid, targetIndex); 372 } 373 } 374 375 private: 376 size_t mOrder; 377 PointDataGridT& mPoints; 378 const SamplerT& mSampler; 379 const FilterT& mFilter; 380 InterrupterT* const mInterrupter; 381 const bool mThreaded; 382 }; // class PointDataSampler 383 384 385 template <typename PointDataGridT, typename ValueT> 386 struct AppendAttributeOp 387 { 388 static void append(PointDataGridT& points, const Name& attribute) 389 { 390 appendAttribute<ValueT>(points.tree(), attribute); 391 } 392 }; 393 // partial specialization to disable attempts to append attribute type of DummySampleType 394 template <typename PointDataGridT> 395 struct AppendAttributeOp<PointDataGridT, DummySampleType> 396 { 397 static void append(PointDataGridT&, const Name&) { } 398 }; 399 400 } // namespace point_sample_internal 401 402 /// @endcond 403 404 //////////////////////////////////////// 405 406 407 template<typename ValueT, typename SamplerT, typename AccessorT> 408 ValueT SampleWithRounding::sample(const AccessorT& accessor, const Vec3d& position) const 409 { 410 using namespace point_sample_internal; 411 using SourceValueT = typename AccessorT::ValueType; 412 static const bool staggered = SamplerTraits<SamplerT>::Staggered; 413 static const bool compatible = CompatibleTypes</*from=*/SourceValueT, /*to=*/ValueT>::value && 414 (!staggered || (staggered && VecTraits<SourceValueT>::Size == 3)); 415 static const bool round = std::is_floating_point<SourceValueT>::value && 416 std::is_integral<ValueT>::value; 417 ValueT value; 418 SampleWithRoundingOp<ValueT, SamplerT, AccessorT, round, compatible>::sample( 419 value, accessor, position); 420 return value; 421 } 422 423 424 //////////////////////////////////////// 425 426 427 template<typename PointDataGridT, typename SourceGridT, typename TargetValueT, 428 typename SamplerT, typename FilterT, typename InterrupterT> 429 inline void sampleGrid( size_t order, 430 PointDataGridT& points, 431 const SourceGridT& sourceGrid, 432 const Name& targetAttribute, 433 const FilterT& filter, 434 const SamplerT& sampler, 435 InterrupterT* const interrupter, 436 const bool threaded) 437 { 438 using point_sample_internal::AppendAttributeOp; 439 using point_sample_internal::PointDataSampler; 440 441 // use the name of the grid if no target attribute name supplied 442 Name attribute(targetAttribute); 443 if (targetAttribute.empty()) { 444 attribute = sourceGrid.getName(); 445 } 446 447 // we do not allow sampling onto the "P" attribute 448 if (attribute == "P") { 449 OPENVDB_THROW(RuntimeError, "Cannot sample onto the \"P\" attribute"); 450 } 451 452 auto leaf = points.tree().cbeginLeaf(); 453 if (!leaf) return; 454 455 PointDataSampler<PointDataGridT, SamplerT, FilterT, InterrupterT> pointDataSampler( 456 order, points, sampler, filter, interrupter, threaded); 457 458 const auto& descriptor = leaf->attributeSet().descriptor(); 459 size_t targetIndex = descriptor.find(attribute); 460 const bool attributeExists = targetIndex != AttributeSet::INVALID_POS; 461 462 if (std::is_same<TargetValueT, DummySampleType>::value) { 463 if (!attributeExists) { 464 // append attribute of source grid value type 465 appendAttribute<typename SourceGridT::ValueType>(points.tree(), attribute); 466 targetIndex = leaf->attributeSet().descriptor().find(attribute); 467 assert(targetIndex != AttributeSet::INVALID_POS); 468 469 // sample using same type as source grid 470 pointDataSampler.template sample<SourceGridT>(sourceGrid, Index(targetIndex)); 471 } else { 472 auto targetIdx = static_cast<Index>(targetIndex); 473 // attempt to explicitly sample using type of existing attribute 474 const Name& targetType = descriptor.valueType(targetIndex); 475 if (targetType == typeNameAsString<Vec3f>()) { 476 pointDataSampler.template sample<SourceGridT, Vec3f>(sourceGrid, targetIdx); 477 } else if (targetType == typeNameAsString<Vec3d>()) { 478 pointDataSampler.template sample<SourceGridT, Vec3d>(sourceGrid, targetIdx); 479 } else if (targetType == typeNameAsString<Vec3i>()) { 480 pointDataSampler.template sample<SourceGridT, Vec3i>(sourceGrid, targetIdx); 481 } else if (targetType == typeNameAsString<int8_t>()) { 482 pointDataSampler.template sample<SourceGridT, int8_t>(sourceGrid, targetIdx); 483 } else if (targetType == typeNameAsString<int16_t>()) { 484 pointDataSampler.template sample<SourceGridT, int16_t>(sourceGrid, targetIdx); 485 } else if (targetType == typeNameAsString<int32_t>()) { 486 pointDataSampler.template sample<SourceGridT, int32_t>(sourceGrid, targetIdx); 487 } else if (targetType == typeNameAsString<int64_t>()) { 488 pointDataSampler.template sample<SourceGridT, int64_t>(sourceGrid, targetIdx); 489 } else if (targetType == typeNameAsString<float>()) { 490 pointDataSampler.template sample<SourceGridT, float>(sourceGrid, targetIdx); 491 } else if (targetType == typeNameAsString<double>()) { 492 pointDataSampler.template sample<SourceGridT, double>(sourceGrid, targetIdx); 493 } else if (targetType == typeNameAsString<bool>()) { 494 pointDataSampler.template sample<SourceGridT, bool>(sourceGrid, targetIdx); 495 } else { 496 std::ostringstream ostr; 497 ostr << "Cannot sample attribute of type - " << targetType; 498 OPENVDB_THROW(TypeError, ostr.str()); 499 } 500 } 501 } else { 502 if (!attributeExists) { 503 // append attribute of target value type 504 // (point_sample_internal wrapper disables the ability to use DummySampleType) 505 AppendAttributeOp<PointDataGridT, TargetValueT>::append(points, attribute); 506 targetIndex = leaf->attributeSet().descriptor().find(attribute); 507 assert(targetIndex != AttributeSet::INVALID_POS); 508 } 509 else { 510 const Name targetType = typeNameAsString<TargetValueT>(); 511 const Name attributeType = descriptor.valueType(targetIndex); 512 if (targetType != attributeType) { 513 std::ostringstream ostr; 514 ostr << "Requested attribute type " << targetType << " for sampling " 515 << " does not match existing attribute type " << attributeType; 516 OPENVDB_THROW(TypeError, ostr.str()); 517 } 518 } 519 520 // sample using target value type 521 pointDataSampler.template sample<SourceGridT, TargetValueT>( 522 sourceGrid, static_cast<Index>(targetIndex)); 523 } 524 } 525 526 template<typename PointDataGridT, typename SourceGridT, typename FilterT, typename InterrupterT> 527 inline void pointSample(PointDataGridT& points, 528 const SourceGridT& sourceGrid, 529 const Name& targetAttribute, 530 const FilterT& filter, 531 InterrupterT* const interrupter) 532 { 533 SampleWithRounding sampler; 534 sampleGrid(/*order=*/0, points, sourceGrid, targetAttribute, filter, sampler, interrupter); 535 } 536 537 template<typename PointDataGridT, typename SourceGridT, typename FilterT, typename InterrupterT> 538 inline void boxSample( PointDataGridT& points, 539 const SourceGridT& sourceGrid, 540 const Name& targetAttribute, 541 const FilterT& filter, 542 InterrupterT* const interrupter) 543 { 544 SampleWithRounding sampler; 545 sampleGrid(/*order=*/1, points, sourceGrid, targetAttribute, filter, sampler, interrupter); 546 } 547 548 template<typename PointDataGridT, typename SourceGridT, typename FilterT, typename InterrupterT> 549 inline void quadraticSample(PointDataGridT& points, 550 const SourceGridT& sourceGrid, 551 const Name& targetAttribute, 552 const FilterT& filter, 553 InterrupterT* const interrupter) 554 { 555 SampleWithRounding sampler; 556 sampleGrid(/*order=*/2, points, sourceGrid, targetAttribute, filter, sampler, interrupter); 557 } 558 559 560 //////////////////////////////////////// 561 562 563 } // namespace points 564 } // namespace OPENVDB_VERSION_NAME 565 } // namespace openvdb 566 567 #endif // OPENVDB_POINTS_POINT_SAMPLE_HAS_BEEN_INCLUDED 568