< Summary

Information
Class: GridForge.Spatial.RectangularDirectionUtility
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Spatial/RectangularDirectionUtility.cs
Line coverage
100%
Covered lines: 200
Uncovered lines: 0
Coverable lines: 200
Total lines: 334
Line coverage: 100%
Branch coverage
94%
Covered branches: 17
Total branches: 18
Branch coverage: 94.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Offsets()100%11100%
.cctor()100%11100%
get_All()100%11100%
get_Primary()100%11100%
get_Perpendicular()100%11100%
get_Planar()100%11100%
get_Vertical()100%11100%
get_BelowLayer()100%11100%
get_AboveLayer()100%11100%
get_VerticalDiagonal()100%11100%
get_Diagonal()100%11100%
IsPerpendicularNeighbor(...)100%11100%
IsDiagonalNeighbor(...)100%11100%
GetDirectionFromOffset(...)100%22100%
GetBoundaryRange(...)100%44100%
IsAxisFacingBoundary(...)100%44100%
CreateDirectionLookup()100%22100%
TryGetOffsetLookupKey(...)83.33%66100%

File(s)

/home/runner/work/GridForge/GridForge/src/GridForge/Spatial/RectangularDirectionUtility.cs

#LineLine coverage
 1//=======================================================================
 2// RectangularDirectionUtility.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.Runtime.CompilerServices;
 10
 11namespace GridForge.Spatial;
 12
 13/// <summary>
 14/// Provides rectangular-prism neighbor direction utilities.
 15/// </summary>
 16public static class RectangularDirectionUtility
 17{
 18    #region Fields & Properties
 19
 20    private const int OffsetLookupSize = 27;
 21
 22    /// <summary>
 23    /// Predefined offsets for a rectangular-prism 3x3x3 neighbor structure, excluding the center position.
 24    /// </summary>
 125825    public static ReadOnlySpan<(int x, int y, int z)> Offsets => OffsetValues;
 26
 227    private static readonly (int x, int y, int z)[] OffsetValues = new (int x, int y, int z)[26]
 228    {
 229        (-1, 0, 0),
 230        (0, 0, -1),
 231        (1, 0, 0),
 232        (0, 0, 1),
 233        (0, -1, 0),
 234        (0, 1, 0),
 235        (-1, 0, -1),
 236        (-1, 0, 1),
 237        (1, 0, -1),
 238        (1, 0, 1),
 239        (-1, -1, 0),
 240        (0, -1, -1),
 241        (1, -1, 0),
 242        (0, -1, 1),
 243        (-1, 1, 0),
 244        (0, 1, -1),
 245        (1, 1, 0),
 246        (0, 1, 1),
 247        (-1, -1, -1),
 248        (-1, -1, 1),
 249        (1, -1, -1),
 250        (1, -1, 1),
 251        (-1, 1, -1),
 252        (-1, 1, 1),
 253        (1, 1, -1),
 254        (1, 1, 1)
 255    };
 56
 257    private static readonly RectangularDirection[] DirectionLookup = CreateDirectionLookup();
 58
 59    /// <summary>
 60    /// All 26 neighbor directions excluding None.
 61    /// </summary>
 5862    public static ReadOnlySpan<RectangularDirection> All => AllValues;
 63
 264    private static readonly RectangularDirection[] AllValues =
 265    {
 266        RectangularDirection.West,
 267        RectangularDirection.South,
 268        RectangularDirection.East,
 269        RectangularDirection.North,
 270        RectangularDirection.Below,
 271        RectangularDirection.Above,
 272        RectangularDirection.SouthWest,
 273        RectangularDirection.NorthWest,
 274        RectangularDirection.SouthEast,
 275        RectangularDirection.NorthEast,
 276        RectangularDirection.BelowWest,
 277        RectangularDirection.BelowSouth,
 278        RectangularDirection.BelowEast,
 279        RectangularDirection.BelowNorth,
 280        RectangularDirection.AboveWest,
 281        RectangularDirection.AboveSouth,
 282        RectangularDirection.AboveEast,
 283        RectangularDirection.AboveNorth,
 284        RectangularDirection.BelowSouthWest,
 285        RectangularDirection.BelowNorthWest,
 286        RectangularDirection.BelowSouthEast,
 287        RectangularDirection.BelowNorthEast,
 288        RectangularDirection.AboveSouthWest,
 289        RectangularDirection.AboveNorthWest,
 290        RectangularDirection.AboveSouthEast,
 291        RectangularDirection.AboveNorthEast
 292    };
 93
 94    /// <summary>
 95    /// The 6 face-adjacent rectangular-prism directions.
 96    /// </summary>
 297    public static ReadOnlySpan<RectangularDirection> Primary => PrimaryValues;
 98
 299    private static readonly RectangularDirection[] PrimaryValues =
 2100    {
 2101        RectangularDirection.West,
 2102        RectangularDirection.South,
 2103        RectangularDirection.East,
 2104        RectangularDirection.North,
 2105        RectangularDirection.Below,
 2106        RectangularDirection.Above
 2107    };
 108
 109    /// <summary>
 110    /// All 6 perpendicular neighbor directions excluding None.
 111    /// </summary>
 2112    public static ReadOnlySpan<RectangularDirection> Perpendicular => PerpendicularValues;
 113
 2114    private static readonly RectangularDirection[] PerpendicularValues =
 2115    {
 2116        RectangularDirection.West,
 2117        RectangularDirection.South,
 2118        RectangularDirection.East,
 2119        RectangularDirection.North,
 2120        RectangularDirection.Below,
 2121        RectangularDirection.Above
 2122    };
 123
 124    /// <summary>
 125    /// All 8 same-layer XZ-plane directions excluding None.
 126    /// </summary>
 2127    public static ReadOnlySpan<RectangularDirection> Planar => PlanarValues;
 128
 2129    private static readonly RectangularDirection[] PlanarValues =
 2130    {
 2131        RectangularDirection.West,
 2132        RectangularDirection.South,
 2133        RectangularDirection.East,
 2134        RectangularDirection.North,
 2135        RectangularDirection.SouthWest,
 2136        RectangularDirection.NorthWest,
 2137        RectangularDirection.SouthEast,
 2138        RectangularDirection.NorthEast
 2139    };
 140
 141    /// <summary>
 142    /// All 2 vertical directions.
 143    /// </summary>
 1144    public static ReadOnlySpan<RectangularDirection> Vertical => VerticalValues;
 145
 2146    private static readonly RectangularDirection[] VerticalValues =
 2147    {
 2148        RectangularDirection.Below,
 2149        RectangularDirection.Above
 2150    };
 151
 152    /// <summary>
 153    /// All 9 neighbor directions on the layer below.
 154    /// </summary>
 2155    public static ReadOnlySpan<RectangularDirection> BelowLayer => BelowLayerValues;
 156
 2157    private static readonly RectangularDirection[] BelowLayerValues =
 2158    {
 2159        RectangularDirection.Below,
 2160        RectangularDirection.BelowWest,
 2161        RectangularDirection.BelowSouth,
 2162        RectangularDirection.BelowEast,
 2163        RectangularDirection.BelowNorth,
 2164        RectangularDirection.BelowSouthWest,
 2165        RectangularDirection.BelowNorthWest,
 2166        RectangularDirection.BelowSouthEast,
 2167        RectangularDirection.BelowNorthEast
 2168    };
 169
 170    /// <summary>
 171    /// All 9 neighbor directions on the layer above.
 172    /// </summary>
 2173    public static ReadOnlySpan<RectangularDirection> AboveLayer => AboveLayerValues;
 174
 2175    private static readonly RectangularDirection[] AboveLayerValues =
 2176    {
 2177        RectangularDirection.Above,
 2178        RectangularDirection.AboveWest,
 2179        RectangularDirection.AboveSouth,
 2180        RectangularDirection.AboveEast,
 2181        RectangularDirection.AboveNorth,
 2182        RectangularDirection.AboveSouthWest,
 2183        RectangularDirection.AboveNorthWest,
 2184        RectangularDirection.AboveSouthEast,
 2185        RectangularDirection.AboveNorthEast
 2186    };
 187
 188    /// <summary>
 189    /// All 16 non-vertical directions on the layers above and below.
 190    /// </summary>
 2191    public static ReadOnlySpan<RectangularDirection> VerticalDiagonal => VerticalDiagonalValues;
 192
 2193    private static readonly RectangularDirection[] VerticalDiagonalValues =
 2194    {
 2195        RectangularDirection.BelowWest,
 2196        RectangularDirection.BelowSouth,
 2197        RectangularDirection.BelowEast,
 2198        RectangularDirection.BelowNorth,
 2199        RectangularDirection.BelowSouthWest,
 2200        RectangularDirection.BelowNorthWest,
 2201        RectangularDirection.BelowSouthEast,
 2202        RectangularDirection.BelowNorthEast,
 2203        RectangularDirection.AboveWest,
 2204        RectangularDirection.AboveSouth,
 2205        RectangularDirection.AboveEast,
 2206        RectangularDirection.AboveNorth,
 2207        RectangularDirection.AboveSouthWest,
 2208        RectangularDirection.AboveNorthWest,
 2209        RectangularDirection.AboveSouthEast,
 2210        RectangularDirection.AboveNorthEast
 2211    };
 212
 213    /// <summary>
 214    /// All 20 diagonal neighbor directions excluding None.
 215    /// </summary>
 3216    public static ReadOnlySpan<RectangularDirection> Diagonal => DiagonalValues;
 217
 2218    private static readonly RectangularDirection[] DiagonalValues =
 2219    {
 2220        RectangularDirection.SouthWest,
 2221        RectangularDirection.NorthWest,
 2222        RectangularDirection.SouthEast,
 2223        RectangularDirection.NorthEast,
 2224        RectangularDirection.BelowWest,
 2225        RectangularDirection.BelowSouth,
 2226        RectangularDirection.BelowEast,
 2227        RectangularDirection.BelowNorth,
 2228        RectangularDirection.AboveWest,
 2229        RectangularDirection.AboveSouth,
 2230        RectangularDirection.AboveEast,
 2231        RectangularDirection.AboveNorth,
 2232        RectangularDirection.BelowSouthWest,
 2233        RectangularDirection.BelowNorthWest,
 2234        RectangularDirection.BelowSouthEast,
 2235        RectangularDirection.BelowNorthEast,
 2236        RectangularDirection.AboveSouthWest,
 2237        RectangularDirection.AboveNorthWest,
 2238        RectangularDirection.AboveSouthEast,
 2239        RectangularDirection.AboveNorthEast
 2240    };
 241
 242    #endregion
 243
 244    #region Methods
 245
 246    /// <summary>True for pure axis-aligned directions.</summary>
 247    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 248    public static bool IsPerpendicularNeighbor(RectangularDirection dir) =>
 2249        (uint)dir <= (uint)RectangularDirection.Above;
 250
 251    /// <summary>True for any diagonal step (multiple axes).</summary>
 252    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 253    public static bool IsDiagonalNeighbor(RectangularDirection dir) =>
 2254        (uint)((int)dir - (int)RectangularDirection.SouthWest) < Diagonal.Length;
 255
 256    /// <summary>
 257    /// Converts a rectangular local offset in the range [-1, 1] on each axis into a direction.
 258    /// </summary>
 259    public static RectangularDirection GetDirectionFromOffset((int x, int y, int z) offset)
 260    {
 77261        return TryGetOffsetLookupKey(offset, out int lookupKey)
 77262            ? DirectionLookup[lookupKey]
 77263            : RectangularDirection.None;
 264    }
 265
 266    /// <summary>
 267    /// Gets the boundary range for a given offset and size,
 268    /// returning (0, 0) for negative offsets, (size-1, size-1) for positive offsets,
 269    /// and (0, size-1) for zero offsets.
 270    /// </summary>
 271    /// <param name="offset">The offset value.</param>
 272    /// <param name="size">The size of the dimension.</param>
 273    /// <returns>A tuple representing the start and end of the boundary range.</returns>
 274    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 275    public static (int start, int end) GetBoundaryRange(int offset, int size)
 276    {
 9277        return offset switch
 9278        {
 2279            < 0 => (0, 0),
 4280            > 0 => (size - 1, size - 1),
 3281            _ => (0, size - 1)
 9282        };
 283    }
 284
 285    /// <summary>
 286    /// Determines if a coordinate is facing the boundary of an axis given an offset and size.
 287    /// </summary>
 288    /// <param name="coordinate">The coordinate value.</param>
 289    /// <param name="offset">The offset value.</param>
 290    /// <param name="size">The size of the dimension.</param>
 291    /// <returns>True if the coordinate is facing the boundary, otherwise false.</returns>
 292    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 293    public static bool IsAxisFacingBoundary(int coordinate, int offset, int size)
 294    {
 130295        return offset switch
 130296        {
 44297            < 0 => coordinate == 0,
 45298            > 0 => coordinate == size - 1,
 41299            _ => true
 130300        };
 301    }
 302
 303    private static RectangularDirection[] CreateDirectionLookup()
 304    {
 2305        RectangularDirection[] lookup = new RectangularDirection[OffsetLookupSize];
 2306        Array.Fill(lookup, RectangularDirection.None);
 307
 108308        for (int i = 0; i < OffsetValues.Length; i++)
 309        {
 52310            TryGetOffsetLookupKey(OffsetValues[i], out int lookupKey);
 52311            lookup[lookupKey] = (RectangularDirection)i;
 312        }
 313
 2314        return lookup;
 315    }
 316
 317    private static bool TryGetOffsetLookupKey((int x, int y, int z) offset, out int lookupKey)
 318    {
 129319        int x = offset.x + 1;
 129320        int y = offset.y + 1;
 129321        int z = offset.z + 1;
 322
 129323        if ((uint)x > 2u || (uint)y > 2u || (uint)z > 2u)
 324        {
 2325            lookupKey = -1;
 2326            return false;
 327        }
 328
 127329        lookupKey = x * 9 + y * 3 + z;
 127330        return true;
 331    }
 332
 333    #endregion
 334}