< Summary

Information
Class: GridForge.Grids.GridWorld
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Managers/GridWorld.cs
Line coverage
78%
Covered lines: 231
Uncovered lines: 64
Coverable lines: 295
Total lines: 790
Line coverage: 78.3%
Branch coverage
59%
Covered branches: 115
Total branches: 192
Branch coverage: 59.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
get_VoxelSize()100%11100%
get_SpatialGridCellSize()100%11100%
get_VoxelResolution()100%11100%
get_ActiveGrids()100%11100%
get_BoundsTracker()100%11100%
get_SpatialGridHash()100%11100%
get_SpawnToken()100%11100%
get_Version()100%11100%
get_IsActive()100%11100%
.ctor(...)100%11100%
add_OnActiveGridAdded(...)100%11100%
remove_OnActiveGridAdded(...)100%11100%
add_OnActiveGridRemoved(...)100%11100%
remove_OnActiveGridRemoved(...)100%11100%
add_OnActiveGridChange(...)100%210%
remove_OnActiveGridChange(...)100%210%
add_OnReset(...)100%11100%
remove_OnReset(...)100%11100%
Reset(...)64.28%151482.14%
Dispose()100%11100%
TryAddGrid(...)62.5%272482.5%
TryRemoveGrid(...)90.9%222296.42%
TryGetGrid(...)50%171266.66%
TryGetGrid(...)77.77%191886.66%
TryGetGrid(...)87.5%88100%
TryGetGridAndVoxel(...)75%44100%
TryGetGridAndVoxel(...)75%44100%
TryGetVoxel(...)75%44100%
TryGetVoxel(...)25%44100%
NormalizeConfiguration(...)100%11100%
IncrementGridVersion(...)0%7280%
GetSpatialGridCells()100%66100%
GetSpatialGridCellBounds(...)50%66100%
FindOverlappingGrids(...)0%272160%
GetSpatialGridKey(...)100%11100%
CeilToVoxelSize(...)100%11100%
FloorToVoxelSize(...)100%11100%
SnapBoundsToVoxelSize(...)100%1010100%
NotifyActiveGridChange(...)50%4475%
ResolveVoxelSize(...)50%8660%
ResolveSpatialGridCellSize(...)25%6450%
CreateGridEventInfo(...)100%11100%
NotifyActiveGridAdded(...)66.66%7672.72%
NotifyActiveGridRemoved(...)66.66%7672.72%
NotifyActiveGridChange(...)16.66%20627.27%
SnapToSpatialGrid(...)100%11100%

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Grids/Managers/GridWorld.cs

#LineLine coverage
 1using FixedMathSharp;
 2using GridForge.Configuration;
 3using GridForge.Spatial;
 4using SwiftCollections;
 5using System;
 6using System.Collections.Generic;
 7using System.Threading;
 8
 9namespace GridForge.Grids;
 10
 11/// <summary>
 12/// Owns the mutable runtime state for one GridForge world.
 13/// </summary>
 14public sealed class GridWorld : IDisposable
 15{
 16    #region Constants
 17
 18    /// <summary>
 19    /// Maximum number of grids that can be managed within a world.
 20    /// </summary>
 21    public const ushort MaxGrids = ushort.MaxValue - 1;
 22
 23    /// <summary>
 24    /// The default size of each grid voxel in world units.
 25    /// </summary>
 126    public static readonly Fixed64 DefaultVoxelSize = Fixed64.One;
 27
 28    /// <summary>
 29    /// The default size of a spatial hash cell used for grid lookup.
 30    /// </summary>
 31    public const int DefaultSpatialGridCellSize = 50;
 32
 33    #endregion
 34
 35    #region Properties
 36
 37    /// <summary>
 38    /// The size of each grid voxel in world units for this world.
 39    /// </summary>
 24956240    public Fixed64 VoxelSize { get; }
 41
 42    /// <summary>
 43    /// The size of a spatial hash cell used for grid lookup in this world.
 44    /// </summary>
 434445    public int SpatialGridCellSize { get; }
 46
 47    /// <summary>
 48    /// Resolution for snapping or searching within the grid.
 49    /// </summary>
 3050    public Fixed64 VoxelResolution => VoxelSize * Fixed64.Half;
 51
 52    /// <summary>
 53    /// Collection of all active grids owned by this world.
 54    /// </summary>
 295655    public SwiftBucket<VoxelGrid> ActiveGrids { get; }
 56
 57    /// <summary>
 58    /// Dictionary mapping exact bounds keys to grid indices to prevent duplicate grids.
 59    /// </summary>
 55560    public SwiftDictionary<BoundsKey, ushort> BoundsTracker { get; }
 61
 62    /// <summary>
 63    /// Dictionary mapping spatial hash keys to grid indices for fast lookups.
 64    /// </summary>
 175065    public SwiftDictionary<int, SwiftHashSet<ushort>> SpatialGridHash { get; }
 66
 67    /// <summary>
 68    /// Runtime token identifying this specific world instance.
 69    /// </summary>
 8044270    public int SpawnToken { get; private set; }
 71
 72    /// <summary>
 73    /// The current version of the world, incremented on major changes.
 74    /// </summary>
 57075    public uint Version { get; private set; }
 76
 77    /// <summary>
 78    /// Indicates whether this world is currently active.
 79    /// </summary>
 177980    public bool IsActive { get; private set; }
 81
 18282    private readonly ReaderWriterLockSlim _gridLock = new();
 83
 84    #endregion
 85
 86    #region Events
 87
 88    private Action<GridEventInfo>? _onActiveGridAdded;
 89    private Action<GridEventInfo>? _onActiveGridRemoved;
 90    private Action<GridEventInfo>? _onActiveGridChange;
 91    private Action? _onReset;
 92
 93    /// <summary>
 94    /// Event triggered when a new grid is added to this world.
 95    /// </summary>
 96    public event Action<GridEventInfo> OnActiveGridAdded
 97    {
 13598        add => _onActiveGridAdded += value;
 13599        remove => _onActiveGridAdded -= value;
 100    }
 101
 102    /// <summary>
 103    /// Event triggered when a grid is removed from this world.
 104    /// </summary>
 105    public event Action<GridEventInfo> OnActiveGridRemoved
 106    {
 135107        add => _onActiveGridRemoved += value;
 135108        remove => _onActiveGridRemoved -= value;
 109    }
 110
 111    /// <summary>
 112    /// Event triggered when a grid in this world undergoes a significant change.
 113    /// </summary>
 114    public event Action<GridEventInfo> OnActiveGridChange
 115    {
 0116        add => _onActiveGridChange += value;
 0117        remove => _onActiveGridChange -= value;
 118    }
 119
 120    /// <summary>
 121    /// Event triggered when this world is reset.
 122    /// </summary>
 123    public event Action OnReset
 124    {
 135125        add => _onReset += value;
 135126        remove => _onReset -= value;
 127    }
 128
 129    #endregion
 130
 131    /// <summary>
 132    /// Initializes a new world with the supplied voxel and spatial-hash settings.
 133    /// </summary>
 134    /// <param name="voxelSize">Optional voxel size for this world.</param>
 135    /// <param name="spatialGridCellSize">Optional spatial hash cell size for this world.</param>
 182136    public GridWorld(
 182137        Fixed64? voxelSize = null,
 182138        int spatialGridCellSize = DefaultSpatialGridCellSize)
 139    {
 182140        ActiveGrids = new SwiftBucket<VoxelGrid>();
 182141        BoundsTracker = new SwiftDictionary<BoundsKey, ushort>();
 182142        SpatialGridHash = new SwiftDictionary<int, SwiftHashSet<ushort>>();
 143
 182144        VoxelSize = ResolveVoxelSize(voxelSize);
 182145        SpatialGridCellSize = ResolveSpatialGridCellSize(spatialGridCellSize);
 182146        SpawnToken = GetHashCode();
 182147        Version = 1;
 182148        IsActive = true;
 182149    }
 150
 151    #region Lifecycle
 152
 153    /// <summary>
 154    /// Clears all grids and spatial data owned by this world.
 155    /// </summary>
 156    /// <param name="deactivate">If true, marks the world inactive and releases its event handlers.</param>
 157    public void Reset(bool deactivate = false)
 158    {
 179159        if (!IsActive)
 160        {
 0161            GridForgeLogger.Channel.Warn($"Grid world not active. Cannot reset an inactive world.");
 0162            return;
 163        }
 164
 179165        Action? resetHandlers = _onReset;
 179166        if (resetHandlers != null)
 167        {
 16168            var handlerDelegates = resetHandlers.GetInvocationList();
 282169            for (int i = 0; i < handlerDelegates.Length; i++)
 170            {
 171                try
 172                {
 125173                    ((Action)handlerDelegates[i])();
 125174                }
 0175                catch (Exception ex)
 176                {
 0177                    GridForgeLogger.Channel.Error($"World reset notification error: {ex.Message}");
 0178                }
 179            }
 180        }
 181
 698182        foreach (VoxelGrid grid in ActiveGrids)
 170183            Pools.GridPool.Release(grid);
 184
 179185        ActiveGrids.Clear();
 179186        BoundsTracker.Clear();
 179187        SpatialGridHash.Clear();
 179188        GridOccupantManager.ClearTrackedOccupancies(this);
 189
 179190        if (!deactivate)
 1191            return;
 192
 178193        GridOccupantManager.ReleaseTrackedOccupancies(this);
 178194        IsActive = false;
 178195        SpawnToken = 0;
 178196        _onActiveGridAdded = null;
 178197        _onActiveGridRemoved = null;
 178198        _onActiveGridChange = null;
 178199        _onReset = null;
 178200    }
 201
 202    /// <inheritdoc />
 203    public void Dispose()
 204    {
 178205        Reset(deactivate: true);
 178206        _gridLock.Dispose();
 178207        GC.SuppressFinalize(this);
 178208    }
 209
 210    #endregion
 211
 212    #region Grid Management
 213
 214    /// <summary>
 215    /// Adds a new grid to this world and registers it in the spatial hash.
 216    /// </summary>
 217    /// <param name="configuration">The grid configuration to normalize and register.</param>
 218    /// <param name="allocatedIndex">The allocated world-local grid slot on success.</param>
 219    /// <returns>True if the grid was added; otherwise false.</returns>
 220    public bool TryAddGrid(GridConfiguration configuration, out ushort allocatedIndex)
 221    {
 182222        allocatedIndex = ushort.MaxValue;
 223
 182224        if (!IsActive)
 225        {
 0226            GridForgeLogger.Channel.Error($"Grid world not active. Cannot add grids to an inactive world.");
 0227            return false;
 228        }
 229
 182230        if ((uint)ActiveGrids.Count > MaxGrids)
 231        {
 0232            GridForgeLogger.Channel.Warn($"No more grids can be added at this time.");
 0233            return false;
 234        }
 235
 182236        GridConfiguration normalizedConfiguration = NormalizeConfiguration(configuration);
 182237        BoundsKey boundsKey = normalizedConfiguration.ToBoundsKey();
 238
 182239        _gridLock.EnterReadLock();
 240        try
 241        {
 182242            if (BoundsTracker.TryGetValue(boundsKey, out allocatedIndex))
 243            {
 0244                GridForgeLogger.Channel.Warn($"A grid with these bounds has already been allocated.");
 0245                return false;
 246            }
 182247        }
 248        finally
 249        {
 182250            _gridLock.ExitReadLock();
 182251        }
 252
 182253        VoxelGrid newGrid = Pools.GridPool.Rent();
 182254        GridEventInfo addedGridInfo = default;
 255
 182256        _gridLock.EnterWriteLock();
 257        try
 258        {
 182259            allocatedIndex = (ushort)ActiveGrids.Add(newGrid);
 182260            BoundsTracker.Add(boundsKey, allocatedIndex);
 261
 182262            newGrid.Initialize(this, allocatedIndex, normalizedConfiguration);
 812263            foreach (int cellIndex in GetSpatialGridCells(normalizedConfiguration.BoundsMin, normalizedConfiguration.Bou
 264            {
 224265                if (!SpatialGridHash.ContainsKey(cellIndex))
 206266                    SpatialGridHash.Add(cellIndex, new SwiftHashSet<ushort>());
 267
 494268                foreach (ushort neighborIndex in SpatialGridHash[cellIndex])
 269                {
 23270                    if (!ActiveGrids.IsAllocated(neighborIndex) || neighborIndex == allocatedIndex)
 271                        continue;
 272
 23273                    VoxelGrid neighborGrid = ActiveGrids[neighborIndex];
 23274                    if (!VoxelGrid.IsGridOverlapValid(newGrid, neighborGrid))
 275                        continue;
 276
 14277                    newGrid.TryAddGridNeighbor(neighborGrid);
 14278                    neighborGrid.TryAddGridNeighbor(newGrid);
 279                }
 280
 224281                SpatialGridHash[cellIndex].Add(allocatedIndex);
 282            }
 283
 182284            Version++;
 182285            addedGridInfo = CreateGridEventInfo(newGrid);
 182286        }
 287        finally
 288        {
 182289            _gridLock.ExitWriteLock();
 182290        }
 291
 182292        NotifyActiveGridAdded(addedGridInfo);
 182293        return true;
 0294    }
 295
 296    /// <summary>
 297    /// Removes a grid from this world and updates all references to ensure integrity.
 298    /// </summary>
 299    /// <param name="removeIndex">The world-local grid slot to remove.</param>
 300    /// <returns>True if the grid was removed; otherwise false.</returns>
 301    public bool TryRemoveGrid(ushort removeIndex)
 302    {
 12303        if (!IsActive || !ActiveGrids.IsAllocated(removeIndex))
 0304            return false;
 305
 306        VoxelGrid gridToRemove;
 12307        GridEventInfo removedGridInfo = default;
 308
 12309        _gridLock.EnterWriteLock();
 310        try
 311        {
 12312            gridToRemove = ActiveGrids[removeIndex];
 48313            foreach (int cellIndex in GetSpatialGridCells(gridToRemove.BoundsMin, gridToRemove.BoundsMax))
 314            {
 12315                if (!SpatialGridHash.ContainsKey(cellIndex))
 316                    continue;
 317
 12318                SpatialGridHash[cellIndex].Remove(gridToRemove.GridIndex);
 319
 12320                if (gridToRemove.IsConjoined)
 321                {
 20322                    foreach (ushort neighborIndex in SpatialGridHash[cellIndex])
 323                    {
 6324                        if (!ActiveGrids.IsAllocated(neighborIndex) || neighborIndex == removeIndex)
 325                            continue;
 326
 6327                        VoxelGrid neighborGrid = ActiveGrids[neighborIndex];
 6328                        if (!VoxelGrid.IsGridOverlapValid(gridToRemove, neighborGrid))
 329                            continue;
 330
 6331                        neighborGrid.TryRemoveGridNeighbor(gridToRemove);
 332                    }
 333                }
 334
 12335                if (SpatialGridHash[cellIndex].Count == 0)
 6336                    SpatialGridHash.Remove(cellIndex);
 337            }
 338
 12339            BoundsTracker.Remove(gridToRemove.Configuration.ToBoundsKey());
 12340            ActiveGrids.RemoveAt(removeIndex);
 341
 12342            Version++;
 12343            removedGridInfo = CreateGridEventInfo(gridToRemove);
 12344        }
 345        finally
 346        {
 12347            _gridLock.ExitWriteLock();
 12348        }
 349
 12350        Pools.GridPool.Release(gridToRemove);
 12351        NotifyActiveGridRemoved(removedGridInfo);
 352
 12353        if (ActiveGrids.Count == 0)
 6354            ActiveGrids.TrimExcessCapacity();
 355
 12356        return true;
 357    }
 358
 359    #endregion
 360
 361    #region Lookup
 362
 363    /// <summary>
 364    /// Retrieves a grid by its world-local index.
 365    /// </summary>
 366    /// <param name="index">The world-local grid slot to resolve.</param>
 367    /// <param name="outGrid">The resolved grid, if found.</param>
 368    /// <returns>True if the grid was resolved; otherwise false.</returns>
 369    public bool TryGetGrid(int index, out VoxelGrid? outGrid)
 370    {
 275371        outGrid = null;
 275372        if (!IsActive)
 373        {
 0374            GridForgeLogger.Channel.Warn($"Grid world not active. Cannot resolve grids.");
 0375            return false;
 376        }
 377
 275378        if ((uint)index > ActiveGrids.Count)
 379        {
 0380            GridForgeLogger.Channel.Error($"GridIndex '{index}' is out-of-bounds for ActiveGrids.");
 0381            return false;
 382        }
 383
 275384        if (!ActiveGrids.IsAllocated(index))
 385        {
 1386            GridForgeLogger.Channel.Error($"GridIndex '{index}' has not been allocated to ActiveGrids.");
 1387            return false;
 388        }
 389
 274390        outGrid = ActiveGrids[index];
 274391        return true;
 392    }
 393
 394    /// <summary>
 395    /// Retrieves the grid containing a given world position.
 396    /// </summary>
 397    /// <param name="position">The world position to resolve.</param>
 398    /// <param name="outGrid">The resolved grid, if found.</param>
 399    /// <returns>True if a containing grid was found; otherwise false.</returns>
 400    public bool TryGetGrid(Vector3d position, out VoxelGrid? outGrid)
 401    {
 197402        outGrid = null;
 197403        if (!IsActive)
 404        {
 0405            GridForgeLogger.Channel.Warn($"Grid world not active. Cannot resolve positions.");
 0406            return false;
 407        }
 408
 197409        int cellIndex = GetSpatialGridKey(position);
 197410        if (!SpatialGridHash.TryGetValue(cellIndex, out SwiftHashSet<ushort> gridList))
 1411            return false;
 412
 866413        foreach (ushort candidateIndex in gridList)
 414        {
 246415            if (!TryGetGrid(candidateIndex, out VoxelGrid? candidateGrid) || !ActiveGrids[candidateIndex].IsActive)
 416                continue;
 417
 246418            if (candidateGrid?.IsInBounds(position) == true)
 419            {
 18420                outGrid = candidateGrid;
 18421                return true;
 422            }
 423        }
 424
 178425        GridForgeLogger.Channel.Info($"No grid contains position {position}.");
 178426        return false;
 18427    }
 428
 429    /// <summary>
 430    /// Retrieves a grid by a world-scoped voxel identity.
 431    /// </summary>
 432    /// <param name="worldVoxelIndex">The voxel identity whose grid should be resolved.</param>
 433    /// <param name="result">The resolved grid, if found.</param>
 434    /// <returns>True if the grid was resolved; otherwise false.</returns>
 435    public bool TryGetGrid(WorldVoxelIndex worldVoxelIndex, out VoxelGrid? result)
 436    {
 35437        result = null;
 35438        if (worldVoxelIndex.WorldSpawnToken != SpawnToken
 35439            || !TryGetGrid(worldVoxelIndex.GridIndex, out VoxelGrid? resolvedGrid)
 35440            || worldVoxelIndex.GridSpawnToken != resolvedGrid?.SpawnToken)
 441        {
 12442            return false;
 443        }
 444
 23445        result = resolvedGrid;
 23446        return true;
 447    }
 448
 449    /// <summary>
 450    /// Retrieves the grid and voxel containing a given world position.
 451    /// </summary>
 452    /// <param name="position">The world position to resolve.</param>
 453    /// <param name="outGrid">The resolved grid, if found.</param>
 454    /// <param name="outVoxel">The resolved voxel, if found.</param>
 455    /// <returns>True if both the grid and voxel were resolved; otherwise false.</returns>
 456    public bool TryGetGridAndVoxel(
 457        Vector3d position,
 458        out VoxelGrid? outGrid,
 459        out Voxel? outVoxel)
 460    {
 14461        outVoxel = null;
 14462        return TryGetGrid(position, out outGrid)
 14463            && outGrid?.TryGetVoxel(position, out outVoxel) == true;
 464    }
 465
 466    /// <summary>
 467    /// Retrieves the grid and voxel for a given voxel identity.
 468    /// </summary>
 469    /// <param name="worldVoxelIndex">The voxel identity to resolve.</param>
 470    /// <param name="outGrid">The resolved grid, if found.</param>
 471    /// <param name="result">The resolved voxel, if found.</param>
 472    /// <returns>True if both the grid and voxel were resolved; otherwise false.</returns>
 473    public bool TryGetGridAndVoxel(
 474        WorldVoxelIndex worldVoxelIndex,
 475        out VoxelGrid? outGrid,
 476        out Voxel? result)
 477    {
 33478        result = null;
 33479        return TryGetGrid(worldVoxelIndex, out outGrid)
 33480            && outGrid?.TryGetVoxel(worldVoxelIndex.VoxelIndex, out result) == true;
 481    }
 482
 483    /// <summary>
 484    /// Retrieves a voxel from a world position.
 485    /// </summary>
 486    /// <param name="position">The world position to resolve.</param>
 487    /// <param name="result">The resolved voxel, if found.</param>
 488    /// <returns>True if the voxel was resolved; otherwise false.</returns>
 489    public bool TryGetVoxel(
 490        Vector3d position,
 491        out Voxel? result)
 492    {
 183493        result = null;
 183494        return TryGetGrid(position, out VoxelGrid? grid)
 183495            && grid?.TryGetVoxel(position, out result) == true;
 496    }
 497
 498    /// <summary>
 499    /// Retrieves a voxel from a world-scoped voxel identity.
 500    /// </summary>
 501    /// <param name="worldVoxelIndex">The voxel identity to resolve.</param>
 502    /// <param name="result">The resolved voxel, if found.</param>
 503    /// <returns>True if the voxel was resolved; otherwise false.</returns>
 504    public bool TryGetVoxel(
 505        WorldVoxelIndex worldVoxelIndex,
 506        out Voxel? result)
 507    {
 1508        result = null;
 1509        return TryGetGrid(worldVoxelIndex, out VoxelGrid? grid)
 1510            && grid?.TryGetVoxel(worldVoxelIndex.VoxelIndex, out result) == true;
 511    }
 512
 513    #endregion
 514
 515    #region Internal Helpers
 516
 517    internal GridConfiguration NormalizeConfiguration(GridConfiguration configuration)
 518    {
 182519        (Vector3d boundsMin, Vector3d boundsMax) =
 182520            SnapBoundsToVoxelSize(configuration.BoundsMin, configuration.BoundsMax);
 521
 182522        return new GridConfiguration(boundsMin, boundsMax, configuration.ScanCellSize);
 523    }
 524
 525    /// <summary>
 526    /// Increments the version of the specified grid and optionally the world version.
 527    /// </summary>
 528    public void IncrementGridVersion(int index, bool significant = false)
 529    {
 0530        if (!IsActive)
 531        {
 0532            GridForgeLogger.Channel.Warn($"Grid world not active. Cannot increment grid versions.");
 0533            return;
 534        }
 535
 0536        _gridLock.EnterWriteLock();
 537        try
 538        {
 0539            if (significant)
 0540                Version++;
 541
 0542            if (ActiveGrids.IsAllocated(index))
 0543                ActiveGrids[index].IncrementVersion();
 0544        }
 545        finally
 546        {
 0547            _gridLock.ExitWriteLock();
 0548        }
 0549    }
 550
 551    /// <summary>
 552    /// Enumerates the spatial-hash cells that intersect the supplied bounds.
 553    /// </summary>
 554    public IEnumerable<int> GetSpatialGridCells(Vector3d min, Vector3d max)
 555    {
 359556        (int xMin, int yMin, int zMin, int xMax, int yMax, int zMax) = GetSpatialGridCellBounds(min, max);
 557
 1460558        for (int z = zMin; z <= zMax; z++)
 559        {
 1484560            for (int y = yMin; y <= yMax; y++)
 561            {
 1572562                for (int x = xMin; x <= xMax; x++)
 415563                    yield return SwiftHashTools.CombineHashCodes(x, y, z);
 564            }
 565        }
 359566    }
 567
 568    /// <summary>
 569    /// Computes normalized spatial-hash cell bounds for the supplied world-space bounds.
 570    /// </summary>
 571    internal (int xMin, int yMin, int zMin, int xMax, int yMax, int zMax) GetSpatialGridCellBounds(
 572        Vector3d min,
 573        Vector3d max)
 574    {
 624575        (int xMin, int yMin, int zMin) = SnapToSpatialGrid(min);
 624576        (int xMax, int yMax, int zMax) = SnapToSpatialGrid(max);
 577
 624578        (xMin, xMax) = xMin > xMax ? (xMax, xMin) : (xMin, xMax);
 624579        (yMin, yMax) = yMin > yMax ? (yMax, yMin) : (yMin, yMax);
 624580        (zMin, zMax) = zMin > zMax ? (zMax, zMin) : (zMin, zMax);
 581
 624582        return (xMin, yMin, zMin, xMax, yMax, zMax);
 583    }
 584
 585    /// <summary>
 586    /// Finds active grids in this world that overlap the supplied target grid.
 587    /// </summary>
 588    public IEnumerable<VoxelGrid> FindOverlappingGrids(VoxelGrid targetGrid)
 589    {
 0590        SwiftHashSet<VoxelGrid> overlappingGrids = new();
 591
 0592        if (!IsActive)
 593        {
 0594            GridForgeLogger.Channel.Warn($"Grid world not active. Cannot resolve overlaps.");
 0595            return overlappingGrids;
 596        }
 597
 0598        foreach (int cellIndex in GetSpatialGridCells(targetGrid.BoundsMin, targetGrid.BoundsMax))
 599        {
 0600            if (!SpatialGridHash.TryGetValue(cellIndex, out SwiftHashSet<ushort> gridList))
 601                continue;
 602
 0603            foreach (ushort neighborIndex in gridList)
 604            {
 0605                if (!ActiveGrids.IsAllocated(neighborIndex) || neighborIndex == targetGrid.GridIndex)
 606                    continue;
 607
 0608                VoxelGrid neighborGrid = ActiveGrids[neighborIndex];
 0609                if (VoxelGrid.IsGridOverlapValid(targetGrid, neighborGrid))
 0610                    overlappingGrids.Add(neighborGrid);
 611            }
 612        }
 613
 0614        return overlappingGrids;
 615    }
 616
 617    /// <summary>
 618    /// Computes the spatial-hash key for the supplied world-space position.
 619    /// </summary>
 620    public int GetSpatialGridKey(Vector3d position)
 621    {
 200622        (int x, int y, int z) = (
 200623            position.x.FloorToInt() / SpatialGridCellSize,
 200624            position.y.FloorToInt() / SpatialGridCellSize,
 200625            position.z.FloorToInt() / SpatialGridCellSize
 200626        );
 627
 200628        return SwiftHashTools.CombineHashCodes(x, y, z);
 629    }
 630
 631    /// <summary>
 632    /// Ceil-snaps a world-space position to this world's voxel size.
 633    /// </summary>
 634    public Vector3d CeilToVoxelSize(Vector3d position)
 635    {
 613636        return new Vector3d(
 613637            (position.x.Abs() / VoxelSize).CeilToInt() * VoxelSize * position.x.Sign(),
 613638            (position.y.Abs() / VoxelSize).CeilToInt() * VoxelSize * position.y.Sign(),
 613639            (position.z.Abs() / VoxelSize).CeilToInt() * VoxelSize * position.z.Sign()
 613640        );
 641    }
 642
 643    /// <summary>
 644    /// Floor-snaps a world-space position to this world's voxel size.
 645    /// </summary>
 646    public Vector3d FloorToVoxelSize(Vector3d position)
 647    {
 763648        return new Vector3d(
 763649            (position.x.Abs() / VoxelSize).FloorToInt() * VoxelSize * position.x.Sign(),
 763650            (position.y.Abs() / VoxelSize).FloorToInt() * VoxelSize * position.y.Sign(),
 763651            (position.z.Abs() / VoxelSize).FloorToInt() * VoxelSize * position.z.Sign()
 763652        );
 653    }
 654
 655    /// <summary>
 656    /// Snaps the supplied bounds to this world's voxel size.
 657    /// </summary>
 658    public (Vector3d min, Vector3d max) SnapBoundsToVoxelSize(
 659        Vector3d min,
 660        Vector3d max,
 661        Fixed64? padding = null)
 662    {
 612663        Fixed64 fixedPadding = padding.HasValue && padding.Value > Fixed64.Zero
 612664            ? padding.Value
 612665            : Fixed64.Zero;
 666
 612667        min -= fixedPadding;
 612668        max += fixedPadding;
 669
 612670        Vector3d snapMin = FloorToVoxelSize(min);
 612671        Vector3d snapMax = CeilToVoxelSize(max);
 672
 612673        (snapMin.x, snapMax.x) = snapMin.x > snapMax.x ? (snapMax.x, snapMin.x) : (snapMin.x, snapMax.x);
 612674        (snapMin.y, snapMax.y) = snapMin.y > snapMax.y ? (snapMax.y, snapMin.y) : (snapMin.y, snapMax.y);
 612675        (snapMin.z, snapMax.z) = snapMin.z > snapMax.z ? (snapMax.z, snapMin.z) : (snapMin.z, snapMax.z);
 676
 612677        return (snapMin, snapMax);
 678    }
 679
 680    internal void NotifyActiveGridChange(VoxelGrid grid)
 681    {
 1237682        if (grid == null || !grid.IsActive)
 0683            return;
 684
 1237685        NotifyActiveGridChange(CreateGridEventInfo(grid));
 1237686    }
 687
 688    #endregion
 689
 690    #region Private Helpers
 691
 692    private static Fixed64 ResolveVoxelSize(Fixed64? voxelSize)
 693    {
 182694        Fixed64 resolved = voxelSize ?? DefaultVoxelSize;
 182695        if (resolved <= Fixed64.Zero)
 696        {
 0697            GridForgeLogger.Channel.Warn($"Voxel size must be greater than zero. Falling back to default size {DefaultVo
 0698            return DefaultVoxelSize;
 699        }
 700
 182701        return resolved;
 702    }
 703
 704    private static int ResolveSpatialGridCellSize(int spatialGridCellSize)
 705    {
 182706        if (spatialGridCellSize <= 0)
 707        {
 0708            GridForgeLogger.Channel.Warn($"Spatial grid cell size must be greater than zero. Falling back to default siz
 0709            return DefaultSpatialGridCellSize;
 710        }
 711
 182712        return spatialGridCellSize;
 713    }
 714
 715    private GridEventInfo CreateGridEventInfo(VoxelGrid grid)
 716    {
 1431717        return new GridEventInfo(SpawnToken, grid.GridIndex, grid.SpawnToken, grid.Configuration, grid.Version);
 718    }
 719
 720    private void NotifyActiveGridAdded(GridEventInfo eventInfo)
 721    {
 182722        Action<GridEventInfo>? handlers = _onActiveGridAdded;
 182723        if (handlers == null)
 178724            return;
 725
 4726        var handlerDelegates = handlers.GetInvocationList();
 16727        for (int i = 0; i < handlerDelegates.Length; i++)
 728        {
 729            try
 730            {
 4731                ((Action<GridEventInfo>)handlerDelegates[i])(eventInfo);
 4732            }
 0733            catch (Exception ex)
 734            {
 0735                GridForgeLogger.Channel.Error($"[Grid {eventInfo.GridIndex}] added notification error: {ex.Message}");
 0736            }
 737        }
 4738    }
 739
 740    private void NotifyActiveGridRemoved(GridEventInfo eventInfo)
 741    {
 12742        Action<GridEventInfo>? handlers = _onActiveGridRemoved;
 12743        if (handlers == null)
 10744            return;
 745
 2746        var handlerDelegates = handlers.GetInvocationList();
 8747        for (int i = 0; i < handlerDelegates.Length; i++)
 748        {
 749            try
 750            {
 2751                ((Action<GridEventInfo>)handlerDelegates[i])(eventInfo);
 2752            }
 0753            catch (Exception ex)
 754            {
 0755                GridForgeLogger.Channel.Error($"[Grid {eventInfo.GridIndex}] removed notification error: {ex.Message}");
 0756            }
 757        }
 2758    }
 759
 760    private void NotifyActiveGridChange(GridEventInfo eventInfo)
 761    {
 1237762        Action<GridEventInfo>? handlers = _onActiveGridChange;
 1237763        if (handlers == null)
 1237764            return;
 765
 0766        var handlerDelegates = handlers.GetInvocationList();
 0767        for (int i = 0; i < handlerDelegates.Length; i++)
 768        {
 769            try
 770            {
 0771                ((Action<GridEventInfo>)handlerDelegates[i])(eventInfo);
 0772            }
 0773            catch (Exception ex)
 774            {
 0775                GridForgeLogger.Channel.Error($"[Grid {eventInfo.GridIndex}] change notification error: {ex.Message}");
 0776            }
 777        }
 0778    }
 779
 780    private (int xMin, int yMin, int zMin) SnapToSpatialGrid(Vector3d position)
 781    {
 1248782        return (
 1248783            (position.x.Abs() / SpatialGridCellSize).FloorToInt() * position.x.Sign(),
 1248784            (position.y.Abs() / SpatialGridCellSize).FloorToInt() * position.y.Sign(),
 1248785            (position.z.Abs() / SpatialGridCellSize).FloorToInt() * position.z.Sign()
 1248786        );
 787    }
 788
 789    #endregion
 790}

Methods/Properties

.cctor()
get_VoxelSize()
get_SpatialGridCellSize()
get_VoxelResolution()
get_ActiveGrids()
get_BoundsTracker()
get_SpatialGridHash()
get_SpawnToken()
get_Version()
get_IsActive()
.ctor(System.Nullable`1<FixedMathSharp.Fixed64>,System.Int32)
add_OnActiveGridAdded(System.Action`1<GridForge.Grids.GridEventInfo>)
remove_OnActiveGridAdded(System.Action`1<GridForge.Grids.GridEventInfo>)
add_OnActiveGridRemoved(System.Action`1<GridForge.Grids.GridEventInfo>)
remove_OnActiveGridRemoved(System.Action`1<GridForge.Grids.GridEventInfo>)
add_OnActiveGridChange(System.Action`1<GridForge.Grids.GridEventInfo>)
remove_OnActiveGridChange(System.Action`1<GridForge.Grids.GridEventInfo>)
add_OnReset(System.Action)
remove_OnReset(System.Action)
Reset(System.Boolean)
Dispose()
TryAddGrid(GridForge.Configuration.GridConfiguration,System.UInt16&)
TryRemoveGrid(System.UInt16)
TryGetGrid(System.Int32,GridForge.Grids.VoxelGrid&)
TryGetGrid(FixedMathSharp.Vector3d,GridForge.Grids.VoxelGrid&)
TryGetGrid(GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.VoxelGrid&)
TryGetGridAndVoxel(FixedMathSharp.Vector3d,GridForge.Grids.VoxelGrid&,GridForge.Grids.Voxel&)
TryGetGridAndVoxel(GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.VoxelGrid&,GridForge.Grids.Voxel&)
TryGetVoxel(FixedMathSharp.Vector3d,GridForge.Grids.Voxel&)
TryGetVoxel(GridForge.Spatial.WorldVoxelIndex,GridForge.Grids.Voxel&)
NormalizeConfiguration(GridForge.Configuration.GridConfiguration)
IncrementGridVersion(System.Int32,System.Boolean)
GetSpatialGridCells()
GetSpatialGridCellBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FindOverlappingGrids(GridForge.Grids.VoxelGrid)
GetSpatialGridKey(FixedMathSharp.Vector3d)
CeilToVoxelSize(FixedMathSharp.Vector3d)
FloorToVoxelSize(FixedMathSharp.Vector3d)
SnapBoundsToVoxelSize(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.Fixed64>)
NotifyActiveGridChange(GridForge.Grids.VoxelGrid)
ResolveVoxelSize(System.Nullable`1<FixedMathSharp.Fixed64>)
ResolveSpatialGridCellSize(System.Int32)
CreateGridEventInfo(GridForge.Grids.VoxelGrid)
NotifyActiveGridAdded(GridForge.Grids.GridEventInfo)
NotifyActiveGridRemoved(GridForge.Grids.GridEventInfo)
NotifyActiveGridChange(GridForge.Grids.GridEventInfo)
SnapToSpatialGrid(FixedMathSharp.Vector3d)