Move Vectormath main
C++20 game and graphics mathematics
Loading...
Searching...
No Matches
BoundsQueries.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <cstddef>
6#include <limits>
7#include <optional>
8
15
16namespace mv::math
17{
18 namespace detail
19 {
20 template <typename T>
21 [[nodiscard]] inline T Component(const Vec3<T>& value,
22 std::size_t axis) noexcept
23 {
24 if (axis == 0U)
25 {
26 return value.X();
27 }
28 return axis == 1U ? value.Y() : value.Z();
29 }
30
31 template <typename T>
32 [[nodiscard]] inline T Component(const Point3<T>& value,
33 std::size_t axis) noexcept
34 {
35 if (axis == 0U)
36 {
37 return value.X();
38 }
39 return axis == 1U ? value.Y() : value.Z();
40 }
41
42 template <typename T>
43 [[nodiscard]] inline Normal3<T> AxisNormal(std::size_t axis,
44 T sign) noexcept
45 {
47 : axis == 1U ? Normal3<T>::AxisY()
49 return sign < T(0) ? -normal : normal;
50 }
51
52 template <typename T>
53 [[nodiscard]] inline bool IsStrictlyInside(
54 const Aabb3<T>& box, const Point3<T>& point) noexcept
55 {
56 return point.X() > box.Minimum().X() &&
57 point.Y() > box.Minimum().Y() &&
58 point.Z() > box.Minimum().Z() &&
59 point.X() < box.Maximum().X() &&
60 point.Y() < box.Maximum().Y() &&
61 point.Z() < box.Maximum().Z();
62 }
63
64 template <typename T>
76
77 // Williams et al. ray/box slab test (JGT 2005); Move explicitly handles
78 // parallel axes and returns a clipped interval with surface normals.
79 template <typename T>
80 [[nodiscard]] inline std::optional<RayAabbSlabSolution<T>>
82 const Aabb3<T>& box) noexcept
83 {
84 const Ray3<T>& ray = prepared.Ray();
85 if (box.IsEmpty())
86 {
87 return std::nullopt;
88 }
89
90 T entryDistance = T(0);
91 T exitDistance = std::numeric_limits<T>::infinity();
92 std::size_t entryAxis = 0U;
93 std::size_t exitAxis = 0U;
94 T entrySign = T(1);
95 T exitSign = T(1);
96 bool hasEntryAxis = false;
97 bool hasExitAxis = false;
98
99 for (std::size_t axis = 0U; axis < 3U; ++axis)
100 {
101 const T origin = Component(ray.Origin(), axis);
102 const T minimum = Component(box.Minimum(), axis);
103 const T maximum = Component(box.Maximum(), axis);
104 if (prepared.IsParallel(axis))
105 {
107 {
108 return std::nullopt;
109 }
110 continue;
111 }
112
113 const T reciprocal =
114 Component(prepared.ReciprocalDirection(), axis);
117 T nearSign = T(-1);
118 T farSign = T(1);
120 {
121 std::swap(nearDistance, farDistance);
122 std::swap(nearSign, farSign);
123 }
124
126 {
128 entryAxis = axis;
130 hasEntryAxis = true;
131 }
133 {
135 exitAxis = axis;
137 hasExitAxis = true;
138 }
140 {
141 return std::nullopt;
142 }
143 }
144
145 if (!(exitDistance >= T(0)) || !std::isfinite(exitDistance) ||
147 {
148 return std::nullopt;
149 }
150
153 IsStrictlyInside(box, ray.Origin()),
154 entryAxis,
155 exitAxis,
156 entrySign,
157 exitSign,
159 }
160
161 // Predicate-only Williams et al. slab test (JGT 2005). Keeping this
162 // separate from the detailed solver avoids tracking hit axes, signs,
163 // and inside state when the caller only needs a boolean result.
164 template <typename T>
166 const PreparedRay3<T>& prepared, const Aabb3<T>& box) noexcept
167 {
168 if (box.IsEmpty())
169 {
170 return false;
171 }
172
173 const Ray3<T>& ray = prepared.Ray();
174 T entryDistance = T(0);
175 T exitDistance = std::numeric_limits<T>::infinity();
176 for (std::size_t axis = 0U; axis < 3U; ++axis)
177 {
178 const T origin = Component(ray.Origin(), axis);
179 const T minimum = Component(box.Minimum(), axis);
180 const T maximum = Component(box.Maximum(), axis);
181 if (prepared.IsParallel(axis))
182 {
184 {
185 return false;
186 }
187 continue;
188 }
189
190 const T reciprocal =
191 Component(prepared.ReciprocalDirection(), axis);
195 {
196 std::swap(nearDistance, farDistance);
197 }
201 {
202 return false;
203 }
204 }
205 return exitDistance >= T(0) && std::isfinite(exitDistance);
206 }
207
208 template <typename T>
215
216 // Ray/sphere quadratic cross-checked against DirectXMath
217 // BoundingSphere::Intersects and GLM intersectRaySphere (both MIT).
218 template <typename T>
219 [[nodiscard]] inline std::optional<RaySphereSolution<T>>
221 const Sphere3<T>& sphere) noexcept
222 {
224 const Point3<T> center = sphere.Center();
225 const Calculation mx = static_cast<Calculation>(ray.Origin().X()) -
226 static_cast<Calculation>(center.X());
227 const Calculation my = static_cast<Calculation>(ray.Origin().Y()) -
228 static_cast<Calculation>(center.Y());
229 const Calculation mz = static_cast<Calculation>(ray.Origin().Z()) -
230 static_cast<Calculation>(center.Z());
231 const Calculation dx =
232 static_cast<Calculation>(ray.Direction().Vector().X());
233 const Calculation dy =
234 static_cast<Calculation>(ray.Direction().Vector().Y());
235 const Calculation dz =
236 static_cast<Calculation>(ray.Direction().Vector().Z());
237 const Calculation radius =
238 static_cast<Calculation>(sphere.Radius());
239 const Calculation b = mx * dx + my * dy + mz * dz;
240 const Calculation c = mx * mx + my * my + mz * mz - radius * radius;
241 const Calculation discriminant = b * b - c;
242 if (!(discriminant >= Calculation(0)) ||
243 !std::isfinite(discriminant))
244 {
245 return std::nullopt;
246 }
247
248 const Calculation root = std::sqrt(discriminant);
249 const Calculation nearDistance = -b - root;
250 const Calculation farDistance = -b + root;
251 if (!(farDistance >= Calculation(0)) || !std::isfinite(farDistance))
252 {
253 return std::nullopt;
254 }
255
256 const bool startsInside = c < Calculation(0);
257 const T entryDistance =
259 ? T(0)
260 : static_cast<T>(std::max(nearDistance, Calculation(0)));
261 const T exitDistance = static_cast<T>(farDistance);
262 if (!(entryDistance >= T(0)) || !std::isfinite(entryDistance) ||
263 !std::isfinite(exitDistance))
264 {
265 return std::nullopt;
266 }
269 }
270 } // namespace detail
271
272 template <typename T>
273 [[nodiscard]] inline std::optional<RaySphereHit3<T>> Intersect(
274 const Ray3<T>& ray, const Sphere3<T>& sphere) noexcept
275 {
277 if (!solution)
278 {
279 return std::nullopt;
280 }
281
282 std::optional<Normal3<T>> entryNormal;
283 if (!solution->StartsInside)
284 {
286 ray.PointAt(solution->EntryDistance) - sphere.Center());
287 }
288 const auto exitNormal = Normal3<T>::TryFrom(
289 ray.PointAt(solution->ExitDistance) - sphere.Center());
290
291 return RaySphereHit3<T>{solution->EntryDistance, solution->ExitDistance,
292 solution->StartsInside, entryNormal,
293 exitNormal};
294 }
295
296 template <typename T>
297 [[nodiscard]] inline bool Intersects(const Ray3<T>& ray,
298 const Sphere3<T>& sphere) noexcept
299 {
300 return detail::TryIntersectRaySphere(ray, sphere).has_value();
301 }
302
303 template <typename T>
304 [[nodiscard]] inline std::optional<RayAabbHit3<T>> Intersect(
305 const PreparedRay3<T>& ray, const Aabb3<T>& box) noexcept
306 {
308 if (!solution)
309 {
310 return std::nullopt;
311 }
312
313 std::optional<Normal3<T>> entryNormal;
314 if (!solution->StartsInside && solution->HasEntryAxis)
315 {
317 detail::AxisNormal(solution->EntryAxis, solution->EntrySign);
318 }
319 return RayAabbHit3<T>{
320 solution->EntryDistance, solution->ExitDistance,
321 solution->StartsInside, entryNormal,
322 detail::AxisNormal(solution->ExitAxis, solution->ExitSign)};
323 }
324
325 template <typename T>
326 [[nodiscard]] inline std::optional<RayAabbHit3<T>> Intersect(
327 const Ray3<T>& ray, const Aabb3<T>& box) noexcept
328 {
330 }
331
332 template <typename T>
333 [[nodiscard]] inline bool Intersects(const PreparedRay3<T>& ray,
334 const Aabb3<T>& box) noexcept
335 {
337 }
338
339 template <typename T>
340 [[nodiscard]] inline bool Intersects(const Ray3<T>& ray,
341 const Aabb3<T>& box) noexcept
342 {
344 }
345
346 // Standard squared-distance bounds tests; cross-checked against DirectXMath
347 // BoundingSphere/BoundingBox intersections (MIT). Touching intersects.
348 template <typename T>
349 [[nodiscard]] inline bool Intersects(const Sphere3<T>& left,
350 const Sphere3<T>& right) noexcept
351 {
353 const Point3<T> leftCenter = left.Center();
354 const Point3<T> rightCenter = right.Center();
355 const Calculation x = static_cast<Calculation>(leftCenter.X()) -
356 static_cast<Calculation>(rightCenter.X());
357 const Calculation y = static_cast<Calculation>(leftCenter.Y()) -
358 static_cast<Calculation>(rightCenter.Y());
359 const Calculation z = static_cast<Calculation>(leftCenter.Z()) -
360 static_cast<Calculation>(rightCenter.Z());
362 static_cast<Calculation>(left.Radius()) +
363 static_cast<Calculation>(right.Radius());
364 return x * x + y * y + z * z <= combinedRadius * combinedRadius;
365 }
366
367 template <typename T>
368 [[nodiscard]] inline bool Intersects(const Aabb3<T>& left,
369 const Aabb3<T>& right) noexcept
370 {
371 return left.Intersects(right);
372 }
373
374 template <typename T>
375 [[nodiscard]] inline bool Intersects(const Sphere3<T>& sphere,
376 const Aabb3<T>& box) noexcept
377 {
378 if (box.IsEmpty())
379 {
380 return false;
381 }
382
384 const Point3<T> center = sphere.Center();
385 const Calculation closestX =
386 std::clamp(static_cast<Calculation>(center.X()),
387 static_cast<Calculation>(box.Minimum().X()),
388 static_cast<Calculation>(box.Maximum().X()));
389 const Calculation closestY =
390 std::clamp(static_cast<Calculation>(center.Y()),
391 static_cast<Calculation>(box.Minimum().Y()),
392 static_cast<Calculation>(box.Maximum().Y()));
393 const Calculation closestZ =
394 std::clamp(static_cast<Calculation>(center.Z()),
395 static_cast<Calculation>(box.Minimum().Z()),
396 static_cast<Calculation>(box.Maximum().Z()));
397 const Calculation x = static_cast<Calculation>(center.X()) - closestX;
398 const Calculation y = static_cast<Calculation>(center.Y()) - closestY;
399 const Calculation z = static_cast<Calculation>(center.Z()) - closestZ;
400 const Calculation radius = static_cast<Calculation>(sphere.Radius());
401 return x * x + y * y + z * z <= radius * radius;
402 }
403
404 template <typename T>
405 [[nodiscard]] inline bool Intersects(const Aabb3<T>& box,
406 const Sphere3<T>& sphere) noexcept
407 {
408 return Intersects(sphere, box);
409 }
410
411 // Sphere-swept-volume reduction from Ericson, Real-Time Collision
412 // Detection (2005), section 4.5.1: compare the distance between the inner
413 // structures with the sum of sweep radii. Touching intersects.
414 template <typename T>
415 [[nodiscard]] inline bool Intersects(const Capsule3<T>& capsule,
416 const Sphere3<T>& sphere) noexcept
417 {
418 const T combinedRadius = capsule.Radius() + sphere.Radius();
419 return DistanceSquared(sphere.Center(), capsule.CenterLine()) <=
421 }
422
423 template <typename T>
424 [[nodiscard]] inline bool Intersects(const Sphere3<T>& sphere,
425 const Capsule3<T>& capsule) noexcept
426 {
427 return Intersects(capsule, sphere);
428 }
429
430 template <typename T>
431 [[nodiscard]] inline bool Intersects(const Capsule3<T>& first,
432 const Capsule3<T>& second) noexcept
433 {
434 const T combinedRadius = first.Radius() + second.Radius();
435 return DistanceSquared(first.CenterLine(), second.CenterLine()) <=
437 }
438} // namespace mv::math
static Normal3 AxisX() noexcept
Definition Normal3.hpp:70
static Normal3 AxisY() noexcept
Definition Normal3.hpp:75
static Normal3 AxisZ() noexcept
Definition Normal3.hpp:80
static std::optional< Normal3 > TryFrom(const Vec3< T > &value) noexcept
Definition Normal3.hpp:46
std::optional< RayAabbSlabSolution< T > > TryIntersectPreparedAabbSlabs(const PreparedRay3< T > &prepared, const Aabb3< T > &box) noexcept
Normal3< T > AxisNormal(std::size_t axis, T sign) noexcept
std::optional< RaySphereSolution< T > > TryIntersectRaySphere(const Ray3< T > &ray, const Sphere3< T > &sphere) noexcept
bool IsStrictlyInside(const Aabb3< T > &box, const Point3< T > &point) noexcept
bool IntersectsPreparedAabbSlabs(const PreparedRay3< T > &prepared, const Aabb3< T > &box) noexcept
T Component(const Vec3< T > &value, std::size_t axis) noexcept
std::conditional_t< std::is_same_v< T, float >, double, long double > QueryCalculation
bool Intersects(const Ray3< T > &ray, const Sphere3< T > &sphere) noexcept
std::optional< RaySphereHit3< T > > Intersect(const Ray3< T > &ray, const Sphere3< T > &sphere) noexcept
T DistanceSquared(const Point3< T > &point, const Line3< T > &line) noexcept