1 // Copyright Contributors to the OpenVDB Project
2 // SPDX-License-Identifier: MPL-2.0
3 
4 /// @file Ray.h
5 ///
6 /// @author Ken Museth
7 ///
8 /// @brief A Ray class.
9 
10 #ifndef NANOVDB_RAY_H_HAS_BEEN_INCLUDED
11 #define NANOVDB_RAY_H_HAS_BEEN_INCLUDED
12 
13 #include <nanovdb/NanoVDB.h> // for Vec3
14 
15 namespace nanovdb {
16 
17 template<typename RealT>
18 class Ray
19 {
20 public:
21     using RealType = RealT;
22     using Vec3Type = Vec3<RealT>;
23     using Vec3T = Vec3Type;
24 
25     struct TimeSpan
26     {
27         RealT t0, t1;
28         /// @brief Default constructor
TimeSpanTimeSpan29         __hostdev__ TimeSpan() {}
30         /// @brief Constructor
TimeSpanTimeSpan31         __hostdev__ TimeSpan(RealT _t0, RealT _t1)
32             : t0(_t0)
33             , t1(_t1)
34         {
35         }
36         /// @brief Set both times
setTimeSpan37         __hostdev__ void set(RealT _t0, RealT _t1)
38         {
39             t0 = _t0;
40             t1 = _t1;
41         }
42         /// @brief Get both times
getTimeSpan43         __hostdev__ void get(RealT& _t0, RealT& _t1) const
44         {
45             _t0 = t0;
46             _t1 = t1;
47         }
48         /// @brief Return @c true if t1 is larger than t0 by at least eps.
49         __hostdev__ bool valid(RealT eps = Delta<RealT>::value()) const { return (t1 - t0) > eps; }
50         /// @brief Return the midpoint of the ray.
midTimeSpan51         __hostdev__ RealT mid() const { return 0.5 * (t0 + t1); }
52         /// @brief Multiplies both times
scaleTimeSpan53         __hostdev__ void scale(RealT s)
54         {
55             assert(s > 0);
56             t0 *= s;
57             t1 *= s;
58         }
59         /// @brief Return @c true if time is inclusive
testTimeSpan60         __hostdev__ bool test(RealT t) const { return (t >= t0 && t <= t1); }
61     };
62 
63     __hostdev__ Ray(const Vec3Type& eye = Vec3Type(0, 0, 0),
64                     const Vec3Type& direction = Vec3Type(1, 0, 0),
65                     RealT           t0 = Delta<RealT>::value(),
66                     RealT           t1 = Maximum<RealT>::value())
mEye(eye)67         : mEye(eye)
68         , mDir(direction)
69         , mInvDir(1 / mDir[0], 1 / mDir[1], 1 / mDir[2])
70         , mTimeSpan(t0, t1)
71         , mSign{mInvDir[0] < 0, mInvDir[1] < 0, mInvDir[2] < 0}
72     {
73     }
74 
offsetEye(RealT offset)75     __hostdev__ Ray& offsetEye(RealT offset)
76     {
77         mEye[0] += offset;
78         mEye[1] += offset;
79         mEye[2] += offset;
80         return *this;
81     }
82 
setEye(const Vec3Type & eye)83     __hostdev__ Ray& setEye(const Vec3Type& eye)
84     {
85         mEye = eye;
86         return *this;
87     }
88 
setDir(const Vec3Type & dir)89     __hostdev__ Ray& setDir(const Vec3Type& dir)
90     {
91         mDir = dir;
92         mInvDir[0] = 1.0 / mDir[0];
93         mInvDir[1] = 1.0 / mDir[1];
94         mInvDir[2] = 1.0 / mDir[2];
95         mSign[0] = mInvDir[0] < 0;
96         mSign[1] = mInvDir[1] < 0;
97         mSign[2] = mInvDir[2] < 0;
98         return *this;
99     }
100 
setMinTime(RealT t0)101     __hostdev__ Ray& setMinTime(RealT t0)
102     {
103         mTimeSpan.t0 = t0;
104         return *this;
105     }
106 
setMaxTime(RealT t1)107     __hostdev__ Ray& setMaxTime(RealT t1)
108     {
109         mTimeSpan.t1 = t1;
110         return *this;
111     }
112 
113     __hostdev__ Ray& setTimes(
114         RealT t0 = Delta<RealT>::value(),
115         RealT t1 = Maximum<RealT>::value())
116     {
117         assert(t0 > 0 && t1 > 0);
118         mTimeSpan.set(t0, t1);
119         return *this;
120     }
121 
scaleTimes(RealT scale)122     __hostdev__ Ray& scaleTimes(RealT scale)
123     {
124         mTimeSpan.scale(scale);
125         return *this;
126     }
127 
128     __hostdev__ Ray& reset(
129         const Vec3Type& eye,
130         const Vec3Type& direction,
131         RealT           t0 = Delta<RealT>::value(),
132         RealT           t1 = Maximum<RealT>::value())
133     {
134         this->setEye(eye);
135         this->setDir(direction);
136         this->setTimes(t0, t1);
137         return *this;
138     }
139 
eye()140     __hostdev__ const Vec3T& eye() const { return mEye; }
141 
dir()142     __hostdev__ const Vec3T& dir() const { return mDir; }
143 
invDir()144     __hostdev__ const Vec3T& invDir() const { return mInvDir; }
145 
t0()146     __hostdev__ RealT t0() const { return mTimeSpan.t0; }
147 
t1()148     __hostdev__ RealT t1() const { return mTimeSpan.t1; }
149 
sign(int i)150     __hostdev__ int sign(int i) const { return mSign[i]; }
151 
152     /// @brief Return the position along the ray at the specified time.
operator()153     __hostdev__ Vec3T operator()(RealT time) const
154     {
155 #if 1
156         return Vec3T(fmaf(time, mDir[0], mEye[0]),
157                      fmaf(time, mDir[1], mEye[1]),
158                      fmaf(time, mDir[2], mEye[2]));
159 #else
160         return mEye + mDir * time;
161 #endif
162     }
163 
164     /// @brief Return the starting point of the ray.
start()165     __hostdev__ Vec3T start() const { return (*this)(mTimeSpan.t0); }
166 
167     /// @brief Return the endpoint of the ray.
end()168     __hostdev__ Vec3T end() const { return (*this)(mTimeSpan.t1); }
169 
170     /// @brief Return the midpoint of the ray.
mid()171     __hostdev__ Vec3T mid() const { return (*this)(mTimeSpan.mid()); }
172 
173     /// @brief Return @c true if t1 is larger than t0 by at least eps.
174     __hostdev__ bool valid(RealT eps = Delta<float>::value()) const { return mTimeSpan.valid(eps); }
175 
176     /// @brief Return @c true if @a time is within t0 and t1, both inclusive.
test(RealT time)177     __hostdev__ bool test(RealT time) const { return mTimeSpan.test(time); }
178 
179     /// @brief Return a new Ray that is transformed with the specified map.
180     ///
181     /// @param map  the map from which to construct the new Ray.
182     ///
183     /// @warning Assumes a linear map and a normalized direction.
184     ///
185     /// @details The requirement that the direction is normalized
186     ///          follows from the transformation of t0 and t1 - and that fact that
187     ///          we want applyMap and applyInverseMap to be inverse operations.
188     template<typename MapType>
applyMap(const MapType & map)189     __hostdev__ Ray applyMap(const MapType& map) const
190     {
191         const Vec3T eye = map.applyMap(mEye);
192         const Vec3T dir = map.applyJacobian(mDir);
193         const RealT length = dir.length(), invLength = RealT(1) / length;
194         RealT       t1 = mTimeSpan.t1;
195         if (mTimeSpan.t1 < Maximum<RealT>::value()) {
196             t1 *= length;
197         }
198         return Ray(eye, dir * invLength, length * mTimeSpan.t0, t1);
199     }
200     template<typename MapType>
applyMapF(const MapType & map)201     __hostdev__ Ray applyMapF(const MapType& map) const
202     {
203         const Vec3T eye = map.applyMapF(mEye);
204         const Vec3T dir = map.applyJacobianF(mDir);
205         const RealT length = dir.length(), invLength = RealT(1) / length;
206         RealT       t1 = mTimeSpan.t1;
207         if (mTimeSpan.t1 < Maximum<RealT>::value()) {
208             t1 *= length;
209         }
210         return Ray(eye, dir * invLength, length * mTimeSpan.t0, t1);
211     }
212 
213     /// @brief Return a new Ray that is transformed with the inverse of the specified map.
214     ///
215     /// @param map  the map from which to construct the new Ray by inverse mapping.
216     ///
217     /// @warning Assumes a linear map and a normalized direction.
218     ///
219     /// @details The requirement that the direction is normalized
220     ///          follows from the transformation of t0 and t1 - and that fact that
221     ///          we want applyMap and applyInverseMap to be inverse operations.
222     template<typename MapType>
applyInverseMap(const MapType & map)223     __hostdev__ Ray applyInverseMap(const MapType& map) const
224     {
225         const Vec3T eye = map.applyInverseMap(mEye);
226         const Vec3T dir = map.applyInverseJacobian(mDir);
227         const RealT length = dir.length(), invLength = RealT(1) / length;
228         return Ray(eye, dir * invLength, length * mTimeSpan.t0, length * mTimeSpan.t1);
229     }
230     template<typename MapType>
applyInverseMapF(const MapType & map)231     __hostdev__ Ray applyInverseMapF(const MapType& map) const
232     {
233         const Vec3T eye = map.applyInverseMapF(mEye);
234         const Vec3T dir = map.applyInverseJacobianF(mDir);
235         const RealT length = dir.length(), invLength = RealT(1) / length;
236         return Ray(eye, dir * invLength, length * mTimeSpan.t0, length * mTimeSpan.t1);
237     }
238 
239     /// @brief Return a new ray in world space, assuming the existing
240     ///        ray is represented in the index space of the specified grid.
241     template<typename GridType>
indexToWorldF(const GridType & grid)242     __hostdev__ Ray indexToWorldF(const GridType& grid) const
243     {
244         const Vec3T eye = grid.indexToWorldF(mEye);
245         const Vec3T dir = grid.indexToWorldDirF(mDir);
246         const RealT length = dir.length(), invLength = RealT(1) / length;
247         RealT       t1 = mTimeSpan.t1;
248         if (mTimeSpan.t1 < Maximum<RealT>::value()) {
249             t1 *= length;
250         }
251         return Ray(eye, dir * invLength, length * mTimeSpan.t0, t1);
252     }
253 
254     /// @brief Return a new ray in index space, assuming the existing
255     ///        ray is represented in the world space of the specified grid.
256     template<typename GridType>
worldToIndexF(const GridType & grid)257     __hostdev__ Ray worldToIndexF(const GridType& grid) const
258     {
259         const Vec3T eye = grid.worldToIndexF(mEye);
260         const Vec3T dir = grid.worldToIndexDirF(mDir);
261         const RealT length = dir.length(), invLength = RealT(1) / length;
262         RealT       t1 = mTimeSpan.t1;
263         if (mTimeSpan.t1 < Maximum<RealT>::value()) {
264             t1 *= length;
265         }
266         return Ray(eye, dir * invLength, length * mTimeSpan.t0, t1);
267     }
268 
269     /// @brief Return true if this ray intersects the specified sphere.
270     ///
271     /// @param center The center of the sphere in the same space as this ray.
272     /// @param radius The radius of the sphere in the same units as this ray.
273     /// @param t0     The first intersection point if an intersection exists.
274     /// @param t1     The second intersection point if an intersection exists.
275     ///
276     /// @note If the return value is true, i.e. a hit, and t0 =
277     ///       this->t0() or t1 == this->t1() only one true intersection exist.
intersects(const Vec3T & center,RealT radius,RealT & t0,RealT & t1)278     __hostdev__ bool intersects(const Vec3T& center, RealT radius, RealT& t0, RealT& t1) const
279     {
280         const Vec3T origin = mEye - center;
281         const RealT A = mDir.lengthSqr();
282         const RealT B = 2 * mDir.dot(origin);
283         const RealT C = origin.lengthSqr() - radius * radius;
284         const RealT D = B * B - 4 * A * C;
285 
286         if (D < 0) {
287             return false;
288         }
289         const RealT Q = RealT(-0.5) * (B < 0 ? (B + Sqrt(D)) : (B - Sqrt(D)));
290 
291         t0 = Q / A;
292         t1 = C / Q;
293 
294         if (t0 > t1) {
295             RealT tmp = t0;
296             t0 = t1;
297             t1 = tmp;
298         }
299         if (t0 < mTimeSpan.t0) {
300             t0 = mTimeSpan.t0;
301         }
302         if (t1 > mTimeSpan.t1) {
303             t1 = mTimeSpan.t1;
304         }
305         return t0 <= t1;
306     }
307 
308     /// @brief Return true if this ray intersects the specified sphere.
309     ///
310     /// @param center The center of the sphere in the same space as this ray.
311     /// @param radius The radius of the sphere in the same units as this ray.
intersects(const Vec3T & center,RealT radius)312     __hostdev__ bool intersects(const Vec3T& center, RealT radius) const
313     {
314         RealT t0, t1;
315         return this->intersects(center, radius, t0, t1) > 0;
316     }
317 
318     /// @brief Return true if this ray intersects the specified sphere.
319     ///
320     /// @note For intersection this ray is clipped to the two intersection points.
321     ///
322     /// @param center The center of the sphere in the same space as this ray.
323     /// @param radius The radius of the sphere in the same units as this ray.
clip(const Vec3T & center,RealT radius)324     __hostdev__ bool clip(const Vec3T& center, RealT radius)
325     {
326         RealT      t0, t1;
327         const bool hit = this->intersects(center, radius, t0, t1);
328         if (hit) {
329             mTimeSpan.set(t0, t1);
330         }
331         return hit;
332     }
333 #if 0
334     /// @brief Return true if the Ray intersects the specified
335     ///        axisaligned bounding box.
336     ///
337     /// @param bbox Axis-aligned bounding box in the same space as the Ray.
338     /// @param t0   If an intersection is detected this is assigned
339     ///             the time for the first intersection point.
340     /// @param t1   If an intersection is detected this is assigned
341     ///             the time for the second intersection point.
342     template<typename BBoxT>
343     __hostdev__  bool intersects(const BBoxT& bbox, RealT& t0, RealT& t1) const
344     {
345         t0       = (bbox[  mSign[0]][0] - mEye[0]) * mInvDir[0];
346         RealT t2 = (bbox[1-mSign[1]][1] - mEye[1]) * mInvDir[1];
347         if (t0 > t2) return false;
348         t1       = (bbox[1-mSign[0]][0] - mEye[0]) * mInvDir[0];
349         RealT t3 = (bbox[  mSign[1]][1] - mEye[1]) * mInvDir[1];
350         if (t3 > t1) return false;
351         if (t3 > t0) t0 = t3;
352         if (t2 < t1) t1 = t2;
353         t3 = (bbox[  mSign[2]][2] - mEye[2]) * mInvDir[2];
354         if (t3 > t1) return false;
355         t2 = (bbox[1-mSign[2]][2] - mEye[2]) * mInvDir[2];
356         if (t0 > t2) return false;
357         if (t3 > t0) t0 = t3;
358         if (mTimeSpan.t1 < t0) return false;
359         if (t2 < t1) t1 = t2;
360         if (mTimeSpan.t0 > t1) return false;
361         if (mTimeSpan.t0 > t0) t0 = mTimeSpan.t0;
362         if (mTimeSpan.t1 < t1) t1 = mTimeSpan.t1;
363         return true;
364         /*
365         mTimeSpan.get(_t0, _t1);
366         double t0 = _t0, t1 = _t1;
367         for (int i = 0; i < 3; ++i) {
368             //if (abs(mDir[i])<1e-3) continue;
369             double a = (double(bbox.min()[i]) - mEye[i]) * mInvDir[i];
370             double b = (double(bbox.max()[i]) - mEye[i]) * mInvDir[i];
371             if (a > b) {
372                 double tmp = a;
373                 a = b;
374                 b = tmp;
375             }
376             if (a > t0) t0 = a;
377             if (b < t1) t1 = b;
378             if (t0 > t1) {
379                 //if (gVerbose) printf("Missed BBOX: (%i,%i,%i) -> (%i,%i,%i) t0=%f t1=%f\n",
380                 //                     bbox.min()[0], bbox.min()[1], bbox.min()[2],
381                 //                     bbox.max()[0], bbox.max()[1], bbox.max()[2], t0, t1);
382                 return false;
383             }
384         }
385         _t0 = t0; _t1 = t1;
386         return true;
387         */
388     }
389 #else
390     /// @brief Returns true if this ray intersects an index bounding box.
391     ///        If the return value is true t0 and t1 are set to the intersection
392     ///        times along the ray.
393     ///
394     /// @warning Intersection with a CoordBBox internally converts to a floating-point bbox
395     ///          which imples that the max is padded with one voxel, i.e. bbox.max += 1! This
396     ///          avoids gaps between neighboring CoordBBox'es, say from neighboring tree nodes.
intersects(const CoordBBox & bbox,RealT & t0,RealT & t1)397     __hostdev__ bool intersects(const CoordBBox& bbox, RealT& t0, RealT& t1) const
398     {
399         mTimeSpan.get(t0, t1);
400         for (int i = 0; i < 3; ++i) {
401             RealT a = RealT(bbox.min()[i]), b = RealT(bbox.max()[i] + 1);
402             if (a >= b) { // empty bounding box
403                 return false;
404             }
405             a = (a - mEye[i]) * mInvDir[i];
406             b = (b - mEye[i]) * mInvDir[i];
407             if (a > b) {
408                 RealT tmp = a;
409                 a = b;
410                 b = tmp;
411             }
412             if (a > t0) {
413                 t0 = a;
414             }
415             if (b < t1) {
416                 t1 = b;
417             }
418             if (t0 > t1) {
419                 return false;
420             }
421         }
422         return true;
423     }
424     /// @brief Returns true if this ray intersects a floating-point bounding box.
425     ///        If the return value is true t0 and t1 are set to the intersection
426     ///        times along the ray.
427     template<typename OtherVec3T>
intersects(const BBox<OtherVec3T> & bbox,RealT & t0,RealT & t1)428     __hostdev__ bool intersects(const BBox<OtherVec3T>& bbox, RealT& t0, RealT& t1) const
429     {
430         static_assert(is_floating_point<typename OtherVec3T::ValueType>::value, "Ray::intersects: Expected a floating point coordinate");
431         mTimeSpan.get(t0, t1);
432         for (int i = 0; i < 3; ++i) {
433             RealT a = RealT(bbox.min()[i]), b = RealT(bbox.max()[i]);
434             if (a >= b) { // empty bounding box
435                 return false;
436             }
437             a = (a - mEye[i]) * mInvDir[i];
438             b = (b - mEye[i]) * mInvDir[i];
439             if (a > b) {
440                 RealT tmp = a;
441                 a = b;
442                 b = tmp;
443             }
444             if (a > t0) {
445                 t0 = a;
446             }
447             if (b < t1) {
448                 t1 = b;
449             }
450             if (t0 > t1) {
451                 return false;
452             }
453         }
454         return true;
455     }
456 #endif
457 
458     /// @brief Return true if this ray intersects the specified bounding box.
459     ///
460     /// @param bbox Axis-aligned bounding box in the same space as this ray.
461     ///
462     /// @warning If @a bbox is of the type CoordBBox it is converted to a floating-point
463     ///          bounding box, which imples that the max is padded with one voxel, i.e.
464     ///          bbox.max += 1! This avoids gaps between neighboring CoordBBox'es, say
465     ///          from neighboring tree nodes.
466     template<typename BBoxT>
intersects(const BBoxT & bbox)467     __hostdev__ bool intersects(const BBoxT& bbox) const
468     {
469 #if 1
470         RealT t0, t1;
471         return this->intersects(bbox, t0, t1);
472 #else
473         //BBox<Vec3T> bbox(Vec3T(_bbox[0][0]-1e-4,_bbox[0][1]-1e-4,_bbox[0][2]-1e-4),
474         //                 Vec3T(_bbox[1][0]+1e-4,_bbox[1][1]+1e-4,_bbox[1][2]+1e-4));
475         RealT t0 = (bbox[mSign[0]][0] - mEye[0]) * mInvDir[0];
476         RealT t2 = (bbox[1 - mSign[1]][1] - mEye[1]) * mInvDir[1];
477         if (t0 > t2)
478             return false;
479         RealT t1 = (bbox[1 - mSign[0]][0] - mEye[0]) * mInvDir[0];
480         RealT t3 = (bbox[mSign[1]][1] - mEye[1]) * mInvDir[1];
481         if (t3 > t1)
482             return false;
483         if (t3 > t0)
484             t0 = t3;
485         if (t2 < t1)
486             t1 = t2;
487         t3 = (bbox[mSign[2]][2] - mEye[2]) * mInvDir[2];
488         if (t3 > t1)
489             return false;
490         t2 = (bbox[1 - mSign[2]][2] - mEye[2]) * mInvDir[2];
491         if (t0 > t2)
492             return false;
493         //if (t3 > t0) t0 = t3;
494         //if (mTimeSpan.t1 < t0) return false;
495         //if (t2 < t1) t1 = t2;
496         //return mTimeSpan.t0 < t1;
497         return true;
498 #endif
499     }
500 
501     /// @brief Return true if this ray intersects the specified bounding box.
502     ///
503     /// @param bbox Axis-aligned bounding box in the same space as this ray.
504     ///
505     /// @warning If @a bbox is of the type CoordBBox it is converted to a floating-point
506     ///          bounding box, which imples that the max is padded with one voxel, i.e.
507     ///          bbox.max += 1! This avoids gaps between neighboring CoordBBox'es, say
508     ///          from neighboring tree nodes.
509     ///
510     /// @note For intersection this ray is clipped to the two intersection points.
511     template<typename BBoxT>
clip(const BBoxT & bbox)512     __hostdev__ bool clip(const BBoxT& bbox)
513     {
514         RealT      t0, t1;
515         const bool hit = this->intersects(bbox, t0, t1);
516         if (hit) {
517             mTimeSpan.set(t0, t1);
518         }
519         return hit;
520     }
521 
522     /// @brief Return true if the Ray intersects the plane specified
523     ///        by a normal and distance from the origin.
524     ///
525     /// @param normal   Normal of the plane.
526     /// @param distance Distance of the plane to the origin.
527     /// @param t        Time of intersection, if one exists.
intersects(const Vec3T & normal,RealT distance,RealT & t)528     __hostdev__ bool intersects(const Vec3T& normal, RealT distance, RealT& t) const
529     {
530         const RealT cosAngle = mDir.dot(normal);
531         if (isApproxZero(cosAngle)) {
532             return false; // ray is parallel to plane
533         }
534         t = (distance - mEye.dot(normal)) / cosAngle;
535         return this->test(t);
536     }
537 
538     /// @brief Return true if the Ray intersects the plane specified
539     ///        by a normal and point.
540     ///
541     /// @param normal   Normal of the plane.
542     /// @param point    Point in the plane.
543     /// @param t        Time of intersection, if one exists.
intersects(const Vec3T & normal,const Vec3T & point,RealT & t)544     __hostdev__ bool intersects(const Vec3T& normal, const Vec3T& point, RealT& t) const
545     {
546         return this->intersects(normal, point.dot(normal), t);
547     }
548 
549 private:
550     Vec3T    mEye, mDir, mInvDir;
551     TimeSpan mTimeSpan;
552     int      mSign[3];
553 }; // end of Ray class
554 
555 } // namespace nanovdb
556 
557 #endif // NANOVDB_RAY_HAS_BEEN_INCLUDED
558