< 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: 149
Uncovered lines: 0
Coverable lines: 149
Total lines: 387
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(spatialBounds.Min, spatialBounds.Max, candidateGridIds);
 33170    }
 171
 172    private static bool TryGetCandidateGrid(
 173        GridWorld world,
 174        VoxelGrid ownerGrid,
 175        ushort candidateGridId,
 176        VoxelNeighborScope scope,
 177        out VoxelGrid candidateGrid)
 178    {
 70179        candidateGrid = null!;
 70180        VoxelGrid resolvedGrid = world.ActiveGrids[candidateGridId];
 70181        if (!IsCandidateInScope(ownerGrid, resolvedGrid, scope))
 30182            return false;
 183
 40184        candidateGrid = resolvedGrid;
 40185        return true;
 186    }
 187
 188    private static bool TryCollectCandidateVoxels(
 189        Voxel source,
 190        VoxelGrid ownerGrid,
 191        VoxelGrid candidateGrid,
 192        TopologyVoxelAabb queryBounds,
 193        SwiftList<Voxel> voxelCandidates,
 194        SwiftHashSet<Voxel> processedVoxels)
 195    {
 40196        voxelCandidates.Clear();
 40197        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 198        {
 3199            AddSourceGridContactNeighbors(source, ownerGrid, voxelCandidates);
 3200            return true;
 201        }
 202
 37203        if (!TopologyVoxelRangeUtility.TryGetCandidateRange(
 37204            candidateGrid,
 37205            queryBounds,
 37206            out VoxelIndex minIndex,
 37207            out VoxelIndex maxIndex))
 208        {
 1209            return false;
 210        }
 211
 36212        processedVoxels.Clear();
 36213        candidateGrid.AddVoxelsInIndexRange(minIndex, maxIndex, voxelCandidates, processedVoxels);
 36214        SortByVoxelIndex(voxelCandidates);
 36215        return true;
 216    }
 217
 218    private static bool AddOverlappingCandidateVoxels(
 219        Voxel source,
 220        VoxelGrid candidateGrid,
 221        TopologyVoxelAabb sourceBounds,
 222        Fixed64 toleranceValue,
 223        SwiftList<Voxel> voxelCandidates,
 224        SwiftList<Voxel>? results,
 225        bool stopAtFirst)
 226    {
 144227        for (int voxelIndex = 0; voxelIndex < voxelCandidates.Count; voxelIndex++)
 228        {
 38229            Voxel candidateVoxel = voxelCandidates[voxelIndex];
 38230            TopologyVoxelAabb candidateBounds = TopologyVoxelAabb.FromVoxel(candidateGrid, candidateVoxel);
 38231            if (!sourceBounds.Overlaps(candidateBounds, toleranceValue))
 232                continue;
 233
 35234            if (stopAtFirst)
 5235                return true;
 236
 30237            results!.Add(candidateVoxel);
 238        }
 239
 34240        return false;
 241    }
 242
 243    private static bool ResolveSourceGridContactNeighbors(
 244        Voxel source,
 245        VoxelGrid ownerGrid,
 246        SwiftList<Voxel>? results,
 247        bool stopAtFirst)
 248    {
 4249        if (stopAtFirst)
 250        {
 60251            for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 252            {
 29253                if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out _))
 1254                    return true;
 255            }
 256
 1257            return false;
 258        }
 259
 2260        SwiftList<Voxel> resultList = results!;
 2261        AddSourceGridContactNeighbors(source, ownerGrid, resultList);
 262
 2263        return resultList.Count > 0;
 264    }
 265
 266    private static void AddSourceGridContactNeighbors(
 267        Voxel source,
 268        VoxelGrid ownerGrid,
 269        SwiftList<Voxel> results)
 270    {
 270271        for (int slot = 0; slot < ownerGrid.NeighborSlotCount; slot++)
 272        {
 130273            if (TryGetLocalNeighborFromSlot(source, ownerGrid, slot, out Voxel? neighbor))
 4274                results.Add(neighbor!);
 275        }
 5276    }
 277
 278    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 279    private static bool TryGetNeighborFromSlot(
 280        Voxel source,
 281        VoxelGrid ownerGrid,
 282        int slot,
 283        out Voxel? neighbor)
 284    {
 296285        return TryResolveNeighborAtOffset(source, ownerGrid, ownerGrid.GetNeighborOffset(slot), out neighbor);
 286    }
 287
 288    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 289    private static bool TryGetLocalNeighborFromSlot(
 290        Voxel source,
 291        VoxelGrid ownerGrid,
 292        int slot,
 293        out Voxel? neighbor)
 294    {
 159295        VoxelIndex offset = ownerGrid.GetNeighborOffset(slot);
 159296        VoxelIndex neighborCoords = new(
 159297            source.Index.x + offset.x,
 159298            source.Index.y + offset.y,
 159299            source.Index.z + offset.z);
 300
 159301        return ownerGrid.TryGetVoxel(neighborCoords, out neighbor);
 302    }
 303
 304    private static bool TryResolveNeighborAtOffset(
 305        Voxel source,
 306        VoxelGrid ownerGrid,
 307        VoxelIndex offset,
 308        out Voxel? neighbor)
 309    {
 296310        neighbor = null;
 296311        VoxelIndex neighborCoords = new(
 296312            source.Index.x + offset.x,
 296313            source.Index.y + offset.y,
 296314            source.Index.z + offset.z);
 315
 296316        if (ownerGrid.TryGetVoxel(neighborCoords, out neighbor))
 204317            return true;
 318
 92319        GridWorld world = ownerGrid.World!;
 92320        Vector3d neighborPosition = source.WorldPosition + ownerGrid.GetWorldOffset((offset.x, offset.y, offset.z));
 92321        if (!world.TryGetVoxel(neighborPosition, out neighbor) || neighbor == null)
 84322            return false;
 323
 8324        VoxelGrid neighborGrid = world.ActiveGrids[neighbor.WorldIndex.GridIndex];
 8325        if (neighborGrid.TopologyKind == ownerGrid.TopologyKind)
 7326            return true;
 327
 1328        neighbor = null;
 1329        return false;
 330    }
 331
 332    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 333    private static bool IsCandidateInScope(
 334        VoxelGrid ownerGrid,
 335        VoxelGrid candidateGrid,
 336        VoxelNeighborScope scope)
 337    {
 70338        if (candidateGrid.GridIndex == ownerGrid.GridIndex)
 31339            return (scope & VoxelNeighborScope.SourceGrid) != 0;
 340
 39341        return candidateGrid.TopologyKind == ownerGrid.TopologyKind
 39342            ? (scope & VoxelNeighborScope.SameTopologyGrids) != 0
 39343            : (scope & VoxelNeighborScope.MixedTopologyGrids) != 0;
 344    }
 345
 346    private static NeighborResolverScratch RentContactScratch()
 347    {
 33348        NeighborResolverScratch scratch = _contactScratch ??= new NeighborResolverScratch();
 33349        return scratch;
 350    }
 351
 352    private static void ReleaseContactScratch(NeighborResolverScratch scratch)
 353    {
 33354        scratch.Clear();
 33355    }
 356
 357    private static void SortByVoxelIndex(SwiftList<Voxel> voxels)
 358    {
 36359        int count = voxels.Count;
 36360        if (count <= 1)
 34361            return;
 362
 2363        Array.Sort(voxels.InnerArray, 0, count, VoxelIndexComparer.Instance);
 2364    }
 365
 366    private sealed class NeighborResolverScratch
 367    {
 2368        public readonly SwiftList<ushort> CandidateGridIds = new();
 2369        public readonly SwiftList<Voxel> CandidateVoxels = new();
 2370        public readonly SwiftHashSet<Voxel> ProcessedVoxels = new();
 371
 372        public void Clear()
 373        {
 33374            CandidateGridIds.Clear();
 33375            CandidateVoxels.Clear();
 33376            ProcessedVoxels.Clear();
 33377        }
 378    }
 379
 380    private sealed class VoxelIndexComparer : IComparer<Voxel>
 381    {
 1382        public static readonly VoxelIndexComparer Instance = new();
 383
 384        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 2385        public int Compare(Voxel? left, Voxel? right) => left!.Index.CompareTo(right!.Index);
 386    }
 387}

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)