Add MrGameEng.AI utility-AI module; format codebase with CSharpier New MrGameEng.AI module (ResponseCurve, Consideration, UtilityAction, UtilityAi selector, Blackboard) plus CSharpier formatting applied across the whole engine. Documents the CSharpier convention in CLAUDE.md. @
This commit is contained in:
@@ -14,7 +14,15 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
public void Dispose() => Directory.Delete(_root, recursive: true);
|
||||
|
||||
/// <summary>Writes a PNG filled with one RGBA color.</summary>
|
||||
private void WritePng(string relativePath, int width, int height, byte r, byte g, byte b, byte a = 255)
|
||||
private void WritePng(
|
||||
string relativePath,
|
||||
int width,
|
||||
int height,
|
||||
byte r,
|
||||
byte g,
|
||||
byte b,
|
||||
byte a = 255
|
||||
)
|
||||
{
|
||||
var fullPath = Path.Combine(SourceDir, relativePath);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!);
|
||||
@@ -28,25 +36,37 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
}
|
||||
|
||||
using var stream = File.Create(fullPath);
|
||||
new ImageWriter().WritePng(data, width, height, StbImageWriteSharp.ColorComponents.RedGreenBlueAlpha, stream);
|
||||
new ImageWriter().WritePng(
|
||||
data,
|
||||
width,
|
||||
height,
|
||||
StbImageWriteSharp.ColorComponents.RedGreenBlueAlpha,
|
||||
stream
|
||||
);
|
||||
}
|
||||
|
||||
private AtlasBuildOptions Options(int groupDepth = 1, bool force = false) => new()
|
||||
{
|
||||
SourceDirectory = SourceDir,
|
||||
OutputDirectory = OutputDir,
|
||||
GroupDepth = groupDepth,
|
||||
MaxPageSize = 128,
|
||||
Padding = 2,
|
||||
Force = force,
|
||||
};
|
||||
private AtlasBuildOptions Options(int groupDepth = 1, bool force = false) =>
|
||||
new()
|
||||
{
|
||||
SourceDirectory = SourceDir,
|
||||
OutputDirectory = OutputDir,
|
||||
GroupDepth = groupDepth,
|
||||
MaxPageSize = 128,
|
||||
Padding = 2,
|
||||
Force = force,
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[InlineData("Terrain/Surfaces/Marsh.png", 1, "Terrain", "Terrain/Surfaces/Marsh")]
|
||||
[InlineData("Terrain/Surfaces/Marsh.png", 2, "Terrain.Surfaces", "Terrain/Surfaces/Marsh")]
|
||||
[InlineData("Terrain/Surfaces/Marsh.png", 0, "Root", "Terrain/Surfaces/Marsh")]
|
||||
[InlineData("loose.png", 3, "Root", "loose")]
|
||||
public void ClassifyPath_GroupsByDepth(string path, int depth, string expectedAtlas, string expectedKey)
|
||||
public void ClassifyPath_GroupsByDepth(
|
||||
string path,
|
||||
int depth,
|
||||
string expectedAtlas,
|
||||
string expectedKey
|
||||
)
|
||||
{
|
||||
var (atlas, key) = AtlasBuilder.ClassifyPath(path, depth, "Root");
|
||||
|
||||
@@ -67,13 +87,18 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
Assert.Equal(2, group.RegionCount);
|
||||
Assert.False(group.Skipped);
|
||||
|
||||
var metadata = AtlasMetadata.FromJson(File.ReadAllText(Path.Combine(OutputDir, "Terrain.atlas")));
|
||||
var metadata = AtlasMetadata.FromJson(
|
||||
File.ReadAllText(Path.Combine(OutputDir, "Terrain.atlas"))
|
||||
);
|
||||
Assert.Equal(["Terrain/Grass", "Terrain/Water"], metadata.Regions.Select(x => x.Key));
|
||||
|
||||
var grass = metadata.Regions.Single(x => x.Key == "Terrain/Grass");
|
||||
var page = metadata.Pages[grass.Page];
|
||||
using var stream = File.OpenRead(Path.Combine(OutputDir, page.File));
|
||||
var pixels = ImageResult.FromStream(stream, StbImageSharp.ColorComponents.RedGreenBlueAlpha);
|
||||
var pixels = ImageResult.FromStream(
|
||||
stream,
|
||||
StbImageSharp.ColorComponents.RedGreenBlueAlpha
|
||||
);
|
||||
|
||||
// Центральный пиксель региона должен быть цветом исходной картинки.
|
||||
var center = ((grass.Y + 8) * pixels.Width + grass.X + 8) * 4;
|
||||
@@ -101,7 +126,9 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
AtlasBuilder.Build(Options());
|
||||
|
||||
File.SetLastWriteTimeUtc(
|
||||
Path.Combine(SourceDir, "UI/button.png"), DateTime.UtcNow.AddMinutes(1));
|
||||
Path.Combine(SourceDir, "UI/button.png"),
|
||||
DateTime.UtcNow.AddMinutes(1)
|
||||
);
|
||||
var result = AtlasBuilder.Build(Options());
|
||||
|
||||
Assert.False(Assert.Single(result.Groups).Skipped);
|
||||
@@ -192,8 +219,27 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
Name = "Things.Pawn",
|
||||
PageSize = 2048,
|
||||
Padding = 2,
|
||||
Pages = [new AtlasPage { File = "Things.Pawn.atlas.0.png", Width = 256, Height = 128 }],
|
||||
Regions = [new AtlasRegion { Key = "Things/Pawn/Fox", Page = 0, X = 2, Y = 4, Width = 64, Height = 32 }],
|
||||
Pages =
|
||||
[
|
||||
new AtlasPage
|
||||
{
|
||||
File = "Things.Pawn.atlas.0.png",
|
||||
Width = 256,
|
||||
Height = 128,
|
||||
},
|
||||
],
|
||||
Regions =
|
||||
[
|
||||
new AtlasRegion
|
||||
{
|
||||
Key = "Things/Pawn/Fox",
|
||||
Page = 0,
|
||||
X = 2,
|
||||
Y = 4,
|
||||
Width = 64,
|
||||
Height = 32,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
var parsed = AtlasMetadata.FromJson(metadata.ToJson());
|
||||
@@ -203,8 +249,10 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
var page = Assert.Single(parsed.Pages);
|
||||
Assert.Equal(("Things.Pawn.atlas.0.png", 256, 128), (page.File, page.Width, page.Height));
|
||||
var region = Assert.Single(parsed.Regions);
|
||||
Assert.Equal(("Things/Pawn/Fox", 0, 2, 4, 64, 32),
|
||||
(region.Key, region.Page, region.X, region.Y, region.Width, region.Height));
|
||||
Assert.Equal(
|
||||
("Things/Pawn/Fox", 0, 2, 4, 64, 32),
|
||||
(region.Key, region.Page, region.X, region.Y, region.Width, region.Height)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -213,10 +261,26 @@ public sealed class AtlasBuilderTests : IDisposable
|
||||
var metadata = new AtlasMetadata
|
||||
{
|
||||
Name = "Test",
|
||||
Pages = [new AtlasPage { File = "Test.atlas.0.png", Width = 64, Height = 64 }],
|
||||
Pages =
|
||||
[
|
||||
new AtlasPage
|
||||
{
|
||||
File = "Test.atlas.0.png",
|
||||
Width = 64,
|
||||
Height = 64,
|
||||
},
|
||||
],
|
||||
Regions =
|
||||
[
|
||||
new AtlasRegion { Key = "a/b", Page = 0, X = 2, Y = 2, Width = 10, Height = 12 },
|
||||
new AtlasRegion
|
||||
{
|
||||
Key = "a/b",
|
||||
Page = 0,
|
||||
X = 2,
|
||||
Y = 2,
|
||||
Width = 10,
|
||||
Height = 12,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -15,5 +14,4 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\MrGameEng.Atlases\MrGameEng.Atlases.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -46,8 +46,10 @@ public class ShelfPackerTests
|
||||
var a = list[i];
|
||||
var b = list[j];
|
||||
var separated =
|
||||
a.X + a.Width + 2 <= b.X || b.X + b.Width + 2 <= a.X ||
|
||||
a.Y + a.Height + 2 <= b.Y || b.Y + b.Height + 2 <= a.Y;
|
||||
a.X + a.Width + 2 <= b.X
|
||||
|| b.X + b.Width + 2 <= a.X
|
||||
|| a.Y + a.Height + 2 <= b.Y
|
||||
|| b.Y + b.Height + 2 <= a.Y;
|
||||
Assert.True(separated, $"{a.Key} overlaps {b.Key} (padding included)");
|
||||
}
|
||||
}
|
||||
@@ -61,7 +63,10 @@ public class ShelfPackerTests
|
||||
var result = ShelfPacker.Pack(Squares(9, 100), maxPageSize: 256, padding: 2);
|
||||
|
||||
Assert.True(result.PageSizes.Count >= 3);
|
||||
Assert.Equal(Enumerable.Range(0, result.PageSizes.Count), result.Placements.Select(p => p.Page).Distinct().Order());
|
||||
Assert.Equal(
|
||||
Enumerable.Range(0, result.PageSizes.Count),
|
||||
result.Placements.Select(p => p.Page).Distinct().Order()
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -80,7 +85,9 @@ public class ShelfPackerTests
|
||||
[Fact]
|
||||
public void Pack_IsDeterministic_RegardlessOfInputOrder()
|
||||
{
|
||||
var items = Squares(30, 20).Concat(Squares(10, 50).Select(i => i with { Key = "b" + i.Key })).ToList();
|
||||
var items = Squares(30, 20)
|
||||
.Concat(Squares(10, 50).Select(i => i with { Key = "b" + i.Key }))
|
||||
.ToList();
|
||||
var shuffled = items.AsEnumerable().Reverse().ToList();
|
||||
|
||||
var a = ShelfPacker.Pack(items, 128, 2);
|
||||
@@ -88,7 +95,8 @@ public class ShelfPackerTests
|
||||
|
||||
Assert.Equal(
|
||||
a.Placements.OrderBy(p => p.Key, StringComparer.Ordinal),
|
||||
b.Placements.OrderBy(p => p.Key, StringComparer.Ordinal));
|
||||
b.Placements.OrderBy(p => p.Key, StringComparer.Ordinal)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -115,11 +123,14 @@ public class ShelfPackerTests
|
||||
// 40² с padding на лимит 100: POT-округление дало бы 128 > лимита.
|
||||
var result = ShelfPacker.Pack(Squares(10, 40), maxPageSize: 100, padding: 2);
|
||||
|
||||
Assert.All(result.PageSizes, size =>
|
||||
{
|
||||
Assert.True(size.Width <= 100, $"page width {size.Width} > 100");
|
||||
Assert.True(size.Height <= 100, $"page height {size.Height} > 100");
|
||||
});
|
||||
Assert.All(
|
||||
result.PageSizes,
|
||||
size =>
|
||||
{
|
||||
Assert.True(size.Width <= 100, $"page width {size.Width} > 100");
|
||||
Assert.True(size.Height <= 100, $"page height {size.Height} > 100");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user