MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
HitTest.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "SpatialIndex.hpp"
4
6
7namespace MayaFlux::Kinesis {
8
9// =========================================================================
10// Ray
11// =========================================================================
12
13/**
14 * @struct Ray
15 * @brief Origin and normalized direction in world space.
16 */
17struct Ray {
18 glm::vec3 origin;
19 glm::vec3 direction;
20};
21
22// =========================================================================
23// Hit result
24// =========================================================================
25
26/**
27 * @struct HitResult
28 * @brief Entity id and perpendicular distance from the ray axis.
29 */
30struct HitResult {
31 uint32_t id;
32 float distance;
33 float t;
34};
35
36// =========================================================================
37// Ray construction
38// =========================================================================
39
40/**
41 * @brief Construct a world-space ray from window pixel coordinates.
42 * @param window_x X in window space [0, width]
43 * @param window_y Y in window space [0, height] (top-left origin)
44 * @param width Window width in pixels.
45 * @param height Window height in pixels.
46 * @param vt Active ViewTransform (view + projection matrices).
47 * @return Ray with origin at the near plane and normalized direction into the scene.
48 *
49 * Unprojects two points at z=0 (near) and z=1 (far) in NDC through the
50 * inverse of projection * view. The resulting world-space positions define
51 * the ray origin and direction.
52 */
53[[nodiscard]] inline Ray screen_to_ray(
54 double window_x, double window_y,
55 uint32_t width, uint32_t height,
56 const ViewTransform& vt)
57{
58 float ndc_x = (static_cast<float>(window_x) / static_cast<float>(width)) * 2.0F - 1.0F;
59 float ndc_y = 1.0F - (static_cast<float>(window_y) / static_cast<float>(height)) * 2.0F;
60
61 glm::mat4 inv_vp = glm::inverse(vt.projection * vt.view);
62
63 glm::vec4 near_h = inv_vp * glm::vec4(ndc_x, ndc_y, 0.0F, 1.0F);
64 glm::vec4 far_h = inv_vp * glm::vec4(ndc_x, ndc_y, 1.0F, 1.0F);
65
66 glm::vec3 near_w = glm::vec3(near_h) / near_h.w;
67 glm::vec3 far_w = glm::vec3(far_h) / far_h.w;
68
69 return { .origin = near_w, .direction = glm::normalize(far_w - near_w) };
70}
71
72// =========================================================================
73// Point-to-ray distance
74// =========================================================================
75
76/**
77 * @brief Squared perpendicular distance from a point to an infinite ray.
78 * @param ray World-space ray.
79 * @param point World-space position.
80 * @param out_t If non-null, receives the projection parameter along the ray.
81 * Negative values indicate the point is behind the origin.
82 * @return Squared perpendicular distance.
83 */
84[[nodiscard]] inline float point_ray_distance_sq(
85 const Ray& ray,
86 const glm::vec3& point,
87 float* out_t = nullptr)
88{
89 glm::vec3 op = point - ray.origin;
90 float t = glm::dot(op, ray.direction);
91 glm::vec3 closest = ray.origin + ray.direction * t;
92 glm::vec3 diff = point - closest;
93 if (out_t) {
94 *out_t = t;
95 }
96 return glm::dot(diff, diff);
97}
98
99// =========================================================================
100// Hit testing
101// =========================================================================
102
103/**
104 * @brief Find the closest entity to a ray within a tolerance radius.
105 * @param index Published SpatialIndex3D to query.
106 * @param ray World-space ray (from screen_to_ray or constructed manually).
107 * @param tolerance Maximum perpendicular distance from the ray axis for a hit.
108 * @return Closest entity within tolerance, or std::nullopt if nothing was hit.
109 *
110 * Iterates all entities in the published snapshot. Entities behind the ray
111 * origin (t < 0) are rejected. Among forward candidates within tolerance,
112 * returns the one with the smallest perpendicular distance. Ties broken by
113 * smaller t (closer to camera).
114 */
115[[nodiscard]] inline std::optional<HitResult> ray_cast(
116 const SpatialIndex3D& index,
117 const Ray& ray,
118 float tolerance)
119{
120 float tol_sq = tolerance * tolerance;
121 std::optional<HitResult> best;
122
123 for (const auto& [id, pos] : index.all()) {
124 float t = 0.0F;
125 float d_sq = point_ray_distance_sq(ray, pos, &t);
126
127 if (t < 0.0F || d_sq > tol_sq) {
128 continue;
129 }
130
131 float d = std::sqrt(d_sq);
132
133 if (!best || d < best->distance || (d == best->distance && t < best->t)) {
134 best = HitResult { .id = id, .distance = d, .t = t };
135 }
136 }
137
138 return best;
139}
140
141/**
142 * @brief Find all entities within tolerance of a ray, sorted by distance.
143 * @param index Published SpatialIndex3D to query.
144 * @param ray World-space ray.
145 * @param tolerance Maximum perpendicular distance from the ray axis.
146 * @return All hits sorted by ascending perpendicular distance. Empty if none.
147 */
148[[nodiscard]] inline std::vector<HitResult> ray_cast_all(
149 const SpatialIndex3D& index,
150 const Ray& ray,
151 float tolerance)
152{
153 float tol_sq = tolerance * tolerance;
154 std::vector<HitResult> results;
155
156 for (const auto& [id, pos] : index.all()) {
157 float t = 0.0F;
158 float d_sq = point_ray_distance_sq(ray, pos, &t);
159
160 if (t < 0.0F || d_sq > tol_sq) {
161 continue;
162 }
163
164 results.push_back(HitResult {
165 .id = id,
166 .distance = std::sqrt(d_sq),
167 .t = t });
168 }
169
170 std::ranges::sort(results, [](const HitResult& a, const HitResult& b) {
171 return a.distance < b.distance;
172 });
173
174 return results;
175}
176
177} // namespace MayaFlux::Kinesis
size_t a
size_t b
uint32_t width
uint32_t id
std::vector< std::pair< uint32_t, PointT > > all() const
Return all entity ids and positions from the published snapshot.
Lock-free spatial acceleration structure with atomic snapshot publication.
std::vector< HitResult > ray_cast_all(const SpatialIndex3D &index, const Ray &ray, float tolerance)
Find all entities within tolerance of a ray, sorted by distance.
Definition HitTest.hpp:148
Ray screen_to_ray(double window_x, double window_y, uint32_t width, uint32_t height, const ViewTransform &vt)
Construct a world-space ray from window pixel coordinates.
Definition HitTest.hpp:53
std::optional< HitResult > ray_cast(const SpatialIndex3D &index, const Ray &ray, float tolerance)
Find the closest entity to a ray within a tolerance radius.
Definition HitTest.hpp:115
float point_ray_distance_sq(const Ray &ray, const glm::vec3 &point, float *out_t=nullptr)
Squared perpendicular distance from a point to an infinite ray.
Definition HitTest.hpp:84
SpatialField distance(const glm::vec3 &anchor, float radius, DistanceMetric metric=DistanceMetric::EUCLIDEAN)
Normalized distance from an anchor point using the specified metric.
Entity id and perpendicular distance from the ray axis.
Definition HitTest.hpp:30
Origin and normalized direction in world space.
Definition HitTest.hpp:17
View and projection matrices as a named push constant slot.