< Summary

Information
Class: Trailblazer.Pathing.NavigationVolumeEdgeEvaluator
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationVolumeEdgeEvaluator.cs
Line coverage
100%
Covered lines: 177
Uncovered lines: 0
Coverable lines: 177
Total lines: 299
Line coverage: 100%
Branch coverage
100%
Covered branches: 51
Total branches: 51
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Evaluate(...)100%1818100%
TryGetCost(...)100%44100%
CertifyRaySegment(...)100%1010100%
CertifyResolvedSegment(...)100%1919100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationVolumeEdgeEvaluator.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationVolumeEdgeEvaluator.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.Grids;
 10using GridForge.Grids.Topology;
 11using NavigationVolumeEdgeStatus = Trailblazer.Pathing.NavigationTraversalEvaluationStatus;
 12
 13namespace Trailblazer.Pathing;
 14
 15internal enum NavigationTraversalEvaluationStatus : byte
 16{
 17    Passable = 0,
 18    Impassable = 1,
 19    BudgetExceeded = 2,
 20    CostOverflow = 3,
 21    CapacityExceeded = 4,
 22    Stale = 5
 23}
 24
 25/// <summary>Evaluates one centered volume-body movement without surface constraints.</summary>
 26internal readonly struct NavigationVolumeEdgeEvaluator
 27{
 28    private readonly TraversalEvaluator _nodes;
 29    private readonly GridWorld _world;
 30    private readonly NavigationRayWorkspace _workspace;
 31
 32    internal NavigationVolumeEdgeEvaluator(
 33        GridWorld world,
 34        NavigationWorldGraph graph,
 35        NavigationAgentProfile profile,
 36        NavigationAreaPolicy areaPolicy,
 37        TraversalMedium medium,
 38        NavigationRayWorkspace workspace)
 39    {
 131840        _nodes = new TraversalEvaluator(graph, profile, areaPolicy, medium);
 131841        _world = world;
 131842        _workspace = workspace;
 131843    }
 44
 45    internal NavigationVolumeEdgeStatus Evaluate(
 46        NavigationMediumStateRef source,
 47        NavigationMediumStateRef target,
 48        bool isPrimary,
 49        NavigationAutomaticSeamRef seam,
 50        bool hasSeam,
 51        NavigationWorkMeter meter,
 52        NavigationDependencyWorkspace dependencies,
 53        out Fixed64 cost)
 54    {
 266055        cost = default;
 266056        NavigationWorldGraph graph = _nodes.Graph;
 266057        if (source.Medium != target.Medium)
 158            return NavigationVolumeEdgeStatus.Stale;
 265959        bool foundSourceAddress = graph.TryGetNodeAddress(
 265960            source.Node,
 265961            out NavigationCellAddress sourceAddress);
 265962        bool foundTargetAddress = graph.TryGetNodeAddress(
 265963            target.Node,
 265964            out NavigationCellAddress targetAddress);
 265965        bool foundSourcePrism = graph.TryGetSeamPrism(
 265966            sourceAddress,
 265967            out GridCellPrism sourcePrism);
 265968        bool foundTargetPrism = graph.TryGetSeamPrism(
 265969            targetAddress,
 265970            out GridCellPrism targetPrism);
 71        System.Diagnostics.Debug.Assert(
 72            foundSourceAddress && foundTargetAddress && foundSourcePrism && foundTargetPrism,
 73            "Volume edge endpoints retain addresses and prisms in their immutable graph.");
 265974        if (!dependencies.TryRecordPage(
 265975                sourceAddress.MapId,
 265976                source.Node.CellSlot / NavigationSemanticPage.SlotCount)
 265977            || !dependencies.TryRecordPage(
 265978                targetAddress.MapId,
 265979                target.Node.CellSlot / NavigationSemanticPage.SlotCount))
 80        {
 281            return NavigationVolumeEdgeStatus.CapacityExceeded;
 82        }
 265783        if (!_nodes.TryGetPassableNode(
 265784                source.Node,
 265785                out NavigationNodeState sourceState,
 265786                out _)
 265787            || !_nodes.TryGetPassableNode(
 265788                target.Node,
 265789                out NavigationNodeState targetState,
 265790                out NavigationAreaRule targetRule))
 91        {
 13292            return NavigationVolumeEdgeStatus.Impassable;
 93        }
 94
 252595        KinematicBodyShape shape = _nodes.Profile.Shape;
 252596        if (!sourceState.TryGetCenteredVolumeFootAnchor(
 252597                shape.Height,
 252598                out Vector3d sourceAnchor)
 252599            || !targetState.TryGetCenteredVolumeFootAnchor(
 2525100                shape.Height,
 2525101                out Vector3d targetAnchor))
 102        {
 1103            return NavigationVolumeEdgeStatus.CostOverflow;
 104        }
 105        System.Diagnostics.Debug.Assert(
 106            !hasSeam
 107            || (seam.Pair != null
 108                && graph.AutomaticSeams.IsActive(seam)
 109                && sourceAddress.Equals(seam.Source)
 110                && targetAddress.Equals(seam.Destination)
 111                && seam.Portal.IsValid));
 112        System.Diagnostics.Debug.Assert(
 113            !hasSeam
 114            || GridCellGeometry.TryCreateNavigationPortal(
 115                sourcePrism,
 116                targetPrism,
 117                out _));
 2524118        NavigationVolumeEdgeStatus segmentStatus = CertifyResolvedSegment(
 2524119            source,
 2524120            target,
 2524121            sourcePrism,
 2524122            targetPrism,
 2524123            sourceAnchor,
 2524124            targetAnchor,
 2524125            isPrimary,
 2524126            meter,
 2524127            dependencies);
 2524128        if (segmentStatus != NavigationVolumeEdgeStatus.Passable)
 50129            return segmentStatus;
 2474130        if (!TryGetCost(
 2474131                sourceAnchor,
 2474132                targetAnchor,
 2474133                targetState.Cell.EnterCost,
 2474134                targetRule.AdditionalEnterCost,
 2474135                out Fixed64 total))
 136        {
 3137            return NavigationVolumeEdgeStatus.CostOverflow;
 138        }
 139
 2471140        cost = total;
 2471141        return NavigationVolumeEdgeStatus.Passable;
 142    }
 143
 144    internal static bool TryGetCost(
 145        Vector3d sourceAnchor,
 146        Vector3d targetAnchor,
 147        Fixed64 targetEnterCost,
 148        Fixed64 additionalEnterCost,
 2478149        out Fixed64 total) => NavigationDistanceMath.TryCeiling(
 2478150            sourceAnchor,
 2478151            targetAnchor,
 2478152            out total)
 2478153        && Fixed64.TryAdd(total, targetEnterCost, out total)
 2478154        && Fixed64.TryAdd(total, additionalEnterCost, out total);
 155
 156    internal NavigationVolumeEdgeStatus CertifyRaySegment(
 157        NavigationMediumStateRef source,
 158        NavigationMediumStateRef target,
 159        Vector3d sourceFoot,
 160        Vector3d targetFoot,
 161        NavigationWorkMeter meter,
 162        NavigationDependencyWorkspace dependencies)
 163    {
 580164        NavigationWorldGraph graph = _nodes.Graph;
 580165        if (source.Medium != target.Medium)
 1166            return NavigationVolumeEdgeStatus.Stale;
 579167        bool foundSourceAddress = graph.TryGetNodeAddress(
 579168            source.Node,
 579169            out NavigationCellAddress sourceAddress);
 579170        bool foundTargetAddress = graph.TryGetNodeAddress(
 579171            target.Node,
 579172            out NavigationCellAddress targetAddress);
 579173        bool foundSourcePrism = graph.TryGetSeamPrism(
 579174            sourceAddress,
 579175            out GridCellPrism sourcePrism);
 579176        bool foundTargetPrism = graph.TryGetSeamPrism(
 579177            targetAddress,
 579178            out GridCellPrism targetPrism);
 179        System.Diagnostics.Debug.Assert(
 180            foundSourceAddress && foundTargetAddress && foundSourcePrism && foundTargetPrism,
 181            "Certified volume-ray endpoints retain addresses and prisms in their immutable graph.");
 579182        if (!dependencies.TryRecordPage(
 579183                sourceAddress.MapId,
 579184                source.Node.CellSlot / NavigationSemanticPage.SlotCount)
 579185            || !dependencies.TryRecordPage(
 579186                targetAddress.MapId,
 579187                target.Node.CellSlot / NavigationSemanticPage.SlotCount))
 188        {
 1189            return NavigationVolumeEdgeStatus.CapacityExceeded;
 190        }
 578191        if (!_nodes.TryGetPassableNode(source.Node, out _, out _)
 578192            || !_nodes.TryGetPassableNode(target.Node, out _, out _))
 193        {
 2194            return NavigationVolumeEdgeStatus.Impassable;
 195        }
 196
 576197        return CertifyResolvedSegment(
 576198            source,
 576199            target,
 576200            sourcePrism,
 576201            targetPrism,
 576202            sourceFoot,
 576203            targetFoot,
 576204            allowPortalFastPath: true,
 576205            meter,
 576206            dependencies);
 207    }
 208
 209    private NavigationVolumeEdgeStatus CertifyResolvedSegment(
 210        NavigationMediumStateRef source,
 211        NavigationMediumStateRef target,
 212        GridCellPrism sourcePrism,
 213        GridCellPrism targetPrism,
 214        Vector3d sourceFoot,
 215        Vector3d targetFoot,
 216        bool allowPortalFastPath,
 217        NavigationWorkMeter meter,
 218        NavigationDependencyWorkspace dependencies)
 219    {
 3100220        KinematicBodyShape shape = _nodes.Profile.Shape;
 221        bool fastPath;
 3100222        if (source.Node.Equals(target.Node))
 223        {
 303224            fastPath = GridCellGeometry.IsNavigationBodySegmentValid(
 303225                sourcePrism,
 303226                sourceFoot,
 303227                targetFoot,
 303228                shape.Radius,
 303229                shape.Height,
 303230                default,
 303231                default,
 303232                GridNavigationBodySegmentEndpointAllowance.None);
 233        }
 234        else
 235        {
 2797236            fastPath = allowPortalFastPath
 2797237                && GridCellGeometry.TryCreateNavigationPortal(
 2797238                    sourcePrism,
 2797239                    targetPrism,
 2797240                    out GridNavigationPortal portal)
 2797241                && GridCellGeometry.TryGetNavigationPortalTraversalParameters(
 2797242                    sourcePrism,
 2797243                    targetPrism,
 2797244                    portal,
 2797245                    sourceFoot,
 2797246                    targetFoot,
 2797247                    shape.Radius,
 2797248                    shape.Height,
 2797249                    out Fixed64 sourceParameter,
 2797250                    out Fixed64 targetParameter)
 2797251                && GridCellGeometry.IsNavigationBodySegmentValid(
 2797252                    sourcePrism,
 2797253                    sourceFoot,
 2797254                    Vector3d.Lerp(sourceFoot, targetFoot, sourceParameter),
 2797255                    shape.Radius,
 2797256                    shape.Height,
 2797257                    default,
 2797258                    portal,
 2797259                    GridNavigationBodySegmentEndpointAllowance.None)
 2797260                && GridCellGeometry.IsNavigationBodySegmentValid(
 2797261                    targetPrism,
 2797262                    Vector3d.Lerp(sourceFoot, targetFoot, targetParameter),
 2797263                    targetFoot,
 2797264                    shape.Radius,
 2797265                    shape.Height,
 2797266                    portal,
 2797267                    default,
 2797268                    GridNavigationBodySegmentEndpointAllowance.None);
 269        }
 3100270        if (fastPath)
 1567271            return NavigationVolumeEdgeStatus.Passable;
 272
 1533273        var union = new NavigationVolumeAnchorEvaluator(
 1533274            _world,
 1533275            _nodes.Graph,
 1533276            _nodes.Profile,
 1533277            _nodes.AreaPolicy,
 1533278            _workspace);
 1533279        NavigationVolumeAnchorStatus status = union.EvaluateSegment(
 1533280            source,
 1533281            target,
 1533282            sourceFoot,
 1533283            targetFoot,
 1533284            meter,
 1533285            dependencies);
 1533286        return status switch
 1533287        {
 1470288            NavigationVolumeAnchorStatus.Success => NavigationVolumeEdgeStatus.Passable,
 1533289            NavigationVolumeAnchorStatus.BudgetExceeded =>
 9290                NavigationVolumeEdgeStatus.BudgetExceeded,
 1533291            NavigationVolumeAnchorStatus.CostOverflow =>
 1292                NavigationVolumeEdgeStatus.CostOverflow,
 1533293            NavigationVolumeAnchorStatus.CapacityExceeded =>
 2294                NavigationVolumeEdgeStatus.CapacityExceeded,
 4295            NavigationVolumeAnchorStatus.Stale => NavigationVolumeEdgeStatus.Stale,
 47296            _ => NavigationVolumeEdgeStatus.Impassable
 1533297        };
 298    }
 299}