< 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
98%
Covered branches: 61
Total branches: 62
Branch coverage: 98.3%
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(...)87.5%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 FixedMathSharp;
 9using GridForge.Spatial;
 10using System.Runtime.CompilerServices;
 11
 12namespace GridForge.Grids.Topology;
 13
 14internal sealed class HexPrismTopology : IGridTopology
 15{
 16916    public GridTopologyKind Kind => GridTopologyKind.HexPrism;
 17
 18    public GridTopologyMetrics Metrics { get; }
 19
 2220    public Fixed64 OverlapTolerance => MaxCellEdge * Fixed64.Half;
 21
 8322    public Fixed64 MaxCellEdge => Metrics.LargestHexEdge;
 23
 6024    public int NeighborSlotCount => HexDirectionUtility.Offsets.Length;
 25
 7026    public HexPrismTopology(GridTopologyMetrics metrics)
 27    {
 7028        Metrics = GridTopologyMetrics.Normalize(GridTopologyKind.HexPrism, metrics);
 7029    }
 30
 31    public GridDimensions CalculateDimensions(Vector3d boundsMin, Vector3d boundsMax)
 32    {
 11633        VoxelIndex maxIndex = GetMaxIndex(boundsMin, boundsMax);
 11634        int height = ((boundsMax.Y - boundsMin.Y) / Metrics.LayerHeight).FloorToInt() + 1;
 11635        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    {
 7040        Fixed64 fixedPadding = padding.HasValue && padding.Value > Fixed64.Zero
 7041            ? padding.Value
 7042            : Fixed64.Zero;
 43
 7044        min -= fixedPadding;
 7045        max += fixedPadding;
 46
 7047        Fixed64 yMin = FloorToLayerOrigin(min.Y);
 7048        Fixed64 yMax = CeilToLayerOrigin(max.Y);
 7049        Vector3d normalizedMin = new(min.X, yMin, min.Z);
 50
 7051        HexCoordinateUtility.WorldOffsetToAxial(
 7052            max.X - normalizedMin.X,
 7053            max.Z - normalizedMin.Z,
 7054            Metrics,
 7055            out Fixed64 qMax,
 7056            out Fixed64 rMax);
 57
 7058        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 7059        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 7060        int maxY = ((yMax - yMin) / Metrics.LayerHeight).FloorToInt();
 7061        Vector3d normalizedMax = normalizedMin + HexCoordinateUtility.AxialToWorldOffset(new VoxelIndex(maxQ, maxY, maxR
 62
 7063        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) =>
 4474        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    {
 8685        result = default;
 86
 8687        if (!IsInsideCoarseBounds(boundsMin, boundsMax, position))
 2188            return false;
 89
 6590        HexCoordinateUtility.WorldOffsetToAxial(
 6591            position.X - boundsMin.X,
 6592            position.Z - boundsMin.Z,
 6593            Metrics,
 6594            out Fixed64 q,
 6595            out Fixed64 r);
 96
 6597        Fixed64 layer = (position.Y - boundsMin.Y) / Metrics.LayerHeight;
 6598        VoxelIndex rounded = HexCoordinateUtility.RoundAxial(q, layer, r);
 6599        if ((uint)rounded.x >= (uint)width
 65100            || (uint)rounded.y >= (uint)height
 65101            || (uint)rounded.z >= (uint)length)
 102        {
 2103            return false;
 104        }
 105
 63106        result = rounded;
 63107        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) =>
 2472134        boundsMin + HexCoordinateUtility.AxialToWorldOffset(index, Metrics);
 135
 136    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 137    public Vector3d GetWorldOffset((int x, int y, int z) offset) =>
 19138        HexCoordinateUtility.AxialToWorldOffset(new VoxelIndex(offset.x, offset.y, offset.z), Metrics);
 139
 44140    public VoxelIndex GetNeighborOffset(int slot) => HexDirectionUtility.Offsets[slot];
 141
 142    public bool TryGetNeighborSlotFromWorldDelta(Vector3d worldDelta, out int slot)
 143    {
 18144        HexCoordinateUtility.WorldOffsetToAxial(
 18145            worldDelta.X,
 18146            worldDelta.Z,
 18147            Metrics,
 18148            out Fixed64 q,
 18149            out Fixed64 r);
 18150        HexCoordinateUtility.RoundCube(q, r, out int roundedQ, out int roundedR);
 151
 18152        int yDirection = worldDelta.Y.Sign();
 18153        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
 11168        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    {
 16205        VoxelIndex index = ClampToGrid(boundsMin, width, height, length, position);
 16206        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    {
 17242        HexCoordinateUtility.WorldOffsetToAxial(
 17243            position.X - boundsMin.X,
 17244            position.Z - boundsMin.Z,
 17245            Metrics,
 17246            out Fixed64 q,
 17247            out Fixed64 r);
 248
 17249        Fixed64 layer = (position.Y - boundsMin.Y) / Metrics.LayerHeight;
 17250        VoxelIndex rounded = HexCoordinateUtility.RoundAxial(q, layer, r);
 251
 17252        return new VoxelIndex(
 17253            FixedMath.Clamp(rounded.x, 0, width - 1),
 17254            FixedMath.Clamp(rounded.y, 0, height - 1),
 17255            FixedMath.Clamp(rounded.z, 0, length - 1));
 256    }
 257
 258    private VoxelIndex GetMaxIndex(Vector3d boundsMin, Vector3d boundsMax)
 259    {
 116260        HexCoordinateUtility.WorldOffsetToAxial(
 116261            boundsMax.X - boundsMin.X,
 116262            boundsMax.Z - boundsMin.Z,
 116263            Metrics,
 116264            out Fixed64 qMax,
 116265            out Fixed64 rMax);
 266
 116267        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 116268        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 269
 116270        return new VoxelIndex(
 116271            maxQ,
 116272            0,
 116273            maxR);
 274    }
 275
 276    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 277    private bool IsInsideCoarseBounds(Vector3d boundsMin, Vector3d boundsMax, Vector3d position) =>
 86278        boundsMin.X <= position.X && position.X <= boundsMax.X
 86279        && boundsMin.Y <= position.Y && position.Y <= boundsMax.Y
 86280        && boundsMin.Z <= position.Z && position.Z <= boundsMax.Z;
 281
 282    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 283    private Fixed64 FloorToLayerOrigin(Fixed64 coordinate) =>
 70284        (coordinate / Metrics.LayerHeight).FloorToInt() * Metrics.LayerHeight;
 285
 286    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 287    private Fixed64 CeilToLayerOrigin(Fixed64 coordinate) =>
 70288        (coordinate / Metrics.LayerHeight).CeilToInt() * Metrics.LayerHeight;
 289
 290    private static bool TryGetNeighborSlot(VoxelIndex neighborOffset, out int slot)
 291    {
 262292        for (int i = 0; i < HexDirectionUtility.Offsets.Length; i++)
 293        {
 130294            if (HexDirectionUtility.Offsets[i] == neighborOffset)
 295            {
 16296                slot = i;
 16297                return true;
 298            }
 299        }
 300
 1301        slot = -1;
 1302        return false;
 303    }
 304
 305    private static VoxelIndex NormalizePlanarOffset(int q, int r)
 306    {
 17307        if (q > 0 && r == 0)
 7308            return new VoxelIndex(1, 0, 0);
 10309        if (q > 0 && r < 0)
 1310            return new VoxelIndex(1, 0, -1);
 9311        if (q == 0 && r < 0)
 1312            return new VoxelIndex(0, 0, -1);
 8313        if (q < 0 && r == 0)
 3314            return new VoxelIndex(-1, 0, 0);
 5315        if (q < 0 && r > 0)
 2316            return new VoxelIndex(-1, 0, 1);
 3317        if (q == 0 && r > 0)
 1318            return new VoxelIndex(0, 0, 1);
 319
 2320        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)