MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
SpatialIndex.hpp
Go to the documentation of this file.
1#pragma once
2
4
5#include <Eigen/Core>
6
7namespace MayaFlux::Kinesis {
8
9/// @brief Result entry from a spatial query, carrying entity id and squared distance.
11 uint32_t id;
13};
14
15/// @brief Immutable spatial snapshot published atomically for lock-free reads.
16/// @tparam PointT Coordinate type (glm::vec3 or Eigen::VectorXd).
17template <typename PointT>
19 /// @brief Packed position storage indexed by internal slot.
20 std::vector<PointT> positions;
21
22 /// @brief Maps external entity id to internal slot index.
23 std::unordered_map<uint32_t, uint32_t> id_to_slot;
24
25 /// @brief Maps internal slot index back to external entity id.
26 std::vector<uint32_t> slot_to_id;
27
28 /// @brief Grid cell contents. Key is hashed cell coordinate.
29 std::unordered_map<uint64_t, std::vector<uint32_t>> cells;
30
31 /// @brief Cell size used when this snapshot was built.
32 float cell_size {};
33
34 /// @brief Dimensionality (3 for glm::vec3, runtime for Eigen).
35 uint32_t dimensions {};
36};
37
38// =========================================================================
39// Grid hashing
40// =========================================================================
41
42namespace detail {
43
44 inline uint64_t hash_cell_3d(int32_t cx, int32_t cy, int32_t cz)
45 {
46 auto ux = static_cast<uint64_t>(static_cast<uint32_t>(cx));
47 auto uy = static_cast<uint64_t>(static_cast<uint32_t>(cy));
48 auto uz = static_cast<uint64_t>(static_cast<uint32_t>(cz));
49 return (ux * 73856093ULL) ^ (uy * 19349663ULL) ^ (uz * 83492791ULL);
50 }
51
52 inline std::array<int32_t, 3> cell_coords_3d(const glm::vec3& p, float inv_cell)
53 {
54 return {
55 static_cast<int32_t>(std::floor(p.x * inv_cell)),
56 static_cast<int32_t>(std::floor(p.y * inv_cell)),
57 static_cast<int32_t>(std::floor(p.z * inv_cell))
58 };
59 }
60
61 inline uint64_t hash_cell_nd(const Eigen::VectorXd& p, float inv_cell)
62 {
63 uint64_t h = 0;
64 constexpr uint64_t primes[] = {
65 73856093ULL, 19349663ULL, 83492791ULL,
66 48611ULL, 76963ULL, 15485863ULL,
67 32452843ULL, 49979687ULL
68 };
69 Eigen::Index dims = std::min(p.size(), static_cast<Eigen::Index>(8));
70 for (Eigen::Index i = 0; i < dims; ++i) {
71 auto ci = static_cast<int32_t>(std::floor(p(i) * inv_cell));
72 h ^= static_cast<uint64_t>(static_cast<uint32_t>(ci)) * primes[i];
73 }
74 return h;
75 }
76
77} // namespace detail
78
79// =========================================================================
80// SpatialIndex
81// =========================================================================
82
83/**
84 * @class SpatialIndex
85 * @brief Lock-free spatial acceleration structure with atomic snapshot publication.
86 * @tparam PointT Coordinate type. glm::vec3 for 3D scenes, Eigen::VectorXd for
87 * N-dimensional descriptor spaces.
88 *
89 * Single-writer thread (typically graphics) calls insert(), update(), remove(),
90 * then publish() once per frame. Any reader thread (typically audio) calls
91 * within_radius() or k_nearest() against the last published snapshot with no
92 * synchronization overhead.
93 *
94 * Backed by a uniform spatial hash grid for PointT = glm::vec3, and brute-force
95 * scan for PointT = Eigen::VectorXd with dimensionality > 6. Grid cell neighbor
96 * enumeration scales as 3^D, so the grid is only practical for low dimensionality.
97 *
98 * Publication model:
99 * Non-macOS: std::atomic<std::shared_ptr<const SpatialSnapshot<PointT>>>
100 * macOS: raw atomic pointer with hazard pointer array (Apple Clang lacks
101 * std::atomic<std::shared_ptr<T>>)
102 */
103template <typename PointT>
105public:
106 using DistanceFn = std::function<float(const PointT&, const PointT&)>;
107
108 /**
109 * @brief Construct a spatial index.
110 * @param cell_size Grid cell edge length. Ignored when brute-force is selected.
111 * @param distance Distance function returning squared distance between two points.
112 */
113 SpatialIndex(float cell_size, DistanceFn distance);
115
116 SpatialIndex(const SpatialIndex&) = delete;
120
121 // =====================================================================
122 // Mutation (single writer thread only)
123 // =====================================================================
124
125 /**
126 * @brief Insert a new entity at the given position.
127 * @param position Initial coordinates.
128 * @return Stable entity id, never reused after remove().
129 */
130 uint32_t insert(const PointT& position);
131
132 /**
133 * @brief Move an existing entity to a new position.
134 * @param id Entity id returned by insert().
135 * @param position New coordinates.
136 */
137 void update(uint32_t id, const PointT& position);
138
139 /**
140 * @brief Remove an entity from the index.
141 * @param id Entity id returned by insert().
142 */
143 void remove(uint32_t id);
144
145 /**
146 * @brief Atomically publish the current write-side state as a new read snapshot.
147 *
148 * Call once per frame after all mutations are complete. Readers see the
149 * previous snapshot until this call completes.
150 */
151 void publish();
152
153 // =====================================================================
154 // Queries (any thread, reads last published snapshot)
155 // =====================================================================
156
157 /**
158 * @brief Find all entities within a radius of a point.
159 * @param center Query origin.
160 * @param radius Search radius (same units as positions).
161 * @return Unsorted results with squared distances. Empty if no snapshot published.
162 */
163 [[nodiscard]] std::vector<QueryResult> within_radius(
164 const PointT& center,
165 float radius) const;
166
167 /**
168 * @brief Find the k nearest entities to a point.
169 * @param center Query origin.
170 * @param k Maximum number of results.
171 * @return Results sorted by ascending squared distance. May return fewer than k.
172 */
173 [[nodiscard]] std::vector<QueryResult> k_nearest(
174 const PointT& center,
175 uint32_t k) const;
176
177 /**
178 * @brief Read the position of an entity from the published snapshot.
179 * @param id Entity id.
180 * @return Position, or std::nullopt if id is not present in the snapshot.
181 */
182 [[nodiscard]] std::optional<PointT> position_of(uint32_t id) const;
183
184 /**
185 * @brief Entity count in the published snapshot.
186 */
187 [[nodiscard]] size_t count() const;
188
189 /**
190 * @brief Return all entity ids and positions from the published snapshot.
191 * @return Vector of (id, position) pairs. Empty if no snapshot published.
192 */
193 [[nodiscard]] std::vector<std::pair<uint32_t, PointT>> all() const;
194
195 /**
196 * @brief Remove all entities from the write side. Does not auto-publish.
197 */
198 void clear();
199
200private:
204 uint32_t m_next_id { 0 };
205 bool m_use_grid { true };
206
207 // -----------------------------------------------------------------
208 // Write-side state (single writer only)
209 // -----------------------------------------------------------------
210 std::vector<PointT> m_positions;
211 std::unordered_map<uint32_t, uint32_t> m_id_to_slot;
212 std::vector<uint32_t> m_slot_to_id;
213 std::vector<uint32_t> m_free_slots;
214
215 void rebuild_grid(SpatialSnapshot<PointT>& snap) const;
216
217 uint64_t hash_cell(const PointT& p) const;
218
219 void query_grid(
220 const SpatialSnapshot<PointT>& snap,
221 const PointT& center,
222 float radius_sq,
223 std::vector<QueryResult>& out) const;
224
225 void query_brute(
226 const SpatialSnapshot<PointT>& snap,
227 const PointT& center,
228 float radius_sq,
229 std::vector<QueryResult>& out) const;
230
231 // -----------------------------------------------------------------
232 // Read-side snapshot (lock-free publication)
233 // -----------------------------------------------------------------
234
235#ifdef MAYAFLUX_PLATFORM_MACOS
236 std::atomic<const SpatialSnapshot<PointT>*> m_snapshot { nullptr };
237
238 static constexpr size_t MAX_READERS = 16;
239 mutable std::array<std::atomic<const SpatialSnapshot<PointT>*>, MAX_READERS> m_hazard_ptrs {};
240 mutable std::atomic<size_t> m_hazard_counter { 0 };
241
242 /**
243 * @brief Acquire a consistent snapshot pointer via hazard pointer protocol.
244 * @return Pair of (snapshot pointer, hazard slot index). Caller must release
245 * the slot via release_snapshot().
246 */
247 std::pair<const SpatialSnapshot<PointT>*, size_t> acquire_snapshot() const;
248
249 /**
250 * @brief Release a hazard pointer slot after query completion.
251 */
252 void release_snapshot(size_t slot) const;
253
254 /**
255 * @brief Defer deletion of an old snapshot until no hazard pointer references it.
256 */
257 void retire_snapshot(const SpatialSnapshot<PointT>* old);
258
259 std::vector<const SpatialSnapshot<PointT>*> m_retired;
260#else
261 std::atomic<std::shared_ptr<const SpatialSnapshot<PointT>>> m_snapshot;
262#endif
263};
264
265// =========================================================================
266// Type aliases
267// =========================================================================
268
271
272extern template class SpatialIndex<glm::vec3>;
273extern template class SpatialIndex<Eigen::VectorXd>;
274
275// =========================================================================
276// Factory functions
277// =========================================================================
278
279/**
280 * @brief Create a 3D spatial index with Euclidean squared distance.
281 * @param cell_size Grid cell edge length.
282 */
283MAYAFLUX_API std::unique_ptr<SpatialIndex3D> make_spatial_index_3d(float cell_size);
284
285/**
286 * @brief Create an N-dimensional spatial index with Euclidean squared distance.
287 * @param cell_size Grid cell edge length (used only when dimensions <= 6).
288 * @param dimensions Number of dimensions in the coordinate space.
289 */
290MAYAFLUX_API std::unique_ptr<SpatialIndexND> make_spatial_index_nd(float cell_size, uint32_t dimensions);
291
292} // namespace MayaFlux::Kinesis
uint32_t h
Definition InkPress.cpp:25
Range radius
glm::vec3 position
void query_grid(const SpatialSnapshot< PointT > &snap, const PointT &center, float radius_sq, std::vector< QueryResult > &out) const
uint32_t insert(const PointT &position)
Insert a new entity at the given position.
std::vector< uint32_t > m_free_slots
void clear()
Remove all entities from the write side.
SpatialIndex & operator=(SpatialIndex &&)=delete
void query_brute(const SpatialSnapshot< PointT > &snap, const PointT &center, float radius_sq, std::vector< QueryResult > &out) const
void publish()
Atomically publish the current write-side state as a new read snapshot.
SpatialIndex(const SpatialIndex &)=delete
std::vector< QueryResult > k_nearest(const PointT &center, uint32_t k) const
Find the k nearest entities to a point.
std::optional< PointT > position_of(uint32_t id) const
Read the position of an entity from the published snapshot.
uint64_t hash_cell(const PointT &p) const
std::vector< PointT > m_positions
std::vector< std::pair< uint32_t, PointT > > all() const
Return all entity ids and positions from the published snapshot.
void rebuild_grid(SpatialSnapshot< PointT > &snap) const
std::atomic< std::shared_ptr< const SpatialSnapshot< PointT > > > m_snapshot
SpatialIndex(SpatialIndex &&)=delete
void update(uint32_t id, const PointT &position)
Move an existing entity to a new position.
size_t count() const
Entity count in the published snapshot.
SpatialIndex & operator=(const SpatialIndex &)=delete
std::vector< QueryResult > within_radius(const PointT &center, float radius) const
Find all entities within a radius of a point.
std::function< float(const PointT &, const PointT &)> DistanceFn
std::unordered_map< uint32_t, uint32_t > m_id_to_slot
std::vector< uint32_t > m_slot_to_id
void remove(uint32_t id)
Remove an entity from the index.
Lock-free spatial acceleration structure with atomic snapshot publication.
uint64_t hash_cell_3d(int32_t cx, int32_t cy, int32_t cz)
uint64_t hash_cell_nd(const Eigen::VectorXd &p, float inv_cell)
std::array< int32_t, 3 > cell_coords_3d(const glm::vec3 &p, float inv_cell)
std::unique_ptr< SpatialIndexND > make_spatial_index_nd(float cell_size, uint32_t dimensions)
Create an N-dimensional spatial index with Euclidean squared distance.
SpatialField distance(const glm::vec3 &anchor, float radius, DistanceMetric metric=DistanceMetric::EUCLIDEAN)
Normalized distance from an anchor point using the specified metric.
std::unique_ptr< SpatialIndex3D > make_spatial_index_3d(float cell_size)
Create a 3D spatial index with Euclidean squared distance.
Result entry from a spatial query, carrying entity id and squared distance.
std::vector< PointT > positions
Packed position storage indexed by internal slot.
uint32_t dimensions
Dimensionality (3 for glm::vec3, runtime for Eigen).
std::vector< uint32_t > slot_to_id
Maps internal slot index back to external entity id.
std::unordered_map< uint64_t, std::vector< uint32_t > > cells
Grid cell contents. Key is hashed cell coordinate.
std::unordered_map< uint32_t, uint32_t > id_to_slot
Maps external entity id to internal slot index.
float cell_size
Cell size used when this snapshot was built.
Immutable spatial snapshot published atomically for lock-free reads.