< Summary

Information
Class: Trailblazer.Pathing.NavigationMapBuilder
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Map/NavigationMapBuilder.cs
Line coverage
100%
Covered lines: 349
Uncovered lines: 0
Coverable lines: 349
Total lines: 663
Line coverage: 100%
Branch coverage
100%
Covered branches: 140
Total branches: 140
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%
.ctor(...)100%11100%
AddCell(...)100%11100%
AddCell(...)100%11100%
SetDefaultCell(...)100%11100%
AddConnection(...)100%11100%
AddTransition(...)100%11100%
AddTransitionRule(...)100%11100%
Build()100%22100%
ImportDenseRectangular(...)100%1212100%
ImportAxialHex(...)100%22100%
Normalize(...)100%11100%
ValidateCells(...)100%88100%
ValidateConnections(...)100%1616100%
ValidateTransitions(...)100%1616100%
ValidateTransitionRules(...)100%66100%
ValidateWitnesses(...)100%88100%
ValidateGridGeometry()100%1212100%
ValidateConnectionGeometry(...)100%1010100%
IsCompleteLocalChain(...)100%66100%
GetMaximumLocalPrismCount(...)100%66100%
GetMaximumWitnessCount(...)100%44100%
TryProveLowerBound(...)100%1414100%
GetFootAnchor(...)100%11100%
ValidateLocalAnchor(...)100%11100%
ValidateClearance(...)100%22100%
ValidateCellPayload(...)100%11100%
SupportsMedium(...)100%44100%
TryGetAuthoredCell(...)100%66100%
FindCell(...)100%66100%
.cctor()100%11100%
Compare(...)100%11100%
.cctor()100%11100%
Compare(...)100%11100%
.cctor()100%11100%
Compare(...)100%11100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Pathing/Map/NavigationMapBuilder.cs

#LineLine coverage
 1//=======================================================================
 2// NavigationMapBuilder.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.Collections.Generic;
 10using FixedMathSharp;
 11using GridForge.Configuration;
 12using GridForge.Grids.Topology;
 13using GridForge.Spatial;
 14using SwiftCollections;
 15
 16namespace Trailblazer.Pathing;
 17
 18/// <summary>
 19/// Collects one-grid authoring input and builds a validated, canonically ordered map.
 20/// </summary>
 21public sealed class NavigationMapBuilder
 22{
 207423    private readonly SwiftList<NavigationCellEntry> _cells = new();
 207424    private readonly SwiftList<NavigationConnection> _connections = new();
 207425    private readonly SwiftList<TraversalTransitionDefinition> _transitions = new();
 207426    private readonly SwiftList<TraversalTransitionRule> _transitionRules = new();
 27    private NavigationCell? _defaultCell;
 28
 29    /// <summary>
 30    /// The stable host-owned identifier assigned to the resulting map.
 31    /// </summary>
 32    public string MapId { get; }
 33
 34    /// <summary>
 35    /// The normalized storage-neutral GridForge configuration binding.
 36    /// </summary>
 37    public NormalizedGridConfiguration GridBinding { get; }
 38
 39    /// <summary>
 40    /// Creates a builder for an already normalized grid descriptor.
 41    /// </summary>
 207442    public NavigationMapBuilder(
 207443        string mapId,
 207444        NormalizedGridConfiguration gridBinding)
 45    {
 207446        SwiftThrowHelper.ThrowIfArgument(
 207447            string.IsNullOrWhiteSpace(mapId),
 207448            nameof(mapId),
 207449            "Map ID cannot be null, empty, or whitespace.");
 207450        SwiftThrowHelper.ThrowIfArgument(
 207451            !gridBinding.IsValid,
 207452            nameof(gridBinding),
 207453            "Grid binding must contain a valid normalized address space.");
 54
 207455        MapId = mapId;
 207456        GridBinding = gridBinding;
 207457    }
 58
 59    /// <summary>
 60    /// Creates a builder by normalizing an offline GridForge configuration.
 61    /// </summary>
 62    public NavigationMapBuilder(
 63        string mapId,
 64        GridConfiguration gridConfiguration)
 4665        : this(mapId, Normalize(gridConfiguration))
 66    {
 4667    }
 68
 69    /// <summary>
 70    /// Adds one authored cell. Duplicate indices reject when <see cref="Build"/> normalizes the bake.
 71    /// </summary>
 72    public NavigationMapBuilder AddCell(VoxelIndex index, NavigationCell cell)
 73    {
 2313974        _cells.Add(new NavigationCellEntry(index, cell));
 2313975        return this;
 76    }
 77
 78    /// <summary>
 79    /// Adds one authored cell entry.
 80    /// </summary>
 81    public NavigationMapBuilder AddCell(NavigationCellEntry entry)
 82    {
 483        _cells.Add(entry);
 484        return this;
 85    }
 86
 87    /// <summary>
 88    /// Sets the optional complete fallback payload used when no explicit cell wins.
 89    /// </summary>
 90    public NavigationMapBuilder SetDefaultCell(NavigationCell? defaultCell)
 91    {
 6592        _defaultCell = defaultCell;
 6593        return this;
 94    }
 95
 96    /// <summary>
 97    /// Adds one directed source-owned physical connection.
 98    /// </summary>
 99    public NavigationMapBuilder AddConnection(NavigationConnection connection)
 100    {
 303101        SwiftThrowHelper.ThrowIfNull(connection, nameof(connection));
 303102        _connections.Add(connection);
 303103        return this;
 104    }
 105
 106    /// <summary>
 107    /// Adds one directed source-owned semantic transition.
 108    /// </summary>
 109    public NavigationMapBuilder AddTransition(TraversalTransitionDefinition transition)
 110    {
 177111        _transitions.Add(transition);
 177112        return this;
 113    }
 114
 115    /// <summary>Adds one bounded procedural semantic transition rule.</summary>
 116    public NavigationMapBuilder AddTransitionRule(TraversalTransitionRule rule)
 117    {
 80118        _transitionRules.Add(rule);
 80119        return this;
 120    }
 121
 122    /// <summary>
 123    /// Validates and copies all input into an immutable canonical map.
 124    /// </summary>
 125    public NavigationMap Build()
 126    {
 2060127        NavigationCellEntry[] cells = _cells.ToArray();
 2060128        NavigationConnection[] connections = _connections.ToArray();
 2060129        TraversalTransitionDefinition[] transitions = _transitions.ToArray();
 2060130        TraversalTransitionRule[] transitionRules = _transitionRules.ToArray();
 131
 2060132        Array.Sort(cells, NavigationCellEntryComparer.Instance);
 2060133        Array.Sort(connections, NavigationConnectionComparer.Instance);
 2060134        Array.Sort(transitions, TraversalTransitionDefinitionComparer.Instance);
 2060135        Array.Sort(transitionRules, TraversalTransitionRuleComparer.Instance);
 136
 2060137        ValidateGridGeometry();
 2060138        if (_defaultCell.HasValue)
 65139            ValidateCellPayload(_defaultCell.Value, nameof(_defaultCell));
 2059140        ValidateCells(cells);
 2056141        ValidateConnections(cells, connections);
 2046142        ValidateTransitions(cells, transitions);
 2039143        ValidateTransitionRules(transitionRules);
 144
 2037145        return new NavigationMap(
 2037146            MapId,
 2037147            GridBinding,
 2037148            cells,
 2037149            connections,
 2037150            transitions,
 2037151            transitionRules,
 2037152            _defaultCell);
 153    }
 154
 155    /// <summary>
 156    /// Imports an X/Y/Z rectangular dense source into the canonical sparse map representation.
 157    /// Null entries are omitted from the bake.
 158    /// </summary>
 159    public static NavigationMap ImportDenseRectangular(
 160        string mapId,
 161        GridConfiguration gridConfiguration,
 162        NavigationCell?[,,] cells)
 163    {
 4164        SwiftThrowHelper.ThrowIfNull(cells, nameof(cells));
 4165        var builder = new NavigationMapBuilder(mapId, gridConfiguration);
 4166        SwiftThrowHelper.ThrowIfArgument(
 4167            builder.GridBinding.Configuration.TopologyKind != GridTopologyKind.RectangularPrism,
 4168            nameof(gridConfiguration),
 4169            "Dense rectangular import requires rectangular-prism topology.");
 4170        SwiftThrowHelper.ThrowIfArgument(
 4171            cells.GetLength(0) != builder.GridBinding.Width
 4172            || cells.GetLength(1) != builder.GridBinding.Height
 4173            || cells.GetLength(2) != builder.GridBinding.Length,
 4174            nameof(cells),
 4175            "Dense input dimensions must match the normalized grid address space in X/Y/Z order.");
 176
 10177        for (int x = 0; x < cells.GetLength(0); x++)
 178        {
 32179            for (int y = 0; y < cells.GetLength(1); y++)
 180            {
 96181                for (int z = 0; z < cells.GetLength(2); z++)
 182                {
 36183                    NavigationCell? cell = cells[x, y, z];
 36184                    if (cell.HasValue)
 2185                        builder.AddCell(new VoxelIndex(x, y, z), cell.Value);
 186                }
 187            }
 188        }
 189
 1190        return builder.Build();
 191    }
 192
 193    /// <summary>
 194    /// Imports explicit axial Q/layer/R hex cells into the canonical sparse map representation.
 195    /// </summary>
 196    public static NavigationMap ImportAxialHex(
 197        string mapId,
 198        GridConfiguration gridConfiguration,
 199        IEnumerable<NavigationCellEntry> cells)
 200    {
 2201        SwiftThrowHelper.ThrowIfNull(cells, nameof(cells));
 2202        var builder = new NavigationMapBuilder(mapId, gridConfiguration);
 2203        SwiftThrowHelper.ThrowIfArgument(
 2204            builder.GridBinding.Configuration.TopologyKind != GridTopologyKind.HexPrism,
 2205            nameof(gridConfiguration),
 2206            "Axial hex import requires hex-prism topology.");
 207
 12208        foreach (NavigationCellEntry cell in cells)
 4209            builder.AddCell(cell);
 210
 2211        return builder.Build();
 212    }
 213
 214    private static NormalizedGridConfiguration Normalize(GridConfiguration configuration)
 215    {
 46216        bool normalized = configuration.TryNormalize(out NormalizedGridConfiguration descriptor);
 46217        SwiftThrowHelper.ThrowIfArgument(
 46218            !normalized,
 46219            nameof(configuration),
 46220            "Grid configuration could not be normalized into a supported address space.");
 46221        return descriptor;
 222    }
 223
 224    private void ValidateCells(NavigationCellEntry[] cells)
 225    {
 50398226        for (int i = 0; i < cells.Length; i++)
 227        {
 23143228            NavigationCellEntry entry = cells[i];
 23143229            SwiftThrowHelper.ThrowIfArgument(
 23143230                !GridBinding.IsValidIndex(entry.Index),
 23143231                nameof(cells),
 23143232                $"Cell index {entry.Index} is outside the normalized grid address space.");
 23142233            ValidateCellPayload(entry.Cell, nameof(cells));
 234
 23141235            SwiftThrowHelper.ThrowIfArgument(
 23141236                i > 0 && cells[i - 1].Index.Equals(entry.Index),
 23141237                nameof(cells),
 23141238                $"Duplicate authored cell index {entry.Index}.");
 239        }
 2056240    }
 241
 242    private void ValidateConnections(
 243        NavigationCellEntry[] cells,
 244        NavigationConnection[] connections)
 245    {
 2056246        int maximumPrismCount = GetMaximumLocalPrismCount(connections);
 2056247        var orderedPrisms = maximumPrismCount == 0
 2056248            ? Array.Empty<GridCellPrism>()
 2056249            : new GridCellPrism[maximumPrismCount];
 2056250        var portalWaypoints = maximumPrismCount <= 1
 2056251            ? Array.Empty<Vector3d>()
 2056252            : new Vector3d[(maximumPrismCount - 1) * 2];
 2056253        int maximumWitnessCount = GetMaximumWitnessCount(connections);
 2056254        var witnessSet = new SwiftHashSet<NavigationCellAddress>(maximumWitnessCount);
 255
 4698256        for (int i = 0; i < connections.Length; i++)
 257        {
 303258            NavigationConnection connection = connections[i];
 303259            SwiftThrowHelper.ThrowIfArgument(
 303260                i > 0 && string.Equals(connections[i - 1].Id, connection.Id, StringComparison.Ordinal),
 303261                nameof(connections),
 303262                $"Duplicate map-local connection ID '{connection.Id}'.");
 263
 302264            SwiftThrowHelper.ThrowIfArgument(
 302265                !TryGetAuthoredCell(cells, connection.SourceIndex, out NavigationCell sourceCell),
 302266                nameof(connections),
 302267                $"Connection '{connection.Id}' references missing local source {connection.SourceIndex}.");
 301268            ValidateLocalAnchor(
 301269                connection.EntryAnchor,
 301270                connection.SourceIndex,
 301271                nameof(connections),
 301272                "Connection entry anchor is outside its source prism.");
 273
 300274            ValidateClearance(
 300275                sourceCell,
 300276                connection.PortalRadiusClearance,
 300277                connection.PortalHeightClearance,
 300278                nameof(connections),
 300279                "Connection exceeds source-cell clearance.");
 280
 299281            if (string.Equals(connection.Destination.MapId, MapId, StringComparison.Ordinal))
 282            {
 206283                SwiftThrowHelper.ThrowIfArgument(
 206284                    !TryGetAuthoredCell(
 206285                        cells,
 206286                        connection.Destination.Index,
 206287                        out NavigationCell destinationCell),
 206288                    nameof(connections),
 206289                    $"Connection '{connection.Id}' references missing local destination {connection.Destination.Index}."
 205290                ValidateLocalAnchor(
 205291                    connection.ExitAnchor,
 205292                    connection.Destination.Index,
 205293                    nameof(connections),
 205294                    "Connection exit anchor is outside its destination prism.");
 205295                ValidateClearance(
 205296                    destinationCell,
 205297                    connection.PortalRadiusClearance,
 205298                    connection.PortalHeightClearance,
 205299                    nameof(connections),
 205300                    "Connection exceeds destination-cell clearance.");
 301            }
 302
 298303            ValidateWitnesses(cells, connection, witnessSet);
 296304            ValidateConnectionGeometry(cells, connection, orderedPrisms, portalWaypoints);
 305        }
 2046306    }
 307
 308    private void ValidateTransitions(
 309        NavigationCellEntry[] cells,
 310        TraversalTransitionDefinition[] transitions)
 311    {
 4432312        for (int i = 0; i < transitions.Length; i++)
 313        {
 177314            TraversalTransitionDefinition transition = transitions[i];
 177315            SwiftThrowHelper.ThrowIfArgument(
 177316                i > 0 && string.Equals(transitions[i - 1].Id, transition.Id, StringComparison.Ordinal),
 177317                nameof(transitions),
 177318                $"Duplicate map-local transition ID '{transition.Id}'.");
 319
 176320            SwiftThrowHelper.ThrowIfArgument(
 176321                !TryGetAuthoredCell(cells, transition.SourceIndex, out NavigationCell sourceCell),
 176322                nameof(transitions),
 176323                $"Transition '{transition.Id}' references missing local source {transition.SourceIndex}.");
 175324            SwiftThrowHelper.ThrowIfArgument(
 175325                !SupportsMedium(sourceCell, transition.SourceMedium),
 175326                nameof(transitions),
 175327                "Transition source medium is not authored on its source cell.");
 328
 174329            if (transition.HasSourcePointOverride)
 330            {
 8331                ValidateLocalAnchor(
 8332                    transition.SourcePointOverride,
 8333                    transition.SourceIndex,
 8334                    nameof(transitions),
 8335                    "Transition source point is outside its source prism.");
 336            }
 337
 173338            if (!string.Equals(transition.Destination.MapId, MapId, StringComparison.Ordinal))
 339                continue;
 340
 158341            SwiftThrowHelper.ThrowIfArgument(
 158342                !TryGetAuthoredCell(
 158343                    cells,
 158344                    transition.Destination.Index,
 158345                    out NavigationCell destinationCell),
 158346                nameof(transitions),
 158347                $"Transition '{transition.Id}' references missing local destination {transition.Destination.Index}.");
 157348            SwiftThrowHelper.ThrowIfArgument(
 157349                !SupportsMedium(destinationCell, transition.DestinationMedium),
 157350                nameof(transitions),
 157351                "Transition destination medium is not authored on its destination cell.");
 352
 156353            if (transition.HasDestinationPointOverride)
 354            {
 8355                ValidateLocalAnchor(
 8356                    transition.DestinationPointOverride,
 8357                    transition.Destination.Index,
 8358                    nameof(transitions),
 8359                    "Transition destination point is outside its destination prism.");
 360            }
 361        }
 2039362    }
 363
 364    private static void ValidateTransitionRules(TraversalTransitionRule[] rules)
 365    {
 4234366        for (int i = 0; i < rules.Length; i++)
 367        {
 80368            TraversalTransitionRule rule = rules[i];
 80369            rule.Validate();
 79370            SwiftThrowHelper.ThrowIfArgument(
 79371                i > 0 && string.Equals(rules[i - 1].Id, rule.Id, StringComparison.Ordinal),
 79372                nameof(rules),
 79373                $"Duplicate map-local transition rule ID '{rule.Id}'.");
 374        }
 2037375    }
 376
 377    private void ValidateWitnesses(
 378        NavigationCellEntry[] cells,
 379        NavigationConnection connection,
 380        SwiftHashSet<NavigationCellAddress> witnessSet)
 381    {
 298382        witnessSet.Clear();
 1020383        for (int i = 0; i < connection.Witnesses.Count; i++)
 384        {
 214385            NavigationCellAddress witness = connection.Witnesses[i];
 214386            SwiftThrowHelper.ThrowIfArgument(
 214387                !witnessSet.Add(witness),
 214388                nameof(connection),
 214389                $"Connection '{connection.Id}' repeats witness {witness}.");
 390
 213391            if (!string.Equals(witness.MapId, MapId, StringComparison.Ordinal))
 392                continue;
 393
 82394            SwiftThrowHelper.ThrowIfArgument(
 82395                !TryGetAuthoredCell(cells, witness.Index, out NavigationCell witnessCell),
 82396                nameof(connection),
 82397                $"Connection '{connection.Id}' references missing local witness {witness.Index}.");
 81398            ValidateClearance(
 81399                witnessCell,
 81400                connection.PortalRadiusClearance,
 81401                connection.PortalHeightClearance,
 81402                nameof(connection),
 81403                "Connection exceeds witness-cell clearance.");
 404        }
 296405    }
 406
 407    private void ValidateGridGeometry()
 408    {
 2060409        int lastX = GridBinding.Width - 1;
 2060410        int lastY = GridBinding.Height - 1;
 2060411        int lastZ = GridBinding.Length - 1;
 12360412        for (int xSelector = 0; xSelector < 2; xSelector++)
 413        {
 4120414            int x = xSelector == 0 ? 0 : lastX;
 24720415            for (int ySelector = 0; ySelector < 2; ySelector++)
 416            {
 8240417                int y = ySelector == 0 ? 0 : lastY;
 49440418                for (int zSelector = 0; zSelector < 2; zSelector++)
 419                {
 16480420                    int z = zSelector == 0 ? 0 : lastZ;
 16480421                    bool representable = GridBinding.TryGetCellPrism(
 16480422                        new VoxelIndex(x, y, z),
 16480423                        out _);
 16480424                    SwiftThrowHelper.ThrowIfArgument(
 16480425                        !representable,
 16480426                        nameof(GridBinding),
 16480427                        "Grid binding cannot produce exact cell prisms across its address-space extremes.");
 428                }
 429            }
 430        }
 2060431    }
 432
 433    private void ValidateConnectionGeometry(
 434        NavigationCellEntry[] cells,
 435        NavigationConnection connection,
 436        GridCellPrism[] prismScratch,
 437        Vector3d[] waypointScratch)
 438    {
 439        // Full cross-map geometry is validated transactionally when the destination map is present.
 296440        if (!IsCompleteLocalChain(connection))
 94441            return;
 442
 202443        int prismCount = connection.Witnesses.Count + 2;
 202444        GridBinding.TryGetCellPrism(connection.SourceIndex, out prismScratch[0]);
 562445        for (int i = 0; i < connection.Witnesses.Count; i++)
 446        {
 79447            GridBinding.TryGetCellPrism(
 79448                connection.Witnesses[i].Index,
 79449                out prismScratch[i + 1]);
 450        }
 202451        GridBinding.TryGetCellPrism(connection.Destination.Index, out prismScratch[prismCount - 1]);
 452
 202453        bool validCorridor = GridCellGeometry.TryValidateNavigationCorridor(
 202454            prismScratch.AsSpan(0, prismCount),
 202455            connection.EntryAnchor,
 202456            connection.ExitAnchor,
 202457            connection.PortalRadiusClearance,
 202458            connection.PortalHeightClearance,
 202459            waypointScratch.AsSpan(0, (prismCount - 1) * 2),
 202460            out _,
 202461            out Fixed64 corridorCost);
 202462        SwiftThrowHelper.ThrowIfArgument(
 202463            !validCorridor,
 202464            nameof(connection),
 202465            $"Connection '{connection.Id}' does not define a valid clearance-bearing prism corridor.");
 466
 200467        if (!connection.IsLowerBoundCertified)
 198468            return;
 469
 2470        TryGetAuthoredCell(cells, connection.Destination.Index, out NavigationCell destinationCell);
 2471        bool certified = TryProveLowerBound(
 2472            prismScratch[0],
 2473            prismScratch[prismCount - 1],
 2474            connection,
 2475            corridorCost,
 2476            destinationCell.EnterCost);
 2477        SwiftThrowHelper.ThrowIfArgument(
 2478            !certified,
 2479            nameof(connection),
 2480            $"Connection '{connection.Id}' lower-bound declaration cannot be proven by its canonical fixed-point cost.")
 1481    }
 482
 483    private bool IsCompleteLocalChain(NavigationConnection connection)
 484    {
 599485        if (!string.Equals(connection.Destination.MapId, MapId, StringComparison.Ordinal))
 186486            return false;
 487
 1148488        for (int i = 0; i < connection.Witnesses.Count; i++)
 489        {
 163490            if (!string.Equals(connection.Witnesses[i].MapId, MapId, StringComparison.Ordinal))
 2491                return false;
 492        }
 493
 411494        return true;
 495    }
 496
 497    private int GetMaximumLocalPrismCount(NavigationConnection[] connections)
 498    {
 2056499        int maximumPrismCount = 0;
 4718500        for (int i = 0; i < connections.Length; i++)
 501        {
 303502            if (!IsCompleteLocalChain(connections[i]))
 503                continue;
 504
 209505            int prismCount = connections[i].Witnesses.Count + 2;
 209506            if (prismCount > maximumPrismCount)
 56507                maximumPrismCount = prismCount;
 508        }
 509
 2056510        return maximumPrismCount;
 511    }
 512
 513    private static int GetMaximumWitnessCount(NavigationConnection[] connections)
 514    {
 2056515        int maximumWitnessCount = 0;
 4718516        for (int i = 0; i < connections.Length; i++)
 517        {
 303518            if (connections[i].Witnesses.Count > maximumWitnessCount)
 38519                maximumWitnessCount = connections[i].Witnesses.Count;
 520        }
 521
 2056522        return maximumWitnessCount;
 523    }
 524
 525    private static bool TryProveLowerBound(
 526        in GridCellPrism source,
 527        in GridCellPrism destination,
 528        NavigationConnection connection,
 529        Fixed64 corridorCost,
 530        Fixed64 destinationEnterCost)
 531    {
 2532        Vector3d sourceAnchor = GetFootAnchor(source);
 2533        Vector3d destinationAnchor = GetFootAnchor(destination);
 2534        return Vector3d.TryGetDistance(sourceAnchor, connection.EntryAnchor, out Fixed64 approachCost)
 2535            && Vector3d.TryGetDistance(connection.ExitAnchor, destinationAnchor, out Fixed64 departureCost)
 2536            && Fixed64.TryAdd(approachCost, corridorCost, out Fixed64 traversalCost)
 2537            && Fixed64.TryAdd(traversalCost, departureCost, out traversalCost)
 2538            && Fixed64.TryAdd(traversalCost, connection.AdditionalCost, out traversalCost)
 2539            && Fixed64.TryAdd(traversalCost, destinationEnterCost, out traversalCost)
 2540            && NavigationDistanceMath.TryCeiling(
 2541                sourceAnchor,
 2542                destinationAnchor,
 2543                out Fixed64 directCost)
 2544            && traversalCost >= directCost;
 545    }
 546
 547    private static Vector3d GetFootAnchor(in GridCellPrism prism) =>
 4548        new(prism.Center.X, prism.VerticalMin, prism.Center.Z);
 549
 550    private void ValidateLocalAnchor(
 551        Vector3d anchor,
 552        VoxelIndex index,
 553        string parameterName,
 554        string message)
 555    {
 522556        bool representable = GridBinding.TryGetCellPrism(index, out GridCellPrism prism);
 557        System.Diagnostics.Debug.Assert(representable);
 522558        SwiftThrowHelper.ThrowIfArgument(!prism.Contains(anchor), parameterName, message);
 519559    }
 560
 561    private static void ValidateClearance(
 562        NavigationCell cell,
 563        Fixed64 radius,
 564        Fixed64 height,
 565        string parameterName,
 566        string message) =>
 586567        SwiftThrowHelper.ThrowIfArgument(
 586568            radius > cell.RadiusClearance || height > cell.HeightClearance,
 586569            parameterName,
 586570            message);
 571
 572    private static void ValidateCellPayload(NavigationCell cell, string parameterName)
 573    {
 23207574        SwiftThrowHelper.ThrowIfArgument(
 23207575            cell.Media == TraversalMedia.None,
 23207576            parameterName,
 23207577            "Cell media must contain at least one known traversal-medium bit.");
 578
 579        System.Diagnostics.Debug.Assert((cell.Media & ~NavigationCell.KnownMedia) == 0);
 580        System.Diagnostics.Debug.Assert((cell.RequiredCapabilities & ~NavigationCell.KnownCapabilities) == 0);
 581        System.Diagnostics.Debug.Assert(cell.EnterCost >= Fixed64.Zero);
 582        System.Diagnostics.Debug.Assert(cell.RadiusClearance >= Fixed64.Zero);
 583        System.Diagnostics.Debug.Assert(cell.HeightClearance >= Fixed64.Zero);
 584        System.Diagnostics.Debug.Assert((cell.Flags & ~NavigationCell.KnownFlags) == 0);
 23205585    }
 586
 587    private static bool SupportsMedium(NavigationCell cell, TraversalMedium medium)
 588    {
 589        System.Diagnostics.Debug.Assert(NavigationCell.IsKnownMedium(medium));
 332590        TraversalMedia requested = medium == TraversalMedium.Solid
 332591            ? TraversalMedia.Solid
 332592            : medium == TraversalMedium.Gas
 332593                ? TraversalMedia.Gas
 332594                : TraversalMedia.Liquid;
 332595        return (cell.Media & requested) != 0;
 596    }
 597
 598    private bool TryGetAuthoredCell(
 599        NavigationCellEntry[] cells,
 600        VoxelIndex index,
 601        out NavigationCell cell)
 602    {
 926603        int ordinal = FindCell(cells, index);
 926604        if (ordinal >= 0)
 605        {
 913606            cell = cells[ordinal].Cell;
 913607            return true;
 608        }
 13609        if (_defaultCell.HasValue && GridBinding.IsValidIndex(index))
 610        {
 8611            cell = _defaultCell.Value;
 8612            return true;
 613        }
 614
 5615        cell = default;
 5616        return false;
 617    }
 618
 619    private static int FindCell(NavigationCellEntry[] cells, VoxelIndex index)
 620    {
 926621        int low = 0;
 926622        int high = cells.Length - 1;
 1770623        while (low <= high)
 624        {
 1757625            int middle = low + ((high - low) >> 1);
 1757626            int comparison = cells[middle].Index.CompareTo(index);
 1757627            if (comparison == 0)
 913628                return middle;
 844629            if (comparison < 0)
 453630                low = middle + 1;
 631            else
 391632                high = middle - 1;
 633        }
 634
 13635        return -1;
 636    }
 637
 638    private sealed class NavigationCellEntryComparer : IComparer<NavigationCellEntry>
 639    {
 1640        internal static readonly NavigationCellEntryComparer Instance = new();
 641
 642        public int Compare(NavigationCellEntry left, NavigationCellEntry right) =>
 65902643            left.Index.CompareTo(right.Index);
 644    }
 645
 646    private sealed class NavigationConnectionComparer : IComparer<NavigationConnection>
 647    {
 1648        internal static readonly NavigationConnectionComparer Instance = new();
 649
 650        public int Compare(NavigationConnection? left, NavigationConnection? right) =>
 359651            StringComparer.Ordinal.Compare(left!.Id, right!.Id);
 652    }
 653
 654    private sealed class TraversalTransitionDefinitionComparer : IComparer<TraversalTransitionDefinition>
 655    {
 1656        internal static readonly TraversalTransitionDefinitionComparer Instance = new();
 657
 658        public int Compare(
 659            TraversalTransitionDefinition left,
 660            TraversalTransitionDefinition right) =>
 135661            StringComparer.Ordinal.Compare(left.Id, right.Id);
 662    }
 663}

Methods/Properties

.ctor(System.String,GridForge.Configuration.NormalizedGridConfiguration)
.ctor(System.String,GridForge.Configuration.GridConfiguration)
AddCell(GridForge.Spatial.VoxelIndex,Trailblazer.Pathing.NavigationCell)
AddCell(Trailblazer.Pathing.NavigationCellEntry)
SetDefaultCell(System.Nullable`1<Trailblazer.Pathing.NavigationCell>)
AddConnection(Trailblazer.Pathing.NavigationConnection)
AddTransition(Trailblazer.Pathing.TraversalTransitionDefinition)
AddTransitionRule(Trailblazer.Pathing.TraversalTransitionRule)
Build()
ImportDenseRectangular(System.String,GridForge.Configuration.GridConfiguration,System.Nullable`1<Trailblazer.Pathing.NavigationCell>[0...,0...,0...])
ImportAxialHex(System.String,GridForge.Configuration.GridConfiguration,System.Collections.Generic.IEnumerable`1<Trailblazer.Pathing.NavigationCellEntry>)
Normalize(GridForge.Configuration.GridConfiguration)
ValidateCells(Trailblazer.Pathing.NavigationCellEntry[])
ValidateConnections(Trailblazer.Pathing.NavigationCellEntry[],Trailblazer.Pathing.NavigationConnection[])
ValidateTransitions(Trailblazer.Pathing.NavigationCellEntry[],Trailblazer.Pathing.TraversalTransitionDefinition[])
ValidateTransitionRules(Trailblazer.Pathing.TraversalTransitionRule[])
ValidateWitnesses(Trailblazer.Pathing.NavigationCellEntry[],Trailblazer.Pathing.NavigationConnection,SwiftCollections.SwiftHashSet`1<Trailblazer.Pathing.NavigationCellAddress>)
ValidateGridGeometry()
ValidateConnectionGeometry(Trailblazer.Pathing.NavigationCellEntry[],Trailblazer.Pathing.NavigationConnection,GridForge.Grids.Topology.GridCellPrism[],FixedMathSharp.Vector3d[])
IsCompleteLocalChain(Trailblazer.Pathing.NavigationConnection)
GetMaximumLocalPrismCount(Trailblazer.Pathing.NavigationConnection[])
GetMaximumWitnessCount(Trailblazer.Pathing.NavigationConnection[])
TryProveLowerBound(GridForge.Grids.Topology.GridCellPrism&,GridForge.Grids.Topology.GridCellPrism&,Trailblazer.Pathing.NavigationConnection,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64)
GetFootAnchor(GridForge.Grids.Topology.GridCellPrism&)
ValidateLocalAnchor(FixedMathSharp.Vector3d,GridForge.Spatial.VoxelIndex,System.String,System.String)
ValidateClearance(Trailblazer.Pathing.NavigationCell,FixedMathSharp.Fixed64,FixedMathSharp.Fixed64,System.String,System.String)
ValidateCellPayload(Trailblazer.Pathing.NavigationCell,System.String)
SupportsMedium(Trailblazer.Pathing.NavigationCell,Trailblazer.TraversalMedium)
TryGetAuthoredCell(Trailblazer.Pathing.NavigationCellEntry[],GridForge.Spatial.VoxelIndex,Trailblazer.Pathing.NavigationCell&)
FindCell(Trailblazer.Pathing.NavigationCellEntry[],GridForge.Spatial.VoxelIndex)
.cctor()
Compare(Trailblazer.Pathing.NavigationCellEntry,Trailblazer.Pathing.NavigationCellEntry)
.cctor()
Compare(Trailblazer.Pathing.NavigationConnection,Trailblazer.Pathing.NavigationConnection)
.cctor()
Compare(Trailblazer.Pathing.TraversalTransitionDefinition,Trailblazer.Pathing.TraversalTransitionDefinition)