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