< 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: 84
Uncovered lines: 0
Coverable lines: 84
Total lines: 213
Line coverage: 100%
Branch coverage
100%
Covered branches: 36
Total branches: 36
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%
Register(...)100%22100%
Unregister(...)100%22100%
IsRegistered(...)100%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%11100%

File(s)

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

#LineLine coverage
 1//=======================================================================
 2// TrailblazerHeightmapService.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 Trailblazer.Heightmaps;
 12
 13/// <summary>
 14/// Context-owned API for registering and sampling deterministic heightmap layers.
 15/// </summary>
 16public sealed class TrailblazerHeightmapService
 17{
 18    private readonly TrailblazerWorldContext _context;
 19
 98920    internal TrailblazerHeightmapService(TrailblazerWorldContext context)
 21    {
 98922        _context = context;
 98923        State = new HeightmapWorldState();
 98924    }
 25
 26    internal HeightmapWorldState State { get; }
 27
 28    /// <summary>
 29    /// Registers a heightmap layer for this context.
 30    /// </summary>
 31    public bool Register(
 32        HeightmapSurface surface,
 33        Fixed64 minSelectionY,
 34        Fixed64 maxSelectionY,
 35        int priority = 0)
 36    {
 4337        EnsureUsable();
 4338        SwiftThrowHelper.ThrowIfNull(surface, nameof(surface));
 4239        SwiftThrowHelper.ThrowIfArgumentOutOfRange(
 4240            maxSelectionY <= minSelectionY,
 4241            actualValue: null,
 4242            paramName: nameof(maxSelectionY),
 4243            message: "Maximum selection Y must be greater than minimum selection Y.");
 4144        if (State.LayersByName.ContainsKey(surface.Name))
 145            return false;
 46
 4047        var registration = new HeightmapLayerRegistration(
 4048            surface,
 4049            minSelectionY,
 4050            maxSelectionY,
 4051            priority,
 4052            State.NextRegistrationOrder++);
 4053        State.LayersByName[surface.Name] = registration;
 4054        return true;
 55    }
 56
 57    /// <summary>
 58    /// Unregisters a heightmap layer by name.
 59    /// </summary>
 60    public bool Unregister(string layerName)
 61    {
 362        EnsureUsable();
 363        if (string.IsNullOrWhiteSpace(layerName))
 164            return false;
 65
 266        return State.LayersByName.Remove(layerName);
 67    }
 68
 69    /// <summary>
 70    /// Returns true when a layer with the supplied name is registered.
 71    /// </summary>
 72    public bool IsRegistered(string layerName)
 73    {
 1174        EnsureUsable();
 975        return !string.IsNullOrWhiteSpace(layerName) && State.LayersByName.ContainsKey(layerName);
 76    }
 77
 78    /// <summary>
 79    /// Attempts to retrieve context-local registration metadata for a layer.
 80    /// </summary>
 81    public bool TryGetRegistration(string layerName, out HeightmapLayerRegistration registration)
 82    {
 583        EnsureUsable();
 584        if (!string.IsNullOrWhiteSpace(layerName)
 585            && State.LayersByName.TryGetValue(layerName, out HeightmapLayerRegistration found))
 86        {
 187            registration = found;
 188            return true;
 89        }
 90
 491        registration = null!;
 492        return false;
 93    }
 94
 95    /// <summary>
 96    /// Attempts to sample the best deterministic registered layer that contains the query X/Z and contact Y.
 97    /// </summary>
 98    public bool TrySampleGround(Vector3d worldPosition, out HeightmapSample sample)
 99    {
 8100        return TrySampleGround(worldPosition, null, out sample);
 101    }
 102
 103    /// <summary>
 104    /// Attempts to sample the preferred layer when valid, then falls back to deterministic candidate selection.
 105    /// </summary>
 106    public bool TrySampleGround(Vector3d worldPosition, string? preferredLayerName, out HeightmapSample sample)
 107    {
 28108        EnsureUsable();
 109
 28110        if (!string.IsNullOrWhiteSpace(preferredLayerName)
 28111            && State.LayersByName.TryGetValue(preferredLayerName, out HeightmapLayerRegistration preferredRegistration)
 28112            && TrySampleRegistration(preferredRegistration, worldPosition, out Fixed64 preferredGroundY, out Fixed64 pre
 113        {
 3114            sample = CreateSample(preferredRegistration, worldPosition, preferredGroundY, preferredDistance);
 3115            return true;
 116        }
 117
 25118        bool hasBest = false;
 25119        HeightmapLayerRegistration? bestRegistration = null;
 25120        Fixed64 bestGroundY = Fixed64.Zero;
 25121        Fixed64 bestDistance = Fixed64.Zero;
 112122        foreach (HeightmapLayerRegistration registration in State.LayersByName.Values)
 123        {
 31124            if (!TrySampleRegistration(registration, worldPosition, out Fixed64 groundY, out Fixed64 distance))
 125                continue;
 126
 26127            if (hasBest && !IsBetterCandidate(registration, distance, bestRegistration!, bestDistance))
 128                continue;
 129
 24130            hasBest = true;
 24131            bestRegistration = registration;
 24132            bestGroundY = groundY;
 24133            bestDistance = distance;
 134        }
 135
 25136        if (!hasBest || bestRegistration == null)
 137        {
 2138            sample = default;
 2139            return false;
 140        }
 141
 23142        sample = CreateSample(bestRegistration, worldPosition, bestGroundY, bestDistance);
 23143        return true;
 144    }
 145
 146    /// <summary>
 147    /// Clears all heightmap layers registered to this context.
 148    /// </summary>
 149    public void Reset()
 150    {
 22151        EnsureUsable();
 22152        State.Reset();
 22153    }
 154
 155    internal void Dispose()
 156    {
 988157        State.Reset();
 988158    }
 159
 160    private static bool TrySampleRegistration(
 161        HeightmapLayerRegistration registration,
 162        Vector3d worldPosition,
 163        out Fixed64 groundY,
 164        out Fixed64 distance)
 165    {
 36166        if (!registration.ContainsSelectionY(worldPosition.Y)
 36167            || !registration.Surface.TrySampleGround(worldPosition, out groundY))
 168        {
 7169            groundY = Fixed64.Zero;
 7170            distance = Fixed64.Zero;
 7171            return false;
 172        }
 173
 29174        distance = (worldPosition.Y - groundY).Abs();
 29175        return true;
 176    }
 177
 178    private static HeightmapSample CreateSample(
 179        HeightmapLayerRegistration registration,
 180        Vector3d worldPosition,
 181        Fixed64 groundY,
 182        Fixed64 distance)
 183    {
 26184        return new HeightmapSample(
 26185            registration.LayerName,
 26186            worldPosition,
 26187            groundY,
 26188            distance);
 189    }
 190
 191    private static bool IsBetterCandidate(
 192        HeightmapLayerRegistration candidate,
 193        Fixed64 candidateDistance,
 194        HeightmapLayerRegistration current,
 195        Fixed64 currentDistance)
 196    {
 3197        if (candidateDistance != currentDistance)
 1198            return candidateDistance < currentDistance;
 199
 2200        if (candidate.Priority != current.Priority)
 1201            return candidate.Priority > current.Priority;
 202
 1203        return candidate.RegistrationOrder < current.RegistrationOrder;
 204    }
 205
 206    private void EnsureUsable()
 207    {
 112208        SwiftThrowHelper.ThrowIfDisposed(_context.IsDisposed, nameof(TrailblazerWorldContext));
 111209        SwiftThrowHelper.ThrowIfTrue(
 111210            !_context.World.IsActive,
 111211            message: "TrailblazerHeightmapService is bound to an inactive GridWorld.");
 110212    }
 213}