SchematicUnderlay.Fit: the line art stretched onto the hull Shape's Width x Height
Engine-free rule in ServerShared with five behaviour tests. Builds (ServerShared and tests only; Editor wiring follows in the next commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f5def1794
commit
59cbdd1f3d
3 changed files with 145 additions and 0 deletions
36
Assets/Scripts/ServerShared/SchematicUnderlay.cs
Normal file
36
Assets/Scripts/ServerShared/SchematicUnderlay.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CultMath;
|
||||
|
||||
// A mod hull's line-art schematic laid under its cell grids. The one rule for where the lines land: the line art's
|
||||
// top-down bounding box is stretched, per axis, onto the hull Shape's cell rect [0, Width] x [0, Height] as the hull
|
||||
// drawer sets it, never onto the occupied cells (underlay-fits-hull-bounds). Display only: nothing is derived from the
|
||||
// model into the schematic or back (schematic-and-model-are-separate-spaces).
|
||||
public static class SchematicUnderlay
|
||||
{
|
||||
// Points are Blender Z-up XYZ in the Ship Root frame, nose toward -Y and starboard toward -X, so the schematic's
|
||||
// x (starboard right) is -X and its y (forward up) is -Y. Output is in hull cell units, index-aligned with `lines`.
|
||||
public static float2[][] Fit(IReadOnlyList<ShipPolyline> lines, Shape hull)
|
||||
{
|
||||
if (lines == null || lines.Count == 0 || hull == null) return new float2[0][];
|
||||
var top = lines.Select(line => Enumerable.Range(0, (line?.Points?.Length ?? 0) / 3)
|
||||
.Select(i => new float2(-line.Points[i * 3], -line.Points[i * 3 + 1])).ToArray()).ToArray();
|
||||
var all = top.SelectMany(line => line).ToArray();
|
||||
if (all.Length == 0) return top;
|
||||
|
||||
var minX = all.Min(p => p.x);
|
||||
var maxX = all.Max(p => p.x);
|
||||
var minY = all.Min(p => p.y);
|
||||
var maxY = all.Max(p => p.y);
|
||||
return top.Select(line => line.Select(p => new float2(
|
||||
Stretch(p.x, minX, maxX, hull.Width), Stretch(p.y, minY, maxY, hull.Height))).ToArray()).ToArray();
|
||||
}
|
||||
|
||||
// min lands on 0 and max on `size`; an axis with no extent sits at the midpoint.
|
||||
private static float Stretch(float value, float min, float max, int size) =>
|
||||
max > min ? (value - min) / (max - min) * size : size / 2f;
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/SchematicUnderlay.cs.meta
Normal file
11
Assets/Scripts/ServerShared/SchematicUnderlay.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 77b6dfe49272456a9f976d2578732d8a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
98
tests/Aetheria.Shared.Tests/SchematicUnderlayTests.cs
Normal file
98
tests/Aetheria.Shared.Tests/SchematicUnderlayTests.cs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CultMath;
|
||||
using Xunit;
|
||||
|
||||
// SchematicUnderlay: the one rule that registers a mod hull's line art onto the hull Shape's cell rect.
|
||||
public sealed class SchematicUnderlayTests
|
||||
{
|
||||
private static ShipPolyline Line(params float[] xyz) => new ShipPolyline { Points = xyz };
|
||||
|
||||
// Lines spanning Blender X [-3,5], Y [-10,2] (nose at Y -10, starboard at X -3).
|
||||
private static List<ShipPolyline> Lines() => new List<ShipPolyline>
|
||||
{
|
||||
Line(-3, -10, 0, 5, 2, 0),
|
||||
Line(1, -4, 1, 0, 0, 1),
|
||||
};
|
||||
|
||||
private static Shape SixByFour()
|
||||
{
|
||||
var shape = new Shape(6, 4);
|
||||
shape[new int2(1, 1)] = true;
|
||||
shape[new int2(2, 1)] = true;
|
||||
return shape;
|
||||
}
|
||||
|
||||
private static (float2 min, float2 max) Bounds(float2[][] fit)
|
||||
{
|
||||
var all = fit.SelectMany(l => l).ToArray();
|
||||
return (new float2(all.Min(p => p.x), all.Min(p => p.y)), new float2(all.Max(p => p.x), all.Max(p => p.y)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnderlaySpansHullBoundsNotOccupiedCells()
|
||||
{
|
||||
var (min, max) = Bounds(SchematicUnderlay.Fit(Lines(), SixByFour()));
|
||||
|
||||
Assert.Equal(0f, min.x, 4);
|
||||
Assert.Equal(0f, min.y, 4);
|
||||
Assert.Equal(6f, max.x, 4);
|
||||
Assert.Equal(4f, max.y, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnderlayNoseUpStarboardRight()
|
||||
{
|
||||
var fit = SchematicUnderlay.Fit(Lines(), SixByFour());
|
||||
|
||||
// Line 0 point 0 holds both the lowest Blender Y (nose) and the lowest Blender X (starboard).
|
||||
Assert.Equal(4f, fit[0][0].y, 4);
|
||||
Assert.Equal(6f, fit[0][0].x, 4);
|
||||
// Line 0 point 1 holds the highest of both: aft and port.
|
||||
Assert.Equal(0f, fit[0][1].y, 4);
|
||||
Assert.Equal(0f, fit[0][1].x, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnderlayFollowsTheHullDrawersSize()
|
||||
{
|
||||
var hull = SixByFour();
|
||||
hull.Resize(8, 5);
|
||||
|
||||
var (min, max) = Bounds(SchematicUnderlay.Fit(Lines(), hull));
|
||||
|
||||
Assert.Equal(new float2(0, 0), new float2(min.x, min.y));
|
||||
Assert.Equal(8f, max.x, 4);
|
||||
Assert.Equal(5f, max.y, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnderlayStretchesEachAxis()
|
||||
{
|
||||
var lines = new List<ShipPolyline> { Line(0, 0, 0, 4, 2, 0) };
|
||||
|
||||
var (min, max) = Bounds(SchematicUnderlay.Fit(lines, new Shape(3, 3)));
|
||||
|
||||
Assert.Equal(0f, min.x, 4);
|
||||
Assert.Equal(0f, min.y, 4);
|
||||
Assert.Equal(3f, max.x, 4);
|
||||
Assert.Equal(3f, max.y, 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnderlayDegenerateAndEmpty()
|
||||
{
|
||||
var flat = new List<ShipPolyline> { Line(2, 0, 0, 2, 5, 0) };
|
||||
|
||||
var fit = SchematicUnderlay.Fit(flat, new Shape(6, 4));
|
||||
|
||||
Assert.All(fit[0], p => Assert.Equal(3f, p.x, 4));
|
||||
Assert.All(fit[0], p => Assert.True(float.IsFinite(p.y)));
|
||||
var pointless = SchematicUnderlay.Fit(new List<ShipPolyline> { Line() }, new Shape(6, 4));
|
||||
Assert.Single(pointless);
|
||||
Assert.Empty(pointless[0]);
|
||||
Assert.Empty(SchematicUnderlay.Fit(null, new Shape(6, 4)));
|
||||
Assert.Empty(SchematicUnderlay.Fit(new List<ShipPolyline>(), new Shape(6, 4)));
|
||||
Assert.Empty(SchematicUnderlay.Fit(Lines(), null));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue