HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
GridTransformer.h
Go to the documentation of this file.
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @file GridTransformer.h
5 /// @author Peter Cucka
6 
7 #ifndef OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED
8 #define OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED
9 
10 #include <openvdb/Grid.h>
11 #include <openvdb/Types.h>
12 #include <openvdb/math/Math.h> // for isApproxEqual()
14 #include "ChangeBackground.h"
15 #include "Interpolation.h"
16 #include "LevelSetRebuild.h" // for doLevelSetRebuild()
17 #include "SignedFloodFill.h" // for signedFloodFill
18 #include "Prune.h" // for pruneLevelSet
19 #include <tbb/blocked_range.h>
20 #include <tbb/parallel_reduce.h>
21 #include <cmath>
22 #include <functional>
23 
24 namespace openvdb {
26 namespace OPENVDB_VERSION_NAME {
27 namespace tools {
28 
29 /// @brief Resample an input grid into an output grid of the same type such that,
30 /// after resampling, the input and output grids coincide (apart from sampling
31 /// artifacts), but the output grid's transform is unchanged.
32 /// @details Specifically, this function resamples the input grid into the output
33 /// grid's index space, using a sampling kernel like PointSampler, BoxSampler,
34 /// or QuadraticSampler.
35 /// @param inGrid the grid to be resampled
36 /// @param outGrid the grid into which to write the resampled voxel data
37 /// @param interrupter an object adhering to the util::NullInterrupter interface
38 /// @par Example:
39 /// @code
40 /// // Create an input grid with the default identity transform
41 /// // and populate it with a level-set sphere.
42 /// FloatGrid::ConstPtr src = tools::makeSphere(...);
43 /// // Create an output grid and give it a uniform-scale transform.
44 /// FloatGrid::Ptr dest = FloatGrid::create();
45 /// const float voxelSize = 0.5;
46 /// dest->setTransform(math::Transform::createLinearTransform(voxelSize));
47 /// // Resample the input grid into the output grid, reproducing
48 /// // the level-set sphere at a smaller voxel size.
49 /// MyInterrupter interrupter = ...;
50 /// tools::resampleToMatch<tools::QuadraticSampler>(*src, *dest, interrupter);
51 /// @endcode
52 template<typename Sampler, typename Interrupter, typename GridType>
53 inline void
54 resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter);
55 
56 /// @brief Resample an input grid into an output grid of the same type such that,
57 /// after resampling, the input and output grids coincide (apart from sampling
58 /// artifacts), but the output grid's transform is unchanged.
59 /// @details Specifically, this function resamples the input grid into the output
60 /// grid's index space, using a sampling kernel like PointSampler, BoxSampler,
61 /// or QuadraticSampler.
62 /// @param inGrid the grid to be resampled
63 /// @param outGrid the grid into which to write the resampled voxel data
64 /// @par Example:
65 /// @code
66 /// // Create an input grid with the default identity transform
67 /// // and populate it with a level-set sphere.
68 /// FloatGrid::ConstPtr src = tools::makeSphere(...);
69 /// // Create an output grid and give it a uniform-scale transform.
70 /// FloatGrid::Ptr dest = FloatGrid::create();
71 /// const float voxelSize = 0.5;
72 /// dest->setTransform(math::Transform::createLinearTransform(voxelSize));
73 /// // Resample the input grid into the output grid, reproducing
74 /// // the level-set sphere at a smaller voxel size.
75 /// tools::resampleToMatch<tools::QuadraticSampler>(*src, *dest);
76 /// @endcode
77 template<typename Sampler, typename GridType>
78 inline void
79 resampleToMatch(const GridType& inGrid, GridType& outGrid);
80 
81 
82 ////////////////////////////////////////
83 
84 
85 namespace internal {
86 
87 /// @brief A TileSampler wraps a grid sampler of another type (BoxSampler,
88 /// QuadraticSampler, etc.), and for samples that fall within a given tile
89 /// of the grid, it returns a cached tile value instead of accessing the grid.
90 template<typename Sampler, typename TreeT>
91 class TileSampler: public Sampler
92 {
93 public:
94  using ValueT = typename TreeT::ValueType;
95 
96  /// @param b the index-space bounding box of a particular grid tile
97  /// @param tileVal the tile's value
98  /// @param on the tile's active state
99  TileSampler(const CoordBBox& b, const ValueT& tileVal, bool on):
100  mBBox(b.min().asVec3d(), b.max().asVec3d()), mVal(tileVal), mActive(on), mEmpty(false)
101  {
102  mBBox.expand(-this->radius()); // shrink the bounding box by the sample radius
103  mEmpty = mBBox.empty();
104  }
105 
106  bool sample(const TreeT& inTree, const Vec3R& inCoord, ValueT& result) const
107  {
108  if (!mEmpty && mBBox.isInside(inCoord)) { result = mVal; return mActive; }
109  return Sampler::sample(inTree, inCoord, result);
110  }
111 
112 protected:
116 };
117 
118 
119 /// @brief For point sampling, tree traversal is less expensive than testing
120 /// bounding box membership.
121 template<typename TreeT>
122 class TileSampler<PointSampler, TreeT>: public PointSampler {
123 public:
124  TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {}
125 };
126 
127 /// @brief For point sampling, tree traversal is less expensive than testing
128 /// bounding box membership.
129 template<typename TreeT>
131 public:
132  TileSampler(const CoordBBox&, const typename TreeT::ValueType&, bool) {}
133 };
134 
135 } // namespace internal
136 
137 
138 ////////////////////////////////////////
139 
140 
141 /// A GridResampler applies a geometric transformation to an
142 /// input grid using one of several sampling schemes, and stores
143 /// the result in an output grid.
144 ///
145 /// Usage:
146 /// @code
147 /// GridResampler resampler();
148 /// resampler.transformGrid<BoxSampler>(xform, inGrid, outGrid);
149 /// @endcode
150 /// where @c xform is a functor that implements the following methods:
151 /// @code
152 /// bool isAffine() const
153 /// openvdb::Vec3d transform(const openvdb::Vec3d&) const
154 /// openvdb::Vec3d invTransform(const openvdb::Vec3d&) const
155 /// @endcode
156 /// @note When the transform is affine and can be expressed as a 4 x 4 matrix,
157 /// a GridTransformer is much more efficient than a GridResampler.
159 {
160 public:
162  using InterruptFunc = std::function<bool (void)>;
163 
164  GridResampler(): mThreaded(true), mTransformTiles(true) {}
165  virtual ~GridResampler() {}
166 
167  GridResampler(const GridResampler&) = default;
168  GridResampler& operator=(const GridResampler&) = default;
169 
170  /// Enable or disable threading. (Threading is enabled by default.)
171  void setThreaded(bool b) { mThreaded = b; }
172  /// Return @c true if threading is enabled.
173  bool threaded() const { return mThreaded; }
174  /// Enable or disable processing of tiles. (Enabled by default, except for level set grids.)
175  void setTransformTiles(bool b) { mTransformTiles = b; }
176  /// Return @c true if tile processing is enabled.
177  bool transformTiles() const { return mTransformTiles; }
178 
179  /// @brief Allow processing to be aborted by providing an interrupter object.
180  /// The interrupter will be queried periodically during processing.
181  /// @see util/NullInterrupter.h for interrupter interface requirements.
182  template<typename InterrupterType> void setInterrupter(InterrupterType&);
183 
184  template<typename Sampler, typename GridT, typename Transformer>
185  void transformGrid(const Transformer&,
186  const GridT& inGrid, GridT& outGrid) const;
187 
188 protected:
189  template<typename Sampler, typename GridT, typename Transformer>
190  void applyTransform(const Transformer&, const GridT& inGrid, GridT& outGrid) const;
191 
192  bool interrupt() const { return mInterrupt && mInterrupt(); }
193 
194 private:
195  template<typename Sampler, typename InTreeT, typename OutTreeT, typename Transformer>
196  static void transformBBox(const Transformer&, const CoordBBox& inBBox,
197  const InTreeT& inTree, OutTreeT& outTree, const InterruptFunc&,
198  const Sampler& = Sampler());
199 
200  template<typename Sampler, typename TreeT, typename Transformer>
201  class RangeProcessor;
202 
203  bool mThreaded, mTransformTiles;
204  InterruptFunc mInterrupt;
205 };
206 
207 
208 ////////////////////////////////////////
209 
210 
211 /// @brief A GridTransformer applies a geometric transformation to an
212 /// input grid using one of several sampling schemes, and stores
213 /// the result in an output grid.
214 ///
215 /// @note GridTransformer is optimized for affine transformations.
216 ///
217 /// Usage:
218 /// @code
219 /// Mat4R xform = ...;
220 /// GridTransformer transformer(xform);
221 /// transformer.transformGrid<BoxSampler>(inGrid, outGrid);
222 /// @endcode
223 /// or
224 /// @code
225 /// Vec3R pivot = ..., scale = ..., rotate = ..., translate = ...;
226 /// GridTransformer transformer(pivot, scale, rotate, translate);
227 /// transformer.transformGrid<QuadraticSampler>(inGrid, outGrid);
228 /// @endcode
230 {
231 public:
233 
234  GridTransformer(const Mat4R& xform);
236  const Vec3R& pivot,
237  const Vec3R& scale,
238  const Vec3R& rotate,
239  const Vec3R& translate,
240  const std::string& xformOrder = "tsr",
241  const std::string& rotationOrder = "zyx");
242  ~GridTransformer() override = default;
243 
244  GridTransformer(const GridTransformer&) = default;
245  GridTransformer& operator=(const GridTransformer&) = default;
246 
247  const Mat4R& getTransform() const { return mTransform; }
248 
249  template<class Sampler, class GridT>
250  void transformGrid(const GridT& inGrid, GridT& outGrid) const;
251 
252 private:
253  struct MatrixTransform;
254 
255  inline void init(const Vec3R& pivot, const Vec3R& scale,
256  const Vec3R& rotate, const Vec3R& translate,
257  const std::string& xformOrder, const std::string& rotOrder);
258 
259  Vec3R mPivot;
260  Vec3i mMipLevels;
261  Mat4R mTransform, mPreScaleTransform, mPostScaleTransform;
262 };
263 
264 
265 ////////////////////////////////////////
266 
267 
268 namespace local_util {
269 
271 
272 /// @brief Decompose an affine transform into scale, rotation (XYZ order),
273 /// and translation components.
274 /// @return DECOMP_INVALID if the given matrix is not affine or cannot
275 /// be decomposed, DECOMP_UNIQUE if the matrix has a unique decomposition,
276 /// DECOMP_VALID otherwise
277 template<typename T>
278 inline int
281 {
282  if (!math::isAffine(m)) return DECOMP_INVALID;
283 
284  // This is the translation in world space
285  translate = m.getTranslation();
286  // Extract translation.
287  const math::Mat3<T> xform = m.getMat3();
288 
289  const math::Vec3<T> unsignedScale(
290  (math::Vec3<T>(1, 0, 0) * xform).length(),
291  (math::Vec3<T>(0, 1, 0) * xform).length(),
292  (math::Vec3<T>(0, 0, 1) * xform).length());
293 
294  const bool hasUniformScale = unsignedScale.eq(math::Vec3<T>(unsignedScale[0]));
295 
296  bool hasRotation = false;
297  bool validDecomposition = false;
298 
299  T minAngle = std::numeric_limits<T>::max();
300 
301  // If the transformation matrix contains a reflection, test different negative scales
302  // to find a decomposition that favors the optimal resampling algorithm.
303  for (size_t n = 0; n < 8; ++n) {
304  const math::Vec3<T> signedScale(
305  n & 0x1 ? -unsignedScale.x() : unsignedScale.x(),
306  n & 0x2 ? -unsignedScale.y() : unsignedScale.y(),
307  n & 0x4 ? -unsignedScale.z() : unsignedScale.z());
308 
309  // Extract scale and potentially reflection.
310  const math::Mat3<T> mat = xform * math::scale<math::Mat3<T> >(signedScale).inverse();
311  if (mat.det() < T(0.0)) continue; // Skip if mat contains a reflection.
312 
313  const math::Vec3<T> tmpAngle = math::eulerAngles(mat, math::XYZ_ROTATION);
314 
315  const math::Mat3<T> rebuild =
316  math::rotation<math::Mat3<T> >(math::Vec3<T>(0, 0, 1), tmpAngle.z()) *
317  math::rotation<math::Mat3<T> >(math::Vec3<T>(0, 1, 0), tmpAngle.y()) *
318  math::rotation<math::Mat3<T> >(math::Vec3<T>(1, 0, 0), tmpAngle.x()) *
319  math::scale<math::Mat3<T> >(signedScale);
320 
321  if (xform.eq(rebuild)) {
322 
323  const T maxAngle = std::max(std::abs(tmpAngle[0]),
324  std::max(std::abs(tmpAngle[1]), std::abs(tmpAngle[2])));
325 
326  if (!(minAngle < maxAngle)) { // Update if less or equal.
327 
328  minAngle = maxAngle;
329  rotate = tmpAngle;
330  scale = signedScale;
331 
332  hasRotation = !rotate.eq(math::Vec3<T>::zero());
333  validDecomposition = true;
334 
335  if (hasUniformScale || !hasRotation) {
336  // Current decomposition is optimal.
337  break;
338  }
339  }
340  }
341  }
342 
343  if (!validDecomposition) {
344  // The decomposition is invalid if the transformation matrix contains shear.
345  return DECOMP_INVALID;
346  }
347  if (hasRotation && !hasUniformScale) {
348  // No unique decomposition if scale is nonuniform and rotation is nonzero.
349  return DECOMP_VALID;
350  }
351  return DECOMP_UNIQUE;
352 }
353 
354 } // namespace local_util
355 
356 
357 ////////////////////////////////////////
358 
359 
360 /// This class implements the Transformer functor interface (specifically,
361 /// the isAffine(), transform() and invTransform() methods) for a transform
362 /// that is expressed as a 4 x 4 matrix.
364 {
365  MatrixTransform(): mat(Mat4R::identity()), invMat(Mat4R::identity()) {}
366  MatrixTransform(const Mat4R& xform): mat(xform), invMat(xform.inverse()) {}
367 
368  bool isAffine() const { return math::isAffine(mat); }
369 
370  Vec3R transform(const Vec3R& pos) const { return mat.transformH(pos); }
371 
372  Vec3R invTransform(const Vec3R& pos) const { return invMat.transformH(pos); }
373 
375 };
376 
377 
378 ////////////////////////////////////////
379 
380 
381 /// @brief This class implements the Transformer functor interface (specifically,
382 /// the isAffine(), transform() and invTransform() methods) for a transform
383 /// that maps an A grid into a B grid's index space such that, after resampling,
384 /// A's index space and transform match B's index space and transform.
386 {
387 public:
388  /// @param aXform the A grid's transform
389  /// @param bXform the B grid's transform
390  ABTransform(const math::Transform& aXform, const math::Transform& bXform):
391  mAXform(aXform),
392  mBXform(bXform),
393  mIsAffine(mAXform.isLinear() && mBXform.isLinear()),
394  mIsIdentity(mIsAffine && mAXform == mBXform)
395  {}
396 
397  bool isAffine() const { return mIsAffine; }
398 
399  bool isIdentity() const { return mIsIdentity; }
400 
402  {
403  return mBXform.worldToIndex(mAXform.indexToWorld(pos));
404  }
405 
407  {
408  return mAXform.worldToIndex(mBXform.indexToWorld(pos));
409  }
410 
411  const math::Transform& getA() const { return mAXform; }
412  const math::Transform& getB() const { return mBXform; }
413 
414 private:
415  const math::Transform &mAXform, &mBXform;
416  const bool mIsAffine;
417  const bool mIsIdentity;
418 };
419 
420 
421 /// The normal entry points for resampling are the resampleToMatch() functions,
422 /// which correctly handle level set grids under scaling and shearing.
423 /// doResampleToMatch() is mainly for internal use but is typically faster
424 /// for level sets, and correct provided that no scaling or shearing is needed.
425 ///
426 /// @warning Do not use this function to scale or shear a level set grid.
427 template<typename Sampler, typename Interrupter, typename GridType>
428 inline void
429 doResampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter)
430 {
431  ABTransform xform(inGrid.transform(), outGrid.transform());
432 
433  if (Sampler::consistent() && xform.isIdentity()) {
434  // If the transforms of the input and output are identical, the
435  // output tree is simply a deep copy of the input tree.
436  outGrid.setTree(inGrid.tree().copy());
437  } else if (xform.isAffine()) {
438  // If the input and output transforms are both affine, create an
439  // input to output transform (in:index-to-world * out:world-to-index)
440  // and use the fast GridTransformer API.
441  Mat4R mat = xform.getA().baseMap()->getAffineMap()->getMat4() *
442  ( xform.getB().baseMap()->getAffineMap()->getMat4().inverse() );
443 
444  GridTransformer transformer(mat);
445  transformer.setInterrupter(interrupter);
446 
447  // Transform the input grid and store the result in the output grid.
448  transformer.transformGrid<Sampler>(inGrid, outGrid);
449  } else {
450  // If either the input or the output transform is non-affine,
451  // use the slower GridResampler API.
452  GridResampler resampler;
453  resampler.setInterrupter(interrupter);
454 
455  resampler.transformGrid<Sampler>(xform, inGrid, outGrid);
456  }
457 }
458 
459 
460 template<typename ValueType>
461 struct HalfWidthOp {
462  static ValueType eval(const ValueType& background, const Vec3d& voxelSize)
463  {
465  ValueType result(background * (1.0 / voxelSize[0]));
467  return result;
468  }
469 }; // struct HalfWidthOp
470 
471 template<>
472 struct HalfWidthOp<bool> {
473  static bool eval(const bool& background, const Vec3d& /*voxelSize*/)
474  {
475  return background;
476  }
477 }; // struct HalfWidthOp<bool>
478 
479 
480 template<typename Sampler, typename Interrupter, typename GridType>
481 inline void
482 resampleToMatch(const GridType& inGrid, GridType& outGrid, Interrupter& interrupter)
483 {
484  if (inGrid.getGridClass() == GRID_LEVEL_SET) {
485  // If the input grid is a level set, resample it using the level set rebuild tool.
486 
487  if (inGrid.constTransform() == outGrid.constTransform()) {
488  // If the transforms of the input and output grids are identical,
489  // the output tree is simply a deep copy of the input tree.
490  outGrid.setTree(inGrid.tree().copy());
491  return;
492  }
493 
494  // If the output grid is a level set, resample the input grid to have the output grid's
495  // background value. Otherwise, preserve the input grid's background value.
496  using ValueT = typename GridType::ValueType;
497  const bool outIsLevelSet = outGrid.getGridClass() == openvdb::GRID_LEVEL_SET;
498 
499  const ValueT halfWidth = outIsLevelSet
500  ? HalfWidthOp<ValueT>::eval(outGrid.background(), outGrid.voxelSize())
501  : HalfWidthOp<ValueT>::eval(inGrid.background(), inGrid.voxelSize());
502 
503  typename GridType::Ptr tempGrid;
504  try {
505  tempGrid = doLevelSetRebuild(inGrid, /*iso=*/zeroVal<ValueT>(),
506  /*exWidth=*/halfWidth, /*inWidth=*/halfWidth,
507  &outGrid.constTransform(), &interrupter);
508  } catch (TypeError&) {
509  // The input grid is classified as a level set, but it has a value type
510  // that is not supported by the level set rebuild tool. Fall back to
511  // using the generic resampler.
512  tempGrid.reset();
513  }
514  if (tempGrid) {
515  outGrid.setTree(tempGrid->treePtr());
516  return;
517  }
518  }
519 
520  // If the input grid is not a level set, use the generic resampler.
521  doResampleToMatch<Sampler>(inGrid, outGrid, interrupter);
522 }
523 
524 
525 template<typename Sampler, typename GridType>
526 inline void
527 resampleToMatch(const GridType& inGrid, GridType& outGrid)
528 {
529  util::NullInterrupter interrupter;
530  resampleToMatch<Sampler>(inGrid, outGrid, interrupter);
531 }
532 
533 
534 ////////////////////////////////////////
535 
536 
537 inline
539  mPivot(0, 0, 0),
540  mMipLevels(0, 0, 0),
541  mTransform(xform),
542  mPreScaleTransform(Mat4R::identity()),
543  mPostScaleTransform(Mat4R::identity())
544 {
546  if (local_util::decompose(mTransform, scale, rotate, translate)) {
547  // If the transform can be decomposed into affine components,
548  // use them to set up a mipmapping-like scheme for downsampling.
549  init(mPivot, scale, rotate, translate, "rst", "zyx");
550  }
551 }
552 
553 
554 inline
556  const Vec3R& pivot, const Vec3R& scale,
557  const Vec3R& rotate, const Vec3R& translate,
558  const std::string& xformOrder, const std::string& rotOrder):
559  mPivot(0, 0, 0),
560  mMipLevels(0, 0, 0),
561  mPreScaleTransform(Mat4R::identity()),
562  mPostScaleTransform(Mat4R::identity())
563 {
564  init(pivot, scale, rotate, translate, xformOrder, rotOrder);
565 }
566 
567 
568 ////////////////////////////////////////
569 
570 
571 inline void
572 GridTransformer::init(
573  const Vec3R& pivot, const Vec3R& scale,
574  const Vec3R& rotate, const Vec3R& translate,
575  const std::string& xformOrder, const std::string& rotOrder)
576 {
577  if (xformOrder.size() != 3) {
578  OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")");
579  }
580  if (rotOrder.size() != 3) {
581  OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")");
582  }
583 
584  mPivot = pivot;
585 
586  // Scaling is handled via a mipmapping-like scheme of successive
587  // halvings of the tree resolution, until the remaining scale
588  // factor is greater than or equal to 1/2.
589  Vec3R scaleRemainder = scale;
590  for (int i = 0; i < 3; ++i) {
591  double s = std::fabs(scale(i));
592  if (s < 0.5) {
593  mMipLevels(i) = int(std::floor(-std::log(s)/std::log(2.0)));
594  scaleRemainder(i) = scale(i) * (1 << mMipLevels(i));
595  }
596  }
597 
598  // Build pre-scale and post-scale transform matrices based on
599  // the user-specified order of operations.
600  // Note that we iterate over the transform order string in reverse order
601  // (e.g., "t", "r", "s", given "srt"). This is because math::Mat matrices
602  // postmultiply row vectors rather than premultiplying column vectors.
603  mTransform = mPreScaleTransform = mPostScaleTransform = Mat4R::identity();
604  Mat4R* remainder = &mPostScaleTransform;
605  int rpos, spos, tpos;
606  rpos = spos = tpos = 3;
607  for (int ix = 2; ix >= 0; --ix) { // reverse iteration
608  switch (xformOrder[ix]) {
609 
610  case 'r':
611  rpos = ix;
612  mTransform.preTranslate(pivot);
613  remainder->preTranslate(pivot);
614 
615  int xpos, ypos, zpos;
616  xpos = ypos = zpos = 3;
617  for (int ir = 2; ir >= 0; --ir) {
618  switch (rotOrder[ir]) {
619  case 'x':
620  xpos = ir;
621  mTransform.preRotate(math::X_AXIS, rotate.x());
622  remainder->preRotate(math::X_AXIS, rotate.x());
623  break;
624  case 'y':
625  ypos = ir;
626  mTransform.preRotate(math::Y_AXIS, rotate.y());
627  remainder->preRotate(math::Y_AXIS, rotate.y());
628  break;
629  case 'z':
630  zpos = ir;
631  mTransform.preRotate(math::Z_AXIS, rotate.z());
632  remainder->preRotate(math::Z_AXIS, rotate.z());
633  break;
634  }
635  }
636  // Reject rotation order strings that don't contain exactly one
637  // instance of "x", "y" and "z".
638  if (xpos > 2 || ypos > 2 || zpos > 2) {
639  OPENVDB_THROW(ValueError, "invalid rotation order (" + rotOrder + ")");
640  }
641 
642  mTransform.preTranslate(-pivot);
643  remainder->preTranslate(-pivot);
644  break;
645 
646  case 's':
647  spos = ix;
648  mTransform.preTranslate(pivot);
649  mTransform.preScale(scale);
650  mTransform.preTranslate(-pivot);
651 
652  remainder->preTranslate(pivot);
653  remainder->preScale(scaleRemainder);
654  remainder->preTranslate(-pivot);
655  remainder = &mPreScaleTransform;
656  break;
657 
658  case 't':
659  tpos = ix;
660  mTransform.preTranslate(translate);
661  remainder->preTranslate(translate);
662  break;
663  }
664  }
665  // Reject transform order strings that don't contain exactly one
666  // instance of "t", "r" and "s".
667  if (tpos > 2 || rpos > 2 || spos > 2) {
668  OPENVDB_THROW(ValueError, "invalid transform order (" + xformOrder + ")");
669  }
670 }
671 
672 
673 ////////////////////////////////////////
674 
675 
676 template<typename InterrupterType>
677 void
678 GridResampler::setInterrupter(InterrupterType& interrupter)
679 {
680  mInterrupt = std::bind(&InterrupterType::wasInterrupted,
681  /*this=*/&interrupter, /*percent=*/-1);
682 }
683 
684 
685 template<typename Sampler, typename GridT, typename Transformer>
686 void
687 GridResampler::transformGrid(const Transformer& xform,
688  const GridT& inGrid, GridT& outGrid) const
689 {
690  tools::changeBackground(outGrid.tree(), inGrid.background());
691  applyTransform<Sampler>(xform, inGrid, outGrid);
692 }
693 
694 
695 template<class Sampler, class GridT>
696 void
697 GridTransformer::transformGrid(const GridT& inGrid, GridT& outGrid) const
698 {
699  tools::changeBackground(outGrid.tree(), inGrid.background());
700 
701  if (!Sampler::mipmap() || mMipLevels == Vec3i::zero()) {
702  // Skip the mipmapping step.
703  const MatrixTransform xform(mTransform);
704  applyTransform<Sampler>(xform, inGrid, outGrid);
705 
706  } else {
707  bool firstPass = true;
708  const typename GridT::ValueType background = inGrid.background();
709  typename GridT::Ptr tempGrid = GridT::create(background);
710 
711  if (!mPreScaleTransform.eq(Mat4R::identity())) {
712  firstPass = false;
713  // Apply the pre-scale transform to the input grid
714  // and store the result in a temporary grid.
715  const MatrixTransform xform(mPreScaleTransform);
716  applyTransform<Sampler>(xform, inGrid, *tempGrid);
717  }
718 
719  // While the scale factor along one or more axes is less than 1/2,
720  // scale the grid by half along those axes.
721  Vec3i count = mMipLevels; // # of halvings remaining per axis
722  while (count != Vec3i::zero()) {
723  MatrixTransform xform;
724  xform.mat.setTranslation(mPivot);
725  xform.mat.preScale(Vec3R(
726  count.x() ? .5 : 1, count.y() ? .5 : 1, count.z() ? .5 : 1));
727  xform.mat.preTranslate(-mPivot);
728  xform.invMat = xform.mat.inverse();
729 
730  if (firstPass) {
731  firstPass = false;
732  // Scale the input grid and store the result in a temporary grid.
733  applyTransform<Sampler>(xform, inGrid, *tempGrid);
734  } else {
735  // Scale the temporary grid and store the result in a transient grid,
736  // then swap the two and discard the transient grid.
737  typename GridT::Ptr destGrid = GridT::create(background);
738  applyTransform<Sampler>(xform, *tempGrid, *destGrid);
739  tempGrid.swap(destGrid);
740  }
741  // (3, 2, 1) -> (2, 1, 0) -> (1, 0, 0) -> (0, 0, 0), etc.
742  count = math::maxComponent(count - 1, Vec3i::zero());
743  }
744 
745  // Apply the post-scale transform and store the result in the output grid.
746  if (!mPostScaleTransform.eq(Mat4R::identity())) {
747  const MatrixTransform xform(mPostScaleTransform);
748  applyTransform<Sampler>(xform, *tempGrid, outGrid);
749  } else {
750  outGrid.setTree(tempGrid->treePtr());
751  }
752  }
753 }
754 
755 
756 ////////////////////////////////////////
757 
758 
759 template<class Sampler, class TreeT, typename Transformer>
760 class GridResampler::RangeProcessor
761 {
762 public:
763  using LeafIterT = typename TreeT::LeafCIter;
764  using TileIterT = typename TreeT::ValueAllCIter;
765  using LeafRange = typename tree::IteratorRange<LeafIterT>;
766  using TileRange = typename tree::IteratorRange<TileIterT>;
767  using InTreeAccessor = typename tree::ValueAccessor<const TreeT>;
768  using OutTreeAccessor = typename tree::ValueAccessor<TreeT>;
769 
770  RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inT, TreeT& outT):
771  mIsRoot(true), mXform(xform), mBBox(b),
772  mInTree(inT), mOutTree(&outT), mInAcc(mInTree), mOutAcc(*mOutTree)
773  {}
774 
775  RangeProcessor(const Transformer& xform, const CoordBBox& b, const TreeT& inTree):
776  mIsRoot(false), mXform(xform), mBBox(b),
777  mInTree(inTree), mOutTree(new TreeT(inTree.background())),
778  mInAcc(mInTree), mOutAcc(*mOutTree)
779  {}
780 
781  ~RangeProcessor() { if (!mIsRoot) delete mOutTree; }
782 
783  /// Splitting constructor: don't copy the original processor's output tree
784  RangeProcessor(RangeProcessor& other, tbb::split):
785  mIsRoot(false),
786  mXform(other.mXform),
787  mBBox(other.mBBox),
788  mInTree(other.mInTree),
789  mOutTree(new TreeT(mInTree.background())),
790  mInAcc(mInTree),
791  mOutAcc(*mOutTree),
792  mInterrupt(other.mInterrupt)
793  {}
794 
795  void setInterrupt(const InterruptFunc& f) { mInterrupt = f; }
796 
797  /// Transform each leaf node in the given range.
798  void operator()(LeafRange& r)
799  {
800  for ( ; r; ++r) {
801  if (interrupt()) break;
802  LeafIterT i = r.iterator();
803  CoordBBox bbox(i->origin(), i->origin() + Coord(i->dim()));
804  if (!mBBox.empty()) {
805  // Intersect the leaf node's bounding box with mBBox.
806  bbox = CoordBBox(
807  Coord::maxComponent(bbox.min(), mBBox.min()),
808  Coord::minComponent(bbox.max(), mBBox.max()));
809  }
810  if (!bbox.empty()) {
811  transformBBox<Sampler>(mXform, bbox, mInAcc, mOutAcc, mInterrupt);
812  }
813  }
814  }
815 
816  /// Transform each non-background tile in the given range.
817  void operator()(TileRange& r)
818  {
819  for ( ; r; ++r) {
820  if (interrupt()) break;
821 
822  TileIterT i = r.iterator();
823  // Skip voxels and background tiles.
824  if (!i.isTileValue()) continue;
825  if (!i.isValueOn() && math::isApproxEqual(*i, mOutTree->background())) continue;
826 
827  CoordBBox bbox;
828  i.getBoundingBox(bbox);
829  if (!mBBox.empty()) {
830  // Intersect the tile's bounding box with mBBox.
831  bbox = CoordBBox(
832  Coord::maxComponent(bbox.min(), mBBox.min()),
833  Coord::minComponent(bbox.max(), mBBox.max()));
834  }
835  if (!bbox.empty()) {
836  /// @todo This samples the tile voxel-by-voxel, which is much too slow.
837  /// Instead, compute the largest axis-aligned bounding box that is
838  /// contained in the transformed tile (adjusted for the sampler radius)
839  /// and fill it with the tile value. Then transform the remaining voxels.
840  internal::TileSampler<Sampler, InTreeAccessor>
841  sampler(bbox, i.getValue(), i.isValueOn());
842  transformBBox(mXform, bbox, mInAcc, mOutAcc, mInterrupt, sampler);
843  }
844  }
845  }
846 
847  /// Merge another processor's output tree into this processor's tree.
848  void join(RangeProcessor& other)
849  {
850  if (!interrupt()) mOutTree->merge(*other.mOutTree);
851  }
852 
853 private:
854  bool interrupt() const { return mInterrupt && mInterrupt(); }
855 
856  const bool mIsRoot; // true if mOutTree is the top-level tree
857  Transformer mXform;
858  CoordBBox mBBox;
859  const TreeT& mInTree;
860  TreeT* mOutTree;
861  InTreeAccessor mInAcc;
862  OutTreeAccessor mOutAcc;
863  InterruptFunc mInterrupt;
864 };
865 
866 
867 ////////////////////////////////////////
868 
869 
870 template<class Sampler, class GridT, typename Transformer>
871 void
872 GridResampler::applyTransform(const Transformer& xform,
873  const GridT& inGrid, GridT& outGrid) const
874 {
875  using TreeT = typename GridT::TreeType;
876  const TreeT& inTree = inGrid.tree();
877  TreeT& outTree = outGrid.tree();
878 
879  using RangeProc = RangeProcessor<Sampler, TreeT, Transformer>;
880 
881  const GridClass gridClass = inGrid.getGridClass();
882 
883  if (gridClass != GRID_LEVEL_SET && mTransformTiles) {
884  // Independently transform the tiles of the input grid.
885  // Note: Tiles in level sets can only be background tiles, and they
886  // are handled more efficiently with a signed flood fill (see below).
887 
888  RangeProc proc(xform, CoordBBox(), inTree, outTree);
889  proc.setInterrupt(mInterrupt);
890 
891  typename RangeProc::TileIterT tileIter = inTree.cbeginValueAll();
892  tileIter.setMaxDepth(tileIter.getLeafDepth() - 1); // skip leaf nodes
893  typename RangeProc::TileRange tileRange(tileIter);
894 
895  if (mThreaded) {
896  tbb::parallel_reduce(tileRange, proc);
897  } else {
898  proc(tileRange);
899  }
900  }
901 
902  CoordBBox clipBBox;
903  if (gridClass == GRID_LEVEL_SET) {
904  // Inactive voxels in level sets can only be background voxels, and they
905  // are handled more efficiently with a signed flood fill (see below).
906  clipBBox = inGrid.evalActiveVoxelBoundingBox();
907  }
908 
909  // Independently transform the leaf nodes of the input grid.
910 
911  RangeProc proc(xform, clipBBox, inTree, outTree);
912  proc.setInterrupt(mInterrupt);
913 
914  typename RangeProc::LeafRange leafRange(inTree.cbeginLeaf());
915 
916  if (mThreaded) {
917  tbb::parallel_reduce(leafRange, proc);
918  } else {
919  proc(leafRange);
920  }
921 
922  // If the grid is a level set, mark inactive voxels as inside or outside.
923  if (gridClass == GRID_LEVEL_SET) {
924  tools::pruneLevelSet(outTree);
925  tools::signedFloodFill(outTree);
926  }
927 }
928 
929 
930 ////////////////////////////////////////
931 
932 
933 //static
934 template<class Sampler, class InTreeT, class OutTreeT, class Transformer>
935 void
936 GridResampler::transformBBox(
937  const Transformer& xform,
938  const CoordBBox& bbox,
939  const InTreeT& inTree,
940  OutTreeT& outTree,
941  const InterruptFunc& interrupt,
942  const Sampler& sampler)
943 {
944  using ValueT = typename OutTreeT::ValueType;
945 
946  // Transform the corners of the input tree's bounding box
947  // and compute the enclosing bounding box in the output tree.
948  Vec3R
949  inRMin(bbox.min().x(), bbox.min().y(), bbox.min().z()),
950  inRMax(bbox.max().x()+1, bbox.max().y()+1, bbox.max().z()+1),
951  outRMin = math::minComponent(xform.transform(inRMin), xform.transform(inRMax)),
952  outRMax = math::maxComponent(xform.transform(inRMin), xform.transform(inRMax));
953  for (int i = 0; i < 8; ++i) {
954  Vec3R corner(
955  i & 1 ? inRMax.x() : inRMin.x(),
956  i & 2 ? inRMax.y() : inRMin.y(),
957  i & 4 ? inRMax.z() : inRMin.z());
958  outRMin = math::minComponent(outRMin, xform.transform(corner));
959  outRMax = math::maxComponent(outRMax, xform.transform(corner));
960  }
961  Vec3i
962  outMin = local_util::floorVec3(outRMin) - Sampler::radius(),
963  outMax = local_util::ceilVec3(outRMax) + Sampler::radius();
964 
965  if (!xform.isAffine()) {
966  // If the transform is not affine, back-project each output voxel
967  // into the input tree.
968  Vec3R xyz, inXYZ;
969  Coord outXYZ;
970  int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z();
971  for (x = outMin.x(); x <= outMax.x(); ++x) {
972  if (interrupt && interrupt()) break;
973  xyz.x() = x;
974  for (y = outMin.y(); y <= outMax.y(); ++y) {
975  if (interrupt && interrupt()) break;
976  xyz.y() = y;
977  for (z = outMin.z(); z <= outMax.z(); ++z) {
978  xyz.z() = z;
979  inXYZ = xform.invTransform(xyz);
980  ValueT result;
981  if (sampler.sample(inTree, inXYZ, result)) {
982  outTree.setValueOn(outXYZ, result);
983  } else {
984  // Note: Don't overwrite existing active values with inactive values.
985  if (!outTree.isValueOn(outXYZ)) {
986  outTree.setValueOff(outXYZ, result);
987  }
988  }
989  }
990  }
991  }
992  } else { // affine
993  // Compute step sizes in the input tree that correspond to
994  // unit steps in x, y and z in the output tree.
995  const Vec3R
996  translation = xform.invTransform(Vec3R(0, 0, 0)),
997  deltaX = xform.invTransform(Vec3R(1, 0, 0)) - translation,
998  deltaY = xform.invTransform(Vec3R(0, 1, 0)) - translation,
999  deltaZ = xform.invTransform(Vec3R(0, 0, 1)) - translation;
1000 
1001 #if defined(__ICC)
1002  /// @todo The following line is a workaround for bad code generation
1003  /// in opt-icc11.1_64 (but not debug or gcc) builds. It should be
1004  /// removed once the problem has been addressed at its source.
1005  const Vec3R dummy = deltaX;
1006 #endif
1007 
1008  // Step by whole voxels through the output tree, sampling the
1009  // corresponding fractional voxels of the input tree.
1010  Vec3R inStartX = xform.invTransform(Vec3R(outMin));
1011  Coord outXYZ;
1012  int &x = outXYZ.x(), &y = outXYZ.y(), &z = outXYZ.z();
1013  for (x = outMin.x(); x <= outMax.x(); ++x, inStartX += deltaX) {
1014  if (interrupt && interrupt()) break;
1015  Vec3R inStartY = inStartX;
1016  for (y = outMin.y(); y <= outMax.y(); ++y, inStartY += deltaY) {
1017  if (interrupt && interrupt()) break;
1018  Vec3R inXYZ = inStartY;
1019  for (z = outMin.z(); z <= outMax.z(); ++z, inXYZ += deltaZ) {
1020  ValueT result;
1021  if (sampler.sample(inTree, inXYZ, result)) {
1022  outTree.setValueOn(outXYZ, result);
1023  } else {
1024  // Note: Don't overwrite existing active values with inactive values.
1025  if (!outTree.isValueOn(outXYZ)) {
1026  outTree.setValueOff(outXYZ, result);
1027  }
1028  }
1029  }
1030  }
1031  }
1032  }
1033 } // GridResampler::transformBBox()
1034 
1035 } // namespace tools
1036 } // namespace OPENVDB_VERSION_NAME
1037 } // namespace openvdb
1038 
1039 #endif // OPENVDB_TOOLS_GRIDTRANSFORMER_HAS_BEEN_INCLUDED
bool transformTiles() const
Return true if tile processing is enabled.
GLdouble s
Definition: glew.h:1390
void setThreaded(bool b)
Enable or disable threading. (Threading is enabled by default.)
Vec2< T > minComponent(const Vec2< T > &v1, const Vec2< T > &v2)
Return component-wise minimum of the two vectors.
Definition: Vec2.h:503
vint4 max(const vint4 &a, const vint4 &b)
Definition: simd.h:4703
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_END
Definition: Platform.h:188
TileSampler(const CoordBBox &, const typename TreeT::ValueType &, bool)
void transformGrid(const GridT &inGrid, GridT &outGrid) const
Vec3d indexToWorld(const Vec3d &xyz) const
Apply this transformation to the given coordinates.
Definition: Transform.h:108
math::Vec3< Real > Vec3R
Definition: Types.h:50
bool sample(const TreeT &inTree, const Vec3R &inCoord, ValueT &result) const
Vec3< T0 > transformH(const Vec3< T0 > &p) const
Transform a Vec3 by post-multiplication, doing homogenous divison.
Definition: Mat4.h:1034
static bool sample(const TreeT &inTree, const Vec3R &inCoord, typename TreeT::ValueType &result)
Sample inTree at the floating-point index coordinate inCoord and store the result in result...
GridResampler & operator=(const GridResampler &)=default
GLenum GLenum GLenum GLenum GLenum scale
Definition: glew.h:13880
void changeBackground(TreeOrLeafManagerT &tree, const typename TreeOrLeafManagerT::ValueType &background, bool threaded=true, size_t grainSize=32)
Replace the background value in all the nodes of a tree.
openvdb::Vec3R transform(const openvdb::Vec3R &pos) const
GLuint GLfloat GLfloat GLfloat x1
Definition: glew.h:12681
static bool eval(const bool &background, const Vec3d &)
#define OPENVDB_USE_VERSION_NAMESPACE
Definition: version.h:167
void expand(ElementType padding)
Pad this bounding box.
Definition: BBox.h:321
ABTransform(const math::Transform &aXform, const math::Transform &bXform)
const GLdouble * m
Definition: glew.h:9124
Dummy NOOP interrupter class defining interface.
void setTranslation(const Vec3< T > &t)
Definition: Mat4.h:322
void preRotate(Axis axis, T angle)
Left multiplies by a rotation clock-wiseabout the given axis into this matrix.
Definition: Mat4.h:806
Vec3< typename MatType::value_type > eulerAngles(const MatType &mat, RotationOrder rotationOrder, typename MatType::value_type eps=static_cast< typename MatType::value_type >(1.0e-8))
Return the Euler angles composing the given rotation matrix.
Definition: Mat.h:348
GridTransformer & operator=(const GridTransformer &)=default
GLdouble GLdouble z
Definition: glew.h:1559
static ValueType eval(const ValueType &background, const Vec3d &voxelSize)
std::shared_ptr< T > SharedPtr
Definition: Types.h:92
void OIIO_API split(string_view str, std::vector< string_view > &result, string_view sep=string_view(), int maxsplit=-1)
bool isApproxEqual(const Type &a, const Type &b, const Type &tolerance)
Return true if a is equal to b to within the given tolerance.
Definition: Math.h:397
IMATH_INTERNAL_NAMESPACE_HEADER_ENTER T abs(T a)
Definition: ImathFun.h:55
GLclampf f
Definition: glew.h:3499
GLint GLint GLint GLint GLint x
Definition: glew.h:1252
GLint GLint GLint GLint GLint GLint y
Definition: glew.h:1252
void preScale(const Vec3< T0 > &v)
Definition: Mat4.h:744
Vec3< T > getTranslation() const
Return the translation component.
Definition: Mat4.h:317
void applyTransform(const Transformer &, const GridT &inGrid, GridT &outGrid) const
void transformGrid(const Transformer &, const GridT &inGrid, GridT &outGrid) const
General-purpose arithmetic and comparison routines, most of which accept arbitrary value types (or at...
GLuint sampler
Definition: glew.h:3603
Efficient multi-threaded replacement of the background values in tree.
bool eq(const Mat3 &m, T eps=1.0e-8) const
Return true if this matrix is equivalent to m within a tolerance of eps.
Definition: Mat3.h:308
ImageBuf OIIO_API rotate(const ImageBuf &src, float angle, string_view filtername=string_view(), float filterwidth=0.0f, bool recompute_roi=false, ROI roi={}, int nthreads=0)
GLsizei n
Definition: glew.h:4040
openvdb::Vec3R invTransform(const openvdb::Vec3R &pos) const
Defined various multi-threaded utility functions for trees.
GLuint GLsizei GLsizei * length
Definition: glew.h:1825
This class implements the Transformer functor interface (specifically, the isAffine(), transform() and invTransform() methods) for a transform that maps an A grid into a B grid's index space such that, after resampling, A's index space and transform match B's index space and transform.
void preTranslate(const Vec3< T0 > &tr)
Left multiples by the specified translation, i.e. Trans * (*this)
Definition: Mat4.h:711
typedef int(WINAPI *PFNWGLRELEASEPBUFFERDCARBPROC)(HPBUFFERARB hPbuffer
bool threaded() const
Return true if threading is enabled.
bool empty() const
Return true if this bounding box is empty, i.e., it has no (positive) volume.
Definition: BBox.h:196
A TileSampler wraps a grid sampler of another type (BoxSampler, QuadraticSampler, etc...
int floor(T x)
Definition: ImathFun.h:150
Propagate the signs of distance values from the active voxels in the narrow band to the inactive valu...
Provises a unified interface for sampling, i.e. interpolation.
Definition: Interpolation.h:63
GLdouble GLdouble GLdouble b
Definition: glew.h:9122
void resampleToMatch(const GridType &inGrid, GridType &outGrid, Interrupter &interrupter)
Resample an input grid into an output grid of the same type such that, after resampling, the input and output grids coincide (apart from sampling artifacts), but the output grid's transform is unchanged.
GLsizei const GLchar *const * string
Definition: glew.h:1844
MatType scale(const Vec3< typename MatType::value_type > &s)
Return a matrix that scales by s.
Definition: Mat.h:630
T log(const T &v)
Definition: simd.h:7432
rst:: //////////////////////////////////////////////////////////////////
bool eq(const Vec3< T > &v, T eps=static_cast< T >(1.0e-7)) const
Test if "this" vector is equivalent to vector v with tolerance of eps.
Definition: Vec3.h:131
bool eq(const Mat4 &m, T eps=1.0e-8) const
Return true if this matrix is equivalent to m within a tolerance of eps.
Definition: Mat4.h:341
void signedFloodFill(TreeOrLeafManagerT &tree, bool threaded=true, size_t grainSize=1, Index minLevel=0)
Set the values of all inactive voxels and tiles of a narrow-band level set from the signs of the acti...
T & x()
Reference to the component, e.g. v.x() = 4.5f;.
Definition: Vec3.h:83
A GridTransformer applies a geometric transformation to an input grid using one of several sampling s...
#define OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN
Bracket code with OPENVDB_NO_TYPE_CONVERSION_WARNING_BEGIN/_END, to inhibit warnings about type conve...
Definition: Platform.h:187
GLdouble GLdouble GLdouble r
Definition: glew.h:1406
GLuint GLuint GLsizei count
Definition: glew.h:1253
TileSampler(const CoordBBox &b, const ValueT &tileVal, bool on)
Vec2< T > maxComponent(const Vec2< T > &v1, const Vec2< T > &v2)
Return component-wise maximum of the two vectors.
Definition: Vec2.h:512
static const Mat4< Real > & identity()
Predefined constant for identity matrix.
Definition: Mat4.h:125
int decompose(const math::Mat4< T > &m, math::Vec3< T > &scale, math::Vec3< T > &rotate, math::Vec3< T > &translate)
Decompose an affine transform into scale, rotation (XYZ order), and translation components.
GA_API const UT_StringHolder pivot
void setTransformTiles(bool b)
Enable or disable processing of tiles. (Enabled by default, except for level set grids.)
arg_join< It, char > join(It begin, It end, string_view sep)
Definition: format.h:3326
GLuint64EXT * result
Definition: glew.h:14007
INT64 INT64 INT64 remainder
Definition: wglew.h:1182
void setInterrupter(InterrupterType &)
Allow processing to be aborted by providing an interrupter object. The interrupter will be queried pe...
void doResampleToMatch(const GridType &inGrid, GridType &outGrid, Interrupter &interrupter)
PUGI__FN char_t * translate(char_t *buffer, const char_t *from, const char_t *to, size_t to_length)
Definition: pugixml.cpp:8352
vint4 min(const vint4 &a, const vint4 &b)
Definition: simd.h:4694
Vec3d worldToIndex(const Vec3d &xyz) const
Apply this transformation to the given coordinates.
Definition: Transform.h:110
void pruneLevelSet(TreeT &tree, bool threaded=true, size_t grainSize=1)
Reduce the memory footprint of a tree by replacing nodes whose values are all inactive with inactive ...
Definition: Prune.h:389
bool wasInterrupted(T *i, int percent=-1)
ImageBuf OIIO_API zero(ROI roi, int nthreads=0)
math::Mat4< Real > Mat4R
Definition: Types.h:79
bool isAffine(const Mat4< T > &m)
Definition: Mat4.h:1312
#define OPENVDB_VERSION_NAME
The version namespace name for this library version.
Definition: version.h:113
Mat4 inverse(T tolerance=0) const
Definition: Mat4.h:493
#define OPENVDB_THROW(exception, message)
Definition: Exceptions.h:82
bool isInside(const Vec3T &xyz) const
Return true if the given point is inside this bounding box.
Definition: BBox.h:267
MatType rotation(const Quat< typename MatType::value_type > &q, typename MatType::value_type eps=static_cast< typename MatType::value_type >(1.0e-8))
Return the rotation matrix specified by the given quaternion.
Definition: Mat.h:187