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