< Summary

Information
Class: GridForge.Grids.Topology.GridNavigationPortal
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Topology/GridNavigationPortal.cs
Line coverage
100%
Covered lines: 71
Uncovered lines: 0
Coverable lines: 71
Total lines: 184
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
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_IsValid()100%88100%
.ctor(...)100%11100%
TryTranslate(...)100%1010100%
TryResolveProfile(...)100%1414100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// GridNavigationPortal.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 System.Runtime.InteropServices;
 10using FixedMathSharp;
 11
 12namespace GridForge.Grids.Topology;
 13
 14/// <summary>
 15/// Stores one exact, agent-independent navigation crossing compiled from two cell prisms.
 16/// </summary>
 17/// <remarks>
 18/// The value retains no live grid state. Profile resolution uses only the stored face geometry,
 19/// direction, and conservative fixed-point capacities.
 20/// </remarks>
 21[StructLayout(LayoutKind.Sequential)]
 22public readonly struct GridNavigationPortal
 23{
 24    /// <summary>The stable retained value size used by exact byte-accounted storage.</summary>
 25    public const int SizeInBytes = 104;
 26
 27    /// <summary>The orientation of the shared positive-area face.</summary>
 28    public VoxelContactFaceKind FaceKind { get; }
 29
 30    /// <summary>The exact target-center displacement from the source center.</summary>
 31    public Vector3d SourceToTarget { get; }
 32
 33    /// <summary>The canonical foot crossing on the shared face.</summary>
 34    public Vector3d CanonicalFacePoint { get; }
 35
 36    /// <summary>The greatest conservatively representable horizontal body radius at the crossing.</summary>
 37    public Fixed64 MaximumHorizontalRadius { get; }
 38
 39    /// <summary>The greatest body height supported by both directed sides of the crossing.</summary>
 40    public Fixed64 MaximumBodyHeight { get; }
 41
 42    /// <summary>The first exact XZ endpoint certifying a compiled vertical face.</summary>
 43    public Vector2d VerticalFaceSegmentStart { get; }
 44
 45    /// <summary>The second exact XZ endpoint certifying a compiled vertical face.</summary>
 46    public Vector2d VerticalFaceSegmentEnd { get; }
 47
 48    /// <summary>Whether this value contains a compiled positive-area navigation face.</summary>
 49    public bool IsValid =>
 1513950        ((FaceKind == VoxelContactFaceKind.Vertical
 1513951                && VerticalFaceSegmentStart != VerticalFaceSegmentEnd)
 1513952            || FaceKind == VoxelContactFaceKind.Horizontal)
 1513953        && MaximumHorizontalRadius >= Fixed64.Zero
 1513954        && MaximumBodyHeight > Fixed64.Zero;
 55
 56    internal GridNavigationPortal(
 57        VoxelContactFaceKind faceKind,
 58        Vector3d sourceToTarget,
 59        Vector3d canonicalFacePoint,
 60        Fixed64 maximumHorizontalRadius,
 61        Fixed64 maximumBodyHeight,
 62        Vector2d verticalFaceSegmentStart,
 63        Vector2d verticalFaceSegmentEnd)
 64    {
 244365        FaceKind = faceKind;
 244366        SourceToTarget = sourceToTarget;
 244367        CanonicalFacePoint = canonicalFacePoint;
 244368        MaximumHorizontalRadius = maximumHorizontalRadius;
 244369        MaximumBodyHeight = maximumBodyHeight;
 244370        VerticalFaceSegmentStart = verticalFaceSegmentStart;
 244371        VerticalFaceSegmentEnd = verticalFaceSegmentEnd;
 244372    }
 73
 74    /// <summary>
 75    /// Attempts to rigidly translate the canonical face point without fixed-point saturation.
 76    /// </summary>
 77    /// <param name="offset">The exact translation applied to the canonical face point.</param>
 78    /// <param name="translated">The translated portal, or <see langword="default"/> on failure.</param>
 79    /// <returns>
 80    /// <see langword="true"/> when this portal is valid and the translated face point is representable;
 81    /// otherwise, <see langword="false"/>.
 82    /// </returns>
 83    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 84    public bool TryTranslate(Vector3d offset, out GridNavigationPortal translated)
 85    {
 1386        translated = default;
 1387        if (!IsValid
 1388            || !Vector3d.TryAdd(CanonicalFacePoint, offset, out Vector3d translatedFacePoint))
 89        {
 390            return false;
 91        }
 92
 1093        Vector2d translatedSegmentStart = default;
 1094        Vector2d translatedSegmentEnd = default;
 1095        if (FaceKind == VoxelContactFaceKind.Vertical)
 96        {
 997            var horizontalOffset = new Vector2d(offset.X, offset.Z);
 998            if (!Vector2d.TryAdd(
 999                    VerticalFaceSegmentStart,
 9100                    horizontalOffset,
 9101                    out translatedSegmentStart)
 9102                || !Vector2d.TryAdd(
 9103                    VerticalFaceSegmentEnd,
 9104                    horizontalOffset,
 9105                    out translatedSegmentEnd))
 106            {
 2107                return false;
 108            }
 109        }
 110
 8111        translated = new GridNavigationPortal(
 8112            FaceKind,
 8113            SourceToTarget,
 8114            translatedFacePoint,
 8115            MaximumHorizontalRadius,
 8116            MaximumBodyHeight,
 8117            translatedSegmentStart,
 8118            translatedSegmentEnd);
 8119        return true;
 120    }
 121
 122    /// <summary>
 123    /// Attempts to fit a fixed-point body profile and resolve its directed source and target foot anchors.
 124    /// </summary>
 125    /// <param name="horizontalRadius">The required nonnegative horizontal body radius.</param>
 126    /// <param name="bodyHeight">The required positive body height.</param>
 127    /// <param name="sourceFootAnchor">The last canonical foot anchor on the source side.</param>
 128    /// <param name="targetFootAnchor">The first canonical foot anchor on the target side.</param>
 129    /// <returns><see langword="true"/> when the profile fits and both anchors are representable.</returns>
 130    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 131    public bool TryResolveProfile(
 132        Fixed64 horizontalRadius,
 133        Fixed64 bodyHeight,
 134        out Vector3d sourceFootAnchor,
 135        out Vector3d targetFootAnchor)
 136    {
 2351137        SwiftThrowHelper.ThrowIfArgument(
 2351138            horizontalRadius < Fixed64.Zero,
 2351139            nameof(horizontalRadius),
 2351140            "Horizontal radius must be nonnegative.");
 2350141        SwiftThrowHelper.ThrowIfArgument(
 2350142            bodyHeight <= Fixed64.Zero,
 2350143            nameof(bodyHeight),
 2350144            "Body height must be positive.");
 145
 2349146        sourceFootAnchor = default;
 2349147        targetFootAnchor = default;
 2349148        if (!IsValid
 2349149            || horizontalRadius > MaximumHorizontalRadius
 2349150            || bodyHeight > MaximumBodyHeight)
 151        {
 10152            return false;
 153        }
 154
 2339155        if (FaceKind == VoxelContactFaceKind.Vertical)
 156        {
 2322157            sourceFootAnchor = CanonicalFacePoint;
 2322158            targetFootAnchor = CanonicalFacePoint;
 2322159            return true;
 160        }
 161
 17162        if (!Fixed64.TrySubtract(CanonicalFacePoint.Y, bodyHeight, out Fixed64 lowerFootY))
 1163            return false;
 164
 16165        Vector3d lowerFoot = new Vector3d(CanonicalFacePoint.X, lowerFootY, CanonicalFacePoint.Z);
 16166        if (SourceToTarget.Y > Fixed64.Zero)
 167        {
 12168            sourceFootAnchor = lowerFoot;
 12169            targetFootAnchor = CanonicalFacePoint;
 12170            return true;
 171        }
 172
 4173        if (SourceToTarget.Y < Fixed64.Zero)
 174        {
 3175            sourceFootAnchor = CanonicalFacePoint;
 3176            targetFootAnchor = lowerFoot;
 3177            return true;
 178        }
 179
 1180        sourceFootAnchor = default;
 1181        targetFootAnchor = default;
 1182        return false;
 183    }
 184}