HullFootprint: the one footprint cell reading and edit rule (grow, trim, refuse last cell), with tests
Not yet built or run (verify job at the end). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7a44e0c00c
commit
d76b27673a
3 changed files with 153 additions and 0 deletions
43
Assets/Scripts/ServerShared/HullFootprint.cs
Normal file
43
Assets/Scripts/ServerShared/HullFootprint.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/* 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 hardpoint's schematic footprint: its Shape laid over the hull's cells at its Position, in hull orientation.
|
||||
// The footprint is authored for gameplay and never derived from the model or the Blender add-on
|
||||
// (schematic-and-model-are-separate-spaces). This is the one reading of footprint cells in hull space and the one
|
||||
// edit rule, so any host (the Studio drawer now, a Thing host later) edits footprints the same way.
|
||||
public static class HullFootprint
|
||||
{
|
||||
public static IEnumerable<int2> Cells(HardpointData hardpoint) =>
|
||||
hardpoint.Shape.Coordinates.Select(c => c + hardpoint.Position);
|
||||
|
||||
// Toggles one hull cell of the footprint at `position`. A cell outside the footprint grows it toward any edge;
|
||||
// a cell inside is removed and empty border rows and columns are trimmed; every other cell keeps its hull
|
||||
// coordinate. Removing the last cell is refused (false, outputs are the inputs' values). Builds a new Shape and
|
||||
// never edits the one it is given.
|
||||
public static bool Toggle(Shape footprint, int2 position, int2 hullCell, out Shape shape, out int2 newPosition)
|
||||
{
|
||||
var cells = Cells(footprint, position);
|
||||
if (!cells.Remove(hullCell)) cells.Add(hullCell);
|
||||
else if (cells.Count == 0)
|
||||
{
|
||||
shape = footprint;
|
||||
newPosition = position;
|
||||
return false;
|
||||
}
|
||||
|
||||
var min = cells.Aggregate((a, b) => new int2(math.min(a.x, b.x), math.min(a.y, b.y)));
|
||||
var max = cells.Aggregate((a, b) => new int2(math.max(a.x, b.x), math.max(a.y, b.y)));
|
||||
shape = new Shape(max.x - min.x + 1, max.y - min.y + 1);
|
||||
foreach (var cell in cells) shape[cell - min] = true;
|
||||
newPosition = min;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static HashSet<int2> Cells(Shape footprint, int2 position) =>
|
||||
new HashSet<int2>(footprint.Coordinates.Select(c => c + position));
|
||||
}
|
||||
11
Assets/Scripts/ServerShared/HullFootprint.cs.meta
Normal file
11
Assets/Scripts/ServerShared/HullFootprint.cs.meta
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e2b8c4a7d1f4a39b6c0e83f91d7a254
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
99
tests/Aetheria.Shared.Tests/HullFootprintTests.cs
Normal file
99
tests/Aetheria.Shared.Tests/HullFootprintTests.cs
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
using System.Linq;
|
||||
using CultMath;
|
||||
using Xunit;
|
||||
|
||||
// HullFootprint: the one rule for editing a hardpoint's schematic footprint (Position + Shape together) and the
|
||||
// one reading of its cells in hull space.
|
||||
public sealed class HullFootprintTests
|
||||
{
|
||||
private static HardpointData Hardpoint(int2 position, params int2[] cellsInShapeSpace)
|
||||
{
|
||||
var max = cellsInShapeSpace.Aggregate(new int2(0, 0), (a, c) => new int2(CultMath.math.max(a.x, c.x), CultMath.math.max(a.y, c.y)));
|
||||
var shape = new Shape(max.x + 1, max.y + 1);
|
||||
foreach (var c in cellsInShapeSpace) shape[c] = true;
|
||||
return new HardpointData { Position = position, Shape = shape };
|
||||
}
|
||||
|
||||
private static int2[] Hull(HardpointData hp) => HullFootprint.Cells(hp).OrderBy(c => c.y).ThenBy(c => c.x).ToArray();
|
||||
|
||||
private static HardpointData Apply(HardpointData hp, int2 hullCell)
|
||||
{
|
||||
Assert.True(HullFootprint.Toggle(hp.Shape, hp.Position, hullCell, out var shape, out var position));
|
||||
return new HardpointData { Position = position, Shape = shape };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CellsAreTheShapeLaidAtPosition()
|
||||
{
|
||||
var hp = Hardpoint(new int2(3, 3), new int2(0, 0), new int2(1, 0));
|
||||
Assert.Equal(new[] { new int2(3, 3), new int2(4, 3) }, Hull(hp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FootprintGrowsTowardAnyEdgeAndKeepsItsCells()
|
||||
{
|
||||
var hp = Hardpoint(new int2(3, 3), new int2(0, 0));
|
||||
|
||||
var grown = Apply(Apply(hp, new int2(2, 3)), new int2(3, 2));
|
||||
|
||||
Assert.Equal(new int2(2, 2), grown.Position);
|
||||
Assert.Equal(2, grown.Shape.Width);
|
||||
Assert.Equal(2, grown.Shape.Height);
|
||||
Assert.Equal(new[] { new int2(3, 2), new int2(2, 3), new int2(3, 3) }, Hull(grown));
|
||||
|
||||
var right = Apply(Apply(hp, new int2(4, 3)), new int2(3, 4));
|
||||
Assert.Equal(new int2(3, 3), right.Position);
|
||||
Assert.Equal(new[] { new int2(3, 3), new int2(4, 3), new int2(3, 4) }, Hull(right));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FootprintTrimsBackToItsOriginal()
|
||||
{
|
||||
var hp = Hardpoint(new int2(3, 3), new int2(0, 0));
|
||||
var grown = Apply(Apply(hp, new int2(2, 3)), new int2(3, 2));
|
||||
|
||||
var trimmed = Apply(Apply(grown, new int2(2, 3)), new int2(3, 2));
|
||||
|
||||
Assert.Equal(hp.Position, trimmed.Position);
|
||||
Assert.Equal(1, trimmed.Shape.Width);
|
||||
Assert.Equal(1, trimmed.Shape.Height);
|
||||
Assert.Equal(Hull(hp), Hull(trimmed));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FootprintRefusesItsLastCell()
|
||||
{
|
||||
var hp = Hardpoint(new int2(3, 3), new int2(0, 0));
|
||||
|
||||
Assert.False(HullFootprint.Toggle(hp.Shape, hp.Position, new int2(3, 3), out var shape, out var position));
|
||||
|
||||
Assert.Equal(hp.Position, position);
|
||||
Assert.Same(hp.Shape, shape);
|
||||
Assert.Equal(new[] { new int2(3, 3) }, Hull(hp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FootprintToggleLeavesItsInputUnchanged()
|
||||
{
|
||||
var hp = Hardpoint(new int2(3, 3), new int2(0, 0), new int2(1, 0));
|
||||
|
||||
HullFootprint.Toggle(hp.Shape, hp.Position, new int2(2, 3), out _, out _);
|
||||
HullFootprint.Toggle(hp.Shape, hp.Position, new int2(4, 3), out _, out _);
|
||||
|
||||
Assert.Equal(new int2(3, 3), hp.Position);
|
||||
Assert.Equal(2, hp.Shape.Width);
|
||||
Assert.Equal(1, hp.Shape.Height);
|
||||
Assert.Equal(new[] { new int2(3, 3), new int2(4, 3) }, Hull(hp));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TrimmingAnInteriorBorderMovesPositionWithoutMovingCells()
|
||||
{
|
||||
var hp = Hardpoint(new int2(5, 5), new int2(0, 0), new int2(1, 0), new int2(2, 0));
|
||||
|
||||
var trimmed = Apply(hp, new int2(5, 5));
|
||||
|
||||
Assert.Equal(new int2(6, 5), trimmed.Position);
|
||||
Assert.Equal(new[] { new int2(6, 5), new int2(7, 5) }, Hull(trimmed));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue