TDD: ObjectMeshDataSerializerTests + the shared ObjectMeshDataEquality field-by-field comparator (reused by Task 6's equivalence suite) written first, confirmed a compile failure against the not-yet-existing ObjectMeshDataSerializer type. Serializes EVERY field of the ObjectMeshData family per the plan's normative layout: primitives raw LE, arrays as count:i32+payload, blittable arrays (VertexPositionNormalTexture[], ushort[], byte[]) via MemoryMarshal.AsBytes bulk copy, TextureBatches written sorted by the (Width, Height, Format) key tuple for run-to-run determinism regardless of dictionary insertion order, nullable fields as present:byte+value. EnvCellGeometry nests recursively (MeshExtractor can populate one level today; the serializer supports arbitrary depth rather than assuming it). Namespace-trap finding: StagedEmitter.Emitter resolves to DatReaderWriter.DBObjs.ParticleEmitter (the dat DBObj, verified via reflection against the pinned Chorizite.DatReaderWriter 2.1.7 package and confirmed live by MeshExtractor's `emitter.HwGfxObjId.DataId` call site compiling), NOT AcDream.Core.Vfx.ParticleEmitter (the runtime particle-simulation type with a live Particle[] pool that would NOT be serializable asset data). All ~31 of its fields are written explicitly rather than delegating to its own Pack/Unpack, which require a live DatBinWriter/DatBinReader bound to a DatDatabase — coupling our pak's determinism to a third-party wire-format helper we don't control the versioning of. 33 tests green: 9 round-trip fixtures (empty/vertices+indices/multi texture-batch-groups/setup-parts/emitters/nullable-present/nullable- absent/edge-lines/nested-EnvCellGeometry), same-instance-twice byte- identity, and dictionary-insertion-order-independence (two orders -> identical bytes) plus a key-sort-order assertion on the raw bytes.
196 lines
15 KiB
C#
196 lines
15 KiB
C#
using System.Linq;
|
|
using Chorizite.Core.Render.Enums;
|
|
using DatReaderWriter.DBObjs;
|
|
using DatReaderWriter.Types;
|
|
|
|
namespace AcDream.Content.Tests;
|
|
|
|
/// <summary>
|
|
/// Field-by-field deep-equality comparator for <see cref="ObjectMeshData"/> and
|
|
/// its whole family (<see cref="MeshBatchData"/>, <see cref="TextureBatchData"/>,
|
|
/// <see cref="StagedEmitter"/>, <see cref="TextureKey"/>). Used by both the
|
|
/// round-trip tests (Task 3) and the dat-gated equivalence suite (Task 6) so a
|
|
/// mismatch always names the exact field that diverged instead of just
|
|
/// "objects not equal".
|
|
/// </summary>
|
|
public static class ObjectMeshDataEquality {
|
|
public static void AssertEqual(ObjectMeshData? expected, ObjectMeshData? actual, string path = "root") {
|
|
if (expected is null && actual is null) return;
|
|
Assert.True(expected is not null, $"{path}: expected null but actual was non-null");
|
|
Assert.True(actual is not null, $"{path}: expected non-null but actual was null");
|
|
|
|
Assert.True(expected.ObjectId == actual.ObjectId, $"{path}.ObjectId: expected 0x{expected.ObjectId:X16}, got 0x{actual.ObjectId:X16}");
|
|
Assert.True(expected.IsSetup == actual.IsSetup, $"{path}.IsSetup: expected {expected.IsSetup}, got {actual.IsSetup}");
|
|
AssertVerticesEqual(expected.Vertices, actual.Vertices, $"{path}.Vertices");
|
|
|
|
Assert.True(expected.Batches.Count == actual.Batches.Count,
|
|
$"{path}.Batches.Count: expected {expected.Batches.Count}, got {actual.Batches.Count}");
|
|
for (int i = 0; i < expected.Batches.Count; i++)
|
|
AssertBatchEqual(expected.Batches[i], actual.Batches[i], $"{path}.Batches[{i}]");
|
|
|
|
Assert.True(expected.UploadAttempts == actual.UploadAttempts,
|
|
$"{path}.UploadAttempts: expected {expected.UploadAttempts}, got {actual.UploadAttempts}");
|
|
|
|
AssertEqual(expected.EnvCellGeometry, actual.EnvCellGeometry, $"{path}.EnvCellGeometry");
|
|
|
|
Assert.True(expected.SetupParts.Count == actual.SetupParts.Count,
|
|
$"{path}.SetupParts.Count: expected {expected.SetupParts.Count}, got {actual.SetupParts.Count}");
|
|
for (int i = 0; i < expected.SetupParts.Count; i++) {
|
|
var (eId, eT) = expected.SetupParts[i];
|
|
var (aId, aT) = actual.SetupParts[i];
|
|
Assert.True(eId == aId, $"{path}.SetupParts[{i}].GfxObjId: expected 0x{eId:X16}, got 0x{aId:X16}");
|
|
AssertMatrixEqual(eT, aT, $"{path}.SetupParts[{i}].Transform");
|
|
}
|
|
|
|
Assert.True(expected.ParticleEmitters.Count == actual.ParticleEmitters.Count,
|
|
$"{path}.ParticleEmitters.Count: expected {expected.ParticleEmitters.Count}, got {actual.ParticleEmitters.Count}");
|
|
for (int i = 0; i < expected.ParticleEmitters.Count; i++)
|
|
AssertStagedEmitterEqual(expected.ParticleEmitters[i], actual.ParticleEmitters[i], $"{path}.ParticleEmitters[{i}]");
|
|
|
|
AssertTextureBatchesEqual(expected.TextureBatches, actual.TextureBatches, $"{path}.TextureBatches");
|
|
|
|
AssertBoundingBoxEqual(expected.BoundingBox, actual.BoundingBox, $"{path}.BoundingBox");
|
|
AssertVector3Equal(expected.SortCenter, actual.SortCenter, $"{path}.SortCenter");
|
|
Assert.True(expected.DIDDegrade == actual.DIDDegrade, $"{path}.DIDDegrade: expected {expected.DIDDegrade}, got {actual.DIDDegrade}");
|
|
|
|
AssertSphereEqual(expected.SelectionSphere, actual.SelectionSphere, $"{path}.SelectionSphere");
|
|
AssertVector3ArrayEqual(expected.EdgeLines, actual.EdgeLines, $"{path}.EdgeLines");
|
|
}
|
|
|
|
private static void AssertVerticesEqual(VertexPositionNormalTexture[] expected, VertexPositionNormalTexture[] actual, string path) {
|
|
Assert.True(expected.Length == actual.Length, $"{path}.Length: expected {expected.Length}, got {actual.Length}");
|
|
for (int i = 0; i < expected.Length; i++) {
|
|
AssertVector3Equal(expected[i].Position, actual[i].Position, $"{path}[{i}].Position");
|
|
AssertVector3Equal(expected[i].Normal, actual[i].Normal, $"{path}[{i}].Normal");
|
|
Assert.True(expected[i].UV == actual[i].UV, $"{path}[{i}].UV: expected {expected[i].UV}, got {actual[i].UV}");
|
|
}
|
|
}
|
|
|
|
private static void AssertBatchEqual(MeshBatchData expected, MeshBatchData actual, string path) {
|
|
Assert.True(expected.Indices.SequenceEqual(actual.Indices),
|
|
$"{path}.Indices: expected [{string.Join(",", expected.Indices)}], got [{string.Join(",", actual.Indices)}]");
|
|
Assert.True(expected.TextureFormat == actual.TextureFormat,
|
|
$"{path}.TextureFormat: expected {expected.TextureFormat}, got {actual.TextureFormat}");
|
|
AssertTextureKeyEqual(expected.TextureKey, actual.TextureKey, $"{path}.TextureKey");
|
|
Assert.True(expected.TextureIndex == actual.TextureIndex,
|
|
$"{path}.TextureIndex: expected {expected.TextureIndex}, got {actual.TextureIndex}");
|
|
Assert.True(expected.TextureData.SequenceEqual(actual.TextureData),
|
|
$"{path}.TextureData: length expected {expected.TextureData.Length}, got {actual.TextureData.Length}");
|
|
Assert.True(expected.UploadPixelFormat == actual.UploadPixelFormat,
|
|
$"{path}.UploadPixelFormat: expected {expected.UploadPixelFormat}, got {actual.UploadPixelFormat}");
|
|
Assert.True(expected.UploadPixelType == actual.UploadPixelType,
|
|
$"{path}.UploadPixelType: expected {expected.UploadPixelType}, got {actual.UploadPixelType}");
|
|
Assert.True(expected.CullMode == actual.CullMode,
|
|
$"{path}.CullMode: expected {expected.CullMode}, got {actual.CullMode}");
|
|
}
|
|
|
|
private static void AssertTextureBatchDataEqual(TextureBatchData expected, TextureBatchData actual, string path) {
|
|
AssertTextureKeyEqual(expected.Key, actual.Key, $"{path}.Key");
|
|
Assert.True(expected.TextureData.SequenceEqual(actual.TextureData),
|
|
$"{path}.TextureData: length expected {expected.TextureData.Length}, got {actual.TextureData.Length}");
|
|
Assert.True(expected.UploadPixelFormat == actual.UploadPixelFormat,
|
|
$"{path}.UploadPixelFormat: expected {expected.UploadPixelFormat}, got {actual.UploadPixelFormat}");
|
|
Assert.True(expected.UploadPixelType == actual.UploadPixelType,
|
|
$"{path}.UploadPixelType: expected {expected.UploadPixelType}, got {actual.UploadPixelType}");
|
|
Assert.True(expected.Indices.SequenceEqual(actual.Indices),
|
|
$"{path}.Indices: expected [{string.Join(",", expected.Indices)}], got [{string.Join(",", actual.Indices)}]");
|
|
Assert.True(expected.CullMode == actual.CullMode, $"{path}.CullMode: expected {expected.CullMode}, got {actual.CullMode}");
|
|
Assert.True(expected.IsTransparent == actual.IsTransparent, $"{path}.IsTransparent: expected {expected.IsTransparent}, got {actual.IsTransparent}");
|
|
Assert.True(expected.IsAdditive == actual.IsAdditive, $"{path}.IsAdditive: expected {expected.IsAdditive}, got {actual.IsAdditive}");
|
|
Assert.True(expected.HasWrappingUVs == actual.HasWrappingUVs, $"{path}.HasWrappingUVs: expected {expected.HasWrappingUVs}, got {actual.HasWrappingUVs}");
|
|
}
|
|
|
|
private static void AssertTextureBatchesEqual(
|
|
System.Collections.Generic.Dictionary<(int Width, int Height, TextureFormat Format), System.Collections.Generic.List<TextureBatchData>> expected,
|
|
System.Collections.Generic.Dictionary<(int Width, int Height, TextureFormat Format), System.Collections.Generic.List<TextureBatchData>> actual,
|
|
string path) {
|
|
Assert.True(expected.Count == actual.Count, $"{path}.Count: expected {expected.Count}, got {actual.Count}");
|
|
foreach (var key in expected.Keys) {
|
|
Assert.True(actual.ContainsKey(key), $"{path}: missing key {key}");
|
|
var expList = expected[key];
|
|
var actList = actual[key];
|
|
Assert.True(expList.Count == actList.Count, $"{path}[{key}].Count: expected {expList.Count}, got {actList.Count}");
|
|
for (int i = 0; i < expList.Count; i++)
|
|
AssertTextureBatchDataEqual(expList[i], actList[i], $"{path}[{key}][{i}]");
|
|
}
|
|
}
|
|
|
|
private static void AssertStagedEmitterEqual(StagedEmitter expected, StagedEmitter actual, string path) {
|
|
Assert.True(expected.PartIndex == actual.PartIndex, $"{path}.PartIndex: expected {expected.PartIndex}, got {actual.PartIndex}");
|
|
AssertMatrixEqual(expected.Offset, actual.Offset, $"{path}.Offset");
|
|
AssertParticleEmitterEqual(expected.Emitter, actual.Emitter, $"{path}.Emitter");
|
|
}
|
|
|
|
private static void AssertParticleEmitterEqual(ParticleEmitter? expected, ParticleEmitter? actual, string path) {
|
|
if (expected is null && actual is null) return;
|
|
Assert.True(expected is not null, $"{path}: expected null but actual was non-null");
|
|
Assert.True(actual is not null, $"{path}: expected non-null but actual was null");
|
|
|
|
Assert.True(expected.Id == actual.Id, $"{path}.Id: expected 0x{expected.Id:X8}, got 0x{actual.Id:X8}");
|
|
Assert.True(expected.DataCategory == actual.DataCategory, $"{path}.DataCategory: expected {expected.DataCategory}, got {actual.DataCategory}");
|
|
Assert.True(expected.Unknown == actual.Unknown, $"{path}.Unknown: expected {expected.Unknown}, got {actual.Unknown}");
|
|
Assert.True(expected.EmitterType == actual.EmitterType, $"{path}.EmitterType: expected {expected.EmitterType}, got {actual.EmitterType}");
|
|
Assert.True(expected.ParticleType == actual.ParticleType, $"{path}.ParticleType: expected {expected.ParticleType}, got {actual.ParticleType}");
|
|
Assert.True(expected.GfxObjId.DataId == actual.GfxObjId.DataId, $"{path}.GfxObjId: expected 0x{expected.GfxObjId.DataId:X8}, got 0x{actual.GfxObjId.DataId:X8}");
|
|
Assert.True(expected.HwGfxObjId.DataId == actual.HwGfxObjId.DataId, $"{path}.HwGfxObjId: expected 0x{expected.HwGfxObjId.DataId:X8}, got 0x{actual.HwGfxObjId.DataId:X8}");
|
|
Assert.True(expected.Birthrate == actual.Birthrate, $"{path}.Birthrate: expected {expected.Birthrate}, got {actual.Birthrate}");
|
|
Assert.True(expected.MaxParticles == actual.MaxParticles, $"{path}.MaxParticles: expected {expected.MaxParticles}, got {actual.MaxParticles}");
|
|
Assert.True(expected.InitialParticles == actual.InitialParticles, $"{path}.InitialParticles: expected {expected.InitialParticles}, got {actual.InitialParticles}");
|
|
Assert.True(expected.TotalParticles == actual.TotalParticles, $"{path}.TotalParticles: expected {expected.TotalParticles}, got {actual.TotalParticles}");
|
|
Assert.True(expected.TotalSeconds == actual.TotalSeconds, $"{path}.TotalSeconds: expected {expected.TotalSeconds}, got {actual.TotalSeconds}");
|
|
Assert.True(expected.Lifespan == actual.Lifespan, $"{path}.Lifespan: expected {expected.Lifespan}, got {actual.Lifespan}");
|
|
Assert.True(expected.LifespanRand == actual.LifespanRand, $"{path}.LifespanRand: expected {expected.LifespanRand}, got {actual.LifespanRand}");
|
|
AssertVector3Equal(expected.OffsetDir, actual.OffsetDir, $"{path}.OffsetDir");
|
|
Assert.True(expected.MinOffset == actual.MinOffset, $"{path}.MinOffset: expected {expected.MinOffset}, got {actual.MinOffset}");
|
|
Assert.True(expected.MaxOffset == actual.MaxOffset, $"{path}.MaxOffset: expected {expected.MaxOffset}, got {actual.MaxOffset}");
|
|
AssertVector3Equal(expected.A, actual.A, $"{path}.A");
|
|
Assert.True(expected.MinA == actual.MinA, $"{path}.MinA: expected {expected.MinA}, got {actual.MinA}");
|
|
Assert.True(expected.MaxA == actual.MaxA, $"{path}.MaxA: expected {expected.MaxA}, got {actual.MaxA}");
|
|
AssertVector3Equal(expected.B, actual.B, $"{path}.B");
|
|
Assert.True(expected.MinB == actual.MinB, $"{path}.MinB: expected {expected.MinB}, got {actual.MinB}");
|
|
Assert.True(expected.MaxB == actual.MaxB, $"{path}.MaxB: expected {expected.MaxB}, got {actual.MaxB}");
|
|
AssertVector3Equal(expected.C, actual.C, $"{path}.C");
|
|
Assert.True(expected.MinC == actual.MinC, $"{path}.MinC: expected {expected.MinC}, got {actual.MinC}");
|
|
Assert.True(expected.MaxC == actual.MaxC, $"{path}.MaxC: expected {expected.MaxC}, got {actual.MaxC}");
|
|
Assert.True(expected.StartScale == actual.StartScale, $"{path}.StartScale: expected {expected.StartScale}, got {actual.StartScale}");
|
|
Assert.True(expected.FinalScale == actual.FinalScale, $"{path}.FinalScale: expected {expected.FinalScale}, got {actual.FinalScale}");
|
|
Assert.True(expected.ScaleRand == actual.ScaleRand, $"{path}.ScaleRand: expected {expected.ScaleRand}, got {actual.ScaleRand}");
|
|
Assert.True(expected.StartTrans == actual.StartTrans, $"{path}.StartTrans: expected {expected.StartTrans}, got {actual.StartTrans}");
|
|
Assert.True(expected.FinalTrans == actual.FinalTrans, $"{path}.FinalTrans: expected {expected.FinalTrans}, got {actual.FinalTrans}");
|
|
Assert.True(expected.TransRand == actual.TransRand, $"{path}.TransRand: expected {expected.TransRand}, got {actual.TransRand}");
|
|
Assert.True(expected.IsParentLocal == actual.IsParentLocal, $"{path}.IsParentLocal: expected {expected.IsParentLocal}, got {actual.IsParentLocal}");
|
|
}
|
|
|
|
private static void AssertTextureKeyEqual(TextureKey expected, TextureKey actual, string path) {
|
|
Assert.True(expected.Equals(actual),
|
|
$"{path}: expected SurfaceId=0x{expected.SurfaceId:X8} PaletteId=0x{expected.PaletteId:X8} Stippling={expected.Stippling} IsSolid={expected.IsSolid}, " +
|
|
$"got SurfaceId=0x{actual.SurfaceId:X8} PaletteId=0x{actual.PaletteId:X8} Stippling={actual.Stippling} IsSolid={actual.IsSolid}");
|
|
}
|
|
|
|
private static void AssertBoundingBoxEqual(Chorizite.Core.Lib.BoundingBox expected, Chorizite.Core.Lib.BoundingBox actual, string path) {
|
|
AssertVector3Equal(expected.Min, actual.Min, $"{path}.Min");
|
|
AssertVector3Equal(expected.Max, actual.Max, $"{path}.Max");
|
|
}
|
|
|
|
private static void AssertSphereEqual(Sphere? expected, Sphere? actual, string path) {
|
|
if (expected is null && actual is null) return;
|
|
Assert.True(expected is not null, $"{path}: expected null but actual was non-null");
|
|
Assert.True(actual is not null, $"{path}: expected non-null but actual was null");
|
|
AssertVector3Equal(expected.Origin, actual.Origin, $"{path}.Origin");
|
|
Assert.True(expected.Radius == actual.Radius, $"{path}.Radius: expected {expected.Radius}, got {actual.Radius}");
|
|
}
|
|
|
|
private static void AssertVector3Equal(System.Numerics.Vector3 expected, System.Numerics.Vector3 actual, string path) {
|
|
Assert.True(expected == actual, $"{path}: expected {expected}, got {actual}");
|
|
}
|
|
|
|
private static void AssertVector3ArrayEqual(System.Numerics.Vector3[] expected, System.Numerics.Vector3[] actual, string path) {
|
|
Assert.True(expected.Length == actual.Length, $"{path}.Length: expected {expected.Length}, got {actual.Length}");
|
|
for (int i = 0; i < expected.Length; i++)
|
|
AssertVector3Equal(expected[i], actual[i], $"{path}[{i}]");
|
|
}
|
|
|
|
private static void AssertMatrixEqual(System.Numerics.Matrix4x4 expected, System.Numerics.Matrix4x4 actual, string path) {
|
|
Assert.True(expected == actual, $"{path}: expected {expected}, got {actual}");
|
|
}
|
|
}
|