< Summary

Information
Class: GridForge.Grids.Topology.VoxelNeighborResolver
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Topology/VoxelNeighborResolver.cs
Line coverage
100%
Covered lines: 167
Uncovered lines: 0
Coverable lines: 167
Total lines: 428
Line coverage: 100%
Branch coverage
100%
Covered branches: 88
Total branches: 88
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Grids/Topology/VoxelNeighborResolver.cs

#LineLine coverage
 1//=======================================================================
 2// VoxelNeighborResolver.cs
 3//=======================================================================
 4// MIT License, Copyright (c) 2024-present David Oravsky (mrdav30)
 5// See LICENSE file in the project root for full license information.
 6//=======================================================================
 7
 8using FixedMathSharp;
 9using GridForge.Spatial;
 10using SwiftCollections;
 11using SwiftCollections.Utility;
 12using System;
 13using System.Collections.Generic;
 14using System.Runtime.CompilerServices;
 15
 16namespace GridForge.Grids.Topology;
 17
 18internal static class VoxelNeighborResolver
 19{
 20    [ThreadStatic]
 21    private static NeighborResolverScratch? _contactScratch;
 22
 23    internal static void AddContactNeighbors(
 24        Voxel source,
 25        VoxelGrid ownerGrid,
 26        SwiftList<Voxel> results,
 27        VoxelNeighborScope scope,
 28        Fixed64? tolerance = null) =>
 2229        ResolveContactNeighbors(source, ownerGrid, results, stopAtFirst: false, scope, tolerance);
 30
 31    internal static bool HasContactNeighbor(
 32        Voxel source,
 33        VoxelGrid ownerGrid,
 34        VoxelNeighborScope scope,
 35        Fixed64? tolerance = null) =>
 936        ResolveContactNeighbors(source, ownerGrid, results: null, stopAtFirst: true, scope, tolerance);
 37
 38    internal static bool TryGetNeighbor(
 39        Voxel source,
 40        VoxelGrid ownerGrid,
 41        RectangularDirection direction,
 42        out Voxel? neighbor)
 43    {
 7144        neighbor = null;
 7145        if (!ownerGrid.TryGetNeighborSlot(direction, out int slot))
 146            return false;
 47
 7048        return TryGetNeighborFromSlot(source, ownerGrid, slot, out neighbor);
 49    }
 50
 51    internal static bool TryGetNeighbor(
 52        Voxel source,
 53        VoxelGrid ownerGrid,
 54        HexDirection direction,
 55        out Voxel? neighbor)
 56    {
 557        neighbor = null;
 558        if (!ownerGrid.TryGetNeighborSlot(direction, out int slot))
 159            return false;
 60
 461        return TryGetNeighborFromSlot(source, ownerGrid, slot, out neighbor);
 62    }
 63
 64    internal static void AddRectangularNeighbors(
 65        Voxel source,
 66        VoxelGrid ownerGrid,
 67        SwiftList<(RectangularDirection Direction, Voxel Voxel)> results)
 68    {
 869        if (ownerGrid.TopologyKind != GridTopologyKind.RectangularPrism)
 170            return;
 71
 37872        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 73        {
 18274            if (TryGetNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 12575                results.Add(((RectangularDirection)slot, neighbor!));
 76        }
 777    }
 78
 79    internal static void AddHexNeighbors(
 80        Voxel source,
 81        VoxelGrid ownerGrid,
 82        SwiftList<(HexDirection Direction, Voxel Voxel)> results)
 83    {
 384        if (ownerGrid.TopologyKind != GridTopologyKind.HexPrism)
 185            return;
 86
 8487        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 88        {
 4089            if (TryGetNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 2590                results.Add(((HexDirection)slot, neighbor!));
 91        }
 292    }
 93
 94    private static bool ResolveContactNeighbors(
 95        Voxel source,
 96        VoxelGrid ownerGrid,
 97        SwiftList<Voxel>? results,
 98        bool stopAtFirst,
 99        VoxelNeighborScope scope,
 100        Fixed64? tolerance)
 101    {
 31102        if (scope == VoxelNeighborScope.None)
 2103            return false;
 104
 29105        if (IsSourceGridOnly(scope))
 4106            return ResolveSourceGridContactNeighbors(source, ownerGrid, results, stopAtFirst);
 107
 25108        Fixed64 toleranceValue = tolerance.HasValue && tolerance.Value > Fixed64.Zero
 25109            ? tolerance.Value
 25110            : Fixed64.Zero;
 25111        TopologyVoxelAabb sourceBounds = TopologyVoxelAabb.FromVoxel(ownerGrid, source);
 25112        TopologyVoxelAabb queryBounds = sourceBounds.Expand(toleranceValue);
 25113        NeighborResolverScratch scratch = RentContactScratch();
 25114        GridWorld world = ownerGrid.World!;
 25115        SwiftList<ushort> candidateGridIds = scratch.CandidateGridIds;
 25116        SwiftHashSet<ushort> processedGridIds = scratch.ProcessedGridIds;
 25117        SwiftList<Voxel> voxelCandidates = scratch.CandidateVoxels;
 25118        SwiftHashSet<Voxel> processedVoxels = scratch.ProcessedVoxels;
 119
 120        try
 121        {
 25122            PopulateCandidateGridIds(world, ownerGrid, queryBounds, scope, candidateGridIds, processedGridIds);
 123
 138124            for (int i = 0; i < candidateGridIds.Count; i++)
 125            {
 49126                if (!TryGetCandidateGrid(world, ownerGrid, candidateGridIds[i], scope, out VoxelGrid candidateGrid))
 127                    continue;
 128
 26129                if (!TryCollectCandidateVoxels(
 26130                    source,
 26131                    ownerGrid,
 26132                    candidateGrid,
 26133                    queryBounds,
 26134                    voxelCandidates,
 26135                    processedVoxels))
 136                {
 137                    continue;
 138                }
 139
 25140                if (AddOverlappingCandidateVoxels(
 25141                    source,
 25142                    candidateGrid,
 25143                    sourceBounds,
 25144                    toleranceValue,
 25145                    voxelCandidates,
 25146                    results,
 25147                    stopAtFirst))
 148                {
 5149                    return true;
 150                }
 151            }
 152
 20153            return results != null && results.Count > 0;
 154        }
 155        finally
 156        {
 25157            ReleaseContactScratch(scratch);
 25158        }
 25159    }
 160
 161    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 162    private static bool IsSourceGridOnly(VoxelNeighborScope scope) =>
 29163        (scope & ~VoxelNeighborScope.SourceGrid) == VoxelNeighborScope.None;
 164
 165    private static void PopulateCandidateGridIds(
 166        GridWorld world,
 167        VoxelGrid ownerGrid,
 168        TopologyVoxelAabb queryBounds,
 169        VoxelNeighborScope scope,
 170        SwiftList<ushort> candidateGridIds,
 171        SwiftHashSet<ushort> processedGridIds)
 172    {
 25173        CollectCandidateGridIds(world, queryBounds, candidateGridIds, processedGridIds);
 25174        if (candidateGridIds.Count > 1)
 24175            candidateGridIds.SortInPlace();
 25176    }
 177
 178    private static bool TryGetCandidateGrid(
 179        GridWorld world,
 180        VoxelGrid ownerGrid,
 181        ushort candidateGridId,
 182        VoxelNeighborScope scope,
 183        out VoxelGrid candidateGrid)
 184    {
 49185        candidateGrid = null!;
 49186        if (!world.ActiveGrids.IsAllocated(candidateGridId))
 2187            return false;
 188
 47189        VoxelGrid resolvedGrid = world.ActiveGrids[candidateGridId];
 47190        if (!IsCandidateInScope(ownerGrid, resolvedGrid, scope))
 21191            return false;
 192
 26193        candidateGrid = resolvedGrid;
 26194        return true;
 195    }
 196
 197    private static bool TryCollectCandidateVoxels(
 198        Voxel source,
 199        VoxelGrid ownerGrid,
 200        VoxelGrid candidateGrid,
 201        TopologyVoxelAabb queryBounds,
 202        SwiftList<Voxel> voxelCandidates,
 203        SwiftHashSet<Voxel> processedVoxels)
 204    {
 26205        voxelCandidates.Clear();
 26206        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 207        {
 4208            AddSourceGridContactNeighbors(source, ownerGrid, voxelCandidates);
 4209            return true;
 210        }
 211
 22212        if (!TopologyVoxelRangeUtility.TryGetCandidateRange(
 22213            candidateGrid,
 22214            queryBounds,
 22215            out VoxelIndex minIndex,
 22216            out VoxelIndex maxIndex))
 217        {
 1218            return false;
 219        }
 220
 21221        processedVoxels.Clear();
 21222        candidateGrid.AddVoxelsInIndexRange(minIndex, maxIndex, voxelCandidates, processedVoxels);
 21223        SortByVoxelIndex(voxelCandidates);
 21224        return true;
 225    }
 226
 227    private static bool AddOverlappingCandidateVoxels(
 228        Voxel source,
 229        VoxelGrid candidateGrid,
 230        TopologyVoxelAabb sourceBounds,
 231        Fixed64 toleranceValue,
 232        SwiftList<Voxel> voxelCandidates,
 233        SwiftList<Voxel>? results,
 234        bool stopAtFirst)
 235    {
 90236        for (int voxelIndex = 0; voxelIndex < voxelCandidates.Count; voxelIndex++)
 237        {
 25238            Voxel candidateVoxel = voxelCandidates[voxelIndex];
 25239            TopologyVoxelAabb candidateBounds = TopologyVoxelAabb.FromVoxel(candidateGrid, candidateVoxel);
 25240            if (!sourceBounds.Overlaps(candidateBounds, toleranceValue))
 241                continue;
 242
 22243            if (stopAtFirst)
 5244                return true;
 245
 17246            results!.Add(candidateVoxel);
 247        }
 248
 20249        return false;
 250    }
 251
 252    private static bool ResolveSourceGridContactNeighbors(
 253        Voxel source,
 254        VoxelGrid ownerGrid,
 255        SwiftList<Voxel>? results,
 256        bool stopAtFirst)
 257    {
 4258        if (stopAtFirst)
 259        {
 60260            for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 261            {
 29262                if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out _))
 1263                    return true;
 264            }
 265
 1266            return false;
 267        }
 268
 2269        SwiftList<Voxel> resultList = results!;
 2270        AddSourceGridContactNeighbors(source, ownerGrid, resultList);
 271
 2272        return resultList.Count > 0;
 273    }
 274
 275    private static void AddSourceGridContactNeighbors(
 276        Voxel source,
 277        VoxelGrid ownerGrid,
 278        SwiftList<Voxel> results)
 279    {
 324280        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 281        {
 156282            if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 6283                results.Add(neighbor!);
 284        }
 6285    }
 286
 287    private static void CollectCandidateGridIds(
 288        GridWorld world,
 289        TopologyVoxelAabb queryBounds,
 290        SwiftList<ushort> candidateGridIds,
 291        SwiftHashSet<ushort> processedGridIds)
 292    {
 25293        TopologyVoxelAabb spatialBounds = queryBounds.Expand(world.MaxTopologyCellEdge);
 25294        (int cellXMin, int cellYMin, int cellZMin, int cellXMax, int cellYMax, int cellZMax) =
 25295            world.GetSpatialGridCellBounds(spatialBounds.Min, spatialBounds.Max);
 296
 104297        for (int cellZ = cellZMin; cellZ <= cellZMax; cellZ++)
 298        {
 120299            for (int cellY = cellYMin; cellY <= cellYMax; cellY++)
 300            {
 168301                for (int cellX = cellXMin; cellX <= cellXMax; cellX++)
 302                {
 51303                    int cellIndex = SwiftHashTools.CombineHashCodes(cellX, cellY, cellZ);
 51304                    if (!world.SpatialGridHash.TryGetValue(cellIndex, out SwiftHashSet<ushort> gridIds))
 305                        continue;
 306
 156307                    foreach (ushort gridId in gridIds)
 308                    {
 53309                        if (processedGridIds.Add(gridId))
 53310                            candidateGridIds.Add(gridId);
 311                    }
 312                }
 313            }
 314        }
 25315    }
 316
 317    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 318    private static bool TryGetNeighborFromSlot(
 319        Voxel source,
 320        VoxelGrid ownerGrid,
 321        int slot,
 322        out Voxel? neighbor)
 323    {
 296324        return TryResolveNeighborAtOffset(source, ownerGrid, ownerGrid.GetNeighborOffset(slot), out neighbor);
 325    }
 326
 327    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 328    private static bool TryGetLocalNeighborFromSlot(
 329        Voxel source,
 330        VoxelGrid ownerGrid,
 331        int slot,
 332        out Voxel? neighbor)
 333    {
 185334        VoxelIndex offset = ownerGrid.GetNeighborOffset(slot);
 185335        VoxelIndex neighborCoords = new(
 185336            source.Index.x + offset.x,
 185337            source.Index.y + offset.y,
 185338            source.Index.z + offset.z);
 339
 185340        return ownerGrid.TryGetVoxel(neighborCoords, out neighbor);
 341    }
 342
 343    private static bool TryResolveNeighborAtOffset(
 344        Voxel source,
 345        VoxelGrid ownerGrid,
 346        VoxelIndex offset,
 347        out Voxel? neighbor)
 348    {
 296349        neighbor = null;
 296350        VoxelIndex neighborCoords = new(
 296351            source.Index.x + offset.x,
 296352            source.Index.y + offset.y,
 296353            source.Index.z + offset.z);
 354
 296355        if (ownerGrid.TryGetVoxel(neighborCoords, out neighbor))
 204356            return true;
 357
 92358        GridWorld world = ownerGrid.World!;
 92359        Vector3d neighborPosition = source.WorldPosition + ownerGrid.GetWorldOffset((offset.x, offset.y, offset.z));
 92360        if (!world.TryGetVoxel(neighborPosition, out neighbor) || neighbor == null)
 84361            return false;
 362
 8363        VoxelGrid neighborGrid = world.ActiveGrids[neighbor.WorldIndex.GridIndex];
 8364        if (neighborGrid.TopologyKind == ownerGrid.TopologyKind)
 7365            return true;
 366
 1367        neighbor = null;
 1368        return false;
 369    }
 370
 371    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 372    private static bool IsCandidateInScope(
 373        VoxelGrid ownerGrid,
 374        VoxelGrid candidateGrid,
 375        VoxelNeighborScope scope)
 376    {
 47377        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 23378            return (scope & VoxelNeighborScope.SourceGrid) != 0;
 379
 24380        return candidateGrid.TopologyKind == ownerGrid.TopologyKind
 24381            ? (scope & VoxelNeighborScope.SameTopologyGrids) != 0
 24382            : (scope & VoxelNeighborScope.MixedTopologyGrids) != 0;
 383    }
 384
 385    private static NeighborResolverScratch RentContactScratch()
 386    {
 25387        NeighborResolverScratch scratch = _contactScratch ??= new NeighborResolverScratch();
 25388        return scratch;
 389    }
 390
 391    private static void ReleaseContactScratch(NeighborResolverScratch scratch)
 392    {
 25393        scratch.Clear();
 25394    }
 395
 396    private static void SortByVoxelIndex(SwiftList<Voxel> voxels)
 397    {
 21398        int count = voxels.Count;
 21399        if (count <= 1)
 19400            return;
 401
 2402        Array.Sort(voxels.InnerArray, 0, count, VoxelIndexComparer.Instance);
 2403    }
 404
 405    private sealed class NeighborResolverScratch
 406    {
 1407        public readonly SwiftList<ushort> CandidateGridIds = new();
 1408        public readonly SwiftHashSet<ushort> ProcessedGridIds = new();
 1409        public readonly SwiftList<Voxel> CandidateVoxels = new();
 1410        public readonly SwiftHashSet<Voxel> ProcessedVoxels = new();
 411
 412        public void Clear()
 413        {
 25414            CandidateGridIds.Clear();
 25415            ProcessedGridIds.Clear();
 25416            CandidateVoxels.Clear();
 25417            ProcessedVoxels.Clear();
 25418        }
 419    }
 420
 421    private sealed class VoxelIndexComparer : IComparer<Voxel>
 422    {
 1423        public static readonly VoxelIndexComparer Instance = new();
 424
 425        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2426        public int Compare(Voxel? left, Voxel? right) => left!.Index.CompareTo(right!.Index);
 427    }
 428}

Methods/Properties

AddContactNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,GridForge.Spatial.VoxelNeighborScope,System.Nullable`1<FixedMathSharp.Fixed64>)
HasContactNeighbor(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Spatial.VoxelNeighborScope,System.Nullable`1<FixedMathSharp.Fixed64>)
TryGetNeighbor(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Spatial.RectangularDirection,GridForge.Grids.Voxel&)
TryGetNeighbor(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Spatial.HexDirection,GridForge.Grids.Voxel&)
AddRectangularNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<System.ValueTuple`2<GridForge.Spatial.RectangularDirection,GridForge.Grids.Voxel>>)
AddHexNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<System.ValueTuple`2<GridForge.Spatial.HexDirection,GridForge.Grids.Voxel>>)
ResolveContactNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,System.Boolean,GridForge.Spatial.VoxelNeighborScope,System.Nullable`1<FixedMathSharp.Fixed64>)
IsSourceGridOnly(GridForge.Spatial.VoxelNeighborScope)
PopulateCandidateGridIds(GridForge.Grids.GridWorld,GridForge.Grids.VoxelGrid,GridForge.Grids.Topology.TopologyVoxelAabb,GridForge.Spatial.VoxelNeighborScope,SwiftCollections.SwiftList`1<System.UInt16>,SwiftCollections.SwiftHashSet`1<System.UInt16>)
TryGetCandidateGrid(GridForge.Grids.GridWorld,GridForge.Grids.VoxelGrid,System.UInt16,GridForge.Spatial.VoxelNeighborScope,GridForge.Grids.VoxelGrid&)
TryCollectCandidateVoxels(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Grids.VoxelGrid,GridForge.Grids.Topology.TopologyVoxelAabb,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,SwiftCollections.SwiftHashSet`1<GridForge.Grids.Voxel>)
AddOverlappingCandidateVoxels(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Grids.Topology.TopologyVoxelAabb,FixedMathSharp.Fixed64,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,System.Boolean)
ResolveSourceGridContactNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>,System.Boolean)
AddSourceGridContactNeighbors(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>)
CollectCandidateGridIds(GridForge.Grids.GridWorld,GridForge.Grids.Topology.TopologyVoxelAabb,SwiftCollections.SwiftList`1<System.UInt16>,SwiftCollections.SwiftHashSet`1<System.UInt16>)
TryGetNeighborFromSlot(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,System.Int32,GridForge.Grids.Voxel&)
TryGetLocalNeighborFromSlot(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,System.Int32,GridForge.Grids.Voxel&)
TryResolveNeighborAtOffset(GridForge.Grids.Voxel,GridForge.Grids.VoxelGrid,GridForge.Spatial.VoxelIndex,GridForge.Grids.Voxel&)
IsCandidateInScope(GridForge.Grids.VoxelGrid,GridForge.Grids.VoxelGrid,GridForge.Spatial.VoxelNeighborScope)
RentContactScratch()
ReleaseContactScratch(GridForge.Grids.Topology.VoxelNeighborResolver/NeighborResolverScratch)
SortByVoxelIndex(SwiftCollections.SwiftList`1<GridForge.Grids.Voxel>)
.ctor()
Clear()
.cctor()
Compare(GridForge.Grids.Voxel,GridForge.Grids.Voxel)