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 42 of file GridCollisionDetectionSystem.ixx.

42#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
18
19import helios.engine.runtime.world.UpdateContext;
20import helios.engine.ecs.GameObject;
21import helios.engine.runtime.world.GameWorld;
22
23import helios.engine.mechanics.lifecycle.components;
24
25import helios.engine.modules.physics.collision.components.CollisionComponent;
26import helios.engine.modules.physics.collision.components.CollisionStateComponent;
27import helios.engine.modules.physics.collision.components.AabbColliderComponent;
28
29import helios.engine.modules.physics.collision.types.CollisionBehavior;
30import helios.engine.modules.physics.collision.types.HitPolicy;
31
32import helios.engine.ecs.EntityHandle;
33
34import helios.util.Guid;
35import helios.math;
36
37import helios.util.log;
38
41
42#define HELIOS_LOG_SCOPE "helios::engine::modules::physics::systems::GridCollisionDetectionSystem"
43import helios.engine.common.tags.SystemRole;
44
46
47 /**
48 * @brief Collision detection system using uniform spatial partitioning for Broadphase and
49 * AABB overlaps in the Narrowphase.
50 *
51 * @details This system implements a grid-based spatial partitioning approach for efficient
52 * collision detection, following the principles outlined in Ericson's "Real-Time Collision
53 * Detection" (Chapter 7). The algorithm divides the world into a uniform 3D grid of cells
54 * and assigns each collidable entity to the cells it overlaps.
55 *
56 * ## Detection Phases
57 *
58 * 1 **Broadphase:** Entities are inserted into grid cells based on their AABB. Only entities
59 * sharing the same cell are considered potential collision pairs.
60 * 2 **Narrowphase:** For each cell with multiple candidates, AABB intersection tests
61 * determine actual collisions.
62 *
63 * ## Collision Types
64 *
65 * Collision state is updated in the `CollisionStateComponent`, distinguishing between:
66 * - **Solid collisions:** Symmetric collisions where both entities can physically interact.
67 * - **Trigger collisions:** Asymmetric collisions for gameplay logic (e.g., pickups, zones).
68 *
69 * ## Hit Policy
70 *
71 * The system respects each entity's HitPolicy setting:
72 * - **OneHit:** Entity receives only its first collision per frame, then skips further checks.
73 * - **All:** Entity receives collision events for all overlapping entities.
74 *
75 * This allows projectiles to stop on first contact while area effects can damage multiple targets.
76 *
77 * ## Layer Filtering
78 *
79 * During the broadphase, the system uses layer masks to filter which entity types
80 * can collide with each other, enabling fine-grained control over collision pairs.
81 *
82 * @see CollisionComponent
83 * @see AabbColliderComponent
84 * @see HitPolicy
85 *
86 * @see [Eri05, Chapter 7]
87 */
89
90 /**
91 * @brief Helper-struct representing the properties and interaction state of a collision
92 * event between two entities.
93 */
94 struct CollisionStruct {
95 bool isSolidCollision = false;
96 bool isTriggerCollision = false;
97 bool aIsCollisionReporter = false;
98 bool bIsCollisionReporter = false;
101 uint32_t aCollisionLayer = 0;
102 uint32_t bCollisionLayer = 0;
103
104 [[nodiscard]] inline constexpr bool hasAnyInteraction() const noexcept {
105 return (isSolidCollision || isTriggerCollision) && (aIsCollisionReporter || bIsCollisionReporter);
106 }
107 };
108
109 /**
110 * @brief Hash functor for pairs of EntityHandles.
111 *
112 * Enables the use of EntityHandle pairs as keys in unordered containers. The hash combines
113 * both handles using XOR with a bit-shift to reduce collision probability for symmetric pairs.
114 *
115 * @todo switch to uint32_t once helios/#174 is implemented
116 */
117 struct EntityHandlePairHash {
118
119 /**
120 * @brief Computes a hash value for an EntityHandle pair.
121 *
122 * @param pair The pair of EntityHandles to hash.
123 *
124 * @return A combined hash value for both handles.
125 */
126 std::uint64_t operator()(const std::pair<helios::engine::ecs::EntityHandle, helios::engine::ecs::EntityHandle>& pair) const {
127
128 auto g1 = std::hash<helios::engine::ecs::EntityHandle>{}(pair.first);
129 auto g2 = std::hash<helios::engine::ecs::EntityHandle>{}(pair.second);
130
131 // compute the hash for the pair - shift g2 one position left, then xor with g1.
132 return g1 ^ (g2 << 1);
133 };
134
135 };
136
137 /**
138 * @brief Internal structure holding references to a potential collision candidate.
139 *
140 * Bundles the GameObject pointer with its collision-relevant components for
141 * efficient access during the narrow phase.
142 */
143 struct CollisionCandidate {
144
145 /**
146 * @brief Pointer to the GameObject entity.
147 */
149
150 /**
151 * @brief Pointer to the AABB collider component providing world-space bounds.
152 */
153 AabbColliderComponent* aabbColliderComponent;
154
155 /**
156 * @brief Pointer to the collision component defining layer masks and collision behavior.
157 */
158 CollisionComponent* collisionComponent;
159
160 /**
161 * @brief Pointer to the collision state component for storing collision results.
162 */
163 CollisionStateComponent* collisionStateComponent;
164 };
165
166 /**
167 * @brief Represents a single cell in the spatial partitioning grid.
168 *
169 * Each cell stores references to all collision candidates whose AABBs overlap
170 * this cell's spatial region.
171 */
172 struct GridCell {
173
174 /**
175 * @brief List of collision candidates currently occupying this cell.
176 */
177 std::vector<CollisionCandidate> collisionCandidates;
178
179 /**
180 * @brief Clears all candidates from this cell.
181 */
182 void clear() {
183 collisionCandidates.clear();
184 }
185 };
186
187 /**
188 * @brief Scoped logger instance for structured logging within the current context.
189 */
190 static inline const auto& logger_ = helios::util::log::LogManager::loggerForScope(HELIOS_LOG_SCOPE);
191
192 /**
193 * @brief Set of already-processed collision pairs to avoid duplicate events.
194 *
195 * Stores pairs of EntityHandles in canonical order (smaller handle first) to ensure each
196 * collision pair is processed only once per frame, even when entities span multiple cells.
197 */
198 std::unordered_set<std::pair<helios::engine::ecs::EntityHandle, helios::engine::ecs::EntityHandle>, EntityHandlePairHash> solvedCollisions_;
199
200 /**
201 * @brief Size of each grid cell in world units.
202 */
203 float cellSize_;
204
205 /**
206 * @brief World-space bounds defining the spatial region covered by the grid.
207 */
208 helios::math::aabbf gridBounds_;
209
210 /**
211 * @brief Flat storage for all grid cells, indexed as x + y * cellsX + z * (cellsX * cellsY).
212 */
213 std::vector<GridCell> cells_;
214
215 /**
216 * @brief Number of cells along the X axis.
217 */
218 unsigned int cellsX_;
219
220 /**
221 * @brief Number of cells along the Y axis.
222 */
223 unsigned int cellsY_;
224
225 /**
226 * @brief Number of cells along the Z axis.
227 */
228 unsigned int cellsZ_;
229
230 /**
231 * @brief Helper to keep track of updated cells (with potential collisions candidates) in one pass.
232 */
233 std::vector<size_t> trackedCells_;
234
235 /**
236 * @brief Initializes the grid based on world bounds and cell size.
237 *
238 * Computes the number of cells needed in each dimension and allocates
239 * the cell storage.
240 */
241 void initGrid() {
242
243 helios::math::vec3f size = gridBounds_.size();
244
245 cellsX_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[0] / cellSize_)));
246 cellsY_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[1] / cellSize_)));
247 cellsZ_ = std::max(1u, static_cast<unsigned int>(std::ceil(size[2] / cellSize_)));
248
249 const size_t cellCount = static_cast<size_t>(cellsX_) * cellsY_ * cellsZ_;
250
251 // this would make 100'000'000 * sizeof(GridCell) Bytes
252 // if we have 24 Bytes per GridCell, we end up with 2,4 GB alone for this spatial grid.
253 if (cellCount > 100'000'000) {
254 logger_.warn(std::format("Spatial Grid requested {0} cells", cellCount));
255 throw std::runtime_error("Cell count too high.");
256 }
257
258 trackedCells_.reserve(cellCount);
259 cells_.resize(cellCount);
260 }
261
262 /**
263 * @brief Prepares the grid for a new collision detection pass.
264 *
265 * Clears all collision candidates from every cell and resets the set of
266 * already-solved collision pairs.
267 */
268 inline void prepareCollisionDetection() {
269 for (const auto idx : trackedCells_) {
270 cells_[idx].clear();
271 }
272
273 trackedCells_.clear();
274 solvedCollisions_.clear();
275 }
276
277 /**
278 * @brief Updates the CollisionStateComponent for interacting entities.
279 *
280 * @details Updates the CollisionStateComponent of both entities with collision
281 * information including contact point, collision type, behavior, and reporter status.
282 *
283 * @param candidate First collision candidate (potential event source).
284 * @param match Second collision candidate (collision partner).
285 * @param contact The contact point between the two AABBs.
286 * @param collisionStruct Struct containing collision type and behavior information.
287 * @param updateContext Context for pushing events to the event queue.
288 * @param csc_a Collision state component of the first entity.
289 * @param csc_b Collision state component of the second entity.
290 */
291 inline void postEvent(
292 const helios::engine::ecs::GameObject candidate,
294 const helios::math::vec3f contact,
295 const CollisionStruct collisionStruct,
299 ) const noexcept {
300
301 bool aIsCollisionReporter = collisionStruct.aIsCollisionReporter;
302 bool bIsCollisionReporter = collisionStruct.bIsCollisionReporter;
303 bool isSolidCollision = collisionStruct.isSolidCollision;
304 bool isTriggerCollision = collisionStruct.isTriggerCollision;
305 uint32_t aCollisionLayer = collisionStruct.aCollisionLayer;
306 uint32_t bCollisionLayer = collisionStruct.bCollisionLayer;
307
308 assert((isSolidCollision || isTriggerCollision)
309 && (aIsCollisionReporter || bIsCollisionReporter)
310 && "Preconditions not matched for postEvent.");
311
312 // post the events
313 if (isTriggerCollision || isSolidCollision) {
314 csc_a->setState(
315 candidate,
316 contact, isSolidCollision, isTriggerCollision, collisionStruct.aCollisionBehavior,
317 aIsCollisionReporter, match.entityHandle(), aCollisionLayer, bCollisionLayer
318 );
319 csc_b->setState(
320 match,
321 contact, isSolidCollision, isTriggerCollision, collisionStruct.bCollisionBehavior,
322 bIsCollisionReporter, candidate.entityHandle(),
323 // swap collision layer order
324 bCollisionLayer, aCollisionLayer
325 );
326 }
327
328 }
329
330 /**
331 * @brief Determines the collision type between two entities based on their layer masks.
332 *
333 * Evaluates both solid and trigger collision masks to determine if and how two entities
334 * can collide. Solid collisions are symmetric (both entities must accept collision with
335 * each other), while trigger collisions are asymmetric (either entity can trigger).
336 *
337 * @param cc Collision component of the first entity.
338 * @param matchCC Collision component of the second entity.
339 *
340 * @return CollisionStruct a struct with the requested collision information.
341 */
342 [[nodiscard]] inline CollisionStruct findCollisionType(
343 const CollisionComponent* cc,
344 const CollisionComponent* matchCC
345 ) const noexcept {
346
347 auto isSolidCollision = false;
348 auto isTriggerCollision = false;
349
350 auto aIsCollisionReporter = cc->isCollisionReporter();
351 auto bIsCollisionReporter = matchCC->isCollisionReporter();
352
353 // none of the reports a collision as a pair? skip!
354 if (!aIsCollisionReporter && !bIsCollisionReporter) {
355 return CollisionStruct{};
356 }
357
358 bool aCanSolidCollideWithB = (cc->solidCollisionMask() & matchCC->layerId()) != 0;
359 bool bCanSolidCollideWithA = (matchCC->solidCollisionMask() & cc->layerId()) != 0;
360
361 bool aCanTriggerCollideWithB = (cc->triggerCollisionMask() & matchCC->layerId()) != 0;
362 bool bCanTriggerCollideWithA = (matchCC->triggerCollisionMask() & cc->layerId()) != 0;
363
364 // solid collision is treated symmetric
365 isSolidCollision = aCanSolidCollideWithB && bCanSolidCollideWithA;
366
367 #if HELIOS_DEBUG
368 // detect asymmetric solid collision
369 if (aCanSolidCollideWithB && !bCanSolidCollideWithA) {
370 logger_.warn("Collision Asymmetry detected!");
371 }
372 #endif
373
374 // trigger collision is treated asymmetric
375 isTriggerCollision = aCanTriggerCollideWithB || bCanTriggerCollideWithA;
376
377 return CollisionStruct {
378 isSolidCollision,
379 isTriggerCollision,
380 aIsCollisionReporter,
381 bIsCollisionReporter,
382 isSolidCollision
383 ? cc->solidCollisionBehavior(matchCC->layerId()) : cc->triggerCollisionBehavior(matchCC->layerId()),
384 isSolidCollision
385 ? matchCC->solidCollisionBehavior(cc->layerId()) : matchCC->triggerCollisionBehavior(cc->layerId()),
386 // use the layerId of cc
387 cc->layerId(),
388 matchCC->layerId()
389
390 };
391 }
392
393 public:
394
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 */
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 Updates collision state components for detected collisions.
430 *
431 * @param updateContext The update context providing access to GameWorld and event queue.
432 */
434
435 prepareCollisionDetection();
436
437 for (auto [entity, cc, csc, acc, active] : updateContext.view<
442 >().whereEnabled().exclude<DeadTagComponent>()) {
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 */
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.