< 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{
 23416    public GridTopologyKind Kind => GridTopologyKind.HexPrism;
 17
 18    public GridTopologyMetrics Metrics { get; }
 19
 6720    public Fixed64 OverlapTolerance => MaxCellEdge * Fixed64.Half;
 21
 14422    public Fixed64 MaxCellEdge => Metrics.LargestHexEdge;
 23
 7024    public int NeighborSlotCount => HexDirectionUtility.Offsets.Length;
 25
 8326    public HexPrismTopology(GridTopologyMetrics metrics)
 27    {
 8328        Metrics = GridTopologyMetrics.Normalize(GridTopologyKind.HexPrism, metrics);
 8329    }
 30
 31    public GridDimensions CalculateDimensions(Vector3d boundsMin, Vector3d boundsMax)
 32    {
 13633        VoxelIndex maxIndex = GetMaxIndex(boundsMin, boundsMax);
 13634        int height = ((boundsMax.Y - boundsMin.Y) / Metrics.LayerHeight).FloorToInt() + 1;
 13635        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    {
 8140        Fixed64 fixedPadding = padding.HasValue && padding.Value > Fixed64.Zero
 8141            ? padding.Value
 8142            : Fixed64.Zero;
 43
 8144        min -= fixedPadding;
 8145        max += fixedPadding;
 46
 8147        Fixed64 yMin = FloorToLayerOrigin(min.Y);
 8148        Fixed64 yMax = CeilToLayerOrigin(max.Y);
 8149        Vector3d normalizedMin = new(min.X, yMin, min.Z);
 50
 8151        HexCoordinateUtility.WorldOffsetToAxial(
 8152            max.X - normalizedMin.X,
 8153            max.Z - normalizedMin.Z,
 8154            Metrics,
 8155            out Fixed64 qMax,
 8156            out Fixed64 rMax);
 57
 8158        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 8159        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 8160        int maxY = ((yMax - yMin) / Metrics.LayerHeight).FloorToInt();
 8161        Vector3d normalizedMax = normalizedMin + HexCoordinateUtility.AxialToWorldOffset(new VoxelIndex(maxQ, maxY, maxR
 62
 8163        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) =>
 2510134        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    {
 28144        HexCoordinateUtility.WorldOffsetToAxial(
 28145            worldDelta.X,
 28146            worldDelta.Z,
 28147            Metrics,
 28148            out Fixed64 q,
 28149            out Fixed64 r);
 28150        HexCoordinateUtility.RoundCube(q, r, out int roundedQ, out int roundedR);
 151
 28152        int yDirection = worldDelta.Y.Sign();
 28153        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
 21168        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    {
 136260        HexCoordinateUtility.WorldOffsetToAxial(
 136261            boundsMax.X - boundsMin.X,
 136262            boundsMax.Z - boundsMin.Z,
 136263            Metrics,
 136264            out Fixed64 qMax,
 136265            out Fixed64 rMax);
 266
 136267        int maxQ = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(qMax, Fixed64.Zero));
 136268        int maxR = HexCoordinateUtility.CeilToIntWithTolerance(FixedMath.Max(rMax, Fixed64.Zero));
 269
 136270        return new VoxelIndex(
 136271            maxQ,
 136272            0,
 136273            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) =>
 81284        (coordinate / Metrics.LayerHeight).FloorToInt() * Metrics.LayerHeight;
 285
 286    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 287    private Fixed64 CeilToLayerOrigin(Fixed64 coordinate) =>
 81288        (coordinate / Metrics.LayerHeight).CeilToInt() * Metrics.LayerHeight;
 289
 290    private static bool TryGetNeighborSlot(VoxelIndex neighborOffset, out int slot)
 291    {
 306292        for (int i = 0; i < HexDirectionUtility.Offsets.Length; i++)
 293        {
 152294            if (HexDirectionUtility.Offsets[i] == neighborOffset)
 295            {
 26296                slot = i;
 26297                return true;
 298            }
 299        }
 300
 1301        slot = -1;
 1302        return false;
 303    }
 304
 305    private static VoxelIndex NormalizePlanarOffset(int q, int r)
 306    {
 27307        if (q > 0 && r == 0)
 13308            return new VoxelIndex(1, 0, 0);
 14309        if (q > 0 && r < 0)
 1310            return new VoxelIndex(1, 0, -1);
 13311        if (q == 0 && r < 0)
 1312            return new VoxelIndex(0, 0, -1);
 12313        if (q < 0 && r == 0)
 7314            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)