< Summary

Information
Class: GridForge.Grids.VoxelGrid
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/VoxelGrid.cs
Line coverage
99%
Covered lines: 282
Uncovered lines: 1
Coverable lines: 283
Total lines: 784
Line coverage: 99.6%
Branch coverage
94%
Covered branches: 179
Total branches: 190
Branch coverage: 94.2%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_SpawnToken()100%11100%
get_GridIndex()100%11100%
get_World()100%11100%
get_ObstacleSyncRoot()100%11100%
get_OccupantSyncRoot()100%11100%
get_Configuration()100%11100%
get_BoundsMin()100%11100%
get_BoundsMax()100%11100%
get_BoundsCenter()100%11100%
get_Width()100%11100%
get_Height()100%11100%
get_Length()100%11100%
get_Size()100%11100%
get_Voxels()100%11100%
get_Neighbors()100%11100%
get_NeighborCount()100%11100%
get_IsConjoined()100%22100%
get_ScanCellSize()100%11100%
get_ScanCells()100%11100%
get_ActiveScanCells()100%11100%
get_IsActive()100%11100%
get_IsOccupied()100%22100%
get_ObstacleCount()100%11100%
get_Version()100%11100%
get_ActiveVoxelSize()100%11100%
get_ActiveVoxelResolution()100%11100%
Initialize(...)75%44100%
Reset()100%2222100%
IncrementVersion()100%22100%
GenerateScanCells()100%66100%
GenerateVoxels()100%66100%
GetNeighborDirection(...)100%11100%
TryAddGridNeighbor(...)100%88100%
TryRemoveGridNeighbor(...)92.85%1414100%
NotifyBoundaryChange(...)100%1212100%
IsOnBoundary(...)100%1010100%
IsInBounds(...)100%1010100%
IsGridOverlapValid(...)100%1212100%
GetAllGridNeighbors()100%1010100%
IsValidVoxelIndex(...)88.88%1818100%
IsFacingBoundaryDirection(...)100%88100%
TryGetVoxelIndex(...)70%101093.75%
IsVoxelAllocated(...)75%44100%
TryGetVoxel(...)75%44100%
TryGetVoxel(...)100%11100%
TryGetVoxel(...)100%22100%
GetScanCellKey(...)100%22100%
GetScanCellKey(...)75%44100%
GetScanCellKey(...)100%66100%
TryGetScanCell(...)50%22100%
TryGetScanCell(...)100%11100%
TryGetScanCell(...)100%22100%
GetActiveScanCells()100%88100%
CeilToGrid(...)100%11100%
FloorToGrid(...)100%11100%
SnapToScanCell(...)100%11100%
GetHashCode()100%11100%

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Grids/VoxelGrid.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Configuration;
 3using GridForge.Spatial;
 4using SwiftCollections;
 5using SwiftCollections.Dimensions;
 6using SwiftCollections.Pool;
 7using System;
 8using System.Collections.Generic;
 9using System.Runtime.CompilerServices;
 10
 11namespace GridForge.Grids;
 12
 13/// <summary>
 14/// Represents a 3D grid structure for spatial organization, managing voxels and scan cells.
 15/// Handles initialization, neighbor relationships, and occupancy tracking.
 16/// </summary>
 17public class VoxelGrid
 18{
 19    #region Fields & Properties
 20
 21    /// <summary>
 22    /// Unique token identifying the grid instance.
 23    /// </summary>
 8042224    public int SpawnToken { get; private set; }
 25
 26    /// <summary>
 27    /// World-local index of the grid within its owning world.
 28    /// </summary>
 8239229    public ushort GridIndex { get; private set; }
 30
 31    /// <summary>
 32    /// The world that owns this grid instance.
 33    /// </summary>
 32448334    public GridWorld? World { get; private set; }
 35
 36    /// <summary>
 37    /// Synchronizes obstacle mutations for this grid.
 38    /// </summary>
 125439    internal object ObstacleSyncRoot { get; } = new object();
 40
 41    /// <summary>
 42    /// Synchronizes occupant mutations for this grid.
 43    /// </summary>
 47744    internal object OccupantSyncRoot { get; } = new object();
 45
 46    /// <inheritdoc cref="GridConfiguration"/>
 49096047    public GridConfiguration Configuration { get; private set; }
 48
 49    /// <summary>
 50    /// Minimum bounds of the grid in world coordinates.
 51    /// </summary>
 24647452    public Vector3d BoundsMin => Configuration.BoundsMin;
 53
 54    /// <summary>
 55    /// Maximum bounds of the grid in world coordinates.
 56    /// </summary>
 594157    public Vector3d BoundsMax => Configuration.BoundsMax;
 58
 59    /// <summary>
 60    /// Center position of the grid in world space.
 61    /// </summary>
 8662    public Vector3d BoundsCenter => Configuration.GridCenter;
 63
 64    /// <summary>
 65    /// Grid width in number of voxels.
 66    /// </summary>
 7899267    public int Width { get; private set; }
 68
 69    /// <summary>
 70    /// Grid height in number of voxels.
 71    /// </summary>
 2537472    public int Height { get; private set; }
 73
 74    /// <summary>
 75    /// Grid length in number of voxels.
 76    /// </summary>
 9140077    public int Length { get; private set; }
 78
 79    /// <summary>
 80    /// Total number of voxels within the grid.
 81    /// </summary>
 37282    public int Size { get; private set; }
 83
 84    /// <summary>
 85    /// The primary 3D collection of voxels managed by this grid.
 86    /// </summary>
 9225287    public SwiftArray3D<Voxel>? Voxels { get; private set; }
 88
 89    /// <summary>
 90    /// Stores <see cref="SpatialDirection"/> as indices of neighboring grids based on their relative positions.
 91    /// </summary>
 92    /// <remarks>
 93    /// Unlike voxel adjacency (which is always 1:1), grids can share multiple neighbors in the same direction.
 94    /// </remarks>
 39395    public SwiftSparseMap<SwiftHashSet<int>>? Neighbors { get; private set; }
 96
 97    /// <summary>
 98    /// Count of currently linked neighboring grids.
 99    /// </summary>
 113100    public byte NeighborCount { get; private set; }
 101
 102    /// <summary>
 103    /// Determines whether this grid has any linked neighbors.
 104    /// </summary>
 33105    public bool IsConjoined => Neighbors != null && NeighborCount > 0;
 106
 107    /// <summary>
 108    /// Size of a scan cell used for spatial partitioning.
 109    /// </summary>
 236643110    public int ScanCellSize => Configuration.ScanCellSize;
 111
 112    /// <summary>
 113    /// Collection of scan cells indexed by their grid-local scan cell key.
 114    /// </summary>
 3760115    public SwiftSparseMap<ScanCell>? ScanCells { get; private set; }
 116
 117    /// <summary>
 118    /// Stores currently active (occupied) scan cells within the grid.
 119    /// </summary>
 1023120    public SwiftHashSet<int>? ActiveScanCells { get; internal set; }
 121
 122    /// <summary>
 123    /// Indicates whether the grid is currently active.
 124    /// </summary>
 7447125    public bool IsActive { get; private set; }
 126
 127    /// <summary>
 128    /// Determines whether the grid is occupied (active and containing occupants).
 129    /// </summary>
 20130    public bool IsOccupied => ActiveScanCells?.Count > 0;
 131
 132    /// <summary>
 133    /// Tracks the number of obstacles currently registered in the grid.
 134    /// </summary>
 2667135    public int ObstacleCount { get; internal set; }
 136
 137    /// <summary>
 138    /// Tracks the version of the grid, incremented when a <see cref="Voxel"/> is modified.
 139    /// </summary>
 85043140    public uint Version { get; private set; }
 141
 142    private int _scanWidth;
 143    private int _scanHeight;
 144    private int _scanLength;
 145    private int _scanLayerSize;
 146
 240935147    private Fixed64 ActiveVoxelSize => World!.VoxelSize;
 30148    private Fixed64 ActiveVoxelResolution => World!.VoxelResolution;
 149
 150    #endregion
 151
 152    #region Initialization & Reset
 153
 154    /// <summary>
 155    /// Initializes the grid with an explicit owning world.
 156    /// </summary>
 157    /// <param name="world">The world that will own this grid.</param>
 158    /// <param name="gridIndex">The unique index of this grid in the world.</param>
 159    /// <param name="configuration">The normalized configuration settings for the grid.</param>
 160    internal void Initialize(GridWorld world, ushort gridIndex, GridConfiguration configuration)
 161    {
 187162        if (IsActive)
 163        {
 1164            GridForgeLogger.Channel.Warn($"Grid at {nameof(gridIndex)} is already active.");
 1165            return;
 166        }
 167
 186168        Version = 1;
 169
 186170        World = world;
 186171        GridIndex = gridIndex;
 172
 186173        Configuration = configuration;
 174
 186175        SpawnToken = GetHashCode();
 176
 177        // +1 to account for inclusive bounds and to ensure that even the smallest grids (1x1x1) remain valid
 186178        Width = ((BoundsMax.x - BoundsMin.x) / ActiveVoxelSize).FloorToInt() + 1;
 186179        Height = ((BoundsMax.y - BoundsMin.y) / ActiveVoxelSize).FloorToInt() + 1;
 186180        Length = ((BoundsMax.z - BoundsMin.z) / ActiveVoxelSize).FloorToInt() + 1;
 186181        Size = Width * Height * Length;
 182
 186183        GenerateScanCells();
 186184        GenerateVoxels();
 185
 186186        IsActive = true;
 186187    }
 188
 189    /// <summary>
 190    /// Resets the grid, clearing all voxels and scan cells.
 191    /// </summary>
 192    internal void Reset()
 193    {
 187194        if (!IsActive)
 1195            return;
 196
 186197        if (Voxels != null)
 198        {
 156660199            foreach (Voxel voxel in Voxels)
 200            {
 78144201                if (voxel == null)
 202                    continue;
 78143203                voxel.Reset(this);
 78143204                Pools.VoxelPool.Release(voxel);
 205            }
 186206            Voxels = null;
 207        }
 208
 209        // Just incase since voxels should have already cleared any registered obstacles
 186210        ObstacleCount = 0;
 211
 186212        if (ScanCells != null)
 213        {
 2882214            foreach (ScanCell cell in ScanCells.Values)
 215            {
 1255216                if (cell == null)
 217                    continue;
 1255218                Pools.ScanCellPool.Release(cell);
 219            }
 220
 186221            Pools.ScanCellMapPool.Release(ScanCells);
 186222            ScanCells = null;
 223        }
 224
 186225        if (ActiveScanCells != null)
 226        {
 24227            SwiftHashSetPool<int>.Shared.Release(ActiveScanCells);
 24228            ActiveScanCells = null;
 229        }
 230
 186231        if (Neighbors != null)
 232        {
 72233            foreach (SwiftHashSet<int> neighbors in Neighbors.Values)
 234            {
 22235                if (neighbors == null)
 236                    continue;
 22237                SwiftHashSetPool<int>.Shared.Release(neighbors);
 238            }
 14239            Neighbors = null;
 14240            NeighborCount = 0;
 241        }
 242
 186243        Configuration = default;
 186244        World = null;
 245
 186246        SpawnToken = 0;
 186247        Version = 0;
 248
 186249        GridIndex = ushort.MaxValue;
 250
 186251        Width = 0;
 186252        Height = 0;
 186253        Length = 0;
 186254        Size = 0;
 186255        _scanWidth = 0;
 186256        _scanHeight = 0;
 186257        _scanLength = 0;
 186258        _scanLayerSize = 0;
 259
 186260        IsActive = false;
 186261    }
 262
 263    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 264    internal uint IncrementVersion()
 265    {
 1274266        Version = Version == uint.MaxValue ? 1u : Version + 1u;
 1274267        return Version;
 268    }
 269
 270    #endregion
 271
 272    #region Grid Construction
 273
 274    /// <summary>
 275    /// Generates the scan cell overlay for the grid.
 276    /// </summary>
 277    private void GenerateScanCells()
 278    {
 186279        _scanWidth = ((Width - 1) / ScanCellSize) + 1;
 186280        _scanHeight = ((Height - 1) / ScanCellSize) + 1;
 186281        _scanLength = ((Length - 1) / ScanCellSize) + 1;
 186282        _scanLayerSize = _scanWidth * _scanHeight;
 283
 186284        ScanCells = Pools.ScanCellMapPool.Rent();
 285
 1070286        for (int x = 0; x < _scanWidth; x++)
 287        {
 1396288            for (int y = 0; y < _scanHeight; y++)
 289            {
 3208290                for (int z = 0; z < _scanLength; z++)
 291                {
 1255292                    int cellKey = x + y * _scanWidth + z * _scanLayerSize;
 293
 1255294                    ScanCell scanCell = Pools.ScanCellPool.Rent();
 1255295                    scanCell.Initialize(World!, GridIndex, cellKey);
 1255296                    ScanCells.Add(cellKey, scanCell);
 297                }
 298            }
 299        }
 186300    }
 301
 302    /// <summary>
 303    /// Generates the 3D grid structure based on the configured settings.
 304    /// </summary>
 305    private void GenerateVoxels()
 306    {
 307#if DEBUG
 308        long startMem = GC.GetTotalMemory(true);
 309#endif
 310
 186311        Voxels = new SwiftArray3D<Voxel>(Width, Height, Length);
 312
 3976313        for (int x = 0; x < Width; x++)
 314        {
 8068315            for (int y = 0; y < Height; y++)
 316            {
 160752317                for (int z = 0; z < Length; z++)
 318                {
 78144319                    Vector3d position = new(
 78144320                            BoundsMin.x + x * ActiveVoxelSize,
 78144321                            BoundsMin.y + y * ActiveVoxelSize,
 78144322                            BoundsMin.z + z * ActiveVoxelSize
 78144323                        );
 324
 325                    // Rent a voxel from the object pool and initialize it
 78144326                    Voxel voxel = Pools.VoxelPool.Rent();
 327
 78144328                    VoxelIndex index = new(x, y, z);
 78144329                    bool isBoundaryVoxel = IsOnBoundary(index);
 78144330                    int scanCellKey = GetScanCellKey(index);
 331
 78144332                    voxel.Initialize(
 78144333                        new WorldVoxelIndex(World!.SpawnToken, GridIndex, SpawnToken, index),
 78144334                        position,
 78144335                        scanCellKey,
 78144336                        isBoundaryVoxel,
 78144337                        Version);
 338
 78144339                    Voxels[x, y, z] = voxel;
 340                }
 341            }
 342        }
 343
 344#if DEBUG
 345        long usedMem = GC.GetTotalMemory(true) - startMem;
 346        GridForgeLogger.Channel.Info($"Grid generated using {usedMem} Bytes.");
 347#endif
 186348    }
 349
 350    #endregion
 351
 352    #region Boundary Management
 353
 354    /// <summary>
 355    /// Determines the relative direction of a neighboring grid based on its center offset.
 356    /// </summary>
 357    /// <param name="a">The first grid.</param>
 358    /// <param name="b">The second grid.</param>
 359    /// <returns>The direction from grid 'a' to grid 'b'.</returns>
 360    public static SpatialDirection GetNeighborDirection(VoxelGrid a, VoxelGrid b)
 361    {
 43362        Vector3d centerDifference = b.BoundsCenter - a.BoundsCenter;
 43363        (int x, int y, int z) gridOffset = (
 43364                centerDifference.x.Sign(),
 43365                centerDifference.y.Sign(),
 43366                centerDifference.z.Sign()
 43367            );
 43368        return GridDirectionUtility.GetNeighborDirectionFromOffset(gridOffset);
 369    }
 370
 371    /// <summary>
 372    /// Adds a neighboring grid and updates relationships.
 373    /// </summary>
 374    /// <param name="neighborGrid">The neighboring grid to add.</param>
 375    internal bool TryAddGridNeighbor(VoxelGrid neighborGrid)
 376    {
 31377        SpatialDirection neighborDirection = GetNeighborDirection(this, neighborGrid);
 31378        int neightborIndex = (int)neighborDirection;
 379
 31380        if (neightborIndex == -1)
 1381            return false;
 382
 383        // Ensure the neighbor array is allocated and store the new neighbor
 30384        Neighbors ??= new SwiftSparseMap<SwiftHashSet<int>>();
 30385        if (!Neighbors.TryGetValue(neightborIndex, out SwiftHashSet<int> neighborSet))
 386        {
 29387            neighborSet = SwiftHashSetPool<int>.Shared.Rent();
 29388            Neighbors.Add(neightborIndex, neighborSet);
 389        }
 390
 30391        if (!neighborSet.Add(neighborGrid.GridIndex))
 1392            return false;
 393
 29394        NeighborCount++;
 29395        IncrementVersion();
 396
 397        // Notify grid voxels that a new neighbor has been added
 29398        NotifyBoundaryChange(neighborDirection);
 399
 29400        return true;
 401    }
 402
 403    /// <summary>
 404    /// Removes a neighboring grid relationship.
 405    /// </summary>
 406    /// <param name="neighborGrid">The neighboring grid to remove.</param>
 407    internal bool TryRemoveGridNeighbor(VoxelGrid neighborGrid)
 408    {
 10409        if (!IsConjoined)
 1410            return false;
 411
 9412        SpatialDirection neighborDirection = GetNeighborDirection(this, neighborGrid);
 9413        var neighborIndex = (int)neighborDirection;
 9414        if (neighborIndex == -1 || !Neighbors!.TryGetValue(neighborIndex, out SwiftHashSet<int> neighborSet))
 1415            return false;
 416
 8417        if (!neighborSet.Remove(neighborGrid.GridIndex))
 1418            return false;
 419
 7420        if (Neighbors[neighborIndex].Count == 0)
 421        {
 7422            GridForgeLogger.Channel.Info($"Releasing unused neighbor collection.");
 7423            SwiftHashSetPool<int>.Shared.Release(Neighbors[neighborIndex]);
 7424            Neighbors.Remove(neighborIndex);
 425        }
 426
 7427        if (--NeighborCount == 0)
 4428            Neighbors = null;
 429
 7430        IncrementVersion();
 431
 7432        NotifyBoundaryChange(neighborDirection); // Notify voxels of the removed neighbor
 433
 7434        return true;
 435    }
 436
 437    /// <summary>
 438    /// Notifies only the relevant boundary voxels when a neighboring grid is added or removed.
 439    /// Instead of looping through all voxels, it targets specific boundary rows or columns.
 440    /// </summary>
 441    /// <param name="direction">The direction of the affected boundary.</param>
 442    public void NotifyBoundaryChange(SpatialDirection direction)
 443    {
 39444        SwiftThrowHelper.ThrowIfNull(Voxels, nameof(Voxels));
 445
 39446        int directionIndex = (int)direction;
 39447        if (directionIndex < 0 || directionIndex >= SpatialAwareness.DirectionOffsets.Length)
 2448            return;
 449
 37450        (int x, int y, int z) offset = SpatialAwareness.DirectionOffsets[directionIndex];
 451
 37452        (int xStart, int xEnd) = SpatialAwareness.GetBoundaryRange(offset.x, Width);
 37453        (int yStart, int yEnd) = SpatialAwareness.GetBoundaryRange(offset.y, Height);
 37454        (int zStart, int zEnd) = SpatialAwareness.GetBoundaryRange(offset.z, Length);
 455
 252456        for (int x = xStart; x <= xEnd; x++)
 457        {
 356458            for (int y = yStart; y <= yEnd; y++)
 459            {
 468460                for (int z = zStart; z <= zEnd; z++)
 145461                    Voxels[x, y, z]?.InvalidateNeighborCache();
 462            }
 463        }
 37464    }
 465
 466    #endregion
 467
 468    #region Grid Queries
 469
 470    /// <summary>
 471    /// Determines if a voxel coordinate is at the boundary of the grid.
 472    /// Used to determine if a voxel should update when a neighboring grid is added/removed.
 473    /// </summary>
 474    public bool IsOnBoundary(VoxelIndex coord)
 475    {
 78144476        return coord.x == 0 || coord.x == Width - 1
 78144477            || coord.y == 0 || coord.y == Height - 1
 78144478            || coord.z == 0 || coord.z == Length - 1;
 479    }
 480
 481    /// <summary>
 482    /// Checks whether a given position falls within the grid bounds.
 483    /// </summary>
 484    public bool IsInBounds(Vector3d target)
 485    {
 1745486        return BoundsMin.x <= target.x && target.x <= BoundsMax.x
 1745487            && BoundsMin.y <= target.y && target.y <= BoundsMax.y
 1745488            && BoundsMin.z <= target.z && target.z <= BoundsMax.z;
 489    }
 490
 491    /// <summary>
 492    /// Checks if two grids are overlapping within a given tolerance threshold.
 493    /// This is used to determine if grids should be linked as neighbors.
 494    /// </summary>
 495    /// <param name="a">The first grid.</param>
 496    /// <param name="b">The second grid.</param>
 497    /// <param name="tolerance">Optional tolerance to account for minor floating-point errors.</param>
 498    /// <returns>True if the grids overlap within the tolerance, otherwise false.</returns>
 499    public static bool IsGridOverlapValid(VoxelGrid a, VoxelGrid b, Fixed64 tolerance = default)
 500    {
 31501        tolerance = tolerance == default ? a.ActiveVoxelResolution : tolerance;
 502
 31503        return a.BoundsMax.x >= b.BoundsMin.x - tolerance
 31504            && a.BoundsMin.x <= b.BoundsMax.x + tolerance
 31505            && a.BoundsMax.y >= b.BoundsMin.y - tolerance
 31506            && a.BoundsMin.y <= b.BoundsMax.y + tolerance
 31507            && a.BoundsMax.z >= b.BoundsMin.z - tolerance
 31508            && a.BoundsMin.z <= b.BoundsMax.z + tolerance;
 509    }
 510
 511    /// <summary>
 512    /// Retrieves all neighboring grids connected to this grid.
 513    /// </summary>
 514    /// <returns>An enumeration of all neighboring grids.</returns>
 515    public IEnumerable<VoxelGrid> GetAllGridNeighbors()
 516    {
 3517        if (!IsConjoined)
 1518            yield break;
 519
 2520        var values = Neighbors!.DenseValues;
 2521        int count = Neighbors.Count;
 522
 14523        for (int i = 0; i < count; i++)
 524        {
 5525            SwiftHashSet<int> neighborSet = values[i];
 20526            foreach (int neighborIndex in neighborSet)
 527            {
 5528                if (World != null && World.TryGetGrid(neighborIndex, out VoxelGrid? neighborGrid))
 529                {
 5530                    yield return neighborGrid!;
 531                }
 532            }
 533        }
 2534    }
 535
 536    /// <summary>
 537    /// Determines whether the given voxel coordinates are within the valid range of the grid.
 538    /// </summary>
 539    public bool IsValidVoxelIndex(int x, int y, int z)
 540    {
 3370541        if (!IsActive)
 542        {
 2543            GridForgeLogger.Channel.Warn($"This Grid is not currently active.");
 2544            return false;
 545        }
 546
 3368547        bool result = x >= 0 && x < Voxels!.Width
 3368548                && y >= 0 && y < Voxels!.Height
 3368549                && z >= 0 && z < Voxels!.Depth;
 550
 3368551        if (!result)
 193552            GridForgeLogger.Channel.Info($"The coordinate {(x, y, z)} is not valid for this grid.");
 553
 3368554        return result;
 555    }
 556
 557    /// <summary>
 558    /// Determines if a voxel is facing the boundary of the grid in a specific direction.
 559    /// Used to notify voxels when adjacent grids are added/removed.
 560    /// </summary>
 561    public bool IsFacingBoundaryDirection(VoxelIndex voxelIndex, SpatialDirection direction)
 562    {
 55563        int directionIndex = (int)direction;
 55564        if (directionIndex < 0 || directionIndex >= SpatialAwareness.DirectionOffsets.Length)
 1565            return false;
 566
 54567        (int x, int y, int z) = SpatialAwareness.DirectionOffsets[directionIndex];
 568
 54569        return SpatialAwareness.IsAxisFacingBoundary(voxelIndex.x, x, Width)
 54570            && SpatialAwareness.IsAxisFacingBoundary(voxelIndex.y, y, Height)
 54571            && SpatialAwareness.IsAxisFacingBoundary(voxelIndex.z, z, Length);
 572    }
 573
 574    /// <summary>
 575    /// Converts a world position to voxel index within the grid.
 576    /// </summary>
 577    public bool TryGetVoxelIndex(Vector3d position, out VoxelIndex result)
 578    {
 1500579        result = default;
 580
 1500581        if (!IsActive)
 582        {
 1583            GridForgeLogger.Channel.Warn($"This Grid is not currently allocated.");
 1584            return false;
 585        }
 586
 1499587        if (!IsInBounds(position))
 588        {
 52589            GridForgeLogger.Channel.Warn($"Position does not fall in the bounds of this grid");
 52590            return false;
 591        }
 592
 593        // Convert world position to grid indices by subtracting the minimum bound
 594        // and dividing by the voxel size to get a zero-based index
 1447595        (int x, int y, int z) = (
 1447596            ((position.x - BoundsMin.x) / ActiveVoxelSize).FloorToInt(),
 1447597            ((position.y - BoundsMin.y) / ActiveVoxelSize).FloorToInt(),
 1447598            ((position.z - BoundsMin.z) / ActiveVoxelSize).FloorToInt()
 1447599        );
 600
 1447601        if (!IsValidVoxelIndex(x, y, z))
 0602            return false;
 603
 1447604        result = new VoxelIndex(x, y, z);
 1447605        return true;
 606    }
 607
 608    /// <summary>
 609    /// Checks if a voxel at the given coordinates is allocated within the grid.
 610    /// </summary>
 611    public bool IsVoxelAllocated(int x, int y, int z) =>
 1923612        IsValidVoxelIndex(x, y, z) && Voxels![x, y, z]?.IsAllocated == true;
 613
 614    /// <summary>
 615    /// Retrieves the <see cref="Voxel"/> at the specified coordinates, if allocated.
 616    /// </summary>
 617    public bool TryGetVoxel(int x, int y, int z, out Voxel? result)
 618    {
 1919619        result = null;
 620
 1919621        if (!IsVoxelAllocated(x, y, z))
 622        {
 194623            GridForgeLogger.Channel.Warn($"Voxel at coorinate {(x, y, z)} is has not been allocated to the grid.");
 194624            return false;
 625        }
 626
 1725627        result = Voxels![x, y, z];
 1725628        return true;
 629    }
 630
 631    /// <summary>
 632    /// Retrieves a grid voxel from a given coordinate.
 633    /// </summary>
 634    public bool TryGetVoxel(VoxelIndex voxelIndex, out Voxel? result)
 635    {
 491636        return TryGetVoxel(voxelIndex.x, voxelIndex.y, voxelIndex.z, out result);
 637    }
 638
 639    /// <summary>
 640    /// Retrieve <see cref="Voxel"/> from world <see cref="Vector3d"/> points
 641    /// </summary>
 642    /// <returns><see cref="Voxel"/> at the given position or null if the position is not valid.</returns>
 643    public bool TryGetVoxel(Vector3d position, out Voxel? result)
 644    {
 1476645        result = null;
 1476646        return TryGetVoxelIndex(position, out VoxelIndex coordinate)
 1476647            && TryGetVoxel(coordinate.x, coordinate.y, coordinate.z, out result);
 648    }
 649
 650    /// <summary>
 651    /// Computes the scan cell key for a given world position.
 652    /// </summary>
 653    public int GetScanCellKey(Vector3d position)
 654    {
 17655        if (!TryGetVoxelIndex(position, out VoxelIndex voxelIndex))
 1656            return -1;
 657
 16658        return GetScanCellKey(voxelIndex);
 659    }
 660
 661    /// <summary>
 662    /// Calculates the spatial cell index for a given position.
 663    /// </summary>
 664    public int GetScanCellKey(VoxelIndex voxelIndex)
 665    {
 78161666        (int x, int y, int z) = (
 78161667                voxelIndex.x / ScanCellSize,
 78161668                voxelIndex.y / ScanCellSize,
 78161669                voxelIndex.z / ScanCellSize
 78161670            );
 671
 78161672        int scanCellKey = GetScanCellKey(x, y, z);
 78161673        if (scanCellKey == -1)
 674        {
 1675            GridForgeLogger.Channel.Warn($"Position {voxelIndex} is not in the bounds for this grids Scan Cell overlay."
 1676            return -1;
 677        }
 678
 78160679        return scanCellKey;
 680    }
 681
 682    /// <summary>
 683    /// Calculates a unique scan cell key from grid-local scan cell coordinates.
 684    /// </summary>
 685    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 686    internal int GetScanCellKey(int x, int y, int z)
 687    {
 79234688        if ((uint)x >= (uint)_scanWidth
 79234689            || (uint)y >= (uint)_scanHeight
 79234690            || (uint)z >= (uint)_scanLength)
 691        {
 2692            return -1;
 693        }
 694
 79232695        return x + y * _scanWidth + z * _scanLayerSize;
 696    }
 697
 698    /// <summary>
 699    /// Retrieves a scan cell from the grid using its key.
 700    /// </summary>
 701    public bool TryGetScanCell(int key, out ScanCell? outScanCell)
 702    {
 1571703        outScanCell = null;
 1571704        return ScanCells?.TryGetValue(key, out outScanCell) == true;
 705    }
 706
 707    /// <summary>
 708    /// Retrieves the scan cell corresponding to a given world position.
 709    /// </summary>
 710    public bool TryGetScanCell(Vector3d position, out ScanCell? outScanCell)
 711    {
 12712        int key = GetScanCellKey(position);
 12713        return TryGetScanCell(key, out outScanCell);
 714    }
 715
 716    /// <summary>
 717    /// Retrieves the scan cell associated with the given voxel index.
 718    /// </summary>
 719    public bool TryGetScanCell(VoxelIndex voxelIndex, out ScanCell? outScanCell)
 720    {
 2721        outScanCell = null;
 2722        return TryGetVoxel(voxelIndex, out Voxel? voxel)
 2723            && TryGetScanCell(voxel!.ScanCellKey, out outScanCell);
 724    }
 725
 726    /// <summary>
 727    /// Enumerates all currently active scan cells within the grid.
 728    /// </summary>
 729    public IEnumerable<ScanCell> GetActiveScanCells()
 730    {
 4731        if (!IsActive || !IsOccupied)
 3732            yield break;
 733
 6734        foreach (int activeCellKey in ActiveScanCells!)
 735        {
 2736            if (ScanCells!.TryGetValue(activeCellKey, out ScanCell scanCell))
 2737                yield return scanCell;
 738        }
 1739    }
 740
 741    /// <summary>
 742    /// Helper function to ceil snap a <see cref="Vector3d"/> to this grid's voxel size, ensuring it stays within grid b
 743    /// </summary>
 744    public Vector3d CeilToGrid(Vector3d position)
 745    {
 1746        Fixed64 voxelSize = ActiveVoxelSize;
 1747        return new Vector3d(
 1748            FixedMath.Clamp(((position.x - BoundsMin.x) / voxelSize).CeilToInt() * voxelSize + BoundsMin.x, BoundsMin.x,
 1749            FixedMath.Clamp(((position.y - BoundsMin.y) / voxelSize).CeilToInt() * voxelSize + BoundsMin.y, BoundsMin.y,
 1750            FixedMath.Clamp(((position.z - BoundsMin.z) / voxelSize).CeilToInt() * voxelSize + BoundsMin.z, BoundsMin.z,
 1751        );
 752    }
 753
 754    /// <summary>
 755    /// Helper function to floor snap a <see cref="Vector3d"/> to this grid's voxel size, ensuring it stays within grid 
 756    /// </summary>
 757    public Vector3d FloorToGrid(Vector3d position)
 758    {
 1759        Fixed64 voxelSize = ActiveVoxelSize;
 1760        return new Vector3d(
 1761            FixedMath.Clamp(((position.x - BoundsMin.x) / voxelSize).FloorToInt() * voxelSize + BoundsMin.x, BoundsMin.x
 1762            FixedMath.Clamp(((position.y - BoundsMin.y) / voxelSize).FloorToInt() * voxelSize + BoundsMin.y, BoundsMin.y
 1763            FixedMath.Clamp(((position.z - BoundsMin.z) / voxelSize).FloorToInt() * voxelSize + BoundsMin.z, BoundsMin.z
 1764        );
 765    }
 766
 767    /// <summary>
 768    /// Snaps a given position to the closest scan cell in the grid
 769    /// </summary>
 770    public (int x, int y, int z) SnapToScanCell(Vector3d position)
 771    {
 534772        return (
 534773                (int)((position.x - BoundsMin.x) / ActiveVoxelSize) / ScanCellSize,
 534774                (int)((position.y - BoundsMin.y) / ActiveVoxelSize) / ScanCellSize,
 534775                (int)((position.z - BoundsMin.z) / ActiveVoxelSize) / ScanCellSize
 534776            );
 777    }
 778
 779    /// <inheritdoc/>
 780    public override int GetHashCode() =>
 535781        SwiftHashTools.CombineHashCodes(GridIndex, BoundsMin, BoundsMax);
 782
 783    #endregion
 784}

Methods/Properties

get_SpawnToken()
get_GridIndex()
get_World()
get_ObstacleSyncRoot()
get_OccupantSyncRoot()
get_Configuration()
get_BoundsMin()
get_BoundsMax()
get_BoundsCenter()
get_Width()
get_Height()
get_Length()
get_Size()
get_Voxels()
get_Neighbors()
get_NeighborCount()
get_IsConjoined()
get_ScanCellSize()
get_ScanCells()
get_ActiveScanCells()
get_IsActive()
get_IsOccupied()
get_ObstacleCount()
get_Version()
get_ActiveVoxelSize()
get_ActiveVoxelResolution()
Initialize(GridForge.Grids.GridWorld,System.UInt16,GridForge.Configuration.GridConfiguration)
Reset()
IncrementVersion()
GenerateScanCells()
GenerateVoxels()
GetNeighborDirection(GridForge.Grids.VoxelGrid,GridForge.Grids.VoxelGrid)
TryAddGridNeighbor(GridForge.Grids.VoxelGrid)
TryRemoveGridNeighbor(GridForge.Grids.VoxelGrid)
NotifyBoundaryChange(GridForge.Spatial.SpatialDirection)
IsOnBoundary(GridForge.Spatial.VoxelIndex)
IsInBounds(FixedMathSharp.Vector3d)
IsGridOverlapValid(GridForge.Grids.VoxelGrid,GridForge.Grids.VoxelGrid,FixedMathSharp.Fixed64)
GetAllGridNeighbors()
IsValidVoxelIndex(System.Int32,System.Int32,System.Int32)
IsFacingBoundaryDirection(GridForge.Spatial.VoxelIndex,GridForge.Spatial.SpatialDirection)
TryGetVoxelIndex(FixedMathSharp.Vector3d,GridForge.Spatial.VoxelIndex&)
IsVoxelAllocated(System.Int32,System.Int32,System.Int32)
TryGetVoxel(System.Int32,System.Int32,System.Int32,GridForge.Grids.Voxel&)
TryGetVoxel(GridForge.Spatial.VoxelIndex,GridForge.Grids.Voxel&)
TryGetVoxel(FixedMathSharp.Vector3d,GridForge.Grids.Voxel&)
GetScanCellKey(FixedMathSharp.Vector3d)
GetScanCellKey(GridForge.Spatial.VoxelIndex)
GetScanCellKey(System.Int32,System.Int32,System.Int32)
TryGetScanCell(System.Int32,GridForge.Grids.ScanCell&)
TryGetScanCell(FixedMathSharp.Vector3d,GridForge.Grids.ScanCell&)
TryGetScanCell(GridForge.Spatial.VoxelIndex,GridForge.Grids.ScanCell&)
GetActiveScanCells()
CeilToGrid(FixedMathSharp.Vector3d)
FloorToGrid(FixedMathSharp.Vector3d)
SnapToScanCell(FixedMathSharp.Vector3d)
GetHashCode()