Compare commits
4 Commits
a657d6ed1c
...
engine-rew
| Author | SHA1 | Date | |
|---|---|---|---|
| e76952cd0d | |||
| 2207116d0a | |||
| 201b13f80f | |||
| e3f9c1cc52 |
@@ -9,6 +9,8 @@ namespace _2DGAMELIB
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public class BodyTemplate
|
public class BodyTemplate
|
||||||
{
|
{
|
||||||
|
public string Tag = ""; // I didn't remove this, but it has been removed on merging my changes? What? - REDCODE
|
||||||
|
|
||||||
public OrderedDictionary<string, VariantGrid> Difss = new OrderedDictionary<string, VariantGrid>();
|
public OrderedDictionary<string, VariantGrid> Difss = new OrderedDictionary<string, VariantGrid>();
|
||||||
|
|
||||||
private VariantGrid r;
|
private VariantGrid r;
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ namespace _2DGAMELIB
|
|||||||
public bool Dra = true;
|
public bool Dra = true;
|
||||||
public bool Hit = true;
|
public bool Hit = true;
|
||||||
private bool closed;
|
private bool closed;
|
||||||
|
public bool IsClosed => closed;
|
||||||
|
|
||||||
public PartGroup GetParent()
|
public PartGroup GetParent()
|
||||||
{
|
{
|
||||||
|
|||||||
187
SlaveEngine.AssetBuilder/Compiler.cs
Normal file
187
SlaveEngine.AssetBuilder/Compiler.cs
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using SlaveEngine.AssetBuilder.Models;
|
||||||
|
using SlaveEngine.Assets;
|
||||||
|
using SlaveEngine.Assets.Models;
|
||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
using Spectre.Console;
|
||||||
|
using YamlDotNet.Serialization;
|
||||||
|
using YamlDotNet.Serialization.NamingConventions;
|
||||||
|
|
||||||
|
namespace SlaveEngine.AssetBuilder;
|
||||||
|
|
||||||
|
public class Compiler {
|
||||||
|
private readonly string _inputDir;
|
||||||
|
private readonly string _outputDir;
|
||||||
|
private readonly IDeserializer _yaml;
|
||||||
|
|
||||||
|
public Compiler(string inputDir, string outputDir) {
|
||||||
|
_inputDir = inputDir;
|
||||||
|
_outputDir = outputDir;
|
||||||
|
|
||||||
|
_yaml = new DeserializerBuilder()
|
||||||
|
.WithNamingConvention(UnderscoredNamingConvention.Instance)
|
||||||
|
.IgnoreUnmatchedProperties()
|
||||||
|
.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private T LoadYaml<T>(string path) {
|
||||||
|
var text = File.ReadAllText(path);
|
||||||
|
text = NormalizeFlowArrays(text);
|
||||||
|
return _yaml.Deserialize<T>(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeFlowArrays(string yaml) {
|
||||||
|
return Regex.Replace(yaml,
|
||||||
|
@"(position:\s*)\[([\s\S]*?)\]",
|
||||||
|
m => {
|
||||||
|
var prefix = m.Groups[1].Value.TrimEnd();
|
||||||
|
var values = m.Groups[2].Value
|
||||||
|
.Replace('\n', ' ')
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
|
.Select(v => v.Trim())
|
||||||
|
.Where(v => v.Length > 0)
|
||||||
|
.ToList();
|
||||||
|
if (values.Count == 0) return prefix + " []";
|
||||||
|
return prefix + "\n" +
|
||||||
|
string.Join("\n", values.Select(v => $" - {v}"));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Run() {
|
||||||
|
AnsiConsole.Write(new Rule("[yellow]SlaveEngine Asset Builder[/]") { Justification = Justify.Left });
|
||||||
|
AnsiConsole.MarkupLine(" [bold]Input:[/] {0}", _inputDir);
|
||||||
|
AnsiConsole.MarkupLine(" [bold]Output:[/] {0}", _outputDir);
|
||||||
|
AnsiConsole.WriteLine();
|
||||||
|
|
||||||
|
Directory.CreateDirectory(_outputDir);
|
||||||
|
|
||||||
|
var parts = DiscoverParts();
|
||||||
|
if (parts.Count == 0) {
|
||||||
|
AnsiConsole.MarkupLine("[red]No parts found.[/]");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = 0;
|
||||||
|
|
||||||
|
AnsiConsole.Progress()
|
||||||
|
.Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn(), new ElapsedTimeColumn())
|
||||||
|
.Start(ctx => {
|
||||||
|
var task = ctx.AddTask("Compiling parts");
|
||||||
|
task.MaxValue = parts.Count;
|
||||||
|
|
||||||
|
foreach (var (id, partDir) in parts) {
|
||||||
|
var (ok, msg) = CompilePart(partDir);
|
||||||
|
if (ok)
|
||||||
|
AnsiConsole.MarkupLine("[green]{0}[/]", msg);
|
||||||
|
else
|
||||||
|
AnsiConsole.MarkupLine("[red]{0}[/]", msg);
|
||||||
|
if (ok) success++;
|
||||||
|
task.Increment(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success == parts.Count)
|
||||||
|
AnsiConsole.MarkupLine("[green]Done. {0}/{1} compiled.[/]", success, parts.Count);
|
||||||
|
else if (success > 0)
|
||||||
|
AnsiConsole.MarkupLine("[yellow]Done. {0}/{1} compiled.[/]", success, parts.Count);
|
||||||
|
else
|
||||||
|
AnsiConsole.MarkupLine("[red]Done. {0}/{1} compiled.[/]", success, parts.Count);
|
||||||
|
|
||||||
|
return success == parts.Count ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<(string id, string dir)> DiscoverParts() {
|
||||||
|
var parts = new List<(string id, string dir)>();
|
||||||
|
|
||||||
|
var catalogPath = Path.Combine(_inputDir, "Catalog.yaml");
|
||||||
|
if (File.Exists(catalogPath)) {
|
||||||
|
AnsiConsole.MarkupLine("[dim]Using catalog:[/] {0}", catalogPath);
|
||||||
|
var catalog = LoadYaml<CatalogYaml>(catalogPath);
|
||||||
|
if (catalog?.Parts != null) {
|
||||||
|
foreach (var entry in catalog.Parts) {
|
||||||
|
var partDir = Path.GetFullPath(Path.Combine(_inputDir, entry.Path));
|
||||||
|
if (Directory.Exists(partDir))
|
||||||
|
parts.Add((entry.Id, partDir));
|
||||||
|
else
|
||||||
|
AnsiConsole.MarkupLine("[yellow] Warning:[/] directory not found: {0}", partDir);
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scanDir = Path.Combine(_inputDir, "Parts");
|
||||||
|
if (!Directory.Exists(scanDir)) return parts;
|
||||||
|
|
||||||
|
AnsiConsole.MarkupLine("[dim]Scanning:[/] {0}", scanDir);
|
||||||
|
foreach (var dir in Directory.GetDirectories(scanDir)) {
|
||||||
|
var yamlPath = Path.Combine(dir, "part.yaml");
|
||||||
|
if (!File.Exists(yamlPath)) continue;
|
||||||
|
try {
|
||||||
|
var partYaml = LoadYaml<PartYaml>(yamlPath);
|
||||||
|
if (partYaml != null && !string.IsNullOrEmpty(partYaml.Id))
|
||||||
|
parts.Add((partYaml.Id, dir));
|
||||||
|
} catch (Exception ex) {
|
||||||
|
AnsiConsole.MarkupLine("[yellow] Warning:[/] failed to parse {0}: {1}", yamlPath, ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private (bool success, string message) CompilePart(string partDir) {
|
||||||
|
var yamlPath = Path.Combine(partDir, "part.yaml");
|
||||||
|
if (!File.Exists(yamlPath))
|
||||||
|
return (false, $"Skipping (no part.yaml): {partDir}");
|
||||||
|
|
||||||
|
PartYaml partYaml;
|
||||||
|
try {
|
||||||
|
partYaml = LoadYaml<PartYaml>(yamlPath);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return (false, $"Failed to parse {yamlPath}: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partYaml == null)
|
||||||
|
return (false, $"Empty YAML: {yamlPath}");
|
||||||
|
|
||||||
|
try {
|
||||||
|
var variantList = partYaml.Variants ?? new List<VariantYaml>();
|
||||||
|
var variants = new VariantAsset[variantList.Count];
|
||||||
|
for (var i = 0; i < variantList.Count; i++) {
|
||||||
|
var v = variantList[i];
|
||||||
|
var svgPath = Path.Combine(partDir, v.File);
|
||||||
|
if (!File.Exists(svgPath)) {
|
||||||
|
variants[i] = new VariantAsset { X = v.X, Y = v.Y, Groups = Array.Empty<PathGroup>() };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var groups = SvgParser.Parse(svgPath);
|
||||||
|
variants[i] = new VariantAsset { X = v.X, Y = v.Y, Groups = groups };
|
||||||
|
}
|
||||||
|
|
||||||
|
var fieldList = partYaml.Fields ?? new List<FieldYaml>();
|
||||||
|
var jointList = partYaml.Joints ?? new List<JointYaml>();
|
||||||
|
|
||||||
|
var asset = new PartAsset {
|
||||||
|
Guid = Guid.NewGuid(),
|
||||||
|
FileInfo = new AssetFileInfo { Filename = partYaml.Id, FilePath = "" },
|
||||||
|
Id = partYaml.Id,
|
||||||
|
OriginalKey = partYaml.OriginalKey ?? "",
|
||||||
|
Resource = partYaml.Resource ?? "",
|
||||||
|
MorphX = partYaml.MorphX,
|
||||||
|
MorphY = partYaml.MorphY,
|
||||||
|
Fields = fieldList.Select(f => f.Name ?? "").ToArray(),
|
||||||
|
Joints = jointList.Select(j => new Joint {
|
||||||
|
X = j.Position != null && j.Position.Count > 0 ? j.Position[0] : 0,
|
||||||
|
Y = j.Position != null && j.Position.Count > 1 ? j.Position[1] : 0
|
||||||
|
}).ToArray(),
|
||||||
|
Variants = variants
|
||||||
|
};
|
||||||
|
|
||||||
|
var outputPath = Path.Combine(_outputDir, $"{partYaml.Id}.spart");
|
||||||
|
SparWriter.Write(asset, outputPath);
|
||||||
|
|
||||||
|
return (true, $" {partYaml.Id}... OK ({variants.Length} variants)");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return (false, $" {partYaml.Id}... FAILED: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
SlaveEngine.AssetBuilder/Models/YamlModels.cs
Normal file
35
SlaveEngine.AssetBuilder/Models/YamlModels.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
namespace SlaveEngine.AssetBuilder.Models;
|
||||||
|
|
||||||
|
public class PartYaml {
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
public string OriginalKey { get; set; } = "";
|
||||||
|
public string Resource { get; set; } = "";
|
||||||
|
public int MorphX { get; set; }
|
||||||
|
public int MorphY { get; set; }
|
||||||
|
public List<VariantYaml>? Variants { get; set; }
|
||||||
|
public List<FieldYaml>? Fields { get; set; }
|
||||||
|
public List<JointYaml>? Joints { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class VariantYaml {
|
||||||
|
public int X { get; set; }
|
||||||
|
public int Y { get; set; }
|
||||||
|
public string File { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class FieldYaml {
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JointYaml {
|
||||||
|
public List<float>? Position { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CatalogYaml {
|
||||||
|
public List<CatalogEntry>? Parts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CatalogEntry {
|
||||||
|
public string Id { get; set; } = "";
|
||||||
|
public string Path { get; set; } = "";
|
||||||
|
}
|
||||||
32
SlaveEngine.AssetBuilder/Program.cs
Normal file
32
SlaveEngine.AssetBuilder/Program.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System.CommandLine;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
namespace SlaveEngine.AssetBuilder;
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
static int Main(string[] args) {
|
||||||
|
var inputOption = new Option<DirectoryInfo>("--input", "Path to the input SlaveMatrix/Assets folder");
|
||||||
|
inputOption.SetDefaultValueFactory(() => new DirectoryInfo("./SlaveMatrix/Assets"));
|
||||||
|
|
||||||
|
var outputOption = new Option<DirectoryInfo>("--output", "Path to the output directory for compiled .spart files");
|
||||||
|
outputOption.SetDefaultValueFactory(() => new DirectoryInfo("./Assets"));
|
||||||
|
|
||||||
|
var rootCommand = new RootCommand(
|
||||||
|
"SlaveEngine Asset Builder — compiles YAML + SVG assets into .spart binaries");
|
||||||
|
rootCommand.AddOption(inputOption);
|
||||||
|
rootCommand.AddOption(outputOption);
|
||||||
|
|
||||||
|
rootCommand.SetHandler((DirectoryInfo input, DirectoryInfo output) => {
|
||||||
|
if (!input.Exists) {
|
||||||
|
AnsiConsole.MarkupLine("[red]Error:[/] input directory not found: {0}", input.FullName);
|
||||||
|
Environment.Exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var compiler = new Compiler(input.FullName, output.FullName);
|
||||||
|
var result = compiler.Run();
|
||||||
|
Environment.Exit(result);
|
||||||
|
}, inputOption, outputOption);
|
||||||
|
|
||||||
|
return rootCommand.Invoke(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj
Normal file
20
SlaveEngine.AssetBuilder/SlaveEngine.AssetBuilder.csproj
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SlaveEngine.Assets\SlaveEngine.Assets.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Spectre.Console" Version="0.57.0" />
|
||||||
|
<PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
|
||||||
|
<PackageReference Include="YamlDotNet" Version="18.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
73
SlaveEngine.AssetBuilder/SparWriter.cs
Normal file
73
SlaveEngine.AssetBuilder/SparWriter.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Text;
|
||||||
|
using SlaveEngine.Assets;
|
||||||
|
using SlaveEngine.Assets.Models;
|
||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
namespace SlaveEngine.AssetBuilder;
|
||||||
|
|
||||||
|
public static class SparWriter {
|
||||||
|
public static void Write(PartAsset asset, string outputPath) {
|
||||||
|
using var stream = File.Create(outputPath);
|
||||||
|
using var writer = new BinaryWriter(stream);
|
||||||
|
|
||||||
|
writer.Write(Encoding.UTF8.GetBytes("SPRT"));
|
||||||
|
writer.Write(1); // version
|
||||||
|
|
||||||
|
WriteString(writer, asset.Id);
|
||||||
|
WriteString(writer, asset.OriginalKey);
|
||||||
|
WriteString(writer, asset.Resource);
|
||||||
|
writer.Write(asset.MorphX);
|
||||||
|
writer.Write(asset.MorphY);
|
||||||
|
|
||||||
|
writer.Write(asset.Fields.Length);
|
||||||
|
foreach (var f in asset.Fields) WriteString(writer, f);
|
||||||
|
|
||||||
|
writer.Write(asset.Joints.Length);
|
||||||
|
foreach (var j in asset.Joints) {
|
||||||
|
writer.Write(j.X);
|
||||||
|
writer.Write(j.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(asset.Variants.Length);
|
||||||
|
foreach (var variant in asset.Variants) {
|
||||||
|
writer.Write(variant.X);
|
||||||
|
writer.Write(variant.Y);
|
||||||
|
|
||||||
|
writer.Write(variant.Groups.Length);
|
||||||
|
foreach (var group in variant.Groups) {
|
||||||
|
WriteString(writer, group.Name);
|
||||||
|
writer.Write(group.HasTransform);
|
||||||
|
if (group.HasTransform) {
|
||||||
|
writer.Write(group.Tx);
|
||||||
|
writer.Write(group.Ty);
|
||||||
|
writer.Write(group.Angle);
|
||||||
|
writer.Write(group.Sx);
|
||||||
|
writer.Write(group.Sy);
|
||||||
|
writer.Write(group.Bx);
|
||||||
|
writer.Write(group.By);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(group.Paths.Length);
|
||||||
|
foreach (var path in group.Paths) {
|
||||||
|
WriteString(writer, path.Fill);
|
||||||
|
WriteString(writer, path.Stroke);
|
||||||
|
writer.Write(path.StrokeWidth);
|
||||||
|
writer.Write(path.IsClosed);
|
||||||
|
|
||||||
|
writer.Write(path.Commands.Length);
|
||||||
|
foreach (var cmd in path.Commands) {
|
||||||
|
writer.Write((byte)cmd.Type);
|
||||||
|
writer.Write(cmd.Args.Length);
|
||||||
|
foreach (var arg in cmd.Args) writer.Write(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteString(BinaryWriter writer, string s) {
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(s);
|
||||||
|
writer.Write(bytes.Length);
|
||||||
|
writer.Write(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
192
SlaveEngine.AssetBuilder/SvgParser.cs
Normal file
192
SlaveEngine.AssetBuilder/SvgParser.cs
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using SlaveEngine.Assets.Models;
|
||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
namespace SlaveEngine.AssetBuilder;
|
||||||
|
|
||||||
|
public static class SvgParser {
|
||||||
|
private static readonly CultureInfo Inv = CultureInfo.InvariantCulture;
|
||||||
|
|
||||||
|
public static PathGroup[] Parse(string svgPath) {
|
||||||
|
var doc = XDocument.Load(svgPath);
|
||||||
|
var svg = doc.Root;
|
||||||
|
if (svg == null) return Array.Empty<PathGroup>();
|
||||||
|
|
||||||
|
var groups = new List<PathGroup>();
|
||||||
|
CollectGroups(svg, groups, null);
|
||||||
|
return groups.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CollectGroups(XElement parent, List<PathGroup> groups, string? inheritedName) {
|
||||||
|
foreach (var child in parent.Elements()) {
|
||||||
|
var name = child.Name.LocalName;
|
||||||
|
if (name != "g" && name != "path") continue;
|
||||||
|
var id = child.Attribute("id")?.Value ?? inheritedName ?? "";
|
||||||
|
|
||||||
|
if (name == "g") {
|
||||||
|
var transformAttr = child.Attribute("transform")?.Value;
|
||||||
|
|
||||||
|
if (transformAttr != null) {
|
||||||
|
ParseTransform(transformAttr,
|
||||||
|
out var tx, out var ty, out var angle,
|
||||||
|
out var sx, out var sy, out var bx, out var by);
|
||||||
|
|
||||||
|
var paths = CollectPaths(child);
|
||||||
|
groups.Add(new PathGroup {
|
||||||
|
Name = id,
|
||||||
|
HasTransform = true,
|
||||||
|
Tx = tx, Ty = ty, Angle = angle,
|
||||||
|
Sx = sx, Sy = sy, Bx = bx, By = by,
|
||||||
|
Paths = paths
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
CollectGroups(child, groups, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PathData[] CollectPaths(XElement parent) {
|
||||||
|
var paths = new List<PathData>();
|
||||||
|
foreach (var elem in parent.Elements()) {
|
||||||
|
var name = elem.Name.LocalName;
|
||||||
|
if (name == "path") {
|
||||||
|
var d = elem.Attribute("d")?.Value ?? "";
|
||||||
|
var (commands, isClosed) = ParsePathData(d);
|
||||||
|
paths.Add(new PathData {
|
||||||
|
Fill = elem.Attribute("fill")?.Value ?? "none",
|
||||||
|
Stroke = elem.Attribute("stroke")?.Value ?? "none",
|
||||||
|
StrokeWidth = float.Parse(
|
||||||
|
elem.Attribute("stroke-width")?.Value ?? "0", Inv),
|
||||||
|
IsClosed = isClosed,
|
||||||
|
Commands = commands
|
||||||
|
});
|
||||||
|
} else if (name == "g") {
|
||||||
|
paths.AddRange(CollectPaths(elem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseTransform(string t,
|
||||||
|
out float tx, out float ty, out float angle,
|
||||||
|
out float sx, out float sy, out float bx, out float by) {
|
||||||
|
tx = ty = angle = 0;
|
||||||
|
sx = sy = 1;
|
||||||
|
bx = by = 0;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(t)) return;
|
||||||
|
|
||||||
|
var parts = t.Split(')', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var part in parts) {
|
||||||
|
var trimmed = part.TrimStart();
|
||||||
|
if (trimmed.StartsWith("translate(")) {
|
||||||
|
var args = trimmed["translate(".Length..].Split(',');
|
||||||
|
if (args.Length >= 2) {
|
||||||
|
tx = float.Parse(args[0].Trim(), Inv);
|
||||||
|
ty = float.Parse(args[1].Trim(), Inv);
|
||||||
|
}
|
||||||
|
} else if (trimmed.StartsWith("rotate(")) {
|
||||||
|
var arg = trimmed["rotate(".Length..].Trim();
|
||||||
|
angle = float.Parse(arg, Inv);
|
||||||
|
} else if (trimmed.StartsWith("scale(")) {
|
||||||
|
var args = trimmed["scale(".Length..].Split(',');
|
||||||
|
if (args.Length >= 2) {
|
||||||
|
sx = float.Parse(args[0].Trim(), Inv);
|
||||||
|
sy = float.Parse(args[1].Trim(), Inv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static (BezierCommand[] commands, bool isClosed) ParsePathData(string d) {
|
||||||
|
var commands = new List<BezierCommand>();
|
||||||
|
var tokens = Tokenize(d);
|
||||||
|
var i = 0;
|
||||||
|
float cx = 0, cy = 0;
|
||||||
|
|
||||||
|
while (i < tokens.Count) {
|
||||||
|
var cmd = tokens[i][0];
|
||||||
|
i++;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case 'M': case 'm': {
|
||||||
|
var x = float.Parse(tokens[i++], Inv);
|
||||||
|
var y = float.Parse(tokens[i++], Inv);
|
||||||
|
if (cmd == 'm') { x += cx; y += cy; }
|
||||||
|
commands.Add(new BezierCommand { Type = CommandType.MoveTo, Args = new[] { x, y } });
|
||||||
|
cx = x; cy = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'L': case 'l': {
|
||||||
|
var x = float.Parse(tokens[i++], Inv);
|
||||||
|
var y = float.Parse(tokens[i++], Inv);
|
||||||
|
if (cmd == 'l') { x += cx; y += cy; }
|
||||||
|
commands.Add(new BezierCommand { Type = CommandType.LineTo, Args = new[] { x, y } });
|
||||||
|
cx = x; cy = y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'C': case 'c': {
|
||||||
|
var c1x = float.Parse(tokens[i++], Inv);
|
||||||
|
var c1y = float.Parse(tokens[i++], Inv);
|
||||||
|
var c2x = float.Parse(tokens[i++], Inv);
|
||||||
|
var c2y = float.Parse(tokens[i++], Inv);
|
||||||
|
var ex = float.Parse(tokens[i++], Inv);
|
||||||
|
var ey = float.Parse(tokens[i++], Inv);
|
||||||
|
if (cmd == 'c') {
|
||||||
|
c1x += cx; c1y += cy;
|
||||||
|
c2x += cx; c2y += cy;
|
||||||
|
ex += cx; ey += cy;
|
||||||
|
}
|
||||||
|
commands.Add(new BezierCommand {
|
||||||
|
Type = CommandType.CubicBezierTo,
|
||||||
|
Args = new[] { c1x, c1y, c2x, c2y, ex, ey }
|
||||||
|
});
|
||||||
|
cx = ex; cy = ey;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'Z': case 'z': {
|
||||||
|
commands.Add(new BezierCommand { Type = CommandType.Close, Args = Array.Empty<float>() });
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var closed = commands.Count > 0 && commands[^1].Type == CommandType.Close;
|
||||||
|
return (commands.ToArray(), closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> Tokenize(string d) {
|
||||||
|
var tokens = new List<string>();
|
||||||
|
var i = 0;
|
||||||
|
while (i < d.Length) {
|
||||||
|
var c = d[i];
|
||||||
|
if (char.IsWhiteSpace(c) || c == ',') { i++; continue; }
|
||||||
|
if (char.IsLetter(c)) {
|
||||||
|
var upper = char.ToUpperInvariant(c);
|
||||||
|
if (upper is 'M' or 'L' or 'C' or 'Z') {
|
||||||
|
tokens.Add(c.ToString());
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c is '-' or '+' or '.' || char.IsDigit(c)) {
|
||||||
|
var start = i;
|
||||||
|
if (c is '-' or '+') i++;
|
||||||
|
while (i < d.Length && (char.IsDigit(d[i]) || d[i] == '.')) i++;
|
||||||
|
if (i < d.Length && (d[i] is 'e' or 'E')) {
|
||||||
|
i++;
|
||||||
|
if (i < d.Length && (d[i] is '+' or '-')) i++;
|
||||||
|
while (i < d.Length && char.IsDigit(d[i])) i++;
|
||||||
|
}
|
||||||
|
tokens.Add(d[start..i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
SlaveEngine.Assets/Asset.cs
Normal file
6
SlaveEngine.Assets/Asset.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace SlaveEngine.Assets;
|
||||||
|
|
||||||
|
public abstract class Asset {
|
||||||
|
public Guid Guid { get; init; }
|
||||||
|
public AssetFileInfo FileInfo { get; init; }
|
||||||
|
}
|
||||||
6
SlaveEngine.Assets/AssetFileInfo.cs
Normal file
6
SlaveEngine.Assets/AssetFileInfo.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace SlaveEngine.Assets;
|
||||||
|
|
||||||
|
public struct AssetFileInfo {
|
||||||
|
public string Filename;
|
||||||
|
public string FilePath;
|
||||||
|
}
|
||||||
5
SlaveEngine.Assets/AssetProcessor.cs
Normal file
5
SlaveEngine.Assets/AssetProcessor.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace SlaveEngine.Assets;
|
||||||
|
|
||||||
|
public abstract class AssetProcessor {
|
||||||
|
public abstract Asset Process(AssetFileInfo fileInfo , byte[] data);
|
||||||
|
}
|
||||||
46
SlaveEngine.Assets/AssetProcessorAttribute.cs
Normal file
46
SlaveEngine.Assets/AssetProcessorAttribute.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
namespace SlaveEngine.Assets;
|
||||||
|
|
||||||
|
public class AssetProcessorAttribute : Attribute {
|
||||||
|
public Type ManagedType { get; init; }
|
||||||
|
public string[] ManagedExtensions { get; init; }
|
||||||
|
|
||||||
|
public AssetProcessorAttribute(Type managedType, string[] managedExtensions)
|
||||||
|
{
|
||||||
|
ManagedType = managedType ?? throw new ArgumentNullException(nameof(managedType));
|
||||||
|
|
||||||
|
// Validate that the provided type is Asset or a subclass of Asset
|
||||||
|
if (!typeof(Asset).IsSubclassOf(managedType))
|
||||||
|
throw new ArgumentException($"The type '{managedType.FullName}' must derive from '{typeof(Asset).FullName}'.", nameof(managedType));
|
||||||
|
|
||||||
|
// Validate extensions: non-null, non-empty, normalized, start with '.', no whitespace, unique
|
||||||
|
if (managedExtensions == null)
|
||||||
|
throw new ArgumentNullException(nameof(managedExtensions));
|
||||||
|
|
||||||
|
if (managedExtensions.Length == 0)
|
||||||
|
throw new ArgumentException("At least one file extension must be provided.", nameof(managedExtensions));
|
||||||
|
|
||||||
|
var normalized = new List<string>(managedExtensions.Length);
|
||||||
|
foreach (var ext in managedExtensions)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(ext))
|
||||||
|
throw new ArgumentException("Extensions must be non-empty strings.", nameof(managedExtensions));
|
||||||
|
|
||||||
|
var e = ext.Trim();
|
||||||
|
|
||||||
|
// allow either "png" or ".png" as input, normalize to lowercase with leading dot
|
||||||
|
if (!e.StartsWith('.'))
|
||||||
|
e = "." + e;
|
||||||
|
|
||||||
|
if (e.Length < 2)
|
||||||
|
throw new ArgumentException($"Invalid extension '{ext}'.", nameof(managedExtensions));
|
||||||
|
|
||||||
|
if (e.Any(char.IsWhiteSpace))
|
||||||
|
throw new ArgumentException($"Extension '{ext}' contains whitespace.", nameof(managedExtensions));
|
||||||
|
|
||||||
|
normalized.Add(e.ToLowerInvariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure uniqueness
|
||||||
|
ManagedExtensions = normalized.Distinct().ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
14
SlaveEngine.Assets/Models/PartAsset.cs
Normal file
14
SlaveEngine.Assets/Models/PartAsset.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
namespace SlaveEngine.Assets.Models;
|
||||||
|
|
||||||
|
public sealed class PartAsset : Asset {
|
||||||
|
public required string Id { get; init; }
|
||||||
|
public required string OriginalKey { get; init; }
|
||||||
|
public required string Resource { get; init; }
|
||||||
|
public int MorphX { get; init; }
|
||||||
|
public int MorphY { get; init; }
|
||||||
|
public required string[] Fields { get; init; }
|
||||||
|
public required Joint[] Joints { get; init; }
|
||||||
|
public required VariantAsset[] Variants { get; init; }
|
||||||
|
}
|
||||||
11
SlaveEngine.Assets/Models/PathData.cs
Normal file
11
SlaveEngine.Assets/Models/PathData.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
namespace SlaveEngine.Assets.Models;
|
||||||
|
|
||||||
|
public sealed class PathData {
|
||||||
|
public required string Fill { get; init; }
|
||||||
|
public required string Stroke { get; init; }
|
||||||
|
public float StrokeWidth { get; init; }
|
||||||
|
public bool IsClosed { get; init; }
|
||||||
|
public required BezierCommand[] Commands { get; init; }
|
||||||
|
}
|
||||||
14
SlaveEngine.Assets/Models/PathGroup.cs
Normal file
14
SlaveEngine.Assets/Models/PathGroup.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace SlaveEngine.Assets.Models;
|
||||||
|
|
||||||
|
public sealed class PathGroup {
|
||||||
|
public required string Name { get; init; }
|
||||||
|
public bool HasTransform { get; init; }
|
||||||
|
public float Tx { get; init; }
|
||||||
|
public float Ty { get; init; }
|
||||||
|
public float Angle { get; init; }
|
||||||
|
public float Sx { get; init; }
|
||||||
|
public float Sy { get; init; }
|
||||||
|
public float Bx { get; init; }
|
||||||
|
public float By { get; init; }
|
||||||
|
public required PathData[] Paths { get; init; }
|
||||||
|
}
|
||||||
7
SlaveEngine.Assets/Models/VariantAsset.cs
Normal file
7
SlaveEngine.Assets/Models/VariantAsset.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace SlaveEngine.Assets.Models;
|
||||||
|
|
||||||
|
public sealed class VariantAsset {
|
||||||
|
public int X { get; init; }
|
||||||
|
public int Y { get; init; }
|
||||||
|
public required PathGroup[] Groups { get; init; }
|
||||||
|
}
|
||||||
6
SlaveEngine.Assets/Primitives/BezierCommand.cs
Normal file
6
SlaveEngine.Assets/Primitives/BezierCommand.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
public readonly struct BezierCommand {
|
||||||
|
public CommandType Type { get; init; }
|
||||||
|
public float[] Args { get; init; }
|
||||||
|
}
|
||||||
8
SlaveEngine.Assets/Primitives/CommandType.cs
Normal file
8
SlaveEngine.Assets/Primitives/CommandType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
public enum CommandType : byte {
|
||||||
|
MoveTo = 0,
|
||||||
|
LineTo = 1,
|
||||||
|
CubicBezierTo = 2,
|
||||||
|
Close = 3
|
||||||
|
}
|
||||||
6
SlaveEngine.Assets/Primitives/Joint.cs
Normal file
6
SlaveEngine.Assets/Primitives/Joint.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
public readonly struct Joint {
|
||||||
|
public float X { get; init; }
|
||||||
|
public float Y { get; init; }
|
||||||
|
}
|
||||||
120
SlaveEngine.Assets/Processors/PartProcessor.cs
Normal file
120
SlaveEngine.Assets/Processors/PartProcessor.cs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
using System.Text;
|
||||||
|
using SlaveEngine.Assets.Models;
|
||||||
|
using SlaveEngine.Assets.Primitives;
|
||||||
|
|
||||||
|
namespace SlaveEngine.Assets.Processors;
|
||||||
|
|
||||||
|
[AssetProcessor(typeof(PartAsset), managedExtensions: [".spart"])]
|
||||||
|
public sealed class PartProcessor : AssetProcessor {
|
||||||
|
public override Asset Process(AssetFileInfo fileInfo, byte[] data) {
|
||||||
|
using var stream = new MemoryStream(data);
|
||||||
|
using var reader = new BinaryReader(stream);
|
||||||
|
|
||||||
|
var magic = reader.ReadBytes(4);
|
||||||
|
if (magic[0] != 'S' || magic[1] != 'P' || magic[2] != 'R' || magic[3] != 'T')
|
||||||
|
throw new InvalidDataException("Invalid .spart file magic");
|
||||||
|
|
||||||
|
var version = reader.ReadInt32();
|
||||||
|
if (version != 1)
|
||||||
|
throw new InvalidDataException($"Unsupported .spart version: {version}");
|
||||||
|
|
||||||
|
var id = ReadString(reader);
|
||||||
|
var originalKey = ReadString(reader);
|
||||||
|
var resource = ReadString(reader);
|
||||||
|
var morphX = reader.ReadInt32();
|
||||||
|
var morphY = reader.ReadInt32();
|
||||||
|
|
||||||
|
var fieldCount = reader.ReadInt32();
|
||||||
|
var fields = new string[fieldCount];
|
||||||
|
for (var i = 0; i < fieldCount; i++)
|
||||||
|
fields[i] = ReadString(reader);
|
||||||
|
|
||||||
|
var jointCount = reader.ReadInt32();
|
||||||
|
var joints = new Joint[jointCount];
|
||||||
|
for (var i = 0; i < jointCount; i++)
|
||||||
|
joints[i] = new Joint { X = reader.ReadSingle(), Y = reader.ReadSingle() };
|
||||||
|
|
||||||
|
var variantCount = reader.ReadInt32();
|
||||||
|
var variants = new VariantAsset[variantCount];
|
||||||
|
for (var v = 0; v < variantCount; v++) {
|
||||||
|
var vx = reader.ReadInt32();
|
||||||
|
var vy = reader.ReadInt32();
|
||||||
|
|
||||||
|
var groupCount = reader.ReadInt32();
|
||||||
|
var groups = new PathGroup[groupCount];
|
||||||
|
for (var g = 0; g < groupCount; g++) {
|
||||||
|
var groupName = ReadString(reader);
|
||||||
|
var hasTransform = reader.ReadBoolean();
|
||||||
|
float tx = 0, ty = 0, angle = 0, sx = 1, sy = 1, bx = 0, by = 0;
|
||||||
|
if (hasTransform) {
|
||||||
|
tx = reader.ReadSingle();
|
||||||
|
ty = reader.ReadSingle();
|
||||||
|
angle = reader.ReadSingle();
|
||||||
|
sx = reader.ReadSingle();
|
||||||
|
sy = reader.ReadSingle();
|
||||||
|
bx = reader.ReadSingle();
|
||||||
|
by = reader.ReadSingle();
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathCount = reader.ReadInt32();
|
||||||
|
var paths = new PathData[pathCount];
|
||||||
|
for (var p = 0; p < pathCount; p++) {
|
||||||
|
var fill = ReadString(reader);
|
||||||
|
var stroke = ReadString(reader);
|
||||||
|
var strokeWidth = reader.ReadSingle();
|
||||||
|
var isClosed = reader.ReadBoolean();
|
||||||
|
|
||||||
|
var cmdCount = reader.ReadInt32();
|
||||||
|
var cmds = new BezierCommand[cmdCount];
|
||||||
|
for (var c = 0; c < cmdCount; c++) {
|
||||||
|
var cmdType = (CommandType)reader.ReadByte();
|
||||||
|
var argCount = reader.ReadInt32();
|
||||||
|
var args = new float[argCount];
|
||||||
|
for (var a = 0; a < argCount; a++)
|
||||||
|
args[a] = reader.ReadSingle();
|
||||||
|
cmds[c] = new BezierCommand { Type = cmdType, Args = args };
|
||||||
|
}
|
||||||
|
|
||||||
|
paths[p] = new PathData {
|
||||||
|
Fill = fill,
|
||||||
|
Stroke = stroke,
|
||||||
|
StrokeWidth = strokeWidth,
|
||||||
|
IsClosed = isClosed,
|
||||||
|
Commands = cmds
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
groups[g] = new PathGroup {
|
||||||
|
Name = groupName,
|
||||||
|
HasTransform = hasTransform,
|
||||||
|
Tx = tx, Ty = ty, Angle = angle,
|
||||||
|
Sx = sx, Sy = sy, Bx = bx, By = by,
|
||||||
|
Paths = paths
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
variants[v] = new VariantAsset {
|
||||||
|
X = vx, Y = vy, Groups = groups
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PartAsset {
|
||||||
|
Guid = Guid.NewGuid(),
|
||||||
|
FileInfo = fileInfo,
|
||||||
|
Id = id,
|
||||||
|
OriginalKey = originalKey,
|
||||||
|
Resource = resource,
|
||||||
|
MorphX = morphX,
|
||||||
|
MorphY = morphY,
|
||||||
|
Fields = fields,
|
||||||
|
Joints = joints,
|
||||||
|
Variants = variants
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReadString(BinaryReader reader) {
|
||||||
|
var len = reader.ReadInt32();
|
||||||
|
var bytes = reader.ReadBytes(len);
|
||||||
|
return Encoding.UTF8.GetString(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
SlaveEngine.Assets/ResourceManager.cs
Normal file
78
SlaveEngine.Assets/ResourceManager.cs
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SlaveEngine.Assets;
|
||||||
|
|
||||||
|
public sealed class ResourceManager {
|
||||||
|
|
||||||
|
public ResourceManager(string basePath) {
|
||||||
|
if (!Path.Exists(basePath))
|
||||||
|
throw new DirectoryNotFoundException($"The base path '{basePath}' does not exist.");
|
||||||
|
BasePath = basePath;
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResourceManager? Instance { get; private set; }
|
||||||
|
public string BasePath { get; private set; }
|
||||||
|
|
||||||
|
private readonly List<AssetProcessor> _assetProcessors = new();
|
||||||
|
private readonly Dictionary<string, string> _assetIndex = new();
|
||||||
|
private readonly Dictionary<string, Asset> _assetCache = new();
|
||||||
|
|
||||||
|
public void Initialize() {
|
||||||
|
ScanDirectory(BasePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ScanDirectory(string directory) {
|
||||||
|
foreach (var file in Directory.GetFiles(directory, "*.spart")) {
|
||||||
|
try {
|
||||||
|
var id = ReadAssetIdFromSpart(file);
|
||||||
|
_assetIndex[id] = file;
|
||||||
|
} catch {
|
||||||
|
// skip corrupt files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public T? GetAsset<T>(string id) where T : Asset {
|
||||||
|
if (_assetCache.TryGetValue(id, out var cached))
|
||||||
|
return (T)cached;
|
||||||
|
|
||||||
|
if (!_assetIndex.TryGetValue(id, out var path))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var ext = Path.GetExtension(path);
|
||||||
|
var processor = GetProcessorForExtension(ext);
|
||||||
|
if (processor == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var fileInfo = new AssetFileInfo {
|
||||||
|
Filename = Path.GetFileNameWithoutExtension(path),
|
||||||
|
FilePath = path
|
||||||
|
};
|
||||||
|
|
||||||
|
var asset = processor.Process(fileInfo, File.ReadAllBytes(path));
|
||||||
|
_assetCache[id] = asset;
|
||||||
|
return (T)asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyCollection<string> GetAssetIds() => _assetIndex.Keys;
|
||||||
|
|
||||||
|
public void RegisterProcessor(AssetProcessor processor) {
|
||||||
|
if (_assetProcessors.All(p => p.GetType() != processor.GetType()))
|
||||||
|
_assetProcessors.Add(processor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AssetProcessor? GetProcessorForExtension(string extension) =>
|
||||||
|
_assetProcessors.FirstOrDefault(p => p.GetType()
|
||||||
|
.GetCustomAttribute<AssetProcessorAttribute>()?.ManagedExtensions
|
||||||
|
.Contains(extension) == true);
|
||||||
|
|
||||||
|
private static string ReadAssetIdFromSpart(string path) {
|
||||||
|
using var stream = File.OpenRead(path);
|
||||||
|
using var reader = new BinaryReader(stream);
|
||||||
|
reader.ReadBytes(8);
|
||||||
|
var len = reader.ReadInt32();
|
||||||
|
return Encoding.UTF8.GetString(reader.ReadBytes(len));
|
||||||
|
}
|
||||||
|
}
|
||||||
13
SlaveEngine.Assets/SlaveEngine.Assets.csproj
Normal file
13
SlaveEngine.Assets/SlaveEngine.Assets.csproj
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="YamlDotNet" Version="18.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -277,15 +277,15 @@ class Program
|
|||||||
["id"] = engKey,
|
["id"] = engKey,
|
||||||
["original_key"] = originalKey,
|
["original_key"] = originalKey,
|
||||||
["resource"] = name,
|
["resource"] = name,
|
||||||
["morph_x"] = difs.CountX,
|
["morph_x"] = difs.GetCountX(),
|
||||||
["morph_y"] = difs.CountY,
|
["morph_y"] = difs.GetCountY(),
|
||||||
["variants"] = new JArray(),
|
["variants"] = new JArray(),
|
||||||
["fields"] = new JArray()
|
["fields"] = new JArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
var variants = (JArray)partEntry["variants"];
|
var variants = (JArray)partEntry["variants"];
|
||||||
|
|
||||||
for (int x = 0; x < difs.CountX; x++)
|
for (int x = 0; x < difs.GetCountX(); x++)
|
||||||
{
|
{
|
||||||
var dif = difs[x];
|
var dif = difs[x];
|
||||||
for (int y = 0; y < dif.Count; y++)
|
for (int y = 0; y < dif.Count; y++)
|
||||||
@@ -305,7 +305,7 @@ class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string childKey in difs[0][0].Keys)
|
foreach (string childKey in difs[0][0].pars.Keys)
|
||||||
{
|
{
|
||||||
((JArray)partEntry["fields"]).Add(new JObject { ["name"] = childKey });
|
((JArray)partEntry["fields"]).Add(new JObject { ["name"] = childKey });
|
||||||
}
|
}
|
||||||
@@ -432,7 +432,7 @@ class Program
|
|||||||
|
|
||||||
static void ExportParsToSvgInner(PartGroup PartGroup, StringBuilder sb, List<string> foundJoints)
|
static void ExportParsToSvgInner(PartGroup PartGroup, StringBuilder sb, List<string> foundJoints)
|
||||||
{
|
{
|
||||||
foreach (string key in PartGroup.Keys)
|
foreach (string key in PartGroup.pars.Keys)
|
||||||
{
|
{
|
||||||
var val = PartGroup[key];
|
var val = PartGroup[key];
|
||||||
if (val is PartGroup childPars)
|
if (val is PartGroup childPars)
|
||||||
@@ -452,13 +452,13 @@ class Program
|
|||||||
{
|
{
|
||||||
if (!ShapePart.Dra) return;
|
if (!ShapePart.Dra) return;
|
||||||
|
|
||||||
var bx = ShapePart.BasePoint.X;
|
var bx = ShapePart.GetBasePoint().X;
|
||||||
var by = ShapePart.BasePoint.Y;
|
var by = ShapePart.GetBasePoint().Y;
|
||||||
var px = ShapePart.Position.X;
|
var px = ShapePart.GetPosition().X;
|
||||||
var py = ShapePart.Position.Y;
|
var py = ShapePart.GetPosition().Y;
|
||||||
var angle = ShapePart.Angle;
|
var angle = ShapePart.GetAngle();
|
||||||
var sx = ShapePart.Size * ShapePart.SizeX;
|
var sx = ShapePart.GetSize() * ShapePart.GetSizeX();
|
||||||
var sy = ShapePart.Size * ShapePart.SizeY;
|
var sy = ShapePart.GetSize() * ShapePart.GetSizeY();
|
||||||
|
|
||||||
var hasTransform = System.Math.Abs(bx) > 0.001 || System.Math.Abs(by) > 0.001
|
var hasTransform = System.Math.Abs(bx) > 0.001 || System.Math.Abs(by) > 0.001
|
||||||
|| System.Math.Abs(px) > 0.001 || System.Math.Abs(py) > 0.001
|
|| System.Math.Abs(px) > 0.001 || System.Math.Abs(py) > 0.001
|
||||||
@@ -467,22 +467,22 @@ class Program
|
|||||||
if (hasTransform)
|
if (hasTransform)
|
||||||
sb.Append($"<g transform=\"translate({F(px)},{F(py)}) rotate({F(angle)}) scale({F(sx)},{F(sy)}) translate({F(-bx)},{F(-by)})\">");
|
sb.Append($"<g transform=\"translate({F(px)},{F(py)}) rotate({F(angle)}) scale({F(sx)},{F(sy)}) translate({F(-bx)},{F(-by)})\">");
|
||||||
|
|
||||||
foreach (var outObj in ShapePart.OP)
|
foreach (var outObj in ShapePart.GetOP())
|
||||||
{
|
{
|
||||||
var points = outObj.ps;
|
var points = outObj.ps;
|
||||||
if (points.Count < 2) continue;
|
if (points.Count < 2) continue;
|
||||||
|
|
||||||
var d = BuildSvgPath(points, outObj.Tension, ShapePart.Closed);
|
var d = BuildSvgPath(points, outObj.Tension, ShapePart.IsClosed);
|
||||||
var fill = ShapePart.Closed ? "#cccccc" : "none";
|
var fill = ShapePart.IsClosed ? "#cccccc" : "none";
|
||||||
var stroke = outObj.Outline ? "#000000" : "none";
|
var stroke = outObj.Outline ? "#000000" : "none";
|
||||||
var sw_val = outObj.Outline ? System.Math.Max(ShapePart.PenWidth, 0.001) : 0.0;
|
var sw_val = outObj.Outline ? System.Math.Max(ShapePart.GetPenWidth(), 0.001) : 0.0;
|
||||||
sb.AppendLine($"<path d=\"{d}\" fill=\"{fill}\" stroke=\"{stroke}\" stroke-width=\"{F(sw_val)}\"/>");
|
sb.AppendLine($"<path d=\"{d}\" fill=\"{fill}\" stroke=\"{stroke}\" stroke-width=\"{F(sw_val)}\"/>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTransform)
|
if (hasTransform)
|
||||||
sb.Append("</g>");
|
sb.Append("</g>");
|
||||||
|
|
||||||
foreach (var joi in ShapePart.JP)
|
foreach (var joi in ShapePart.GetJP())
|
||||||
{
|
{
|
||||||
foundJoints.Add($"{F(joi.Joint.X)},{F(joi.Joint.Y)}");
|
foundJoints.Add($"{F(joi.Joint.X)},{F(joi.Joint.Y)}");
|
||||||
}
|
}
|
||||||
@@ -579,15 +579,15 @@ class Program
|
|||||||
|
|
||||||
static JObject ExportDifs(VariantGrid VariantGrid)
|
static JObject ExportDifs(VariantGrid VariantGrid)
|
||||||
{
|
{
|
||||||
int xCount = VariantGrid.CountX;
|
int xCount = VariantGrid.GetCountX();
|
||||||
int yCount = VariantGrid.CountY;
|
int yCount = VariantGrid.GetCountY();
|
||||||
|
|
||||||
var result = new JObject
|
var result = new JObject
|
||||||
{
|
{
|
||||||
["Tag"] = VariantGrid.Tag ?? "",
|
["Tag"] = VariantGrid.Tag ?? "",
|
||||||
["ValueX"] = VariantGrid.ValueX,
|
["ValueX"] = VariantGrid.ValueX,
|
||||||
["ValueY"] = VariantGrid.ValueY,
|
["ValueY"] = VariantGrid.ValueY,
|
||||||
["CountX"] = xCount,
|
["GetCountX()"] = xCount,
|
||||||
["CountY"] = yCount,
|
["CountY"] = yCount,
|
||||||
["VariantGrid"] = new JArray()
|
["VariantGrid"] = new JArray()
|
||||||
};
|
};
|
||||||
@@ -616,7 +616,7 @@ class Program
|
|||||||
["Children"] = new JObject()
|
["Children"] = new JObject()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (string key in PartGroup.Keys)
|
foreach (string key in PartGroup.pars.Keys)
|
||||||
{
|
{
|
||||||
var val = PartGroup[key];
|
var val = PartGroup[key];
|
||||||
if (val is PartGroup childPars)
|
if (val is PartGroup childPars)
|
||||||
@@ -638,18 +638,18 @@ class Program
|
|||||||
{
|
{
|
||||||
["Tag"] = ShapePart.Tag ?? "",
|
["Tag"] = ShapePart.Tag ?? "",
|
||||||
["Dra"] = ShapePart.Dra,
|
["Dra"] = ShapePart.Dra,
|
||||||
["PenWidth"] = ShapePart.PenWidth,
|
["PenWidth"] = ShapePart.GetPenWidth(),
|
||||||
["Closed"] = ShapePart.Closed,
|
["Closed"] = ShapePart.IsClosed,
|
||||||
["BasePoint"] = ExportVec(ShapePart.BasePoint),
|
["BasePoint"] = ExportVec(ShapePart.GetBasePoint()),
|
||||||
["Position"] = ExportVec(ShapePart.Position),
|
["Position"] = ExportVec(ShapePart.GetPosition()),
|
||||||
["Angle"] = ShapePart.Angle,
|
["Angle"] = ShapePart.GetAngle(),
|
||||||
["Size"] = ShapePart.Size,
|
["Size"] = ShapePart.GetSize(),
|
||||||
["SizeX"] = ShapePart.SizeX,
|
["SizeX"] = ShapePart.GetSizeX(),
|
||||||
["SizeY"] = ShapePart.SizeY,
|
["SizeY"] = ShapePart.GetSizeY(),
|
||||||
["CurveOutline"] = new JArray()
|
["CurveOutline"] = new JArray()
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach (var outObj in ShapePart.OP)
|
foreach (var outObj in ShapePart.GetOP())
|
||||||
{
|
{
|
||||||
var outJ = new JObject
|
var outJ = new JObject
|
||||||
{
|
{
|
||||||
@@ -662,10 +662,10 @@ class Program
|
|||||||
((JArray)result["CurveOutline"]).Add(outJ);
|
((JArray)result["CurveOutline"]).Add(outJ);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShapePart.JP.Count > 0)
|
if (ShapePart.GetJP().Count > 0)
|
||||||
{
|
{
|
||||||
result["Joints"] = new JArray();
|
result["Joints"] = new JArray();
|
||||||
foreach (var joi in ShapePart.JP)
|
foreach (var joi in ShapePart.GetJP())
|
||||||
((JArray)result["Joints"]).Add(ExportVec(joi.Joint));
|
((JArray)result["Joints"]).Add(ExportVec(joi.Joint));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +676,7 @@ class Program
|
|||||||
{
|
{
|
||||||
var result = ExportPar(ShapePartT);
|
var result = ExportPar(ShapePartT);
|
||||||
result["Text"] = ShapePartT.Text ?? "";
|
result["Text"] = ShapePartT.Text ?? "";
|
||||||
result["FontSize"] = ShapePartT.FontSize;
|
result["FontSize"] = ShapePartT.GetFontSize();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
28
Solution.sln
28
Solution.sln
@@ -9,6 +9,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "2DGAMELIB", "2DGAMELIB\2DGA
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveMatrix.Extract", "SlaveMatrix.Extract\SlaveMatrix.Extract.csproj", "{DAEAB23A-E099-432E-9C85-39D0853BD78A}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveMatrix.Extract", "SlaveMatrix.Extract\SlaveMatrix.Extract.csproj", "{DAEAB23A-E099-432E-9C85-39D0853BD78A}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveEngine.Assets", "SlaveEngine.Assets\SlaveEngine.Assets.csproj", "{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SlaveEngine.AssetBuilder", "SlaveEngine.AssetBuilder\SlaveEngine.AssetBuilder.csproj", "{DE1800EA-9AB1-4558-BB60-40BAA505F475}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|x64 = Debug|x64
|
Debug|x64 = Debug|x64
|
||||||
@@ -55,6 +59,30 @@ Global
|
|||||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|x86.Build.0 = Release|Any CPU
|
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{DAEAB23A-E099-432E-9C85-39D0853BD78A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{11B3B1C3-B393-4A4D-AEC5-B26C86F6C0C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DE1800EA-9AB1-4558-BB60-40BAA505F475}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
Reference in New Issue
Block a user