• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

orion-ecs / keen-eye / 20141096729

11 Dec 2025 05:03PM UTC coverage: 94.761% (-3.1%) from 97.887%
20141096729

push

github

tyevco
docs: Enhance sample READMEs and add documentation cross-references

- Add detailed build/run instructions to all sample READMEs
- Add "Documentation" sections linking to spatial partitioning guides
- Add "See Also" sections in docs with bidirectional sample references
- Improve discoverability between documentation and working examples

Co-authored-by: Tyler Coles <tyevco@users.noreply.github.com>

1126 of 1199 branches covered (93.91%)

Branch coverage included in aggregate %.

6145 of 6474 relevant lines covered (94.92%)

1.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.5
/src/KeenEyes.Spatial/SpatialQueryApi.cs
1
using System.Numerics;
2
using KeenEyes.Spatial.Partitioning;
3

4
namespace KeenEyes.Spatial;
5

6
/// <summary>
7
/// Public API for spatial queries provided by the spatial partitioning plugin.
8
/// </summary>
9
/// <remarks>
10
/// <para>
11
/// This API is exposed as a World extension by the <see cref="SpatialPlugin"/>.
12
/// Access it using <c>world.GetExtension&lt;SpatialQueryApi&gt;()</c>.
13
/// </para>
14
/// <para>
15
/// All query methods return broadphase candidates - entities that may be within
16
/// the query region. For exact results, callers should perform narrowphase checks
17
/// (distance, intersection, containment tests) on the returned entities.
18
/// </para>
19
/// </remarks>
20
/// <example>
21
/// <code>
22
/// // Install the plugin
23
/// world.InstallPlugin(new SpatialPlugin(new SpatialConfig()));
24
///
25
/// // Get the API
26
/// var spatial = world.GetExtension&lt;SpatialQueryApi&gt;();
27
///
28
/// // Query entities within radius
29
/// foreach (var entity in spatial.QueryRadius(playerPos, 100f))
30
/// {
31
///     // Check if entity is actually within distance (narrowphase)
32
///     ref readonly var transform = ref world.Get&lt;Transform3D&gt;(entity);
33
///     float distSq = Vector3.DistanceSquared(playerPos, transform.Position);
34
///     if (distSq &lt;= 100f * 100f)
35
///     {
36
///         // Entity is definitely within radius
37
///     }
38
/// }
39
/// </code>
40
/// </example>
41
public sealed class SpatialQueryApi : IDisposable
42
{
43
    private readonly IWorld world;
44
    private readonly ISpatialPartitioner partitioner;
45

46
    /// <summary>
47
    /// Creates a new spatial query API.
48
    /// </summary>
49
    /// <param name="world">The world this API is associated with.</param>
50
    /// <param name="partitioner">The spatial partitioner implementation.</param>
51
    internal SpatialQueryApi(IWorld world, ISpatialPartitioner partitioner)
1✔
52
    {
53
        this.world = world;
1✔
54
        this.partitioner = partitioner;
1✔
55
    }
1✔
56

57
    /// <summary>
58
    /// Gets the internal partitioner for system use.
59
    /// </summary>
60
    internal ISpatialPartitioner Partitioner => partitioner;
1✔
61

62
    /// <summary>
63
    /// Queries all entities within a spherical radius of a point.
64
    /// </summary>
65
    /// <param name="center">The center point of the query sphere.</param>
66
    /// <param name="radius">The radius of the query sphere.</param>
67
    /// <returns>An enumerable of entities within the radius (broadphase candidates).</returns>
68
    /// <remarks>
69
    /// <para>
70
    /// This is a broadphase query - it may return entities slightly outside the radius.
71
    /// Use <see cref="Vector3.Distance"/> or <see cref="Vector3.DistanceSquared"/>
72
    /// for exact distance checks.
73
    /// </para>
74
    /// <para>
75
    /// Only entities with the <see cref="KeenEyes.Spatial.SpatialIndexed"/> tag are returned.
76
    /// </para>
77
    /// </remarks>
78
    public IEnumerable<Entity> QueryRadius(Vector3 center, float radius)
79
    {
80
        return partitioner.QueryRadius(center, radius);
1✔
81
    }
82

83
    /// <summary>
84
    /// Queries all entities within a spherical radius of a point, filtered by component type.
85
    /// </summary>
86
    /// <typeparam name="T">The component type to filter by.</typeparam>
87
    /// <param name="center">The center point of the query sphere.</param>
88
    /// <param name="radius">The radius of the query sphere.</param>
89
    /// <returns>An enumerable of entities within the radius that have component T.</returns>
90
    /// <remarks>
91
    /// This is a convenience method that combines spatial query with component filtering.
92
    /// Equivalent to <c>QueryRadius(center, radius).Where(e => world.Has&lt;T&gt;(e))</c>.
93
    /// </remarks>
94
    public IEnumerable<Entity> QueryRadius<T>(Vector3 center, float radius)
95
        where T : struct, IComponent
96
    {
97
        foreach (var entity in QueryRadius(center, radius))
1✔
98
        {
99
            if (world.Has<T>(entity))
1✔
100
            {
101
                yield return entity;
1✔
102
            }
103
        }
104
    }
1✔
105

106
    /// <summary>
107
    /// Queries all entities within an axis-aligned bounding box.
108
    /// </summary>
109
    /// <param name="min">The minimum corner of the query box.</param>
110
    /// <param name="max">The maximum corner of the query box.</param>
111
    /// <returns>An enumerable of entities within the box (broadphase candidates).</returns>
112
    /// <remarks>
113
    /// <para>
114
    /// This is a broadphase query - it may return entities slightly outside the box.
115
    /// Use AABB intersection tests for exact results.
116
    /// </para>
117
    /// <para>
118
    /// Only entities with the <see cref="KeenEyes.Spatial.SpatialIndexed"/> tag are returned.
119
    /// </para>
120
    /// </remarks>
121
    public IEnumerable<Entity> QueryBounds(Vector3 min, Vector3 max)
122
    {
123
        return partitioner.QueryBounds(min, max);
1✔
124
    }
125

126
    /// <summary>
127
    /// Queries all entities within an axis-aligned bounding box, filtered by component type.
128
    /// </summary>
129
    /// <typeparam name="T">The component type to filter by.</typeparam>
130
    /// <param name="min">The minimum corner of the query box.</param>
131
    /// <param name="max">The maximum corner of the query box.</param>
132
    /// <returns>An enumerable of entities within the box that have component T.</returns>
133
    /// <remarks>
134
    /// This is a convenience method that combines spatial query with component filtering.
135
    /// </remarks>
136
    public IEnumerable<Entity> QueryBounds<T>(Vector3 min, Vector3 max)
137
        where T : struct, IComponent
138
    {
139
        foreach (var entity in QueryBounds(min, max))
1✔
140
        {
141
            if (world.Has<T>(entity))
1✔
142
            {
143
                yield return entity;
1✔
144
            }
145
        }
146
    }
1✔
147

148
    /// <summary>
149
    /// Queries all entities at a specific point.
150
    /// </summary>
151
    /// <param name="point">The point to query.</param>
152
    /// <returns>An enumerable of entities at or near the point (broadphase candidates).</returns>
153
    /// <remarks>
154
    /// <para>
155
    /// This is a broadphase query - it returns entities in the same spatial region
156
    /// as the point. Use containment tests for exact results.
157
    /// </para>
158
    /// <para>
159
    /// Only entities with the <see cref="KeenEyes.Spatial.SpatialIndexed"/> tag are returned.
160
    /// </para>
161
    /// </remarks>
162
    public IEnumerable<Entity> QueryPoint(Vector3 point)
163
    {
164
        return partitioner.QueryPoint(point);
1✔
165
    }
166

167
    /// <summary>
168
    /// Queries all entities at a specific point, filtered by component type.
169
    /// </summary>
170
    /// <typeparam name="T">The component type to filter by.</typeparam>
171
    /// <param name="point">The point to query.</param>
172
    /// <returns>An enumerable of entities at or near the point that have component T.</returns>
173
    /// <remarks>
174
    /// This is a convenience method that combines spatial query with component filtering.
175
    /// </remarks>
176
    public IEnumerable<Entity> QueryPoint<T>(Vector3 point)
177
        where T : struct, IComponent
178
    {
179
        foreach (var entity in QueryPoint(point))
1✔
180
        {
181
            if (world.Has<T>(entity))
1✔
182
            {
183
                yield return entity;
1✔
184
            }
185
        }
186
    }
1✔
187

188
    /// <summary>
189
    /// Queries all entities within a view frustum (camera-based culling).
190
    /// </summary>
191
    /// <param name="frustum">The view frustum to query.</param>
192
    /// <returns>An enumerable of entities within the frustum (broadphase candidates).</returns>
193
    /// <remarks>
194
    /// <para>
195
    /// This is a broadphase query - it may return entities slightly outside the frustum.
196
    /// Use <see cref="Frustum.Contains"/> or <see cref="Frustum.Intersects(Vector3, Vector3)"/>
197
    /// for exact containment tests.
198
    /// </para>
199
    /// <para>
200
    /// Frustum culling is typically used for rendering optimization - only entities
201
    /// visible to the camera are returned. Use <see cref="Frustum.FromMatrix"/> to create
202
    /// a frustum from a view-projection matrix.
203
    /// </para>
204
    /// <para>
205
    /// Only entities with the <see cref="KeenEyes.Spatial.SpatialIndexed"/> tag are returned.
206
    /// </para>
207
    /// </remarks>
208
    /// <example>
209
    /// <code>
210
    /// var spatial = world.GetExtension&lt;SpatialQueryApi&gt;();
211
    /// var viewProj = camera.ViewMatrix * camera.ProjectionMatrix;
212
    /// var frustum = Frustum.FromMatrix(viewProj);
213
    ///
214
    /// foreach (var entity in spatial.QueryFrustum(frustum))
215
    /// {
216
    ///     // Render entities visible to the camera
217
    ///     ref readonly var transform = ref world.Get&lt;Transform3D&gt;(entity);
218
    ///     RenderEntity(entity, transform);
219
    /// }
220
    /// </code>
221
    /// </example>
222
    public IEnumerable<Entity> QueryFrustum(Frustum frustum)
223
    {
224
        return partitioner.QueryFrustum(frustum);
×
225
    }
226

227
    /// <summary>
228
    /// Queries all entities within a view frustum, filtered by component type.
229
    /// </summary>
230
    /// <typeparam name="T">The component type to filter by.</typeparam>
231
    /// <param name="frustum">The view frustum to query.</param>
232
    /// <returns>An enumerable of entities within the frustum that have component T.</returns>
233
    /// <remarks>
234
    /// This is a convenience method that combines frustum culling with component filtering.
235
    /// Useful for rendering only entities of specific types (e.g., renderables, particles).
236
    /// </remarks>
237
    public IEnumerable<Entity> QueryFrustum<T>(Frustum frustum)
238
        where T : struct, IComponent
239
    {
240
        foreach (var entity in QueryFrustum(frustum))
×
241
        {
242
            if (world.Has<T>(entity))
×
243
            {
244
                yield return entity;
×
245
            }
246
        }
247
    }
×
248

249
    /// <summary>
250
    /// Gets the total number of entities currently in the spatial index.
251
    /// </summary>
252
    public int EntityCount => partitioner.EntityCount;
1✔
253

254
    /// <summary>
255
    /// Queries all entities within a spherical radius into a caller-provided buffer (zero-allocation).
256
    /// </summary>
257
    /// <param name="center">The center point of the query sphere.</param>
258
    /// <param name="radius">The radius of the query sphere.</param>
259
    /// <param name="results">The buffer to write results into. Use stackalloc or ArrayPool for optimal performance.</param>
260
    /// <returns>
261
    /// The number of entities written to the buffer, or -1 if the buffer was too small.
262
    /// When -1 is returned, the buffer contains partial results up to its capacity.
263
    /// </returns>
264
    /// <remarks>
265
    /// <para>
266
    /// This is a zero-allocation query method for performance-critical code paths like
267
    /// collision detection or AI queries that run every frame.
268
    /// </para>
269
    /// <para>
270
    /// Example usage with stackalloc:
271
    /// <code>
272
    /// Span&lt;Entity&gt; buffer = stackalloc Entity[256];
273
    /// int count = spatial.QueryRadius(center, radius, buffer);
274
    /// if (count >= 0)
275
    /// {
276
    ///     foreach (var entity in buffer[..count])
277
    ///     {
278
    ///         // Process entity
279
    ///     }
280
    /// }
281
    /// </code>
282
    /// </para>
283
    /// </remarks>
284
    public int QueryRadius(Vector3 center, float radius, Span<Entity> results)
285
    {
286
        return partitioner.QueryRadius(center, radius, results);
1✔
287
    }
288

289
    /// <summary>
290
    /// Queries all entities within an axis-aligned bounding box into a caller-provided buffer (zero-allocation).
291
    /// </summary>
292
    /// <param name="min">The minimum corner of the query box.</param>
293
    /// <param name="max">The maximum corner of the query box.</param>
294
    /// <param name="results">The buffer to write results into. Use stackalloc or ArrayPool for optimal performance.</param>
295
    /// <returns>
296
    /// The number of entities written to the buffer, or -1 if the buffer was too small.
297
    /// When -1 is returned, the buffer contains partial results up to its capacity.
298
    /// </returns>
299
    /// <remarks>
300
    /// This is a zero-allocation query method for performance-critical code paths.
301
    /// </remarks>
302
    public int QueryBounds(Vector3 min, Vector3 max, Span<Entity> results)
303
    {
304
        return partitioner.QueryBounds(min, max, results);
1✔
305
    }
306

307
    /// <summary>
308
    /// Queries all entities at a specific point into a caller-provided buffer (zero-allocation).
309
    /// </summary>
310
    /// <param name="point">The point to query.</param>
311
    /// <param name="results">The buffer to write results into. Use stackalloc or ArrayPool for optimal performance.</param>
312
    /// <returns>
313
    /// The number of entities written to the buffer, or -1 if the buffer was too small.
314
    /// When -1 is returned, the buffer contains partial results up to its capacity.
315
    /// </returns>
316
    /// <remarks>
317
    /// This is a zero-allocation query method for performance-critical code paths.
318
    /// </remarks>
319
    public int QueryPoint(Vector3 point, Span<Entity> results)
320
    {
321
        return partitioner.QueryPoint(point, results);
1✔
322
    }
323

324
    /// <summary>
325
    /// Queries all entities within a view frustum into a caller-provided buffer (zero-allocation).
326
    /// </summary>
327
    /// <param name="frustum">The view frustum to query.</param>
328
    /// <param name="results">The buffer to write results into. Use stackalloc or ArrayPool for optimal performance.</param>
329
    /// <returns>
330
    /// The number of entities written to the buffer, or -1 if the buffer was too small.
331
    /// When -1 is returned, the buffer contains partial results up to its capacity.
332
    /// </returns>
333
    /// <remarks>
334
    /// This is a zero-allocation query method for performance-critical code paths like
335
    /// frustum culling for rendering.
336
    /// </remarks>
337
    public int QueryFrustum(Frustum frustum, Span<Entity> results)
338
    {
339
        return partitioner.QueryFrustum(frustum, results);
1✔
340
    }
341

342
    /// <inheritdoc/>
343
    public void Dispose()
344
    {
345
        partitioner.Dispose();
1✔
346
    }
1✔
347
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc