< Summary

Information
Class: GridForge.Grids.Topology.GridNavigationCorridorValidationCursor
Assembly: GridForge
File(s): /home/runner/work/GridForge/GridForge/src/GridForge/Grids/Topology/GridNavigationCorridorValidationCursor.cs
Line coverage
100%
Covered lines: 128
Uncovered lines: 0
Coverable lines: 128
Total lines: 304
Line coverage: 100%
Branch coverage
100%
Covered branches: 58
Total branches: 58
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_Status()100%11100%
get_PortalWaypointCount()100%11100%
get_GeometricCost()100%11100%
TryGetCurrentPortal(...)100%22100%
.ctor(...)100%44100%
Advance(...)100%1212100%
PerformNext(...)100%1414100%
ValidateNextPortal(...)100%2020100%
ValidateExitAnchor(...)100%44100%
Fail(...)100%11100%
TryAccumulateDistance(...)100%22100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// GridNavigationCorridorValidationCursor.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 FixedMathSharp;
 10
 11namespace GridForge.Grids.Topology;
 12
 13/// <summary>
 14/// Describes the state of a resumable navigation-corridor validation.
 15/// </summary>
 16public enum GridNavigationCorridorValidationStatus : byte
 17{
 18    /// <summary>The input or corridor geometry is invalid.</summary>
 19    Invalid = 0,
 20
 21    /// <summary>More bounded work is required.</summary>
 22    InProgress = 1,
 23
 24    /// <summary>The corridor certificate is complete.</summary>
 25    Complete = 2,
 26
 27    /// <summary>The canonical geometric cost is not representable.</summary>
 28    CostOverflow = 3
 29}
 30
 31/// <summary>
 32/// Resumably validates one deterministic navigation corridor into caller-owned storage.
 33/// </summary>
 34/// <remarks>
 35/// The ordered cell and waypoint spans are not retained. Their lengths and contents must remain
 36/// stable between calls. A successful corridor of N cells consumes exactly 2N+1 work units.
 37/// Advance one work unit at a time and call <see cref="TryGetCurrentPortal"/> after each call to
 38/// consume every portal certificate in order.
 39/// </remarks>
 40public struct GridNavigationCorridorValidationCursor
 41{
 42    private enum ValidationStage : byte
 43    {
 44        Cells,
 45        EntryAnchor,
 46        Portals,
 47        ExitAnchor
 48    }
 49
 50    private readonly int _cellCount;
 51    private readonly Vector3d _entryAnchor;
 52    private readonly Vector3d _exitAnchor;
 53    private readonly Fixed64 _radiusClearance;
 54    private readonly Fixed64 _heightClearance;
 55    private GridNavigationCorridorValidationStatus _status;
 56    private ValidationStage _stage;
 57    private bool _hasCurrentPortal;
 58    private int _cellIndex;
 59    private int _portalIndex;
 60    private int _portalWaypointCount;
 61    private Vector3d _previousPoint;
 62    private Fixed64 _geometricCost;
 63    private GridNavigationPortal _selectedPortal;
 64
 65    /// <summary>The current validation state.</summary>
 910566    public readonly GridNavigationCorridorValidationStatus Status => _status;
 67
 68    /// <summary>The number of canonical waypoints written, or zero after failure.</summary>
 2469    public readonly int PortalWaypointCount => _portalWaypointCount;
 70
 71    /// <summary>The checked canonical polyline length accumulated so far.</summary>
 2472    public readonly Fixed64 GeometricCost => _geometricCost;
 73
 74    /// <summary>
 75    /// Attempts to get the portal certificate produced by the last completed work unit.
 76    /// </summary>
 77    /// <remarks>
 78    /// The value is available only when the final work unit completed by the most recent advance
 79    /// was successful portal work. Use a one-unit budget to consume every certificate in order.
 80    /// </remarks>
 81    /// <param name="portal">The produced portal, or <see langword="default"/> when unavailable.</param>
 82    /// <returns><see langword="true"/> when a portal certificate is available.</returns>
 83    public readonly bool TryGetCurrentPortal(out GridNavigationPortal portal)
 84    {
 705885        portal = _hasCurrentPortal ? _selectedPortal : default;
 705886        return _hasCurrentPortal;
 87    }
 88
 89    /// <summary>
 90    /// Creates a cursor for one ordered source, witness, and destination cell chain.
 91    /// </summary>
 92    /// <param name="cellCount">The stable number of ordered cells supplied to every advance.</param>
 93    /// <param name="entryAnchor">The source-cell foot anchor.</param>
 94    /// <param name="exitAnchor">The destination-cell foot anchor.</param>
 95    /// <param name="radiusClearance">The required nonnegative horizontal body radius.</param>
 96    /// <param name="heightClearance">The required positive body height.</param>
 97    public GridNavigationCorridorValidationCursor(
 98        int cellCount,
 99        Vector3d entryAnchor,
 100        Vector3d exitAnchor,
 101        Fixed64 radiusClearance,
 102        Fixed64 heightClearance)
 103    {
 1057104        _cellCount = cellCount;
 1057105        _entryAnchor = entryAnchor;
 1057106        _exitAnchor = exitAnchor;
 1057107        _radiusClearance = radiusClearance;
 1057108        _heightClearance = heightClearance;
 1057109        _status = cellCount >= 2
 1057110            && radiusClearance >= Fixed64.Zero
 1057111            && heightClearance > Fixed64.Zero
 1057112                ? GridNavigationCorridorValidationStatus.InProgress
 1057113                : GridNavigationCorridorValidationStatus.Invalid;
 1057114        _stage = ValidationStage.Cells;
 1057115        _cellIndex = 0;
 1057116        _portalIndex = 0;
 1057117        _portalWaypointCount = 0;
 1057118        _previousPoint = entryAnchor;
 1057119        _geometricCost = default;
 1057120        _selectedPortal = default;
 1057121        _hasCurrentPortal = false;
 1057122    }
 123
 124    /// <summary>
 125    /// Performs at most <paramref name="maxWork"/> bounded validation units.
 126    /// </summary>
 127    /// <param name="orderedCells">The unchanged ordered cells supplied for this cursor.</param>
 128    /// <param name="portalWaypoints">Caller-owned storage for twice the portal count.</param>
 129    /// <param name="maxWork">The nonnegative maximum number of work units to perform.</param>
 130    /// <returns>The resulting validation state.</returns>
 131    public GridNavigationCorridorValidationStatus Advance(
 132        ReadOnlySpan<GridCellPrism> orderedCells,
 133        Span<Vector3d> portalWaypoints,
 134        int maxWork)
 135    {
 7113136        if (maxWork < 0)
 1137            throw new ArgumentOutOfRangeException(nameof(maxWork));
 138
 7112139        _hasCurrentPortal = false;
 7112140        if (_status != GridNavigationCorridorValidationStatus.InProgress)
 2141            return _status;
 142
 7110143        if (orderedCells.Length != _cellCount
 7110144            || _cellCount - 1 > portalWaypoints.Length / 2)
 145        {
 1146            return Fail(GridNavigationCorridorValidationStatus.Invalid);
 147        }
 148
 14364149        while (maxWork-- > 0 && _status == GridNavigationCorridorValidationStatus.InProgress)
 7255150            PerformNext(orderedCells, portalWaypoints);
 151
 7109152        return _status;
 153    }
 154
 155    private void PerformNext(
 156        ReadOnlySpan<GridCellPrism> orderedCells,
 157        Span<Vector3d> portalWaypoints)
 158    {
 7255159        _hasCurrentPortal = false;
 7255160        switch (_stage)
 161        {
 162            case ValidationStage.Cells:
 3122163                GridCellPrism cell = orderedCells[_cellIndex++];
 3122164                if (cell.FootprintVertexCount is not 4 and not 6
 3122165                    || cell.VerticalMax < cell.VerticalMin)
 166                {
 2167                    Fail(GridNavigationCorridorValidationStatus.Invalid);
 2168                    return;
 169                }
 170
 3120171                if (_cellIndex == _cellCount)
 1049172                    _stage = ValidationStage.EntryAnchor;
 3120173                return;
 174
 175            case ValidationStage.EntryAnchor:
 1049176                GridCellGeometry.TryCreateNavigationPortal(
 1049177                    orderedCells[0],
 1049178                    orderedCells[1],
 1049179                    out _selectedPortal);
 1049180                if (!GridCellGeometry.IsNavigationBodyAnchorValid(
 1049181                        orderedCells[0],
 1049182                        _entryAnchor,
 1049183                        _radiusClearance,
 1049184                        _heightClearance,
 1049185                        _selectedPortal))
 186                {
 10187                    Fail(GridNavigationCorridorValidationStatus.Invalid);
 10188                    return;
 189                }
 190
 1039191                _stage = ValidationStage.Portals;
 1039192                return;
 193
 194            case ValidationStage.Portals:
 2054195                ValidateNextPortal(orderedCells, portalWaypoints);
 2054196                return;
 197
 198            default:
 1030199                ValidateExitAnchor(orderedCells);
 1030200                return;
 201        }
 202    }
 203
 204    private void ValidateNextPortal(
 205        ReadOnlySpan<GridCellPrism> orderedCells,
 206        Span<Vector3d> portalWaypoints)
 207    {
 2054208        GridCellPrism source = orderedCells[_portalIndex];
 2054209        GridCellPrism target = orderedCells[_portalIndex + 1];
 2054210        GridNavigationPortal incomingPortal = _portalIndex == 0
 2054211            ? default
 2054212            : _selectedPortal;
 2054213        GridNavigationPortal outgoingPortal = _selectedPortal;
 2054214        if ((_portalIndex > 0
 2054215                && !GridCellGeometry.TryCreateNavigationPortal(source, target, out outgoingPortal))
 2054216            || !outgoingPortal.IsValid
 2054217            || !outgoingPortal.TryResolveProfile(
 2054218                _radiusClearance,
 2054219                _heightClearance,
 2054220                out Vector3d sourcePoint,
 2054221                out Vector3d targetPoint)
 2054222            || !GridCellGeometry.IsNavigationBodySegmentValid(
 2054223                source,
 2054224                _previousPoint,
 2054225                sourcePoint,
 2054226                _radiusClearance,
 2054227                _heightClearance,
 2054228                incomingPortal,
 2054229                outgoingPortal,
 2054230                GridNavigationBodySegmentEndpointAllowance.None))
 231        {
 7232            Fail(GridNavigationCorridorValidationStatus.Invalid);
 7233            return;
 234        }
 235
 2047236        portalWaypoints[_portalWaypointCount++] = sourcePoint;
 2047237        if (!TryAccumulateDistance(_previousPoint, sourcePoint))
 238        {
 1239            Fail(GridNavigationCorridorValidationStatus.CostOverflow);
 1240            return;
 241        }
 242
 2046243        _previousPoint = sourcePoint;
 2046244        if (outgoingPortal.FaceKind == VoxelContactFaceKind.Horizontal)
 245        {
 4246            portalWaypoints[_portalWaypointCount++] = targetPoint;
 4247            if (!TryAccumulateDistance(_previousPoint, targetPoint))
 248            {
 1249                Fail(GridNavigationCorridorValidationStatus.CostOverflow);
 1250                return;
 251            }
 252
 3253            _previousPoint = targetPoint;
 254        }
 255
 2045256        _selectedPortal = outgoingPortal;
 2045257        _hasCurrentPortal = true;
 2045258        _portalIndex++;
 2045259        if (_portalIndex == _cellCount - 1)
 1030260            _stage = ValidationStage.ExitAnchor;
 2045261    }
 262
 263    private void ValidateExitAnchor(ReadOnlySpan<GridCellPrism> orderedCells)
 264    {
 1030265        if (!TryAccumulateDistance(_previousPoint, _exitAnchor))
 266        {
 1267            Fail(GridNavigationCorridorValidationStatus.CostOverflow);
 1268            return;
 269        }
 270
 1029271        if (!GridCellGeometry.IsNavigationBodySegmentValid(
 1029272                orderedCells[_cellCount - 1],
 1029273                _previousPoint,
 1029274                _exitAnchor,
 1029275                _radiusClearance,
 1029276                _heightClearance,
 1029277                _selectedPortal,
 1029278                default,
 1029279                GridNavigationBodySegmentEndpointAllowance.None))
 280        {
 3281            Fail(GridNavigationCorridorValidationStatus.Invalid);
 3282            return;
 283        }
 284
 1026285        _status = GridNavigationCorridorValidationStatus.Complete;
 1026286    }
 287
 288    private GridNavigationCorridorValidationStatus Fail(
 289        GridNavigationCorridorValidationStatus status)
 290    {
 26291        _portalWaypointCount = 0;
 26292        _geometricCost = default;
 26293        _hasCurrentPortal = false;
 26294        _status = status;
 26295        return status;
 296    }
 297
 298    private bool TryAccumulateDistance(Vector3d start, Vector3d end)
 299    {
 3081300        return Vector3d.TryGetDistance(start, end, out Fixed64 distance)
 3081301            && Fixed64.TryAdd(_geometricCost, distance, out _geometricCost);
 302    }
 303
 304}