diff --git a/SlaveEngine.Assets/Asset.cs b/SlaveEngine.Assets/Asset.cs
index 93baf03..187fc51 100644
--- a/SlaveEngine.Assets/Asset.cs
+++ b/SlaveEngine.Assets/Asset.cs
@@ -1,6 +1,6 @@
namespace SlaveEngine.Assets;
public abstract class Asset {
- public Guid Guid { get; private set; }
- public AssetFileInfo FileInfo { get; private set; }
+ public Guid Guid { get; init; }
+ public AssetFileInfo FileInfo { get; init; }
}
\ No newline at end of file
diff --git a/SlaveEngine.Assets/Models/PartAsset.cs b/SlaveEngine.Assets/Models/PartAsset.cs
new file mode 100644
index 0000000..c37b446
--- /dev/null
+++ b/SlaveEngine.Assets/Models/PartAsset.cs
@@ -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; }
+}
diff --git a/SlaveEngine.Assets/Models/PathData.cs b/SlaveEngine.Assets/Models/PathData.cs
new file mode 100644
index 0000000..6ce536b
--- /dev/null
+++ b/SlaveEngine.Assets/Models/PathData.cs
@@ -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; }
+}
diff --git a/SlaveEngine.Assets/Models/PathGroup.cs b/SlaveEngine.Assets/Models/PathGroup.cs
new file mode 100644
index 0000000..bc6f746
--- /dev/null
+++ b/SlaveEngine.Assets/Models/PathGroup.cs
@@ -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; }
+}
diff --git a/SlaveEngine.Assets/Models/VariantAsset.cs b/SlaveEngine.Assets/Models/VariantAsset.cs
new file mode 100644
index 0000000..de27ad6
--- /dev/null
+++ b/SlaveEngine.Assets/Models/VariantAsset.cs
@@ -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; }
+}
diff --git a/SlaveEngine.Assets/Primitives/BezierCommand.cs b/SlaveEngine.Assets/Primitives/BezierCommand.cs
new file mode 100644
index 0000000..de81815
--- /dev/null
+++ b/SlaveEngine.Assets/Primitives/BezierCommand.cs
@@ -0,0 +1,6 @@
+namespace SlaveEngine.Assets.Primitives;
+
+public readonly struct BezierCommand {
+ public CommandType Type { get; init; }
+ public float[] Args { get; init; }
+}
diff --git a/SlaveEngine.Assets/Primitives/CommandType.cs b/SlaveEngine.Assets/Primitives/CommandType.cs
new file mode 100644
index 0000000..a74b3d3
--- /dev/null
+++ b/SlaveEngine.Assets/Primitives/CommandType.cs
@@ -0,0 +1,8 @@
+namespace SlaveEngine.Assets.Primitives;
+
+public enum CommandType : byte {
+ MoveTo = 0,
+ LineTo = 1,
+ CubicBezierTo = 2,
+ Close = 3
+}
diff --git a/SlaveEngine.Assets/Primitives/Joint.cs b/SlaveEngine.Assets/Primitives/Joint.cs
new file mode 100644
index 0000000..117403f
--- /dev/null
+++ b/SlaveEngine.Assets/Primitives/Joint.cs
@@ -0,0 +1,6 @@
+namespace SlaveEngine.Assets.Primitives;
+
+public readonly struct Joint {
+ public float X { get; init; }
+ public float Y { get; init; }
+}
diff --git a/SlaveEngine.Assets/Processors/PartProcessor.cs b/SlaveEngine.Assets/Processors/PartProcessor.cs
new file mode 100644
index 0000000..a2740a4
--- /dev/null
+++ b/SlaveEngine.Assets/Processors/PartProcessor.cs
@@ -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);
+ }
+}
diff --git a/SlaveEngine.Assets/SlaveEngine.Assets.csproj b/SlaveEngine.Assets/SlaveEngine.Assets.csproj
index 3a63532..d2df1de 100644
--- a/SlaveEngine.Assets/SlaveEngine.Assets.csproj
+++ b/SlaveEngine.Assets/SlaveEngine.Assets.csproj
@@ -6,4 +6,8 @@
enable
+
+
+
+