GCC Code Coverage Report


Directory: ./
File: openvdb/openvdb/tools/RayIntersector.h
Date: 2022-07-25 17:40:05
Exec Total Coverage
Lines: 74 108 68.5%
Functions: 21 39 53.8%
Branches: 82 244 33.6%

Line Branch Exec Source
1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 ///
4 /// @file RayIntersector.h
5 ///
6 /// @author Ken Museth
7 ///
8 /// @brief Accelerated intersection of a ray with a narrow-band level
9 /// set or a generic (e.g. density) volume. This will of course be
10 /// useful for respectively surface and volume rendering.
11 ///
12 /// @details This file defines two main classes,
13 /// LevelSetRayIntersector and VolumeRayIntersector, as well as the
14 /// three support classes LevelSetHDDA, VolumeHDDA and LinearSearchImpl.
15 /// The LevelSetRayIntersector is templated on the LinearSearchImpl class
16 /// and calls instances of the LevelSetHDDA class. The reason to split
17 /// level set ray intersection into three classes is twofold. First
18 /// LevelSetRayIntersector defines the public API for client code and
19 /// LinearSearchImpl defines the actual algorithm used for the
20 /// ray level-set intersection. In other words this design will allow
21 /// for the public API to be fixed while the intersection algorithm
22 /// can change without resolving to (slow) virtual methods. Second,
23 /// LevelSetHDDA, which implements a hierarchical Differential Digital
24 /// Analyzer, relies on partial template specialization, so it has to
25 /// be a standalone class (as opposed to a member class of
26 /// LevelSetRayIntersector). The VolumeRayIntersector is conceptually
27 /// much simpler than the LevelSetRayIntersector, and hence it only
28 /// depends on VolumeHDDA that implements the hierarchical
29 /// Differential Digital Analyzer.
30
31
32 #ifndef OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED
33 #define OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED
34
35 #include <openvdb/math/DDA.h>
36 #include <openvdb/math/Math.h>
37 #include <openvdb/math/Ray.h>
38 #include <openvdb/math/Stencils.h>
39 #include <openvdb/Grid.h>
40 #include <openvdb/Types.h>
41 #include "Morphology.h"
42 #include <iostream>
43 #include <type_traits>
44
45
46 namespace openvdb {
47 OPENVDB_USE_VERSION_NAMESPACE
48 namespace OPENVDB_VERSION_NAME {
49 namespace tools {
50
51 // Helper class that implements the actual search of the zero-crossing
52 // of the level set along the direction of a ray. This particular
53 // implementation uses iterative linear search.
54 template<typename GridT, int Iterations = 0, typename RealT = double>
55 class LinearSearchImpl;
56
57
58 ///////////////////////////////////// LevelSetRayIntersector /////////////////////////////////////
59
60
61 /// @brief This class provides the public API for intersecting a ray
62 /// with a narrow-band level set.
63 ///
64 /// @details It wraps a SearchImplT with a simple public API and
65 /// performs the actual hierarchical tree node and voxel traversal.
66 ///
67 /// @warning Use the (default) copy-constructor to make sure each
68 /// computational thread has their own instance of this class. This is
69 /// important since the SearchImplT contains a ValueAccessor that is
70 /// not thread-safe. However copying is very efficient.
71 ///
72 /// @see tools/RayTracer.h for examples of intended usage.
73 ///
74 /// @todo Add TrilinearSearchImpl, as an alternative to LinearSearchImpl,
75 /// that performs analytical 3D trilinear intersection tests, i.e., solves
76 /// cubic equations. This is slower but also more accurate than the 1D
77 /// linear interpolation in LinearSearchImpl.
78 template<typename GridT,
79 typename SearchImplT = LinearSearchImpl<GridT>,
80 int NodeLevel = GridT::TreeType::RootNodeType::ChildNodeType::LEVEL,
81 typename RayT = math::Ray<Real> >
82
9/18
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 10 taken 1 times.
✗ Branch 11 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 1 times.
✗ Branch 17 not taken.
9 class LevelSetRayIntersector
83 {
84 public:
85 using GridType = GridT;
86 using RayType = RayT;
87 using RealType = typename RayT::RealType;
88 using Vec3Type = typename RayT::Vec3T;
89 using ValueT = typename GridT::ValueType;
90 using TreeT = typename GridT::TreeType;
91
92 static_assert(NodeLevel >= -1 && NodeLevel < int(TreeT::DEPTH)-1, "NodeLevel out of range");
93 static_assert(std::is_floating_point<ValueT>::value,
94 "level set grids must have scalar, floating-point value types");
95
96 /// @brief Constructor
97 /// @param grid level set grid to intersect rays against.
98 /// @param isoValue optional iso-value for the ray-intersection.
99 18 LevelSetRayIntersector(const GridT& grid, const ValueT& isoValue = zeroVal<ValueT>())
100 18 : mTester(grid, isoValue)
101 {
102
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
18 if (!grid.hasUniformVoxels() ) {
103 OPENVDB_THROW(RuntimeError,
104 "LevelSetRayIntersector only supports uniform voxels!");
105 }
106
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
18 if (grid.getGridClass() != GRID_LEVEL_SET) {
107 OPENVDB_THROW(RuntimeError,
108 "LevelSetRayIntersector only supports level sets!"
109 "\nUse Grid::setGridClass(openvdb::GRID_LEVEL_SET)");
110 }
111 18 }
112
113 /// @brief Return the iso-value used for ray-intersections
114 const ValueT& getIsoValue() const { return mTester.getIsoValue(); }
115
116 /// @brief Return @c true if the index-space ray intersects the level set.
117 /// @param iRay ray represented in index space.
118 bool intersectsIS(const RayType& iRay) const
119 {
120 if (!mTester.setIndexRay(iRay)) return false;//missed bbox
121 return math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester);
122 }
123
124 /// @brief Return @c true if the index-space ray intersects the level set
125 /// @param iRay ray represented in index space.
126 /// @param iTime if an intersection was found it is assigned the time of the
127 /// intersection along the index ray.
128 bool intersectsIS(const RayType& iRay, RealType &iTime) const
129 {
130 if (!mTester.setIndexRay(iRay)) return false;//missed bbox
131 iTime = mTester.getIndexTime();
132 return math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester);
133 }
134
135 /// @brief Return @c true if the index-space ray intersects the level set.
136 /// @param iRay ray represented in index space.
137 /// @param xyz if an intersection was found it is assigned the
138 /// intersection point in index space, otherwise it is unchanged.
139 bool intersectsIS(const RayType& iRay, Vec3Type& xyz) const
140 {
141 if (!mTester.setIndexRay(iRay)) return false;//missed bbox
142 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
143 mTester.getIndexPos(xyz);
144 return true;
145 }
146
147 /// @brief Return @c true if the index-space ray intersects the level set.
148 /// @param iRay ray represented in index space.
149 /// @param xyz if an intersection was found it is assigned the
150 /// intersection point in index space, otherwise it is unchanged.
151 /// @param iTime if an intersection was found it is assigned the time of the
152 /// intersection along the index ray.
153 bool intersectsIS(const RayType& iRay, Vec3Type& xyz, RealType &iTime) const
154 {
155 if (!mTester.setIndexRay(iRay)) return false;//missed bbox
156 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
157 mTester.getIndexPos(xyz);
158 iTime = mTester.getIndexTime();
159 return true;
160 }
161
162 /// @brief Return @c true if the world-space ray intersects the level set.
163 /// @param wRay ray represented in world space.
164 bool intersectsWS(const RayType& wRay) const
165 {
166 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
167 return math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester);
168 }
169
170 /// @brief Return @c true if the world-space ray intersects the level set.
171 /// @param wRay ray represented in world space.
172 /// @param wTime if an intersection was found it is assigned the time of the
173 /// intersection along the world ray.
174 bool intersectsWS(const RayType& wRay, RealType &wTime) const
175 {
176 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
177 wTime = mTester.getWorldTime();
178 return math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester);
179 }
180
181 /// @brief Return @c true if the world-space ray intersects the level set.
182 /// @param wRay ray represented in world space.
183 /// @param world if an intersection was found it is assigned the
184 /// intersection point in world space, otherwise it is unchanged
185 bool intersectsWS(const RayType& wRay, Vec3Type& world) const
186 {
187 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
188 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
189 mTester.getWorldPos(world);
190 return true;
191 }
192
193 /// @brief Return @c true if the world-space ray intersects the level set.
194 /// @param wRay ray represented in world space.
195 /// @param world if an intersection was found it is assigned the
196 /// intersection point in world space, otherwise it is unchanged.
197 /// @param wTime if an intersection was found it is assigned the time of the
198 /// intersection along the world ray.
199 2097168 bool intersectsWS(const RayType& wRay, Vec3Type& world, RealType &wTime) const
200 {
201
2/2
✓ Branch 1 taken 266264 times.
✓ Branch 2 taken 782320 times.
2097168 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
202
2/2
✓ Branch 1 taken 205869 times.
✓ Branch 2 taken 60395 times.
532528 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
203 411738 mTester.getWorldPos(world);
204 411738 wTime = mTester.getWorldTime();
205 411738 return true;
206 }
207
208 /// @brief Return @c true if the world-space ray intersects the level set.
209 /// @param wRay ray represented in world space.
210 /// @param world if an intersection was found it is assigned the
211 /// intersection point in world space, otherwise it is unchanged.
212 /// @param normal if an intersection was found it is assigned the normal
213 /// of the level set surface in world space, otherwise it is unchanged.
214 bool intersectsWS(const RayType& wRay, Vec3Type& world, Vec3Type& normal) const
215 {
216 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
217 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
218 mTester.getWorldPosAndNml(world, normal);
219 return true;
220 }
221
222 /// @brief Return @c true if the world-space ray intersects the level set.
223 /// @param wRay ray represented in world space.
224 /// @param world if an intersection was found it is assigned the
225 /// intersection point in world space, otherwise it is unchanged.
226 /// @param normal if an intersection was found it is assigned the normal
227 /// of the level set surface in world space, otherwise it is unchanged.
228 /// @param wTime if an intersection was found it is assigned the time of the
229 /// intersection along the world ray.
230 bool intersectsWS(const RayType& wRay, Vec3Type& world, Vec3Type& normal, RealType &wTime) const
231 {
232 if (!mTester.setWorldRay(wRay)) return false;//missed bbox
233 if (!math::LevelSetHDDA<TreeT, NodeLevel>::test(mTester)) return false;//missed level set
234 mTester.getWorldPosAndNml(world, normal);
235 wTime = mTester.getWorldTime();
236 return true;
237 }
238
239 private:
240
241 mutable SearchImplT mTester;
242
243 };// LevelSetRayIntersector
244
245
246 ////////////////////////////////////// VolumeRayIntersector //////////////////////////////////////
247
248
249 /// @brief This class provides the public API for intersecting a ray
250 /// with a generic (e.g. density) volume.
251 /// @details Internally it performs the actual hierarchical tree node traversal.
252 /// @warning Use the (default) copy-constructor to make sure each
253 /// computational thread has their own instance of this class. This is
254 /// important since it contains a ValueAccessor that is
255 /// not thread-safe and a CoordBBox of the active voxels that should
256 /// not be re-computed for each thread. However copying is very efficient.
257 /// @par Example:
258 /// @code
259 /// // Create an instance for the master thread
260 /// VolumeRayIntersector inter(grid);
261 /// // For each additional thread use the copy constructor. This
262 /// // amortizes the overhead of computing the bbox of the active voxels!
263 /// VolumeRayIntersector inter2(inter);
264 /// // Before each ray-traversal set the index ray.
265 /// iter.setIndexRay(ray);
266 /// // or world ray
267 /// iter.setWorldRay(ray);
268 /// // Now you can begin the ray-marching using consecutive calls to VolumeRayIntersector::march
269 /// double t0=0, t1=0;// note the entry and exit times are with respect to the INDEX ray
270 /// while ( inter.march(t0, t1) ) {
271 /// // perform line-integration between t0 and t1
272 /// }}
273 /// @endcode
274 template<typename GridT,
275 int NodeLevel = GridT::TreeType::RootNodeType::ChildNodeType::LEVEL,
276 typename RayT = math::Ray<Real> >
277 class VolumeRayIntersector
278 {
279 public:
280 using GridType = GridT;
281 using RayType = RayT;
282 using RealType = typename RayT::RealType;
283 using RootType = typename GridT::TreeType::RootNodeType;
284 using TreeT = tree::Tree<typename RootType::template ValueConverter<bool>::Type>;
285
286 static_assert(NodeLevel >= 0 && NodeLevel < int(TreeT::DEPTH)-1, "NodeLevel out of range");
287
288 /// @brief Grid constructor
289 /// @param grid Generic grid to intersect rays against.
290 /// @param dilationCount The number of voxel dilations performed
291 /// on (a boolean copy of) the input grid. This allows the
292 /// intersector to account for the size of interpolation kernels
293 /// in client code.
294 /// @throw RuntimeError if the voxels of the grid are not uniform
295 /// or the grid is empty.
296 13 VolumeRayIntersector(const GridT& grid, int dilationCount = 0)
297 : mIsMaster(true)
298
2/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 13 times.
✗ Branch 5 not taken.
13 , mTree(new TreeT(grid.tree(), false, TopologyCopy()))
299 , mGrid(&grid)
300
1/2
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
26 , mAccessor(*mTree)
301 {
302
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 13 times.
13 if (!grid.hasUniformVoxels() ) {
303 OPENVDB_THROW(RuntimeError,
304 "VolumeRayIntersector only supports uniform voxels!");
305 }
306
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 13 times.
13 if ( grid.empty() ) {
307 OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids");
308 }
309
310 // Dilate active voxels to better account for the size of interpolation kernels
311 13 tools::dilateActiveValues(*mTree, dilationCount, tools::NN_FACE, tools::IGNORE_TILES);
312
313 13 mTree->root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false);
314
315 mBBox.max().offset(1);//padding so the bbox of a node becomes (origin,origin + node_dim)
316 13 }
317
318 /// @brief Grid and BBox constructor
319 /// @param grid Generic grid to intersect rays against.
320 /// @param bbox The axis-aligned bounding-box in the index space of the grid.
321 /// @warning It is assumed that bbox = (min, min + dim) where min denotes
322 /// to the smallest grid coordinates and dim are the integer length of the bbox.
323 /// @throw RuntimeError if the voxels of the grid are not uniform
324 /// or the grid is empty.
325 VolumeRayIntersector(const GridT& grid, const math::CoordBBox& bbox)
326 : mIsMaster(true)
327 , mTree(new TreeT(grid.tree(), false, TopologyCopy()))
328 , mGrid(&grid)
329 , mAccessor(*mTree)
330 , mBBox(bbox)
331 {
332 if (!grid.hasUniformVoxels() ) {
333 OPENVDB_THROW(RuntimeError,
334 "VolumeRayIntersector only supports uniform voxels!");
335 }
336 if ( grid.empty() ) {
337 OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids");
338 }
339 }
340
341 /// @brief Shallow copy constructor
342 /// @warning This copy constructor creates shallow copies of data
343 /// members of the instance passed as the argument. For
344 /// performance reasons we are not using shared pointers (their
345 /// mutex-lock impairs multi-threading).
346 VolumeRayIntersector(const VolumeRayIntersector& other)
347 : mIsMaster(false)
348 , mTree(other.mTree)//shallow copy
349 , mGrid(other.mGrid)//shallow copy
350 , mAccessor(*mTree)//initialize new (vs deep copy)
351 , mRay(other.mRay)//deep copy
352 , mTmax(other.mTmax)//deep copy
353 , mBBox(other.mBBox)//deep copy
354 {
355 }
356
357 /// @brief Destructor
358
28/108
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 1 times.
✗ Branch 9 not taken.
✓ Branch 12 taken 1 times.
✗ Branch 13 not taken.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
✓ Branch 18 taken 1 times.
✗ Branch 19 not taken.
✓ Branch 20 taken 1 times.
✗ Branch 21 not taken.
✓ Branch 24 taken 1 times.
✗ Branch 25 not taken.
✓ Branch 26 taken 1 times.
✗ Branch 27 not taken.
✓ Branch 30 taken 1 times.
✗ Branch 31 not taken.
✓ Branch 32 taken 1 times.
✗ Branch 33 not taken.
✓ Branch 36 taken 1 times.
✗ Branch 37 not taken.
✓ Branch 38 taken 1 times.
✗ Branch 39 not taken.
✓ Branch 42 taken 1 times.
✗ Branch 43 not taken.
✓ Branch 44 taken 1 times.
✗ Branch 45 not taken.
✓ Branch 48 taken 1 times.
✗ Branch 49 not taken.
✓ Branch 50 taken 1 times.
✗ Branch 51 not taken.
✓ Branch 54 taken 1 times.
✗ Branch 55 not taken.
✓ Branch 56 taken 1 times.
✗ Branch 57 not taken.
✓ Branch 60 taken 1 times.
✗ Branch 61 not taken.
✓ Branch 62 taken 1 times.
✗ Branch 63 not taken.
✓ Branch 65 taken 1 times.
✗ Branch 66 not taken.
✓ Branch 67 taken 1 times.
✗ Branch 68 not taken.
✓ Branch 69 taken 1 times.
✗ Branch 70 not taken.
✓ Branch 72 taken 1 times.
✗ Branch 73 not taken.
✓ Branch 74 taken 1 times.
✗ Branch 75 not taken.
✓ Branch 76 taken 1 times.
✗ Branch 77 not taken.
✗ Branch 80 not taken.
✗ Branch 81 not taken.
✗ Branch 82 not taken.
✗ Branch 83 not taken.
✗ Branch 85 not taken.
✗ Branch 86 not taken.
✗ Branch 87 not taken.
✗ Branch 88 not taken.
✗ Branch 90 not taken.
✗ Branch 91 not taken.
✗ Branch 92 not taken.
✗ Branch 93 not taken.
✗ Branch 95 not taken.
✗ Branch 96 not taken.
✗ Branch 97 not taken.
✗ Branch 98 not taken.
✗ Branch 100 not taken.
✗ Branch 101 not taken.
✗ Branch 102 not taken.
✗ Branch 103 not taken.
✗ Branch 105 not taken.
✗ Branch 106 not taken.
✗ Branch 107 not taken.
✗ Branch 108 not taken.
✗ Branch 110 not taken.
✗ Branch 111 not taken.
✗ Branch 112 not taken.
✗ Branch 113 not taken.
✗ Branch 115 not taken.
✗ Branch 116 not taken.
✗ Branch 117 not taken.
✗ Branch 118 not taken.
✗ Branch 120 not taken.
✗ Branch 121 not taken.
✗ Branch 122 not taken.
✗ Branch 123 not taken.
✗ Branch 125 not taken.
✗ Branch 126 not taken.
✗ Branch 127 not taken.
✗ Branch 128 not taken.
✗ Branch 130 not taken.
✗ Branch 131 not taken.
✗ Branch 132 not taken.
✗ Branch 133 not taken.
✗ Branch 135 not taken.
✗ Branch 136 not taken.
✗ Branch 137 not taken.
✗ Branch 138 not taken.
✗ Branch 140 not taken.
✗ Branch 141 not taken.
✗ Branch 142 not taken.
✗ Branch 143 not taken.
13 ~VolumeRayIntersector() { if (mIsMaster) delete mTree; }
359
360 /// @brief Return @c false if the index ray misses the bbox of the grid.
361 /// @param iRay Ray represented in index space.
362 /// @warning Call this method (or setWorldRay) before the ray
363 /// traversal starts and use the return value to decide if further
364 /// marching is required.
365 13 inline bool setIndexRay(const RayT& iRay)
366 {
367 13 mRay = iRay;
368 13 const bool hit = mRay.clip(mBBox);
369
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (hit) mTmax = mRay.t1();
370 13 return hit;
371 }
372
373 /// @brief Return @c false if the world ray misses the bbox of the grid.
374 /// @param wRay Ray represented in world space.
375 /// @warning Call this method (or setIndexRay) before the ray
376 /// traversal starts and use the return value to decide if further
377 /// marching is required.
378 /// @details Since hit times are computed with respect to the ray
379 /// represented in index space of the current grid, it is
380 /// recommended that either the client code uses getIndexPos to
381 /// compute index position from hit times or alternatively keeps
382 /// an instance of the index ray and instead uses setIndexRay to
383 /// initialize the ray.
384 inline bool setWorldRay(const RayT& wRay)
385 {
386 return this->setIndexRay(wRay.worldToIndex(*mGrid));
387 }
388
389 22 inline typename RayT::TimeSpan march()
390 {
391 22 const typename RayT::TimeSpan t = mHDDA.march(mRay, mAccessor);
392
3/4
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
22 if (t.t1>0) mRay.setTimes(t.t1 + math::Delta<RealType>::value(), mTmax);
393 22 return t;
394 }
395
396 /// @brief Return @c true if the ray intersects active values,
397 /// i.e. either active voxels or tiles. Only when a hit is
398 /// detected are t0 and t1 updated with the corresponding entry
399 /// and exit times along the INDEX ray!
400 /// @note Note that t0 and t1 are only resolved at the node level
401 /// (e.g. a LeafNode with active voxels) as opposed to the individual
402 /// active voxels.
403 /// @param t0 If the return value > 0 this is the time of the
404 /// first hit of an active tile or leaf.
405 /// @param t1 If the return value > t0 this is the time of the
406 /// first hit (> t0) of an inactive tile or exit point of the
407 /// BBOX for the leaf nodes.
408 /// @warning t0 and t1 are computed with respect to the ray represented in
409 /// index space of the current grid, not world space!
410 inline bool march(RealType& t0, RealType& t1)
411 {
412 22 const typename RayT::TimeSpan t = this->march();
413 t.get(t0, t1);
414 return t.valid();
415 }
416
417 /// @brief Generates a list of hits along the ray.
418 ///
419 /// @param list List of hits represented as time spans.
420 ///
421 /// @note ListType is a list of RayType::TimeSpan and is required to
422 /// have the two methods: clear() and push_back(). Thus, it could
423 /// be std::vector<typename RayType::TimeSpan> or
424 /// std::deque<typename RayType::TimeSpan>.
425 template <typename ListType>
426 inline void hits(ListType& list)
427 {
428
2/8
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 10 not taken.
✗ Branch 11 not taken.
2 mHDDA.hits(mRay, mAccessor, list);
429 2 }
430
431 /// @brief Return the floating-point index position along the
432 /// current index ray at the specified time.
433 inline Vec3R getIndexPos(RealType time) const { return mRay(time); }
434
435 /// @brief Return the floating-point world position along the
436 /// current index ray at the specified time.
437 inline Vec3R getWorldPos(RealType time) const { return mGrid->indexToWorld(mRay(time)); }
438
439 inline RealType getWorldTime(RealType time) const
440 {
441 return time*mGrid->transform().baseMap()->applyJacobian(mRay.dir()).length();
442 }
443
444 /// @brief Return a const reference to the input grid.
445 const GridT& grid() const { return *mGrid; }
446
447 /// @brief Return a const reference to the (potentially dilated)
448 /// bool tree used to accelerate the ray marching.
449 const TreeT& tree() const { return *mTree; }
450
451 /// @brief Return a const reference to the BBOX of the grid
452 const math::CoordBBox& bbox() const { return mBBox; }
453
454 /// @brief Print bbox, statistics, memory usage and other information.
455 /// @param os a stream to which to write textual information
456 /// @param verboseLevel 1: print bbox only; 2: include boolean tree
457 /// statistics; 3: include memory usage
458 void print(std::ostream& os = std::cout, int verboseLevel = 1)
459 {
460 if (verboseLevel>0) {
461 os << "BBox: " << mBBox << std::endl;
462 if (verboseLevel==2) {
463 mTree->print(os, 1);
464 } else if (verboseLevel>2) {
465 mTree->print(os, 2);
466 }
467 }
468 }
469
470 private:
471 using AccessorT = typename tree::ValueAccessor<const TreeT,/*IsSafe=*/false>;
472
473 const bool mIsMaster;
474 TreeT* mTree;
475 const GridT* mGrid;
476 AccessorT mAccessor;
477 RayT mRay;
478 RealType mTmax;
479 math::CoordBBox mBBox;
480 math::VolumeHDDA<TreeT, RayType, NodeLevel> mHDDA;
481
482 };// VolumeRayIntersector
483
484
485 //////////////////////////////////////// LinearSearchImpl ////////////////////////////////////////
486
487
488 /// @brief Implements linear iterative search for an iso-value of
489 /// the level set along the direction of the ray.
490 ///
491 /// @note Since this class is used internally in
492 /// LevelSetRayIntersector (define above) and LevelSetHDDA (defined below)
493 /// client code should never interact directly with its API. This also
494 /// explains why we are not concerned with the fact that several of
495 /// its methods are unsafe to call unless roots were already detected.
496 ///
497 /// @details It is approximate due to the limited number of iterations
498 /// which can can be defined with a template parameter. However the default value
499 /// has proven surprisingly accurate and fast. In fact more iterations
500 /// are not guaranteed to give significantly better results.
501 ///
502 /// @warning Since the root-searching algorithm is approximate
503 /// (first-order) it is possible to miss intersections if the
504 /// iso-value is too close to the inside or outside of the narrow
505 /// band (typically a distance less than a voxel unit).
506 ///
507 /// @warning Since this class internally stores a ValueAccessor it is NOT thread-safe,
508 /// so make sure to give each thread its own instance. This of course also means that
509 /// the cost of allocating an instance should (if possible) be amortized over
510 /// as many ray intersections as possible.
511 template<typename GridT, int Iterations, typename RealT>
512 class LinearSearchImpl
513 {
514 public:
515 using RayT = math::Ray<RealT>;
516 using VecT = math::Vec3<RealT>;
517 using ValueT = typename GridT::ValueType;
518 using AccessorT = typename GridT::ConstAccessor;
519 using StencilT = math::BoxStencil<GridT>;
520
521 /// @brief Constructor from a grid.
522 /// @throw RunTimeError if the grid is empty.
523 /// @throw ValueError if the isoValue is not inside the narrow-band.
524 18 LinearSearchImpl(const GridT& grid, const ValueT& isoValue = zeroVal<ValueT>())
525 : mStencil(grid),
526 mIsoValue(isoValue),
527 18 mMinValue(isoValue - ValueT(2 * grid.voxelSize()[0])),
528
2/4
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
54 mMaxValue(isoValue + ValueT(2 * grid.voxelSize()[0]))
529 {
530
2/4
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 9 times.
18 if ( grid.empty() ) {
531 OPENVDB_THROW(RuntimeError, "LinearSearchImpl does not supports empty grids");
532 }
533
2/4
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 9 times.
18 if (mIsoValue<= -grid.background() ||
534 mIsoValue>= grid.background() ){
535 OPENVDB_THROW(ValueError, "The iso-value must be inside the narrow-band!");
536 }
537
1/2
✓ Branch 1 taken 9 times.
✗ Branch 2 not taken.
18 grid.tree().root().evalActiveBoundingBox(mBBox, /*visit individual voxels*/false);
538 18 }
539
540 /// @brief Return the iso-value used for ray-intersections
541 const ValueT& getIsoValue() const { return mIsoValue; }
542
543 /// @brief Return @c false if the ray misses the bbox of the grid.
544 /// @param iRay Ray represented in index space.
545 /// @warning Call this method before the ray traversal starts.
546 inline bool setIndexRay(const RayT& iRay)
547 {
548 mRay = iRay;
549 return mRay.clip(mBBox);//did it hit the bbox
550 }
551
552 /// @brief Return @c false if the ray misses the bbox of the grid.
553 /// @param wRay Ray represented in world space.
554 /// @warning Call this method before the ray traversal starts.
555 2097168 inline bool setWorldRay(const RayT& wRay)
556 {
557 2097168 mRay = wRay.worldToIndex(mStencil.grid());
558 2097168 return mRay.clip(mBBox);//did it hit the bbox
559 }
560
561 /// @brief Get the intersection point in index space.
562 /// @param xyz The position in index space of the intersection.
563 inline void getIndexPos(VecT& xyz) const { xyz = mRay(mTime); }
564
565 /// @brief Get the intersection point in world space.
566 /// @param xyz The position in world space of the intersection.
567 411738 inline void getWorldPos(VecT& xyz) const { xyz = mStencil.grid().indexToWorld(mRay(mTime)); }
568
569 /// @brief Get the intersection point and normal in world space
570 /// @param xyz The position in world space of the intersection.
571 /// @param nml The surface normal in world space of the intersection.
572 inline void getWorldPosAndNml(VecT& xyz, VecT& nml)
573 {
574 this->getIndexPos(xyz);
575 mStencil.moveTo(xyz);
576 nml = mStencil.gradient(xyz);
577 nml.normalize();
578 xyz = mStencil.grid().indexToWorld(xyz);
579 }
580
581 /// @brief Return the time of intersection along the index ray.
582 inline RealT getIndexTime() const { return mTime; }
583
584 /// @brief Return the time of intersection along the world ray.
585 411738 inline RealT getWorldTime() const
586 {
587
2/4
✓ Branch 2 taken 205869 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 205869 times.
✗ Branch 5 not taken.
823476 return mTime*mStencil.grid().transform().baseMap()->applyJacobian(mRay.dir()).length();
588 }
589
590 private:
591
592 /// @brief Initiate the local voxel intersection test.
593 /// @warning Make sure to call this method before the local voxel intersection test.
594 inline void init(RealT t0)
595 {
596 537330 mT[0] = t0;
597 537330 mV[0] = static_cast<ValueT>(this->interpValue(t0));
598 537330 }
599
600 inline void setRange(RealT t0, RealT t1) { mRay.setTimes(t0, t1); }
601
602 /// @brief Return a const reference to the ray.
603 1585724 inline const RayT& ray() const { return mRay; }
604
605 /// @brief Return true if a node of the specified type exists at ijk.
606 template <typename NodeT>
607 inline bool hasNode(const Coord& ijk)
608 {
609 7670073 return mStencil.accessor().template probeConstNode<NodeT>(ijk) != nullptr;
610 }
611
612 /// @brief Return @c true if an intersection is detected.
613 /// @param ijk Grid coordinate of the node origin or voxel being tested.
614 /// @param time Time along the index ray being tested.
615 /// @warning Only if an intersection is detected is it safe to
616 /// call getIndexPos, getWorldPos and getWorldPosAndNml!
617 7285620 inline bool operator()(const Coord& ijk, RealT time)
618 {
619 ValueT V;
620 7285620 if (mStencil.accessor().probeValue(ijk, V) &&//within narrow band
621
6/6
✓ Branch 0 taken 985768 times.
✓ Branch 1 taken 2657042 times.
✓ Branch 2 taken 985767 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 985764 times.
✓ Branch 5 taken 3 times.
7285620 V>mMinValue && V<mMaxValue) {// and close to iso-value?
622 1971528 mT[1] = time;
623
2/2
✓ Branch 1 taken 205869 times.
✓ Branch 2 taken 779895 times.
1971528 mV[1] = static_cast<ValueT>(this->interpValue(time));
624
2/2
✓ Branch 0 taken 205869 times.
✓ Branch 1 taken 779895 times.
1971528 if (math::ZeroCrossing(mV[0], mV[1])) {
625 411738 mTime = this->interpTime();
626 OPENVDB_NO_UNREACHABLE_CODE_WARNING_BEGIN
627
2/2
✓ Branch 0 taken 411722 times.
✓ Branch 1 taken 205861 times.
1235166 for (int n=0; Iterations>0 && n<Iterations; ++n) {//resolved at compile-time
628
2/2
✓ Branch 1 taken 207730 times.
✓ Branch 2 taken 203992 times.
823444 V = static_cast<ValueT>(this->interpValue(mTime));
629
2/2
✓ Branch 0 taken 207730 times.
✓ Branch 1 taken 203992 times.
823444 const int m = math::ZeroCrossing(mV[0], V) ? 1 : 0;
630 823444 mV[m] = V;
631 823444 mT[m] = mTime;
632 823444 mTime = this->interpTime();
633 }
634 OPENVDB_NO_UNREACHABLE_CODE_WARNING_END
635 16 return true;
636 }
637 1559790 mT[0] = mT[1];
638 1559790 mV[0] = mV[1];
639 }
640 return false;
641 }
642
643
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 617591 times.
1235182 inline RealT interpTime()
644 {
645
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 617591 times.
1235182 assert( math::isApproxLarger(mT[1], mT[0], RealT(1e-6) ) );
646 1235182 return mT[0]+(mT[1]-mT[0])*mV[0]/(mV[0]-mV[1]);
647 }
648
649 1934816 inline RealT interpValue(RealT time)
650 {
651 1934816 const VecT pos = mRay(time);
652 1934816 mStencil.moveTo(pos);
653 1934816 return mStencil.interpolation(pos) - mIsoValue;
654 }
655
656 template<typename, int> friend struct math::LevelSetHDDA;
657
658 RayT mRay;
659 StencilT mStencil;
660 RealT mTime;//time of intersection
661 ValueT mV[2];
662 RealT mT[2];
663 const ValueT mIsoValue, mMinValue, mMaxValue;
664 math::CoordBBox mBBox;
665 };// LinearSearchImpl
666
667 } // namespace tools
668 } // namespace OPENVDB_VERSION_NAME
669 } // namespace openvdb
670
671 #endif // OPENVDB_TOOLS_RAYINTERSECTOR_HAS_BEEN_INCLUDED
672