SweptAreaCoverageResolverOrientedRectangleTests.cs 5.29 KB
using System.Reflection;
using Rcs.Application.Services;
using Rcs.Domain.Enums;
using Rcs.Infrastructure.PathFinding.Services;
using Rcs.Infrastructure.Services;
using Xunit;

namespace Rcs.Infrastructure.Tests;

public class SweptAreaCoverageResolverOrientedRectangleTests
{
    [Fact]
    public void ExpandFromMapWithOrientedRectangle_ThetaZero_CoversExpectedNodes()
    {
        var map = new MapCacheData
        {
            Nodes = new List<MapNodeCache>
            {
                CreateNode("N_CENTER", 0d, 0d),
                CreateNode("N_IN", 0.4d, 0.2d),
                CreateNode("N_OUT", 0.7d, 0d)
            }
        };

        var coverage = SweptAreaCoverageResolver.ExpandFromMapWithOrientedRectangle(
            map,
            new SweepPoint(0d, 0d),
            theta: 0d,
            length: 1d,
            width: 0.6d,
            safetyDistance: 0d);

        Assert.Contains("N_CENTER", coverage.NodeCodes);
        Assert.Contains("N_IN", coverage.NodeCodes);
        Assert.DoesNotContain("N_OUT", coverage.NodeCodes);
    }

    [Fact]
    public void ExpandFromMapWithOrientedRectangle_ThetaNinetyDegrees_RotatesLongAxis()
    {
        var map = new MapCacheData
        {
            Nodes = new List<MapNodeCache>
            {
                CreateNode("N_LONG_AXIS", 0d, 0.45d),
                CreateNode("N_WIDTH_OUT", 0.45d, 0d)
            }
        };

        var coverage = SweptAreaCoverageResolver.ExpandFromMapWithOrientedRectangle(
            map,
            new SweepPoint(0d, 0d),
            theta: Math.PI / 2d,
            length: 1d,
            width: 0.6d,
            safetyDistance: 0d);

        Assert.Contains("N_LONG_AXIS", coverage.NodeCodes);
        Assert.DoesNotContain("N_WIDTH_OUT", coverage.NodeCodes);
    }

    [Fact]
    public void ExpandFromMapWithOrientedRectangle_CoversIntersectingEdgesOnly()
    {
        var n1 = CreateNode("N1", -2d, 0d);
        var n2 = CreateNode("N2", 2d, 0d);
        var n3 = CreateNode("N3", -2d, 1d);
        var n4 = CreateNode("N4", 2d, 1d);

        var map = new MapCacheData
        {
            Nodes = new List<MapNodeCache> { n1, n2, n3, n4 },
            Edges = new List<MapEdgeCache>
            {
                CreateStraightEdge("E_HIT", n1.NodeId, n2.NodeId),
                CreateStraightEdge("E_MISS", n3.NodeId, n4.NodeId)
            }
        };

        var coverage = SweptAreaCoverageResolver.ExpandFromMapWithOrientedRectangle(
            map,
            new SweepPoint(0d, 0d),
            theta: 0d,
            length: 1d,
            width: 0.6d,
            safetyDistance: 0d);

        Assert.Contains("E_HIT", coverage.EdgeCodes);
        Assert.DoesNotContain("E_MISS", coverage.EdgeCodes);
    }

    [Fact]
    public void ExpandFromMapWithOrientedRectangle_SafetyDistanceExpandsCoverage()
    {
        var map = new MapCacheData
        {
            Nodes = new List<MapNodeCache>
            {
                CreateNode("N_MARGIN", 0.55d, 0d)
            }
        };

        var noSafetyCoverage = SweptAreaCoverageResolver.ExpandFromMapWithOrientedRectangle(
            map,
            new SweepPoint(0d, 0d),
            theta: 0d,
            length: 1d,
            width: 0.6d,
            safetyDistance: 0d);

        var safetyCoverage = SweptAreaCoverageResolver.ExpandFromMapWithOrientedRectangle(
            map,
            new SweepPoint(0d, 0d),
            theta: 0d,
            length: 1d,
            width: 0.6d,
            safetyDistance: 0.1d);

        Assert.DoesNotContain("N_MARGIN", noSafetyCoverage.NodeCodes);
        Assert.Contains("N_MARGIN", safetyCoverage.NodeCodes);
    }

    [Fact]
    public void RobotCacheService_ResolveSweepTheta_FallsBackToPreviousTheta()
    {
        var method = typeof(RobotCacheService).GetMethod(
            "ResolveSweepTheta",
            BindingFlags.NonPublic | BindingFlags.Static);

        Assert.NotNull(method);

        var resolved = (double?)method!.Invoke(null, new object?[] { null, 1.23d });

        Assert.Equal(1.23d, resolved);
    }

    [Fact]
    public void RobotCacheService_BuildLiveSweepBindingId_ContainsThetaBucketAndChangesWithHeading()
    {
        var method = typeof(RobotCacheService).GetMethod(
            "BuildLiveSweepBindingId",
            BindingFlags.NonPublic | BindingFlags.Static);

        Assert.NotNull(method);

        var id1 = (string?)method!.Invoke(null, new object[] { 100d, 200d, 0d, 1d });
        var id2 = (string?)method!.Invoke(null, new object[] { 100d, 200d, 20d * Math.PI / 180d, 1d });

        Assert.False(string.IsNullOrWhiteSpace(id1));
        Assert.False(string.IsNullOrWhiteSpace(id2));
        Assert.Contains(":T", id1);
        Assert.NotEqual(id1, id2);
    }

    private static MapNodeCache CreateNode(string code, double x, double y)
    {
        return new MapNodeCache
        {
            NodeId = Guid.NewGuid(),
            NodeCode = code,
            X = x,
            Y = y,
            Active = true
        };
    }

    private static MapEdgeCache CreateStraightEdge(string code, Guid fromNode, Guid toNode)
    {
        return new MapEdgeCache
        {
            EdgeId = Guid.NewGuid(),
            EdgeCode = code,
            FromNode = fromNode,
            ToNode = toNode,
            Active = true,
            CurveType = MapEdgeCurveType.Straight
        };
    }
}