< Summary

Information
Class: Trailblazer.Heightmaps.TrailblazerHeightmapService
Assembly: Trailblazer
File(s): /home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Heightmaps/TrailblazerHeightmapService.cs
Line coverage
100%
Covered lines: 81
Uncovered lines: 0
Coverable lines: 81
Total lines: 204
Line coverage: 100%
Branch coverage
95%
Covered branches: 42
Total branches: 44
Branch coverage: 95.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
Register(...)100%66100%
Unregister(...)100%22100%
IsRegistered(...)50%22100%
TryGetRegistration(...)100%44100%
TrySampleGround(...)100%11100%
TrySampleGround(...)100%1818100%
Reset()100%11100%
Dispose()100%11100%
TrySampleRegistration(...)100%44100%
CreateSample(...)100%11100%
IsBetterCandidate(...)100%44100%
EnsureUsable()100%22100%

File(s)

/home/runner/work/Trailblazer/Trailblazer/src/Trailblazer/Heightmaps/TrailblazerHeightmapService.cs

#LineLine coverage
 1using FixedMathSharp;
 2using SwiftCollections;
 3using System;
 4
 5namespace Trailblazer.Heightmaps;
 6
 7/// <summary>
 8/// Context-owned API for registering and sampling deterministic heightmap layers.
 9/// </summary>
 10public sealed class TrailblazerHeightmapService
 11{
 12    private readonly TrailblazerWorldContext _context;
 13
 96114    internal TrailblazerHeightmapService(TrailblazerWorldContext context)
 15    {
 96116        _context = context ?? throw new ArgumentNullException(nameof(context));
 96117        State = new HeightmapWorldState();
 96118    }
 19
 20    internal HeightmapWorldState State { get; }
 21
 22    /// <summary>
 23    /// Registers a heightmap layer for this context.
 24    /// </summary>
 25    public bool Register(
 26        HeightmapSurface surface,
 27        Fixed64 minSelectionY,
 28        Fixed64 maxSelectionY,
 29        int priority = 0)
 30    {
 3731        EnsureUsable();
 3732        if (surface == null)
 133            throw new ArgumentNullException(nameof(surface));
 3634        if (maxSelectionY <= minSelectionY)
 135            throw new ArgumentOutOfRangeException(nameof(maxSelectionY), "Maximum selection Y must be greater than minim
 3536        if (State.LayersByName.ContainsKey(surface.Name))
 137            return false;
 38
 3439        var registration = new HeightmapLayerRegistration(
 3440            surface,
 3441            minSelectionY,
 3442            maxSelectionY,
 3443            priority,
 3444            State.NextRegistrationOrder++);
 3445        State.LayersByName[surface.Name] = registration;
 3446        return true;
 47    }
 48
 49    /// <summary>
 50    /// Unregisters a heightmap layer by name.
 51    /// </summary>
 52    public bool Unregister(string layerName)
 53    {
 354        EnsureUsable();
 355        if (string.IsNullOrWhiteSpace(layerName))
 156            return false;
 57
 258        return State.LayersByName.Remove(layerName);
 59    }
 60
 61    /// <summary>
 62    /// Returns true when a layer with the supplied name is registered.
 63    /// </summary>
 64    public bool IsRegistered(string layerName)
 65    {
 866        EnsureUsable();
 667        return !string.IsNullOrWhiteSpace(layerName) && State.LayersByName.ContainsKey(layerName);
 68    }
 69
 70    /// <summary>
 71    /// Attempts to retrieve context-local registration metadata for a layer.
 72    /// </summary>
 73    public bool TryGetRegistration(string layerName, out HeightmapLayerRegistration registration)
 74    {
 275        EnsureUsable();
 276        if (!string.IsNullOrWhiteSpace(layerName)
 277            && State.LayersByName.TryGetValue(layerName, out HeightmapLayerRegistration found))
 78        {
 179            registration = found;
 180            return true;
 81        }
 82
 183        registration = null!;
 184        return false;
 85    }
 86
 87    /// <summary>
 88    /// Attempts to sample the best deterministic registered layer that contains the query X/Z and contact Y.
 89    /// </summary>
 90    public bool TrySampleGround(Vector3d worldPosition, out HeightmapSample sample)
 91    {
 892        return TrySampleGround(worldPosition, null, out sample);
 93    }
 94
 95    /// <summary>
 96    /// Attempts to sample the preferred layer when valid, then falls back to deterministic candidate selection.
 97    /// </summary>
 98    public bool TrySampleGround(Vector3d worldPosition, string? preferredLayerName, out HeightmapSample sample)
 99    {
 22100        EnsureUsable();
 101
 22102        if (!string.IsNullOrWhiteSpace(preferredLayerName)
 22103            && State.LayersByName.TryGetValue(preferredLayerName, out HeightmapLayerRegistration preferredRegistration)
 22104            && TrySampleRegistration(preferredRegistration, worldPosition, out Fixed64 preferredGroundY, out Fixed64 pre
 105        {
 3106            sample = CreateSample(preferredRegistration, worldPosition, preferredGroundY, preferredDistance);
 3107            return true;
 108        }
 109
 19110        bool hasBest = false;
 19111        HeightmapLayerRegistration? bestRegistration = null;
 19112        Fixed64 bestGroundY = Fixed64.Zero;
 19113        Fixed64 bestDistance = Fixed64.Zero;
 88114        foreach (HeightmapLayerRegistration registration in State.LayersByName.Values)
 115        {
 25116            if (!TrySampleRegistration(registration, worldPosition, out Fixed64 groundY, out Fixed64 distance))
 117                continue;
 118
 20119            if (hasBest && !IsBetterCandidate(registration, distance, bestRegistration!, bestDistance))
 120                continue;
 121
 18122            hasBest = true;
 18123            bestRegistration = registration;
 18124            bestGroundY = groundY;
 18125            bestDistance = distance;
 126        }
 127
 19128        if (!hasBest || bestRegistration == null)
 129        {
 2130            sample = default;
 2131            return false;
 132        }
 133
 17134        sample = CreateSample(bestRegistration, worldPosition, bestGroundY, bestDistance);
 17135        return true;
 136    }
 137
 138    /// <summary>
 139    /// Clears all heightmap layers registered to this context.
 140    /// </summary>
 141    public void Reset()
 142    {
 25143        EnsureUsable();
 25144        State.Reset();
 25145    }
 146
 147    internal void Dispose()
 148    {
 961149        State.Reset();
 961150    }
 151
 152    private static bool TrySampleRegistration(
 153        HeightmapLayerRegistration registration,
 154        Vector3d worldPosition,
 155        out Fixed64 groundY,
 156        out Fixed64 distance)
 157    {
 30158        if (!registration.ContainsSelectionY(worldPosition.y)
 30159            || !registration.Surface.TrySampleGround(worldPosition, out groundY))
 160        {
 7161            groundY = Fixed64.Zero;
 7162            distance = Fixed64.Zero;
 7163            return false;
 164        }
 165
 23166        distance = (worldPosition.y - groundY).Abs();
 23167        return true;
 168    }
 169
 170    private static HeightmapSample CreateSample(
 171        HeightmapLayerRegistration registration,
 172        Vector3d worldPosition,
 173        Fixed64 groundY,
 174        Fixed64 distance)
 175    {
 20176        return new HeightmapSample(
 20177            registration.LayerName,
 20178            worldPosition,
 20179            groundY,
 20180            distance);
 181    }
 182
 183    private static bool IsBetterCandidate(
 184        HeightmapLayerRegistration candidate,
 185        Fixed64 candidateDistance,
 186        HeightmapLayerRegistration current,
 187        Fixed64 currentDistance)
 188    {
 3189        if (candidateDistance != currentDistance)
 1190            return candidateDistance < currentDistance;
 191
 2192        if (candidate.Priority != current.Priority)
 1193            return candidate.Priority > current.Priority;
 194
 1195        return candidate.RegistrationOrder < current.RegistrationOrder;
 196    }
 197
 198    private void EnsureUsable()
 199    {
 97200        SwiftThrowHelper.ThrowIfDisposed(_context.IsDisposed, nameof(TrailblazerWorldContext));
 96201        if (!_context.World.IsActive)
 1202            throw new InvalidOperationException("TrailblazerHeightmapService is bound to an inactive GridWorld.");
 95203    }
 204}