Skip to main content

GridCollisionDetectionSystem.ixx File

Spatial-partitioning based collision detection using a uniform 3D grid. More...

Included Headers

Namespaces Index

namespacehelios
namespaceengine

Main engine module aggregating core infrastructure and game systems. More...

namespacemodules

Domain-specific components and systems. More...

namespacephysics

Physics simulation and collision detection subsystem for the game engine. More...

namespacecollision
namespacesystems

Collision detection and response systems. More...

Classes Index

classGridCollisionDetectionSystem

Collision detection system using uniform spatial partitioning for Broadphase and AABB overlaps in the Narrowphase. More...

structCollisionStruct

Helper-struct representing the properties and interaction state of a collision event between two entities. More...

structEntityHandlePairHash

Hash functor for pairs of EntityHandles. More...

structCollisionCandidate

Internal structure holding references to a potential collision candidate. More...

structGridCell

Represents a single cell in the spatial partitioning grid. More...

Macro Definitions Index

#defineHELIOS_LOG_SCOPE   "helios::engine::modules::physics::systems::GridCollisionDetectionSystem"

Description

Spatial-partitioning based collision detection using a uniform 3D grid.

Macro Definitions

HELIOS_LOG_SCOPE

#define HELIOS_LOG_SCOPE   "helios::engine::modules::physics::systems::GridCollisionDetectionSystem"

Definition at line 45 of file GridCollisionDetectionSystem.ixx.

45#define HELIOS_LOG_SCOPE "helios::engine::modules::physics::systems::GridCollisionDetectionSystem"

File Listing

The file content with the documentation metadata removed is:

1/**
2 * @file GridCollisionDetectionSystem.ixx
3 * @brief Spatial-partitioning based collision detection using a uniform 3D grid.
4 */
5module;
6
7#include <algorithm>
8#include <cassert>
9#include <cmath>
10#include <format>
11#include <helios/helios_config.h>
12#include <unordered_set>
13#include <vector>
14
15
16export module helios.engine.modules.physics.collision.systems.GridCollisionDetectionSystem;
17
18import helios.engine.ecs.System;
19import helios.engine.runtime.world.UpdateContext;
20import helios.engine.ecs.GameObject;
21import helios.engine.runtime.world.GameWorld;
22
23import helios.engine.mechanics.lifecycle.components.Active;
24
25import helios.engine.modules.physics.collision.events.TriggerCollisionEvent;
26import helios.engine.modules.physics.collision.events.SolidCollisionEvent;
27
28import helios.engine.modules.physics.collision.components.CollisionComponent;
29import helios.engine.modules.physics.collision.components.CollisionStateComponent;
30import helios.engine.modules.physics.collision.components.AabbColliderComponent;
31
32import helios.engine.modules.physics.collision.types.CollisionBehavior;
33import helios.engine.modules.physics.collision.types.HitPolicy;
34
35import helios.engine.ecs.EntityHandle;
36
37import helios.util.Guid;
38import helios.math;
39
40import helios.util.log;
41
44
45#define HELIOS_LOG_SCOPE "helios::engine::modules::physics::systems::GridCollisionDetectionSystem"
47
48 /**
49 * @brief Collision detection system using uniform spatial partitioning for Broadphase and
50 * AABB overlaps in the Narrowphase.
51 *
52 * @details This system implements a grid-based spatial partitioning approach for efficient
53 * collision detection, following the principles outlined in Ericson's "Real-Time Collision
54 * Detection" (Chapter 7). The algorithm divides the world into a uniform 3D grid of cells
55 * and assigns each collidable entity to the cells it overlaps.
56 *
57 * ## Detection Phases
58 *
59 * 1 **Broadphase:** Entities are inserted into grid cells based on their AABB. Only entities
60 * sharing the same cell are considered potential collision pairs.
61 * 2 **Narrowphase:** For each cell with multiple candidates, AABB intersection tests
62 * determine actual collisions.
63 *
64 * ## Collision Types
65 *
66 * Collision events are published to the `UpdateContext`'s event sink, distinguishing between:
67 * - **Solid collisions:** Symmetric collisions where both entities can physically interact.
68 * - **Trigger collisions:** Asymmetric collisions for gameplay logic (e.g., pickups, zones).
69 *
70 * ## Hit Policy
71 *
72 * The system respects each entity's HitPolicy setting:
73 * - **OneHit:** Entity receives only its first collision per frame, then skips further checks.
74 * - **All:** Entity receives collision events for all overlapping entities.
75 *
76 * This allows projectiles to stop on first contact while area effects can damage multiple targets.
77 *
78 * ## Layer Filtering
79 *
80 * During the broadphase, the system uses layer masks to filter which entity types
81 * can collide with each other, enabling fine-grained control over collision pairs.
82 *
83 * @see CollisionComponent
84 * @see AabbColliderComponent
85 * @see TriggerCollisionEvent
86 * @see SolidCollisionEvent
87 * @see HitPolicy
88 *
89 * @see [Eri05, Chapter 7]
90 */
92
93 /**
94 * @brief Helper-struct representing the properties and interaction state of a collision
95 * event between two entities.
96 */
97 struct CollisionStruct {
98 bool isSolidCollision = false;
99 bool isTriggerCollision = false;
100 bool aIsCollisionReporter = false;
101 bool bIsCollisionReporter = false;
104 uint32_t collisionLayer = 0;
105 uint32_t otherCollisionLayer = 0;
106
107 [[nodiscard]] inline constexpr bool hasAnyInteraction() const noexcept {
108 return (isSolidCollision || isTriggerCollision) && (aIsCollisionReporter || bIsCollisionReporter);
109 }
110 };
111
112 /**
113 * @brief Hash functor for pairs of EntityHandles.
114 *
115 * Enables the use of EntityHandle pairs as keys in unordered containers. The hash combines
116 * both handles using XOR with a bit-shift to reduce collision probability for symmetric pairs.
117 *
118 * @todo switch to uint32_t once helios/#174 is implemented
119 */
120 struct EntityHandlePairHash {
121
122 /**
123 * @brief Computes a hash value for an EntityHandle pair.
124 *
125 * @param pair The pair of EntityHandles to hash.
126 *
127 * @return A combined hash value for both handles.
128 */
129 std::uint64_t operator()(const std::pair<helios::engine::ecs::EntityHandle, helios::engine::ecs::EntityHandle>& pair) const {
130
131 auto g1 = std::hash<helios::engine::ecs::EntityHandle>{}(pair.first);
132 auto g2 = std::hash<helios::engine::ecs::EntityHandle>{}(pair.second);
133
134 // compute the hash for the pair - shift g2 one position left, then xor with g1.
135 return g1 ^ (g2 << 1);
136 };
137
138 };
139
140 /**
141 * @brief Internal structure holding references to a potential collision candidate.
142 *
143 * Bundles the GameObject pointer with its collision-relevant components for
144 * efficient access during the narrow phase.
145 */
146 struct CollisionCandidate {
147
148 /**
149 * @brief Pointer to the GameObject entity.
150 */
152
153 /**
154 * @brief Pointer to the AABB collider component providing world-space bounds.
155 */
156 AabbColliderComponent* aabbColliderComponent;
157
158 /**
159 * @brief Pointer to the collision component defining layer masks and collision behavior.
160 */
161 CollisionComponent* collisionComponent;
162
163 /**
164 * @brief Pointer to the collision state component for storing collision results.
165 */
166 CollisionStateComponent* collisionStateComponent;
167 };
168
169 /**
170 * @brief Represents a single cell in the spatial partitioning grid.
171 *
172 * Each cell stores references to all collision candidates whose AABBs overlap
173 * this cell's spatial region.
174 */
175 struct GridCell {
176
177 /**
178 * @brief List of collision candidates currently occupying this cell.
179 */
180 std::vector<CollisionCandidate> collisionCandidates;
181
182 /**
183 * @brief Clears all candidates from this cell.
184 */
185 void clear() {
186 collisionCandidates.clear();
187 }
188 };
189
190 /**
191 * @brief Scoped logger instance for structured logging within the current context.
192 */
193 static inline const auto& logger_ = helios::util::log::LogManager::loggerForScope(HELIOS_LOG_SCOPE);
194
195 /**
196 * @brief Set of already-processed collision pairs to avoid duplicate events.
197 *
198 * Stores pairs of EntityHandles in canonical order (smaller handle first) to ensure each
199 * collision pair is processed only once per frame, even when entities span multiple cells.
200 */
201 std::unordered_set<std::pair<helios::engine::ecs::EntityHandle, helios::engine::ecs::EntityHandle>, EntityHandlePairHash> solvedCollisions_;
202
203 /**
204 * @brief Size of each grid cell in world units.
205 */
206 float cellSize_;
207
208 /**
209 * @brief World-space bounds defining the spatial region covered by the grid.
210 */
211 helios::math::aabbf gridBounds_;
212
213 /**
214 * @brief Flat storage for all grid cells, indexed as x + y * cellsX + z * (cellsX * cellsY).
215 */
216 std::vector<GridCell> cells_;
217
218 /**
219 * @brief Number of cells along the X axis.
220 */
221 unsigned int cellsX_;
222
223 /**
224 * @brief Number of cells along the Y axis.
225 */
226 unsigned int cellsY_;
227
228 /**
229 * @brief Number of cells along the Z axis.
230 */
231 unsigned int cellsZ_;
232
233 /**
234 * @brief Helper to keep track of updated cells (with potential collisions candidates) in one pass.
235 */
236 std::vector<size_t> trackedCells_;
237
238 /**
239 * @brief Initializes the grid based on world bounds and cell size.
240 *
241 * Computes the number of cells needed in each dimension and allocates
242 * the cell storage.
243 */
244 void initGrid() {
245
246 helios::math::vec3f size = gridBounds_.size();
247
248 cellsX_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[0] / cellSize_)));
249 cellsY_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[1] / cellSize_)));
250 cellsZ_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[2] / cellSize_)));
251
252 const size_t cellCount = static_cast<size_t>(cellsX_) * cellsY_ * cellsZ_;
253
254 // this would make 100'000'000 * sizeof(GridCell) Bytes
255 // if we have 24 Bytes per GridCell, we end up with 2,4 GB alone for this spatial grid.
256 if (cellCount > 100'000'000) {
257 logger_.warn(std::format("Spatial Grid requested {0} cells", cellCount));
258 throw std::runtime_error("Cell count too high.");
259 }
260
261 trackedCells_.reserve(cellCount);
262 cells_.resize(cellCount);
263 }
264
265 /**
266 * @brief Prepares the grid for a new collision detection pass.
267 *
268 * Clears all collision candidates from every cell and resets the set of
269 * already-solved collision pairs.
270 */
271 inline void prepareCollisionDetection() {
272 for (const auto idx : trackedCells_) {
273 cells_[idx].clear();
274 }
275
276 trackedCells_.clear();
277 solvedCollisions_.clear();
278 }
279
280 /**
281 * @brief Posts collision events to the UpdateContext's event sink.
282 *
283 * @details Updates the CollisionStateComponent of both entities with collision
284 * information including contact point, collision type, behavior, and reporter status.
285 * An entity marked as collision reporter receives the event as the "source" entity.
286 *
287 * @param candidate First collision candidate (potential event source).
288 * @param match Second collision candidate (collision partner).
289 * @param contact The contact point between the two AABBs.
290 * @param collisionStruct Struct containing collision type and behavior information.
291 * @param updateContext Context for pushing events to the event queue.
292 * @param csc_a Collision state component of the first entity.
293 * @param csc_b Collision state component of the second entity.
294 */
295 inline void postEvent(
296 const helios::engine::ecs::GameObject candidate,
298 const helios::math::vec3f contact,
299 const CollisionStruct collisionStruct,
303 ) const noexcept {
304
305 bool aIsCollisionReporter = collisionStruct.aIsCollisionReporter;
306 bool bIsCollisionReporter = collisionStruct.bIsCollisionReporter;
307 bool isSolidCollision = collisionStruct.isSolidCollision;
308 bool isTriggerCollision = collisionStruct.isTriggerCollision;
309 uint32_t collisionLayer = collisionStruct.collisionLayer;
310 uint32_t otherCollisionLayer = collisionStruct.otherCollisionLayer;
311
312 assert((isSolidCollision || isTriggerCollision)
313 && (aIsCollisionReporter || bIsCollisionReporter)
314 && "Preconditions not matched for postEvent.");
315
316 // post the events
317 if (isTriggerCollision || isSolidCollision) {
318 csc_a->setState(
319 candidate,
320 contact, isSolidCollision, isTriggerCollision, collisionStruct.aCollisionBehavior,
321 aIsCollisionReporter, match.entityHandle(), collisionLayer, otherCollisionLayer
322 );
323 csc_b->setState(
324 match,
325 contact, isSolidCollision, isTriggerCollision, collisionStruct.bCollisionBehavior,
326 bIsCollisionReporter, candidate.entityHandle(), collisionLayer, otherCollisionLayer
327 );
328 }
329
330 }
331
332 /**
333 * @brief Determines the collision type between two entities based on their layer masks.
334 *
335 * Evaluates both solid and trigger collision masks to determine if and how two entities
336 * can collide. Solid collisions are symmetric (both entities must accept collision with
337 * each other), while trigger collisions are asymmetric (either entity can trigger).
338 *
339 * @param cc Collision component of the first entity.
340 * @param matchCC Collision component of the second entity.
341 *
342 * @return CollisionStruct a struct with the requested collision information.
343 */
344 [[nodiscard]] inline CollisionStruct findCollisionType(
345 const CollisionComponent* cc,
346 const CollisionComponent* matchCC
347 ) const noexcept {
348
349 auto isSolidCollision = false;
350 auto isTriggerCollision = false;
351
352 auto aIsCollisionReporter = cc->isCollisionReporter();
353 auto bIsCollisionReporter = matchCC->isCollisionReporter();
354
355 // none of the reports a collision as a pair? skip!
356 if (!aIsCollisionReporter && !bIsCollisionReporter) {
357 return CollisionStruct{};
358 }
359
360 bool aCanSolidCollideWithB = (cc->solidCollisionMask() & matchCC->layerId()) != 0;
361 bool bCanSolidCollideWithA = (matchCC->solidCollisionMask() & cc->layerId()) != 0;
362
363 bool aCanTriggerCollideWithB = (cc->triggerCollisionMask() & matchCC->layerId()) != 0;
364 bool bCanTriggerCollideWithA = (matchCC->triggerCollisionMask() & cc->layerId()) != 0;
365
366 // solid collision is treated symmetric
367 isSolidCollision = aCanSolidCollideWithB && bCanSolidCollideWithA;
368
369 #if HELIOS_DEBUG
370 // detect asymmetric solid collision
371 if (aCanSolidCollideWithB && !bCanSolidCollideWithA) {
372 logger_.warn("Collision Asymmetry detected!");
373 }
374 #endif
375
376 // trigger collision is treated asymmetric
377 isTriggerCollision = aCanTriggerCollideWithB || bCanTriggerCollideWithA;
378
379 return CollisionStruct {
380 isSolidCollision,
381 isTriggerCollision,
382 aIsCollisionReporter,
383 bIsCollisionReporter,
384 isSolidCollision
385 ? cc->solidCollisionBehavior(matchCC->layerId()) : cc->triggerCollisionBehavior(matchCC->layerId()),
386 isSolidCollision
387 ? matchCC->solidCollisionBehavior(cc->layerId()) : matchCC->triggerCollisionBehavior(cc->layerId()),
388 // use the layerId of cc
389 cc->layerId(),
390 matchCC->layerId()
391
392 };
393 }
394
395 public:
396
397 /**
398 * @brief Constructs a GridCollisionDetectionSystem with specified bounds and cell size.
399 *
400 * The grid is initialized to cover the given world bounds. Cell size determines
401 * the granularity of spatial partitioning; smaller cells reduce false positives
402 * but increase memory and insertion overhead.
403 *
404 * @param bounds World-space AABB defining the region where collision detection is active.
405 * @param cellSize Size of each grid cell in world units. Must be greater than zero.
406 *
407 * @note Given n objects, the 1/3 rule should be applied for determining the number of cells for partitioning,
408 * i.e. with n objects, the cube should consist of k x k x k cells, with k = n^{1/3} - that is, on average,
409 * one cell fits one object that should be tested for collision. Dimensions of said cells
410 * are derived by taking the reference space into account, e.g. WorldBounds.size/k (see [HS99]).
411 */
413 const helios::math::aabbf& bounds,
414 const float cellSize
415 ) : gridBounds_(bounds),
416 cellSize_(cellSize) {
417 assert(cellSize_ > 0.0f && "cellSize must not be <= 0.0f");
418 initGrid();
419 }
420
421 /**
422 * @brief Performs collision detection for all active entities.
423 *
424 * Executes the complete collision detection pipeline each frame:
425 * 1 Clears all grid cells and resets solved collision pairs.
426 * 2 Iterates all active GameObjects with CollisionComponent and AabbColliderComponent.
427 * 3 Inserts each entity into all grid cells its AABB overlaps.
428 * 4 For each cell with multiple candidates, runs narrow-phase AABB intersection tests.
429 * 5 Publishes collision events (TriggerCollisionEvent, SolidCollisionEvent) to the event queue.
430 *
431 * @param updateContext The update context providing access to GameWorld and event queue.
432 */
433 void update(helios::engine::runtime::world::UpdateContext& updateContext) noexcept override {
434
435 prepareCollisionDetection();
436
437 for (auto [entity, cc, csc, acc, active] : gameWorld_->view<
442 >().whereEnabled()) {
443
444 if (!acc->boundsInitialized()) {
445 continue;
446 }
447
448 if (!cc->isEnabled() || (cc->triggerCollisionMask() == 0 && cc->solidCollisionMask() == 0)) {
449 continue;
450 }
451
452 const auto& worldBounds = acc->bounds();
453
455 entity,
456 worldBoundsToGridBounds(worldBounds),
457 acc,
458 cc,
459 csc
460 );
461 }
462
463 for (const auto idx : trackedCells_) {
464
465 if (cells_[idx].collisionCandidates.size() < 2) {
466 continue;
467 }
468
469 solveCell(cells_[idx], updateContext);
470 }
471 }
472
473
474 /**
475 * @brief Converts world-space AABB bounds to grid cell indices.
476 *
477 * Transforms a floating-point AABB in world space to integer cell coordinates,
478 * clamped to the valid grid range. Used to determine which cells an entity occupies.
479 *
480 * @param aabbf The world-space AABB to convert.
481 *
482 * @return An integer AABB representing the range of grid cell indices the entity spans.
483 */
484 [[nodiscard]] helios::math::aabbi worldBoundsToGridBounds(const helios::math::aabbf& aabbf) const noexcept {
485
486 helios::math::vec3f min = aabbf.min() - gridBounds_.min();
487 helios::math::vec3f max = aabbf.max() - gridBounds_.min();
488
489 int xMin = static_cast<int>(std::floor(min[0] / cellSize_));
490 int yMin = static_cast<int>(std::floor(min[1] / cellSize_));
491 int zMin = static_cast<int>(std::floor(min[2] / cellSize_));
492
493 int xMax = static_cast<int>(std::floor(max[0] / cellSize_));
494 int yMax = static_cast<int>(std::floor(max[1] / cellSize_));
495 int zMax = static_cast<int>(std::floor(max[2] / cellSize_));
496
498 std::clamp(xMin, 0, static_cast<int>(cellsX_ - 1)),
499 std::clamp(yMin, 0, static_cast<int>(cellsY_ - 1)),
500 std::clamp(zMin, 0, static_cast<int>(cellsZ_ - 1)),
501 std::clamp(xMax, 0, static_cast<int>(cellsX_ - 1)),
502 std::clamp(yMax, 0, static_cast<int>(cellsY_ - 1)),
503 std::clamp(zMax, 0, static_cast<int>(cellsZ_ - 1)),
504 };
505 }
506
507
508 /**
509 * @brief Inserts a collision candidate into all grid cells it overlaps.
510 *
511 * Iterates through all cells within the given bounds and adds the candidate
512 * to each cell's collision candidate list for subsequent narrow-phase testing.
513 *
514 * @param go Pointer to the GameObject entity.
515 * @param bounds Grid cell index bounds (integer AABB) the entity spans.
516 * @param aabbColliderComponent Pointer to the entity's AABB collider component.
517 * @param collisionComponent Pointer to the entity's collision component.
518 */
521 const helios::math::aabbi& bounds,
522 AabbColliderComponent* aabbColliderComponent,
523 CollisionComponent* collisionComponent,
524 CollisionStateComponent* collisionStateComponent
525 ) {
526 const auto xMin = bounds.min()[0];
527 const auto xMax = bounds.max()[0];
528 const auto yMin = bounds.min()[1];
529 const auto yMax = bounds.max()[1];
530 const auto zMin = bounds.min()[2];
531 const auto zMax = bounds.max()[2];
532
533 for (int x = xMin; x <= xMax; x++) {
534 for (int y = yMin; y <= yMax; y++) {
535 for (int z = zMin; z <= zMax; z++) {
536 auto& [collisionCandidates] = cell(x, y, z);
537
538 collisionCandidates.push_back(
539 CollisionCandidate{
540 go,
541 aabbColliderComponent,
542 collisionComponent,
543 collisionStateComponent
544 }
545 );
546
547 // only consider the first added to prevent dups
548 if (collisionCandidates.size() == 1) {
549 const auto idx = cellIndex(x, y, z);
550 trackedCells_.push_back(idx);
551 }
552 }
553 }
554 }
555
556 }
557
558
559 /**
560 * @brief Performs narrow-phase collision detection for all candidates in a cell.
561 *
562 * Tests all unique pairs of collision candidates within the cell using AABB intersection.
563 * For each collision detected, determines collision type (solid/trigger) based on layer
564 * masks and publishes appropriate events. Uses a solved-set to avoid duplicate events
565 * when entities span multiple cells.
566 *
567 * @param cell The grid cell containing collision candidates to test.
568 * @param updateContext Context for pushing collision events to the event queue.
569 */
570 inline void solveCell(GridCell& cell, helios::engine::runtime::world::UpdateContext& updateContext) {
571
572 auto& candidates = cell.collisionCandidates;
573
574 // we will skip this cell if none of the candidates reports a collision
575 bool isCollisionReporter = false;
576 for (const auto& candidate : candidates) {
577 if (candidate.collisionComponent->isCollisionReporter()) {
578 isCollisionReporter = true;
579 break;
580 }
581 }
582
583 if (!isCollisionReporter) {
584 return;
585 }
586
587 for (size_t i = 0; i < candidates.size(); i++) {
588
589 CollisionCandidate& candidate = candidates[i];
590 CollisionComponent* cc = candidate.collisionComponent;
591 CollisionStateComponent* csc = candidate.collisionStateComponent;
592
593 auto hitPolicy = cc->hitPolicy();
595 continue;
596 }
597
598 const helios::math::aabbf& aabbCandidate = candidate.aabbColliderComponent->bounds();
599
600 for (size_t j = i+1; j < candidates.size(); j++) {
601
602 auto& [gameObject, aabbColliderComponent, collisionComponent, collisionStateComponent] = candidates[j];
603
604 CollisionComponent* matchCC = collisionComponent;
605 CollisionStateComponent* matchCSC = collisionStateComponent;
606
607 const auto collisionStruct = findCollisionType(cc, matchCC);
608
609 if (!collisionStruct.hasAnyInteraction()) {
610 continue;
611 }
612
613 const helios::math::aabbf& aabbMatch = aabbColliderComponent->bounds();
614 if (!aabbCandidate.intersects(aabbMatch)) {
615 continue;
616 }
617
618 auto lHandle = candidate.gameObject.entityHandle();
619 auto rHandle = gameObject.entityHandle();
620
621 if (lHandle > rHandle) {
622 std::swap(lHandle, rHandle);
623 }
624
625 // if we have already processed a collision, do not add this collision again.
626 if (solvedCollisions_.contains({lHandle, rHandle})) {
627 continue;
628 }
629
630 solvedCollisions_.insert({lHandle, rHandle});
631
632 postEvent(
633 candidate.gameObject, gameObject,
634 helios::math::overlapCenter(aabbCandidate, aabbMatch),
635 collisionStruct,
636 updateContext, csc, matchCSC
637 );
638
640 break;
641 }
642 }
643 }
644 }
645
646 /**
647 * @brief Accesses a grid cell by its 3D coordinates.
648 *
649 * @param x X-coordinate of the cell (0 to cellsX - 1).
650 * @param y Y-coordinate of the cell (0 to cellsY - 1).
651 * @param z Z-coordinate of the cell (0 to cellsZ - 1).
652 *
653 * @return Reference to the GridCell at the specified coordinates.
654 */
655 [[nodiscard]] inline GridCell& cell(const unsigned int x, const unsigned int y, const unsigned int z) noexcept {
656 return cells_[cellIndex(x, y, z)];
657 }
658
659 /**
660 * @brief Computes the 1D index of a cell in a 3D grid based on its x, y, and z coordinates.
661 *
662 * @param x The x-coordinate of the cell (must be less than the grid's x-dimension).
663 * @param y The y-coordinate of the cell (must be less than the grid's y-dimension).
664 * @param z The z-coordinate of the cell (must be less than the grid's z-dimension).
665 *
666 * @return The 1D index of the cell in the grid.
667 */
668 [[nodiscard]] inline constexpr size_t cellIndex(const unsigned int x, const unsigned int y, const unsigned int z) const noexcept {
669 assert (x < cellsX_ && y < cellsY_ && z < cellsZ_ && "Invalid range values");
670
671 return x + y * cellsX_ + (z * cellsX_ * cellsY_);
672 }
673
674 /**
675 * @brief Returns the number of cells along the X axis.
676 *
677 * @return Number of grid cells in the X dimension.
678 */
679 [[nodiscard]] unsigned int cellsX() const noexcept {
680 return cellsX_;
681 }
682
683 /**
684 * @brief Returns the number of cells along the Y axis.
685 *
686 * @return Number of grid cells in the Y dimension.
687 */
688 [[nodiscard]] unsigned int cellsY() const noexcept {
689 return cellsY_;
690 }
691
692 /**
693 * @brief Returns the number of cells along the Z axis.
694 *
695 * @return Number of grid cells in the Z dimension.
696 */
697 [[nodiscard]] unsigned int cellsZ() const noexcept {
698 return cellsZ_;
699 }
700
701 };
702
703
704}

Generated via doxygen2docusaurus 2.0.0 by Doxygen 1.15.0.