< Summary

Information
Class: GridForge.Grids.Topology.HexPrismTopology
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Topology/HexPrismTopology.cs
Line coverage
100%
Covered lines: 149
Uncovered lines: 0
Coverable lines: 149
Total lines: 322
Line coverage: 100%
Branch coverage
100%
Covered branches: 62
Total branches: 62
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Kind()100%11100%
get_OverlapTolerance()100%11100%
get_MaxCellEdge()100%11100%
get_NeighborSlotCount()100%11100%
.ctor(...)100%11100%
CalculateDimensions(...)100%11100%
NormalizeBounds(...)100%44100%
IsInBounds(...)100%11100%
TryGetVoxelIndex(...)100%88100%
GetClosestVoxelIndex(...)100%11100%
GetWorldPosition(...)100%11100%
GetWorldOffset(...)100%11100%
GetNeighborOffset(...)100%11100%
TryGetNeighborSlotFromWorldDelta(...)100%88100%
IsFacingBoundary(...)100%44100%
GetBoundaryRange(...)100%11100%
FloorToGrid(...)100%11100%
CeilToGrid(...)100%11100%
SnapToScanCell(...)100%11100%
ClampToGrid(...)100%11100%
GetMaxIndex(...)100%11100%
IsInsideCoarseBounds(...)100%1010100%
FloorToLayerOrigin(...)100%11100%
CeilToLayerOrigin(...)100%11100%
TryGetNeighborSlot(...)100%44100%
NormalizePlanarOffset(...)100%2424100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// HexPrismTopology.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.Runtime.CompilerServices;
 9using FixedMathSharp;
 10using GridForge.Spatial;
 11
 12namespace GridForge.Grids.Topology;
 13
 14internal sealed class HexPrismTopology : IGridTopology
 15{
 36816    public GridTopologyKind Kind => GridTopologyKind.HexPrism;
 17
 18    public GridTopologyMetrics Metrics { get; }
 19
 9720    public Fixed64 OverlapTolerance => MaxCellEdge * Fixed64.Half;
 21
 20722    public Fixed64 MaxCellEdge => Metrics.LargestHexEdge;
 23
 26824    public int NeighborSlotCount => HexDirectionUtility.Offsets.Length;
 25
 12026    public HexPrismTopology(GridTopologyMetrics metrics)
 27    {
 12028        Metrics = GridTopologyMetrics.Normalize(GridTopologyKind.HexPrism, metrics);
 12029    }
 30
 31    public GridDimensions CalculateDimensions(Vector3d boundsMin, Vector3d boundsMax)
 32    {
 20333        VoxelIndex maxIndex = GetMaxIndex(boundsMin, boundsMax);
 20334        int height = ((boundsMax.Y - boundsMin.Y) / Metrics.LayerHeight).FloorToInt() + 1;
 20335        return new GridDimensions(maxIndex.x + 1, height, maxIndex.z + 1);
 36    }
 37
 38    public (Vector3d min, Vector3d max) NormalizeBounds(Vector3d min, Vector3d max, Fixed64? padding = null)
 39    {
 11840        Fixed64 fixedPadding = padding.HasValue && padding.Value > Fixed64.Zero
 11841            ? padding.Value
 11842            : Fixed64.Zero;
 43
 11844        min -= fixedPadding;
 11845        max += fixedPadding;
 46
 11847        Fixed64 yMin = FloorToLayerOrigin(min.Y);
 11848        Fixed64 yMax = CeilToLayerOrigin(max.Y);
 11849        Vector3d normalizedMin = new(min.X, yMin, min.Z);
 50
 11851        HexCoordinateUtility.WorldOffsetToAxial(
 11852            max.X - normalizedMin.X,
 11853            max.Z - normalizedMin.Z,
 11854            Metrics,
 11855            out Fixed64 qMax,
 11856            out Fixed64 rMax);
 57
 11858        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 11859        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 11860        int maxY = ((yMax - yMin) / Metrics.LayerHeight).FloorToInt();
 11861        Vector3d normalizedMax = normalizedMin + HexCoordinateUtility.AxialToWorldOffset(new VoxelIndex(maxQ, maxY, maxR
 62
 11863        return (normalizedMin, normalizedMax);
 64    }
 65
 66    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 67    public bool IsInBounds(
 68        Vector3d boundsMin,
 69        Vector3d boundsMax,
 70        int width,
 71        int height,
 72        int length,
 73        Vector3d position) =>
 2474        TryGetVoxelIndex(boundsMin, boundsMax, width, height, length, position, out _);
 75
 76    public bool TryGetVoxelIndex(
 77        Vector3d boundsMin,
 78        Vector3d boundsMax,
 79        int width,
 80        int height,
 81        int length,
 82        Vector3d position,
 83        out VoxelIndex result)
 84    {
 7285        result = default;
 86
 7287        if (!IsInsideCoarseBounds(boundsMin, boundsMax, position))
 588            return false;
 89
 6790        HexCoordinateUtility.WorldOffsetToAxial(
 6791            position.X - boundsMin.X,
 6792            position.Z - boundsMin.Z,
 6793            Metrics,
 6794            out Fixed64 q,
 6795            out Fixed64 r);
 96
 6797        Fixed64 layer = (position.Y - boundsMin.Y) / Metrics.LayerHeight;
 6798        VoxelIndex rounded = HexCoordinateUtility.RoundAxial(q, layer, r);
 6799        if ((uint)rounded.x >= (uint)width
 67100            || (uint)rounded.y >= (uint)height
 67101            || (uint)rounded.z >= (uint)length)
 102        {
 7103            return false;
 104        }
 105
 60106        result = rounded;
 60107        return true;
 108    }
 109
 110    public VoxelIndex GetClosestVoxelIndex(
 111        Vector3d boundsMin,
 112        int width,
 113        int height,
 114        int length,
 115        Vector3d position)
 116    {
 4117        HexCoordinateUtility.WorldOffsetToAxial(
 4118            position.X - boundsMin.X,
 4119            position.Z - boundsMin.Z,
 4120            Metrics,
 4121            out Fixed64 q,
 4122            out Fixed64 r);
 4123        HexCoordinateUtility.RoundCube(q, r, out int roundedQ, out int roundedR);
 124
 4125        int roundedY = ((position.Y - boundsMin.Y) / Metrics.LayerHeight).RoundToInt();
 4126        return new VoxelIndex(
 4127            FixedMath.Clamp(roundedQ, 0, width - 1),
 4128            FixedMath.Clamp(roundedY, 0, height - 1),
 4129            FixedMath.Clamp(roundedR, 0, length - 1));
 130    }
 131
 132    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 133    public Vector3d GetWorldPosition(Vector3d boundsMin, VoxelIndex index) =>
 2842134        boundsMin + HexCoordinateUtility.AxialToWorldOffset(index, Metrics);
 135
 136    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 137    public Vector3d GetWorldOffset((int x, int y, int z) offset) =>
 209138        HexCoordinateUtility.AxialToWorldOffset(new VoxelIndex(offset.x, offset.y, offset.z), Metrics);
 139
 234140    public VoxelIndex GetNeighborOffset(int slot) => HexDirectionUtility.Offsets[slot];
 141
 142    public bool TryGetNeighborSlotFromWorldDelta(Vector3d worldDelta, out int slot)
 143    {
 34144        HexCoordinateUtility.WorldOffsetToAxial(
 34145            worldDelta.X,
 34146            worldDelta.Z,
 34147            Metrics,
 34148            out Fixed64 q,
 34149            out Fixed64 r);
 34150        HexCoordinateUtility.RoundCube(q, r, out int roundedQ, out int roundedR);
 151
 34152        int yDirection = worldDelta.Y.Sign();
 34153        if (yDirection != 0)
 154        {
 7155            if (roundedQ == 0 && roundedR == 0)
 1156                return TryGetNeighborSlot(new VoxelIndex(0, yDirection, 0), out slot);
 157
 6158            VoxelIndex planarOffset = NormalizePlanarOffset(roundedQ, roundedR);
 6159            if (planarOffset == default)
 160            {
 1161                slot = -1;
 1162                return false;
 163            }
 164
 5165            return TryGetNeighborSlot(new VoxelIndex(planarOffset.x, yDirection, planarOffset.z), out slot);
 166        }
 167
 27168        return TryGetNeighborSlot(NormalizePlanarOffset(roundedQ, roundedR), out slot);
 169    }
 170
 171    public bool IsFacingBoundary(VoxelIndex voxelIndex, int slot, int width, int height, int length)
 172    {
 4173        VoxelIndex offset = HexDirectionUtility.Offsets[slot];
 4174        return RectangularDirectionUtility.IsAxisFacingBoundary(voxelIndex.x, offset.x, width)
 4175            && RectangularDirectionUtility.IsAxisFacingBoundary(voxelIndex.y, offset.y, height)
 4176            && RectangularDirectionUtility.IsAxisFacingBoundary(voxelIndex.z, offset.z, length);
 177    }
 178
 179    public void GetBoundaryRange(
 180        int slot,
 181        int width,
 182        int height,
 183        int length,
 184        out int xStart,
 185        out int xEnd,
 186        out int yStart,
 187        out int yEnd,
 188        out int zStart,
 189        out int zEnd)
 190    {
 1191        VoxelIndex offset = HexDirectionUtility.Offsets[slot];
 1192        (xStart, xEnd) = RectangularDirectionUtility.GetBoundaryRange(offset.x, width);
 1193        (yStart, yEnd) = RectangularDirectionUtility.GetBoundaryRange(offset.y, height);
 1194        (zStart, zEnd) = RectangularDirectionUtility.GetBoundaryRange(offset.z, length);
 1195    }
 196
 197    public Vector3d FloorToGrid(
 198        Vector3d boundsMin,
 199        Vector3d boundsMax,
 200        int width,
 201        int height,
 202        int length,
 203        Vector3d position)
 204    {
 19205        VoxelIndex index = ClampToGrid(boundsMin, width, height, length, position);
 19206        return GetWorldPosition(boundsMin, index);
 207    }
 208
 209    public Vector3d CeilToGrid(
 210        Vector3d boundsMin,
 211        Vector3d boundsMax,
 212        int width,
 213        int height,
 214        int length,
 215        Vector3d position)
 216    {
 1217        VoxelIndex index = ClampToGrid(boundsMin, width, height, length, position);
 1218        return GetWorldPosition(boundsMin, index);
 219    }
 220
 221    public (int x, int y, int z) SnapToScanCell(Vector3d boundsMin, Vector3d position, int scanCellSize)
 222    {
 1223        HexCoordinateUtility.WorldOffsetToAxial(
 1224            position.X - boundsMin.X,
 1225            position.Z - boundsMin.Z,
 1226            Metrics,
 1227            out Fixed64 q,
 1228            out Fixed64 r);
 229
 1230        Fixed64 layer = (position.Y - boundsMin.Y) / Metrics.LayerHeight;
 1231        VoxelIndex index = HexCoordinateUtility.RoundAxial(q, layer, r);
 1232        return (index.x / scanCellSize, index.y / scanCellSize, index.z / scanCellSize);
 233    }
 234
 235    private VoxelIndex ClampToGrid(
 236        Vector3d boundsMin,
 237        int width,
 238        int height,
 239        int length,
 240        Vector3d position)
 241    {
 20242        HexCoordinateUtility.WorldOffsetToAxial(
 20243            position.X - boundsMin.X,
 20244            position.Z - boundsMin.Z,
 20245            Metrics,
 20246            out Fixed64 q,
 20247            out Fixed64 r);
 248
 20249        Fixed64 layer = (position.Y - boundsMin.Y) / Metrics.LayerHeight;
 20250        VoxelIndex rounded = HexCoordinateUtility.RoundAxial(q, layer, r);
 251
 20252        return new VoxelIndex(
 20253            FixedMath.Clamp(rounded.x, 0, width - 1),
 20254            FixedMath.Clamp(rounded.y, 0, height - 1),
 20255            FixedMath.Clamp(rounded.z, 0, length - 1));
 256    }
 257
 258    private VoxelIndex GetMaxIndex(Vector3d boundsMin, Vector3d boundsMax)
 259    {
 203260        HexCoordinateUtility.WorldOffsetToAxial(
 203261            boundsMax.X - boundsMin.X,
 203262            boundsMax.Z - boundsMin.Z,
 203263            Metrics,
 203264            out Fixed64 qMax,
 203265            out Fixed64 rMax);
 266
 203267        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 203268        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 269
 203270        return new VoxelIndex(
 203271            maxQ,
 203272            0,
 203273            maxR);
 274    }
 275
 276    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 277    private bool IsInsideCoarseBounds(Vector3d boundsMin, Vector3d boundsMax, Vector3d position) =>
 72278        boundsMin.X <= position.X && position.X <= boundsMax.X
 72279        && boundsMin.Y <= position.Y && position.Y <= boundsMax.Y
 72280        && boundsMin.Z <= position.Z && position.Z <= boundsMax.Z;
 281
 282    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 283    private Fixed64 FloorToLayerOrigin(Fixed64 coordinate) =>
 118284        (coordinate / Metrics.LayerHeight).FloorToInt() * Metrics.LayerHeight;
 285
 286    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 287    private Fixed64 CeilToLayerOrigin(Fixed64 coordinate) =>
 118288        (coordinate / Metrics.LayerHeight).CeilToInt() * Metrics.LayerHeight;
 289
 290    private static bool TryGetNeighborSlot(VoxelIndex neighborOffset, out int slot)
 291    {
 558292        for (int i = 0; i < HexDirectionUtility.Offsets.Length; i++)
 293        {
 272294            if (HexDirectionUtility.Offsets[i] == neighborOffset)
 295            {
 26296                slot = i;
 26297                return true;
 298            }
 299        }
 300
 7301        slot = -1;
 7302        return false;
 303    }
 304
 305    private static VoxelIndex NormalizePlanarOffset(int q, int r)
 306    {
 33307        if (q > 0 && r == 0)
 13308            return new VoxelIndex(1, 0, 0);
 20309        if (q > 0 && r < 0)
 1310            return new VoxelIndex(1, 0, -1);
 19311        if (q == 0 && r < 0)
 1312            return new VoxelIndex(0, 0, -1);
 18313        if (q < 0 && r == 0)
 7314            return new VoxelIndex(-1, 0, 0);
 11315        if (q < 0 && r > 0)
 2316            return new VoxelIndex(-1, 0, 1);
 9317        if (q == 0 && r > 0)
 1318            return new VoxelIndex(0, 0, 1);
 319
 8320        return default;
 321    }
 322}

Methods/Properties

get_Kind()
get_OverlapTolerance()
get_MaxCellEdge()
get_NeighborSlotCount()
.ctor(GridForge.Grids.Topology.GridTopologyMetrics)
CalculateDimensions(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
NormalizeBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Nullable`1<FixedMathSharp.Fixed64>)
IsInBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d)
TryGetVoxelIndex(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d,GridForge.Spatial.VoxelIndex&)
GetClosestVoxelIndex(FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d)
GetWorldPosition(FixedMathSharp.Vector3d,GridForge.Spatial.VoxelIndex)
GetWorldOffset(System.ValueTuple`3<System.Int32,System.Int32,System.Int32>)
GetNeighborOffset(System.Int32)
TryGetNeighborSlotFromWorldDelta(FixedMathSharp.Vector3d,System.Int32&)
IsFacingBoundary(GridForge.Spatial.VoxelIndex,System.Int32,System.Int32,System.Int32,System.Int32)
GetBoundaryRange(System.Int32,System.Int32,System.Int32,System.Int32,System.Int32&,System.Int32&,System.Int32&,System.Int32&,System.Int32&,System.Int32&)
FloorToGrid(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d)
CeilToGrid(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d)
SnapToScanCell(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,System.Int32)
ClampToGrid(FixedMathSharp.Vector3d,System.Int32,System.Int32,System.Int32,FixedMathSharp.Vector3d)
GetMaxIndex(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
IsInsideCoarseBounds(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d)
FloorToLayerOrigin(FixedMathSharp.Fixed64)
CeilToLayerOrigin(FixedMathSharp.Fixed64)
TryGetNeighborSlot(GridForge.Spatial.VoxelIndex,System.Int32&)
NormalizePlanarOffset(System.Int32,System.Int32)