< 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: 153
Uncovered lines: 0
Coverable lines: 153
Total lines: 391
Line coverage: 100%
Branch coverage
100%
Covered branches: 72
Total branches: 72
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 System;
 9using System.Collections.Generic;
 10using System.Runtime.CompilerServices;
 11using FixedMathSharp;
 12using GridForge.Spatial;
 13using SwiftCollections;
 14
 15namespace GridForge.Grids.Topology;
 16
 17internal static class VoxelNeighborResolver
 18{
 19    [ThreadStatic]
 20    private static NeighborResolverScratch? _contactScratch;
 21
 22    internal static void AddContactNeighbors(
 23        Voxel source,
 24        VoxelGrid ownerGrid,
 25        SwiftList<Voxel> results,
 26        VoxelNeighborScope scope,
 27        Fixed64? tolerance = null) =>
 3028        ResolveContactNeighbors(source, ownerGrid, results, stopAtFirst: false, scope, tolerance);
 29
 30    internal static bool HasContactNeighbor(
 31        Voxel source,
 32        VoxelGrid ownerGrid,
 33        VoxelNeighborScope scope,
 34        Fixed64? tolerance = null) =>
 935        ResolveContactNeighbors(source, ownerGrid, results: null, stopAtFirst: true, scope, tolerance);
 36
 37    internal static bool TryGetNeighbor(
 38        Voxel source,
 39        VoxelGrid ownerGrid,
 40        RectangularDirection direction,
 41        out Voxel? neighbor)
 42    {
 7143        neighbor = null;
 7144        if (!ownerGrid.TryGetNeighborSlot(direction, out int slot))
 145            return false;
 46
 7047        return TryGetNeighborFromSlot(source, ownerGrid, slot, out neighbor);
 48    }
 49
 50    internal static bool TryGetNeighbor(
 51        Voxel source,
 52        VoxelGrid ownerGrid,
 53        HexDirection direction,
 54        out Voxel? neighbor)
 55    {
 556        neighbor = null;
 557        if (!ownerGrid.TryGetNeighborSlot(direction, out int slot))
 158            return false;
 59
 460        return TryGetNeighborFromSlot(source, ownerGrid, slot, out neighbor);
 61    }
 62
 63    internal static void AddRectangularNeighbors(
 64        Voxel source,
 65        VoxelGrid ownerGrid,
 66        SwiftList<(RectangularDirection Direction, Voxel Voxel)> results)
 67    {
 868        if (ownerGrid.TopologyKind != GridTopologyKind.RectangularPrism)
 169            return;
 70
 37871        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 72        {
 18273            if (TryGetNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 12574                results.Add(((RectangularDirection)slot, neighbor!));
 75        }
 776    }
 77
 78    internal static void AddHexNeighbors(
 79        Voxel source,
 80        VoxelGrid ownerGrid,
 81        SwiftList<(HexDirection Direction, Voxel Voxel)> results)
 82    {
 383        if (ownerGrid.TopologyKind != GridTopologyKind.HexPrism)
 184            return;
 85
 8486        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 87        {
 4088            if (TryGetNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 2589                results.Add(((HexDirection)slot, neighbor!));
 90        }
 291    }
 92
 93    private static bool ResolveContactNeighbors(
 94        Voxel source,
 95        VoxelGrid ownerGrid,
 96        SwiftList<Voxel>? results,
 97        bool stopAtFirst,
 98        VoxelNeighborScope scope,
 99        Fixed64? tolerance)
 100    {
 39101        if (scope == VoxelNeighborScope.None)
 2102            return false;
 103
 37104        if (IsSourceGridOnly(scope))
 4105            return ResolveSourceGridContactNeighbors(source, ownerGrid, results, stopAtFirst);
 106
 33107        Fixed64 toleranceValue = tolerance.HasValue && tolerance.Value > Fixed64.Zero
 33108            ? tolerance.Value
 33109            : Fixed64.Zero;
 33110        TopologyVoxelAabb sourceBounds = TopologyVoxelAabb.FromVoxel(ownerGrid, source);
 33111        TopologyVoxelAabb queryBounds = sourceBounds.Expand(toleranceValue);
 33112        NeighborResolverScratch scratch = RentContactScratch();
 33113        GridWorld world = ownerGrid.World!;
 33114        SwiftList<ushort> candidateGridIds = scratch.CandidateGridIds;
 33115        SwiftList<Voxel> voxelCandidates = scratch.CandidateVoxels;
 33116        SwiftHashSet<Voxel> processedVoxels = scratch.ProcessedVoxels;
 117
 118        try
 119        {
 33120            PopulateCandidateGridIds(world, queryBounds, candidateGridIds);
 121
 196122            for (int i = 0; i < candidateGridIds.Count; i++)
 123            {
 70124                if (!TryGetCandidateGrid(world, ownerGrid, candidateGridIds[i], scope, out VoxelGrid candidateGrid))
 125                    continue;
 126
 40127                if (!TryCollectCandidateVoxels(
 40128                    source,
 40129                    ownerGrid,
 40130                    candidateGrid,
 40131                    queryBounds,
 40132                    voxelCandidates,
 40133                    processedVoxels))
 134                {
 135                    continue;
 136                }
 137
 39138                if (AddOverlappingCandidateVoxels(
 39139                    source,
 39140                    candidateGrid,
 39141                    sourceBounds,
 39142                    toleranceValue,
 39143                    voxelCandidates,
 39144                    results,
 39145                    stopAtFirst))
 146                {
 5147                    return true;
 148                }
 149            }
 150
 28151            return results != null && results.Count > 0;
 152        }
 153        finally
 154        {
 33155            ReleaseContactScratch(scratch);
 33156        }
 33157    }
 158
 159    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 160    private static bool IsSourceGridOnly(VoxelNeighborScope scope) =>
 37161        (scope & ~VoxelNeighborScope.SourceGrid) == VoxelNeighborScope.None;
 162
 163    private static void PopulateCandidateGridIds(
 164        GridWorld world,
 165        TopologyVoxelAabb queryBounds,
 166        SwiftList<ushort> candidateGridIds)
 167    {
 33168        TopologyVoxelAabb spatialBounds = queryBounds.Expand(world.MaxTopologyCellEdge);
 33169        _ = world.CollectGridCandidates(
 33170            spatialBounds.Min,
 33171            spatialBounds.Max,
 33172            candidateGridIds,
 33173            GridWorld.MaxGrids);
 33174    }
 175
 176    private static bool TryGetCandidateGrid(
 177        GridWorld world,
 178        VoxelGrid ownerGrid,
 179        ushort candidateGridId,
 180        VoxelNeighborScope scope,
 181        out VoxelGrid candidateGrid)
 182    {
 70183        candidateGrid = null!;
 70184        VoxelGrid resolvedGrid = world.ActiveGrids[candidateGridId];
 70185        if (!IsCandidateInScope(ownerGrid, resolvedGrid, scope))
 30186            return false;
 187
 40188        candidateGrid = resolvedGrid;
 40189        return true;
 190    }
 191
 192    private static bool TryCollectCandidateVoxels(
 193        Voxel source,
 194        VoxelGrid ownerGrid,
 195        VoxelGrid candidateGrid,
 196        TopologyVoxelAabb queryBounds,
 197        SwiftList<Voxel> voxelCandidates,
 198        SwiftHashSet<Voxel> processedVoxels)
 199    {
 40200        voxelCandidates.Clear();
 40201        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 202        {
 3203            AddSourceGridContactNeighbors(source, ownerGrid, voxelCandidates);
 3204            return true;
 205        }
 206
 37207        if (!TopologyVoxelRangeUtility.TryGetCandidateRange(
 37208            candidateGrid,
 37209            queryBounds,
 37210            out VoxelIndex minIndex,
 37211            out VoxelIndex maxIndex))
 212        {
 1213            return false;
 214        }
 215
 36216        processedVoxels.Clear();
 36217        candidateGrid.AddVoxelsInIndexRange(minIndex, maxIndex, voxelCandidates, processedVoxels);
 36218        SortByVoxelIndex(voxelCandidates);
 36219        return true;
 220    }
 221
 222    private static bool AddOverlappingCandidateVoxels(
 223        Voxel source,
 224        VoxelGrid candidateGrid,
 225        TopologyVoxelAabb sourceBounds,
 226        Fixed64 toleranceValue,
 227        SwiftList<Voxel> voxelCandidates,
 228        SwiftList<Voxel>? results,
 229        bool stopAtFirst)
 230    {
 144231        for (int voxelIndex = 0; voxelIndex < voxelCandidates.Count; voxelIndex++)
 232        {
 38233            Voxel candidateVoxel = voxelCandidates[voxelIndex];
 38234            TopologyVoxelAabb candidateBounds = TopologyVoxelAabb.FromVoxel(candidateGrid, candidateVoxel);
 38235            if (!sourceBounds.Overlaps(candidateBounds, toleranceValue))
 236                continue;
 237
 35238            if (stopAtFirst)
 5239                return true;
 240
 30241            results!.Add(candidateVoxel);
 242        }
 243
 34244        return false;
 245    }
 246
 247    private static bool ResolveSourceGridContactNeighbors(
 248        Voxel source,
 249        VoxelGrid ownerGrid,
 250        SwiftList<Voxel>? results,
 251        bool stopAtFirst)
 252    {
 4253        if (stopAtFirst)
 254        {
 60255            for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 256            {
 29257                if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out _))
 1258                    return true;
 259            }
 260
 1261            return false;
 262        }
 263
 2264        SwiftList<Voxel> resultList = results!;
 2265        AddSourceGridContactNeighbors(source, ownerGrid, resultList);
 266
 2267        return resultList.Count > 0;
 268    }
 269
 270    private static void AddSourceGridContactNeighbors(
 271        Voxel source,
 272        VoxelGrid ownerGrid,
 273        SwiftList<Voxel> results)
 274    {
 270275        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 276        {
 130277            if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 4278                results.Add(neighbor!);
 279        }
 5280    }
 281
 282    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 283    private static bool TryGetNeighborFromSlot(
 284        Voxel source,
 285        VoxelGrid ownerGrid,
 286        int slot,
 287        out Voxel? neighbor)
 288    {
 296289        return TryResolveNeighborAtOffset(source, ownerGrid, ownerGrid.GetNeighborOffset(slot), out neighbor);
 290    }
 291
 292    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 293    private static bool TryGetLocalNeighborFromSlot(
 294        Voxel source,
 295        VoxelGrid ownerGrid,
 296        int slot,
 297        out Voxel? neighbor)
 298    {
 159299        VoxelIndex offset = ownerGrid.GetNeighborOffset(slot);
 159300        VoxelIndex neighborCoords = new(
 159301            source.Index.x + offset.x,
 159302            source.Index.y + offset.y,
 159303            source.Index.z + offset.z);
 304
 159305        return ownerGrid.TryGetVoxel(neighborCoords, out neighbor);
 306    }
 307
 308    private static bool TryResolveNeighborAtOffset(
 309        Voxel source,
 310        VoxelGrid ownerGrid,
 311        VoxelIndex offset,
 312        out Voxel? neighbor)
 313    {
 296314        neighbor = null;
 296315        VoxelIndex neighborCoords = new(
 296316            source.Index.x + offset.x,
 296317            source.Index.y + offset.y,
 296318            source.Index.z + offset.z);
 319
 296320        if (ownerGrid.TryGetVoxel(neighborCoords, out neighbor))
 204321            return true;
 322
 92323        GridWorld world = ownerGrid.World!;
 92324        Vector3d neighborPosition = source.WorldPosition + ownerGrid.GetWorldOffset((offset.x, offset.y, offset.z));
 92325        if (!world.TryGetVoxel(neighborPosition, out neighbor) || neighbor == null)
 84326            return false;
 327
 8328        VoxelGrid neighborGrid = world.ActiveGrids[neighbor.WorldIndex.GridIndex];
 8329        if (neighborGrid.TopologyKind == ownerGrid.TopologyKind)
 7330            return true;
 331
 1332        neighbor = null;
 1333        return false;
 334    }
 335
 336    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 337    private static bool IsCandidateInScope(
 338        VoxelGrid ownerGrid,
 339        VoxelGrid candidateGrid,
 340        VoxelNeighborScope scope)
 341    {
 70342        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 31343            return (scope & VoxelNeighborScope.SourceGrid) != 0;
 344
 39345        return candidateGrid.TopologyKind == ownerGrid.TopologyKind
 39346            ? (scope & VoxelNeighborScope.SameTopologyGrids) != 0
 39347            : (scope & VoxelNeighborScope.MixedTopologyGrids) != 0;
 348    }
 349
 350    private static NeighborResolverScratch RentContactScratch()
 351    {
 33352        NeighborResolverScratch scratch = _contactScratch ??= new NeighborResolverScratch();
 33353        return scratch;
 354    }
 355
 356    private static void ReleaseContactScratch(NeighborResolverScratch scratch)
 357    {
 33358        scratch.Clear();
 33359    }
 360
 361    private static void SortByVoxelIndex(SwiftList<Voxel> voxels)
 362    {
 36363        int count = voxels.Count;
 36364        if (count <= 1)
 34365            return;
 366
 2367        Array.Sort(voxels.InnerArray, 0, count, VoxelIndexComparer.Instance);
 2368    }
 369
 370    private sealed class NeighborResolverScratch
 371    {
 2372        public readonly SwiftList<ushort> CandidateGridIds = new();
 2373        public readonly SwiftList<Voxel> CandidateVoxels = new();
 2374        public readonly SwiftHashSet<Voxel> ProcessedVoxels = new();
 375
 376        public void Clear()
 377        {
 33378            CandidateGridIds.Clear();
 33379            CandidateVoxels.Clear();
 33380            ProcessedVoxels.Clear();
 33381        }
 382    }
 383
 384    private sealed class VoxelIndexComparer : IComparer<Voxel>
 385    {
 1386        public static readonly VoxelIndexComparer Instance = new();
 387
 388        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2389        public int Compare(Voxel? left, Voxel? right) => left!.Index.CompareTo(right!.Index);
 390    }
 391}

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.Topology.TopologyVoxelAabb,SwiftCollections.SwiftList`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>)
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)