< Summary

Information
Class: Trailblazer.Pathing.NavigationTransitionEdgeEvaluator
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Graph/NavigationTransitionEdgeEvaluator.cs
Line coverage
100%
Covered lines: 281
Uncovered lines: 0
Coverable lines: 281
Total lines: 446
Line coverage: 100%
Branch coverage
100%
Covered branches: 97
Total branches: 97
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%
EvaluateDefinition(...)100%2828100%
EvaluateRule(...)100%3232100%
TryGetCost(...)100%1010100%
TryGetAnchors(...)100%22100%
TryGetAnchor(...)100%22100%
EvaluateLeg(...)100%2323100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// NavigationTransitionEdgeEvaluator.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 NavigationTransitionEdgeStatus = Trailblazer.Pathing.NavigationTraversalEvaluationStatus;
 12
 13namespace Trailblazer.Pathing;
 14
 15/// <summary>Preserves the exact cost and action endpoints selected for a transition.</summary>
 16internal readonly struct NavigationTransitionEdgeEvidence
 17{
 18    internal NavigationTransitionEdgeEvidence(
 19        Fixed64 cost,
 20        Vector3d sourceAction,
 21        Vector3d destinationAction)
 22    {
 23        Cost = cost;
 24        SourceAction = sourceAction;
 25        DestinationAction = destinationAction;
 26    }
 27
 28    internal Fixed64 Cost { get; }
 29
 30    internal Vector3d SourceAction { get; }
 31
 32    internal Vector3d DestinationAction { get; }
 33}
 34
 35/// <summary>Evaluates authored transition action legs and costs without endpoint travel.</summary>
 36internal readonly struct NavigationTransitionEdgeEvaluator
 37{
 38    private readonly NavigationWorldGraph _graph;
 39    private readonly NavigationAgentProfile _profile;
 40    private readonly NavigationAreaPolicy _areaPolicy;
 41    private readonly GridWorld _world;
 42    private readonly NavigationRayWorkspace _workspace;
 43
 44    internal NavigationTransitionEdgeEvaluator(
 45        GridWorld world,
 46        NavigationWorldGraph graph,
 47        NavigationAgentProfile profile,
 48        NavigationAreaPolicy areaPolicy,
 49        NavigationRayWorkspace workspace)
 50    {
 24851        _graph = graph;
 24852        _profile = profile;
 24853        _areaPolicy = areaPolicy;
 24854        _world = world;
 24855        _workspace = workspace;
 24856    }
 57
 58    internal NavigationTransitionEdgeStatus EvaluateDefinition(
 59        NavigationMediumStateRef source,
 60        NavigationPublishedTransition transition,
 61        NavigationWorkMeter meter,
 62        NavigationDependencyWorkspace dependencies,
 63        out NavigationMediumStateRef target,
 64        out NavigationTransitionEdgeEvidence evidence)
 65    {
 11866        target = default;
 11867        evidence = default;
 11868        TraversalTransitionDefinition definition = transition.Definition;
 11869        bool found = _graph.TryGetNodeAddress(
 11870            source.Node,
 11871            out NavigationCellAddress sourceAddress);
 72        System.Diagnostics.Debug.Assert(found,
 73            "Transition candidates are evaluated only from published graph nodes.");
 11874        if (!sourceAddress.Equals(transition.SourceAddress)
 11875            || source.Medium != definition.SourceMedium)
 76        {
 277            return NavigationTransitionEdgeStatus.Stale;
 78        }
 11679        if (!_graph.TryGetNodeRef(
 11680                definition.Destination,
 11681                out NavigationNodeRef targetNode))
 82        {
 183            return NavigationTransitionEdgeStatus.Impassable;
 84        }
 11585        target = new NavigationMediumStateRef(
 11586            targetNode,
 11587            definition.DestinationMedium);
 11588        if (!dependencies.TryRecordPage(
 11589                sourceAddress.MapId,
 11590                source.Node.CellSlot / NavigationSemanticPage.SlotCount)
 11591            || !dependencies.TryRecordPage(
 11592                definition.Destination.MapId,
 11593                target.Node.CellSlot / NavigationSemanticPage.SlotCount))
 94        {
 295            return NavigationTransitionEdgeStatus.CapacityExceeded;
 96        }
 11397        if ((_profile.Capabilities & definition.RequiredCapabilities)
 11398                != definition.RequiredCapabilities)
 99        {
 1100            return NavigationTransitionEdgeStatus.Impassable;
 101        }
 102
 112103        var sourceNodes = new TraversalEvaluator(
 112104            _graph,
 112105            _profile,
 112106            _areaPolicy,
 112107            source.Medium);
 112108        var targetNodes = new TraversalEvaluator(
 112109            _graph,
 112110            _profile,
 112111            _areaPolicy,
 112112            target.Medium);
 112113        if (!sourceNodes.TryGetPassableNode(
 112114                source.Node,
 112115                out NavigationNodeState sourceState,
 112116                out _)
 112117            || !targetNodes.TryGetPassableNode(
 112118                target.Node,
 112119                out NavigationNodeState targetState,
 112120                out NavigationAreaRule targetRule))
 121        {
 4122            return NavigationTransitionEdgeStatus.Impassable;
 123        }
 108124        if (!TryGetAnchors(
 108125                sourceState,
 108126                source.Medium,
 108127                targetState,
 108128                target.Medium,
 108129                out Vector3d sourceAnchor,
 108130                out Vector3d targetAnchor))
 131        {
 2132            return NavigationTransitionEdgeStatus.Impassable;
 133        }
 134
 106135        Vector3d sourceAction = definition.HasSourcePointOverride
 106136            ? definition.SourcePointOverride
 106137            : sourceAnchor;
 106138        Vector3d targetAction = definition.HasDestinationPointOverride
 106139            ? definition.DestinationPointOverride
 106140            : targetAnchor;
 106141        NavigationTransitionEdgeStatus sourceLeg = EvaluateLeg(
 106142            source,
 106143            sourceAnchor,
 106144            sourceAction,
 106145            meter,
 106146            dependencies);
 106147        if (sourceLeg != NavigationTransitionEdgeStatus.Passable)
 1148            return sourceLeg;
 105149        NavigationTransitionEdgeStatus targetLeg = EvaluateLeg(
 105150            target,
 105151            targetAction,
 105152            targetAnchor,
 105153            meter,
 105154            dependencies);
 105155        if (targetLeg != NavigationTransitionEdgeStatus.Passable)
 7156            return targetLeg;
 157
 98158        if (!TryGetCost(
 98159                sourceAnchor,
 98160                sourceAction,
 98161                definition.ActionCost,
 98162                targetAction,
 98163                targetAnchor,
 98164                targetState.Cell.EnterCost,
 98165                targetRule.AdditionalEnterCost,
 98166                out Fixed64 total))
 167        {
 4168            return NavigationTransitionEdgeStatus.CostOverflow;
 169        }
 170
 94171        evidence = new NavigationTransitionEdgeEvidence(
 94172            total,
 94173            sourceAction,
 94174            targetAction);
 94175        return NavigationTransitionEdgeStatus.Passable;
 176    }
 177
 178    internal NavigationTransitionEdgeStatus EvaluateRule(
 179        NavigationMediumStateRef source,
 180        NavigationMediumStateRef target,
 181        TraversalTransitionRule rule,
 182        NavigationWorkMeter meter,
 183        NavigationDependencyWorkspace dependencies,
 184        out NavigationTransitionEdgeEvidence evidence)
 185    {
 131186        evidence = default;
 131187        bool foundSource = _graph.TryGetNodeAddress(
 131188            source.Node,
 131189            out NavigationCellAddress address);
 131190        bool foundTarget = _graph.TryGetNodeAddress(
 131191            target.Node,
 131192            out NavigationCellAddress targetAddress);
 193        System.Diagnostics.Debug.Assert(foundSource && foundTarget,
 194            "Transition rule pairs contain only published graph nodes.");
 131195        if ((rule.Scope == TraversalTransitionRuleScope.SameCell
 131196                && !source.Node.Equals(target.Node))
 131197            || source.Medium != rule.SourceMedium
 131198            || target.Medium != rule.DestinationMedium
 131199            || (_profile.Capabilities & rule.RequiredCapabilities)
 131200                != rule.RequiredCapabilities)
 201        {
 14202            return NavigationTransitionEdgeStatus.Impassable;
 203        }
 117204        if (!dependencies.TryRecordPage(
 117205                address.MapId,
 117206                source.Node.CellSlot / NavigationSemanticPage.SlotCount)
 117207            || !dependencies.TryRecordPage(
 117208                targetAddress.MapId,
 117209                target.Node.CellSlot / NavigationSemanticPage.SlotCount))
 210        {
 1211            return NavigationTransitionEdgeStatus.CapacityExceeded;
 212        }
 213
 116214        var sourceNodes = new TraversalEvaluator(
 116215            _graph,
 116216            _profile,
 116217            _areaPolicy,
 116218            source.Medium);
 116219        var targetNodes = new TraversalEvaluator(
 116220            _graph,
 116221            _profile,
 116222            _areaPolicy,
 116223            target.Medium);
 116224        if (!sourceNodes.TryGetPassableNodeState(
 116225                source.Node,
 116226                out NavigationNodeState sourceState)
 116227            || !targetNodes.TryGetPassableNode(
 116228                target.Node,
 116229                out NavigationNodeState targetState,
 116230                out NavigationAreaRule targetRule))
 231        {
 26232            return NavigationTransitionEdgeStatus.Impassable;
 233        }
 90234        if (!TryGetAnchors(
 90235                sourceState,
 90236                source.Medium,
 90237                targetState,
 90238                target.Medium,
 90239                out Vector3d sourceAnchor,
 90240                out Vector3d targetAnchor))
 241        {
 2242            return NavigationTransitionEdgeStatus.Impassable;
 243        }
 244
 88245        Vector3d sourceAction = sourceAnchor;
 88246        Vector3d targetAction = targetAnchor;
 88247        GridNavigationPortal portal = default;
 88248        if (rule.Scope == TraversalTransitionRuleScope.PositiveFaceContact)
 249        {
 52250            bool foundSourcePrism = _graph.TryGetSeamPrism(
 52251                address,
 52252                out GridCellPrism sourcePrism);
 52253            bool foundTargetPrism = _graph.TryGetSeamPrism(
 52254                targetAddress,
 52255                out GridCellPrism targetPrism);
 256            System.Diagnostics.Debug.Assert(foundSourcePrism && foundTargetPrism,
 257                "Published transition endpoints belong to their bound grid prisms.");
 52258            if (!GridCellGeometry.TryCreateNavigationPortal(
 52259                    sourcePrism,
 52260                    targetPrism,
 52261                    out portal))
 262            {
 24263                return NavigationTransitionEdgeStatus.Impassable;
 264            }
 28265            if (!GridCellGeometry.TryGetNavigationPortalTraversalParameters(
 28266                sourcePrism,
 28267                targetPrism,
 28268                portal,
 28269                sourceAnchor,
 28270                targetAnchor,
 28271                _profile.Shape.Radius,
 28272                _profile.Shape.Height,
 28273                out Fixed64 sourceParameter,
 28274                out Fixed64 targetParameter))
 275            {
 1276                return NavigationTransitionEdgeStatus.Impassable;
 277            }
 27278            sourceAction = Vector3d.Lerp(sourceAnchor, targetAnchor, sourceParameter);
 27279            targetAction = Vector3d.Lerp(sourceAnchor, targetAnchor, targetParameter);
 280        }
 63281        NavigationTransitionEdgeStatus sourceLeg = EvaluateLeg(
 63282            source,
 63283            sourceAnchor,
 63284            sourceAction,
 63285            meter,
 63286            dependencies,
 63287            outgoingPortal: portal);
 63288        if (sourceLeg != NavigationTransitionEdgeStatus.Passable)
 1289            return sourceLeg;
 62290        NavigationTransitionEdgeStatus targetLeg = EvaluateLeg(
 62291            target,
 62292            targetAction,
 62293            targetAnchor,
 62294            meter,
 62295            dependencies,
 62296            incomingPortal: portal);
 62297        if (targetLeg != NavigationTransitionEdgeStatus.Passable)
 3298            return targetLeg;
 59299        if (!TryGetCost(
 59300                sourceAnchor,
 59301                sourceAction,
 59302                rule.ActionCost,
 59303                targetAction,
 59304                targetAnchor,
 59305                targetState.Cell.EnterCost,
 59306                targetRule.AdditionalEnterCost,
 59307                out Fixed64 total))
 308        {
 5309            return NavigationTransitionEdgeStatus.CostOverflow;
 310        }
 311
 54312        evidence = new NavigationTransitionEdgeEvidence(
 54313            total,
 54314            sourceAction,
 54315            targetAction);
 54316        return NavigationTransitionEdgeStatus.Passable;
 317    }
 318
 319    internal static bool TryGetCost(
 320        Vector3d sourceAnchor,
 321        Vector3d sourceAction,
 322        Fixed64 actionCost,
 323        Vector3d targetAction,
 324        Vector3d targetAnchor,
 325        Fixed64 targetEnterCost,
 326        Fixed64 additionalEnterCost,
 164327        out Fixed64 total) => NavigationDistanceMath.TryCeiling(
 164328            sourceAnchor,
 164329            sourceAction,
 164330            out total)
 164331        && Fixed64.TryAdd(total, actionCost, out total)
 164332        && NavigationDistanceMath.TryCeiling(
 164333            targetAction,
 164334            targetAnchor,
 164335            out Fixed64 targetDistance)
 164336        && Fixed64.TryAdd(total, targetDistance, out total)
 164337        && Fixed64.TryAdd(total, targetEnterCost, out total)
 164338        && Fixed64.TryAdd(total, additionalEnterCost, out total);
 339
 340    private bool TryGetAnchors(
 341        NavigationNodeState source,
 342        TraversalMedium sourceMedium,
 343        NavigationNodeState target,
 344        TraversalMedium targetMedium,
 345        out Vector3d sourceAnchor,
 346        out Vector3d targetAnchor)
 347    {
 198348        if (!TryGetAnchor(source, sourceMedium, out sourceAnchor))
 349        {
 2350            targetAnchor = default;
 2351            return false;
 352        }
 196353        return TryGetAnchor(target, targetMedium, out targetAnchor);
 354    }
 355
 356    private bool TryGetAnchor(
 357        NavigationNodeState state,
 358        TraversalMedium medium,
 359        out Vector3d anchor)
 360    {
 394361        if (medium == TraversalMedium.Solid)
 362        {
 148363            anchor = state.FootAnchor;
 148364            return true;
 365        }
 246366        return state.TryGetCenteredVolumeFootAnchor(_profile.Shape.Height, out anchor);
 367    }
 368
 369    private NavigationTransitionEdgeStatus EvaluateLeg(
 370        NavigationMediumStateRef state,
 371        Vector3d start,
 372        Vector3d end,
 373        NavigationWorkMeter meter,
 374        NavigationDependencyWorkspace dependencies,
 375        GridNavigationPortal incomingPortal = default,
 376        GridNavigationPortal outgoingPortal = default)
 377    {
 336378        if (start == end && state.Medium == TraversalMedium.Solid)
 137379            return NavigationTransitionEdgeStatus.Passable;
 199380        if (state.Medium == TraversalMedium.Solid)
 381        {
 8382            _graph.TryGetNodeAddress(state.Node, out NavigationCellAddress address);
 8383            _graph.TryGetSeamPrism(address, out GridCellPrism prism);
 384            System.Diagnostics.Debug.Assert(
 385                address.MapId != null && prism.FootprintVertexCount > 0);
 8386            bool valid = GridCellGeometry.IsNavigationBodySegmentValid(
 8387                prism,
 8388                start,
 8389                end,
 8390                _profile.Shape.Radius,
 8391                _profile.Shape.Height,
 8392                incomingPortal,
 8393                outgoingPortal,
 8394                GridNavigationBodySegmentEndpointAllowance.None);
 395            System.Diagnostics.Debug.Assert(valid,
 396                "Published transition actions and computed portals are valid in-cell segments.");
 8397            return NavigationTransitionEdgeStatus.Passable;
 398        }
 399
 191400        if ((incomingPortal.IsValid || outgoingPortal.IsValid)
 191401            && _graph.TryGetNodeAddress(
 191402                state.Node,
 191403                out NavigationCellAddress volumeAddress)
 191404            && _graph.TryGetSeamPrism(volumeAddress, out GridCellPrism volumePrism)
 191405            && GridCellGeometry.IsNavigationBodySegmentValid(
 191406                volumePrism,
 191407                start,
 191408                end,
 191409                _profile.Shape.Radius,
 191410                _profile.Shape.Height,
 191411                incomingPortal,
 191412                outgoingPortal,
 191413                GridNavigationBodySegmentEndpointAllowance.None))
 414        {
 54415            return NavigationTransitionEdgeStatus.Passable;
 416        }
 417
 137418        var volume = new NavigationVolumeAnchorEvaluator(
 137419            _world,
 137420            _graph,
 137421            _profile,
 137422            _areaPolicy,
 137423            _workspace);
 137424        NavigationVolumeAnchorStatus status = volume.EvaluateSegment(
 137425            state,
 137426            state,
 137427            start,
 137428            end,
 137429            meter,
 137430            dependencies);
 137431        return status switch
 137432        {
 137433            NavigationVolumeAnchorStatus.Success =>
 125434                NavigationTransitionEdgeStatus.Passable,
 137435            NavigationVolumeAnchorStatus.BudgetExceeded =>
 5436                NavigationTransitionEdgeStatus.BudgetExceeded,
 137437            NavigationVolumeAnchorStatus.CostOverflow =>
 1438                NavigationTransitionEdgeStatus.CostOverflow,
 137439            NavigationVolumeAnchorStatus.CapacityExceeded =>
 3440                NavigationTransitionEdgeStatus.CapacityExceeded,
 137441            NavigationVolumeAnchorStatus.Stale =>
 2442                NavigationTransitionEdgeStatus.Stale,
 1443            _ => NavigationTransitionEdgeStatus.Impassable
 137444        };
 445    }
 446}

Methods/Properties

.ctor(GridForge.Grids.GridWorld,Trailblazer.Pathing.NavigationWorldGraph,Trailblazer.Pathing.NavigationAgentProfile,Trailblazer.Pathing.NavigationAreaPolicy,Trailblazer.Pathing.NavigationRayWorkspace)
EvaluateDefinition(Trailblazer.Pathing.NavigationMediumStateRef,Trailblazer.Pathing.NavigationPublishedTransition,Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,Trailblazer.Pathing.NavigationMediumStateRef&,Trailblazer.Pathing.NavigationTransitionEdgeEvidence&)
EvaluateRule(Trailblazer.Pathing.NavigationMediumStateRef,Trailblazer.Pathing.NavigationMediumStateRef,Trailblazer.Pathing.TraversalTransitionRule,Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,Trailblazer.Pathing.NavigationTransitionEdgeEvidence&)
TryGetCost(FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64&)
TryGetAnchors(Trailblazer.Pathing.NavigationNodeState,Trailblazer.TraversalMedium,Trailblazer.Pathing.NavigationNodeState,Trailblazer.TraversalMedium,FixedMathSharp.Vector3d&,FixedMathSharp.Vector3d&)
TryGetAnchor(Trailblazer.Pathing.NavigationNodeState,Trailblazer.TraversalMedium,FixedMathSharp.Vector3d&)
EvaluateLeg(Trailblazer.Pathing.NavigationMediumStateRef,FixedMathSharp.Vector3d,FixedMathSharp.Vector3d,Trailblazer.Pathing.NavigationWorkMeter,Trailblazer.Pathing.NavigationDependencyWorkspace,GridForge.Grids.Topology.GridNavigationPortal,GridForge.Grids.Topology.GridNavigationPortal)