feat(phys): A6.P3 #98 — GfxObj dump infrastructure (ACDREAM_DUMP_GFXOBJS)
Mirror the existing ACDREAM_DUMP_CELLS pattern for GfxObj-owned geometry:
when ACDREAM_DUMP_GFXOBJS lists a hex GfxObj id, the first
PhysicsDataCache.CacheGfxObj for that id writes the full resolved
polygon table to a JSON fixture under
tests/AcDream.Core.Tests/Fixtures/issue98/0x{id:X8}.gfxobj.json (override
dir via ACDREAM_DUMP_GFXOBJS_DIR).
Motivation: the existing [resolve-bldg] probe captures GfxObj-level
metadata (id, BSP root radius, entity origin) but emits
"hitPoly: n/a (BSP path — side-channel not written)" because the
BSPQuery wire site that would populate LastBspHitPoly never landed.
A polygon-level dump at cache time bypasses that gap — one capture run
yields the FULL polygon table, fixture-loadable by the harness's
RegisterCottageGfxObj helper (next commit).
See docs/research/2026-05-23-a6-p3-issue98-comparison-harness-findings.md
for the cottage GfxObj 0x01000A2B context: landblock-baked static at
entity origin (130.5, 11.5, 94.0), responsible for the head-sphere cap
from below at world Z=94.0 that issue #98 is documenting.
Test baseline: 1183 + 8 pre-existing failures (serial run; +5 new tests
all pass; was 1178 + 8 pre-session).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4d83ba5620
commit
cc3afbcbeb
5 changed files with 484 additions and 1 deletions
198
src/AcDream.Core/Physics/GfxObjDump.cs
Normal file
198
src/AcDream.Core/Physics/GfxObjDump.cs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
|
||||
namespace AcDream.Core.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// A6.P3 issue #98 (2026-05-23 evening v2) — JSON-serializable snapshot of
|
||||
/// a single <see cref="GfxObjPhysics"/> instance, capturing the polygons
|
||||
/// the engine actually queries during collision. Parallel to
|
||||
/// <see cref="CellDump"/> but for GfxObj-owned geometry (landblock-baked
|
||||
/// statics like the cottage building 0x01000A2B in this investigation).
|
||||
///
|
||||
/// <para>
|
||||
/// Polygons are captured in OBJECT-LOCAL frame — the same frame the
|
||||
/// engine uses at collision time after applying the ShadowEntry's
|
||||
/// world transform. The harness loads the dump, attaches a synthetic
|
||||
/// single-leaf BSP, and registers the GfxObj at the appropriate world
|
||||
/// transform via the test's ShadowObjects.Register call.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// What's intentionally NOT captured: the raw DAT
|
||||
/// <c>PhysicsBSPTree</c> (the dump's consumer wraps polygons in a
|
||||
/// single-leaf BSP), the original <c>PhysicsPolygons</c> + raw
|
||||
/// <c>VertexArray</c> dicts (the engine uses <see cref="ResolvedPolygon"/>
|
||||
/// during collision; the raw dicts are placeholders post-hydrate).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed record GfxObjDump(
|
||||
uint GfxObjId,
|
||||
Vector3Dto BoundingSphereOrigin,
|
||||
float BoundingSphereRadius,
|
||||
IReadOnlyList<PolygonDump> ResolvedPolygons);
|
||||
|
||||
public static class GfxObjDumpSerializer
|
||||
{
|
||||
private static readonly JsonSerializerOptions WriteOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
|
||||
private static readonly JsonSerializerOptions ReadOptions = new()
|
||||
{
|
||||
NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot a <see cref="GfxObjPhysics"/> instance into a
|
||||
/// JSON-friendly <see cref="GfxObjDump"/> DTO. Polygon vertices
|
||||
/// stay in object-local frame.
|
||||
/// </summary>
|
||||
public static GfxObjDump Capture(uint gfxObjId, GfxObjPhysics gfx)
|
||||
{
|
||||
var resolved = new List<PolygonDump>(gfx.Resolved.Count);
|
||||
foreach (var (id, poly) in gfx.Resolved)
|
||||
{
|
||||
var verts = new List<Vector3Dto>(poly.Vertices.Length);
|
||||
foreach (var v in poly.Vertices)
|
||||
verts.Add(Vector3Dto.From(v));
|
||||
|
||||
resolved.Add(new PolygonDump(
|
||||
Id: id,
|
||||
NumPoints: poly.NumPoints,
|
||||
SidesType: (int)poly.SidesType,
|
||||
Plane: PlaneDto.From(poly.Plane),
|
||||
Vertices: verts));
|
||||
}
|
||||
|
||||
// Fall back to (0,0,0) + 0 when BoundingSphere is unset — the
|
||||
// hydrate path recomputes a covering sphere from the polygon
|
||||
// vertices in that case.
|
||||
var bsOrigin = gfx.BoundingSphere?.Origin ?? Vector3.Zero;
|
||||
var bsRadius = gfx.BoundingSphere?.Radius ?? 0f;
|
||||
|
||||
return new GfxObjDump(
|
||||
GfxObjId: gfxObjId,
|
||||
BoundingSphereOrigin: Vector3Dto.From(bsOrigin),
|
||||
BoundingSphereRadius: bsRadius,
|
||||
ResolvedPolygons: resolved);
|
||||
}
|
||||
|
||||
public static void Write(GfxObjDump dump, string filePath)
|
||||
{
|
||||
var dir = Path.GetDirectoryName(filePath);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
using var stream = File.Create(filePath);
|
||||
JsonSerializer.Serialize(stream, dump, WriteOptions);
|
||||
}
|
||||
|
||||
public static GfxObjDump Read(string filePath)
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var dump = JsonSerializer.Deserialize<GfxObjDump>(stream, ReadOptions);
|
||||
if (dump is null)
|
||||
throw new InvalidDataException(
|
||||
$"GfxObj dump deserialized to null: {filePath}");
|
||||
return dump;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Re-hydrate a <see cref="GfxObjPhysics"/> from a
|
||||
/// <see cref="GfxObjDump"/>. Constructs a synthetic single-leaf
|
||||
/// <see cref="PhysicsBSPTree"/> referencing every polygon — sufficient
|
||||
/// for BSPQuery traversal at collision time. PhysicsPolygons and
|
||||
/// Vertices are placeholders (the engine reads <see cref="GfxObjPhysics.Resolved"/>
|
||||
/// during collision, not the raw dat dicts).
|
||||
/// </summary>
|
||||
public static GfxObjPhysics Hydrate(GfxObjDump dump)
|
||||
{
|
||||
var resolved = new Dictionary<ushort, ResolvedPolygon>(
|
||||
dump.ResolvedPolygons.Count);
|
||||
foreach (var p in dump.ResolvedPolygons)
|
||||
{
|
||||
var verts = new Vector3[p.Vertices.Count];
|
||||
for (int i = 0; i < verts.Length; i++)
|
||||
verts[i] = p.Vertices[i].ToVector3();
|
||||
|
||||
resolved[p.Id] = new ResolvedPolygon
|
||||
{
|
||||
Vertices = verts,
|
||||
Plane = p.Plane.ToPlane(),
|
||||
NumPoints = p.NumPoints,
|
||||
SidesType = (DatReaderWriter.Enums.CullMode)p.SidesType,
|
||||
Id = p.Id,
|
||||
};
|
||||
}
|
||||
|
||||
// Bounding sphere: use the captured value, OR fall back to a sphere
|
||||
// that covers every polygon (computed from the centroid + max
|
||||
// distance to any vertex).
|
||||
var bsOrigin = dump.BoundingSphereOrigin.ToVector3();
|
||||
var bsRadius = dump.BoundingSphereRadius;
|
||||
if (bsRadius <= 0f && resolved.Count > 0)
|
||||
{
|
||||
(bsOrigin, bsRadius) = ComputeCoveringSphere(resolved);
|
||||
}
|
||||
|
||||
var leaf = new PhysicsBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = bsOrigin,
|
||||
Radius = bsRadius,
|
||||
},
|
||||
};
|
||||
foreach (var id in resolved.Keys)
|
||||
leaf.Polygons.Add(id);
|
||||
|
||||
var bspTree = new PhysicsBSPTree { Root = leaf };
|
||||
|
||||
return new GfxObjPhysics
|
||||
{
|
||||
BSP = bspTree,
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
Vertices = new VertexArray(),
|
||||
Resolved = resolved,
|
||||
BoundingSphere = leaf.BoundingSphere,
|
||||
};
|
||||
}
|
||||
|
||||
private static (Vector3 origin, float radius) ComputeCoveringSphere(
|
||||
Dictionary<ushort, ResolvedPolygon> resolved)
|
||||
{
|
||||
var sum = Vector3.Zero;
|
||||
int count = 0;
|
||||
foreach (var poly in resolved.Values)
|
||||
foreach (var v in poly.Vertices)
|
||||
{
|
||||
sum += v;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
return (Vector3.Zero, 0f);
|
||||
|
||||
var centroid = sum / count;
|
||||
float maxDistSq = 0f;
|
||||
foreach (var poly in resolved.Values)
|
||||
foreach (var v in poly.Vertices)
|
||||
{
|
||||
float distSq = Vector3.DistanceSquared(centroid, v);
|
||||
if (distSq > maxDistSq) maxDistSq = distSq;
|
||||
}
|
||||
|
||||
return (centroid, MathF.Sqrt(maxDistSq));
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ public sealed class PhysicsDataCache
|
|||
if (gfxObj.PhysicsBSP?.Root is null) return;
|
||||
if (gfxObj.VertexArray is null) return;
|
||||
|
||||
_gfxObj[gfxObjId] = new GfxObjPhysics
|
||||
var physics = new GfxObjPhysics
|
||||
{
|
||||
BSP = gfxObj.PhysicsBSP,
|
||||
PhysicsPolygons = gfxObj.PhysicsPolygons,
|
||||
|
|
@ -54,6 +54,27 @@ public sealed class PhysicsDataCache
|
|||
Vertices = gfxObj.VertexArray,
|
||||
Resolved = ResolvePolygons(gfxObj.PhysicsPolygons, gfxObj.VertexArray),
|
||||
};
|
||||
_gfxObj[gfxObjId] = physics;
|
||||
|
||||
if (PhysicsDiagnostics.ProbeDumpGfxObjsEnabled
|
||||
&& PhysicsDiagnostics.ProbeDumpGfxObjIds.Contains(gfxObjId))
|
||||
{
|
||||
try
|
||||
{
|
||||
var dump = GfxObjDumpSerializer.Capture(gfxObjId, physics);
|
||||
var path = System.IO.Path.Combine(
|
||||
PhysicsDiagnostics.ProbeDumpGfxObjsPath,
|
||||
System.FormattableString.Invariant($"0x{gfxObjId:X8}.gfxobj.json"));
|
||||
GfxObjDumpSerializer.Write(dump, path);
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[gfxobj-dump] wrote 0x{gfxObjId:X8} polys={dump.ResolvedPolygons.Count} → {path}"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[gfxobj-dump] FAILED to dump 0x{gfxObjId:X8}: {ex.GetType().Name}: {ex.Message}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -429,6 +429,45 @@ public static class PhysicsDiagnostics
|
|||
|
||||
public static bool ProbeDumpCellsEnabled => ProbeDumpCellIds.Count > 0;
|
||||
|
||||
/// <summary>
|
||||
/// A6.P3 issue #98 (2026-05-23 evening v2) — GfxObj-equivalent of
|
||||
/// <see cref="ProbeDumpCellIds"/>. When non-empty, dumps the polygon
|
||||
/// table + BSP root metadata of any cached GfxObj whose id matches
|
||||
/// one of the listed values, as a JSON fixture under
|
||||
/// <see cref="ProbeDumpGfxObjsPath"/>. One-shot per id (a second
|
||||
/// cache of the same GfxObj is a no-op).
|
||||
///
|
||||
/// <para>
|
||||
/// Configured via <c>ACDREAM_DUMP_GFXOBJS</c> as a comma-separated
|
||||
/// list of hex GfxObj ids (with or without <c>0x</c> prefix). Output
|
||||
/// defaults to <c>tests/AcDream.Core.Tests/Fixtures/issue98</c>
|
||||
/// (relative to the worktree root) with one file per id named
|
||||
/// <c>0x{id:X8}.gfxobj.json</c> so it doesn't collide with cell
|
||||
/// dumps in the same directory. Override directory via
|
||||
/// <c>ACDREAM_DUMP_GFXOBJS_DIR=<dir></c>.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The motivation: the existing <c>[resolve-bldg]</c> probe captures
|
||||
/// the GfxObj-level metadata (id, BSP root radius, entity origin) but
|
||||
/// emits <c>hitPoly: n/a (BSP path — side-channel not written)</c>
|
||||
/// because the BSPQuery wire site that would populate
|
||||
/// <see cref="LastBspHitPoly"/> never landed. A polygon-level dump
|
||||
/// at cache time bypasses that gap entirely — one capture run yields
|
||||
/// the FULL polygon table, suitable for fixture-loading in
|
||||
/// <c>CellarUpTrajectoryReplayTests</c>'s <c>RegisterCottageGfxObj</c>
|
||||
/// helper.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static IReadOnlySet<uint> ProbeDumpGfxObjIds { get; set; } =
|
||||
ParseHexIdList(Environment.GetEnvironmentVariable("ACDREAM_DUMP_GFXOBJS"));
|
||||
|
||||
public static string ProbeDumpGfxObjsPath { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_DUMP_GFXOBJS_DIR")
|
||||
?? "tests/AcDream.Core.Tests/Fixtures/issue98";
|
||||
|
||||
public static bool ProbeDumpGfxObjsEnabled => ProbeDumpGfxObjIds.Count > 0;
|
||||
|
||||
private static IReadOnlySet<uint> ParseHexIdList(string? raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
|
|
|
|||
201
tests/AcDream.Core.Tests/Physics/GfxObjDumpRoundTripTests.cs
Normal file
201
tests/AcDream.Core.Tests/Physics/GfxObjDumpRoundTripTests.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Types;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// A6.P3 issue #98 (2026-05-23 evening v2). Sanity check for
|
||||
/// <see cref="GfxObjDumpSerializer"/>: hand-construct a small
|
||||
/// <see cref="GfxObjPhysics"/>, snapshot it, serialize → deserialize →
|
||||
/// hydrate, then assert the round-trip preserves every field the harness's
|
||||
/// <c>RegisterCottageGfxObj</c> path depends on.
|
||||
///
|
||||
/// <para>
|
||||
/// Mirrors <see cref="CellDumpRoundTripTests"/> in shape — sanity-only,
|
||||
/// no engine wiring. The fixture's polygons are in OBJECT-LOCAL frame
|
||||
/// (matching the production capture path's frame), so the hydrated
|
||||
/// instance is suitable for placement under any world transform via
|
||||
/// <c>ShadowObjects.Register</c>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class GfxObjDumpRoundTripTests
|
||||
{
|
||||
[Fact]
|
||||
public void Capture_Then_Hydrate_PreservesPolygons()
|
||||
{
|
||||
var original = MakeFixtureGfx();
|
||||
|
||||
var dump = GfxObjDumpSerializer.Capture(0x01000A2Bu, original);
|
||||
var hydrated = GfxObjDumpSerializer.Hydrate(dump);
|
||||
|
||||
Assert.Equal(original.Resolved.Count, hydrated.Resolved.Count);
|
||||
foreach (var (id, originalPoly) in original.Resolved)
|
||||
{
|
||||
Assert.True(hydrated.Resolved.TryGetValue(id, out var rehydrated));
|
||||
Assert.Equal(originalPoly.NumPoints, rehydrated!.NumPoints);
|
||||
Assert.Equal(originalPoly.SidesType, rehydrated.SidesType);
|
||||
Assert.Equal(originalPoly.Plane.Normal, rehydrated.Plane.Normal);
|
||||
Assert.Equal(originalPoly.Plane.D, rehydrated.Plane.D);
|
||||
Assert.Equal(originalPoly.Vertices.Length, rehydrated.Vertices.Length);
|
||||
for (int i = 0; i < originalPoly.Vertices.Length; i++)
|
||||
Assert.Equal(originalPoly.Vertices[i], rehydrated.Vertices[i]);
|
||||
Assert.Equal(originalPoly.Id, rehydrated.Id);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hydrate_ConstructsSyntheticSingleLeafBspWithAllPolygons()
|
||||
{
|
||||
var original = MakeFixtureGfx();
|
||||
var dump = GfxObjDumpSerializer.Capture(0x01000A2Bu, original);
|
||||
var hydrated = GfxObjDumpSerializer.Hydrate(dump);
|
||||
|
||||
// Synthetic BSP is single-leaf, references every resolved poly id.
|
||||
Assert.NotNull(hydrated.BSP);
|
||||
Assert.NotNull(hydrated.BSP!.Root);
|
||||
Assert.Equal(BSPNodeType.Leaf, hydrated.BSP.Root.Type);
|
||||
Assert.Equal(original.Resolved.Count, hydrated.BSP.Root.Polygons.Count);
|
||||
foreach (var id in original.Resolved.Keys)
|
||||
Assert.Contains(id, hydrated.BSP.Root.Polygons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WriteRead_OnDisk_PreservesContent()
|
||||
{
|
||||
var original = MakeFixtureGfx();
|
||||
var dump = GfxObjDumpSerializer.Capture(0x01000A2Bu, original);
|
||||
|
||||
var path = Path.Combine(Path.GetTempPath(),
|
||||
$"acdream-gfxobjdump-test-{System.Guid.NewGuid():N}.json");
|
||||
try
|
||||
{
|
||||
GfxObjDumpSerializer.Write(dump, path);
|
||||
Assert.True(File.Exists(path));
|
||||
|
||||
var readBack = GfxObjDumpSerializer.Read(path);
|
||||
Assert.Equal(dump.GfxObjId, readBack.GfxObjId);
|
||||
Assert.Equal(dump.ResolvedPolygons.Count, readBack.ResolvedPolygons.Count);
|
||||
|
||||
var hydrated = GfxObjDumpSerializer.Hydrate(readBack);
|
||||
Assert.Equal(original.Resolved.Count, hydrated.Resolved.Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (File.Exists(path)) File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hydrate_RecomputesCoveringSphereWhenDumpHasZeroRadius()
|
||||
{
|
||||
// Force the hydrate-side covering-sphere computation by capturing
|
||||
// a GfxObj whose BoundingSphere is null. The hydrate path should
|
||||
// compute a sphere that encloses every vertex.
|
||||
var resolved = new Dictionary<ushort, ResolvedPolygon>
|
||||
{
|
||||
[0x0001] = new ResolvedPolygon
|
||||
{
|
||||
Id = 0x0001,
|
||||
NumPoints = 3,
|
||||
SidesType = CullMode.Clockwise,
|
||||
Plane = new Plane(new Vector3(0, 0, 1), 0f),
|
||||
Vertices = new[]
|
||||
{
|
||||
new Vector3(0f, 0f, 0f),
|
||||
new Vector3(4f, 0f, 0f),
|
||||
new Vector3(0f, 3f, 0f),
|
||||
},
|
||||
},
|
||||
};
|
||||
var leaf = new PhysicsBSPNode { Type = BSPNodeType.Leaf };
|
||||
leaf.Polygons.Add(0x0001);
|
||||
var src = new GfxObjPhysics
|
||||
{
|
||||
BSP = new PhysicsBSPTree { Root = leaf },
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
Vertices = new VertexArray(),
|
||||
Resolved = resolved,
|
||||
BoundingSphere = null,
|
||||
};
|
||||
|
||||
var dump = GfxObjDumpSerializer.Capture(0x42u, src);
|
||||
// BoundingSphere=null in source → radius 0 in dump.
|
||||
Assert.Equal(0f, dump.BoundingSphereRadius);
|
||||
|
||||
var hydrated = GfxObjDumpSerializer.Hydrate(dump);
|
||||
Assert.NotNull(hydrated.BoundingSphere);
|
||||
Assert.True(hydrated.BoundingSphere!.Radius > 0f);
|
||||
// Each vertex must lie within the covering sphere (with float-EPS slack).
|
||||
foreach (var v in resolved[0x0001].Vertices)
|
||||
{
|
||||
float dist = Vector3.Distance(hydrated.BoundingSphere.Origin, v);
|
||||
Assert.True(dist <= hydrated.BoundingSphere.Radius + 1e-4f,
|
||||
$"Vertex {v} at distance {dist} exceeds covering radius {hydrated.BoundingSphere.Radius}");
|
||||
}
|
||||
}
|
||||
|
||||
private static GfxObjPhysics MakeFixtureGfx()
|
||||
{
|
||||
// Two polygons modelling a fragment of the cottage GfxObj (object-
|
||||
// local frame): a downward-facing horizontal "cottage floor" and
|
||||
// the cellar ramp (the polys live capture pinpointed).
|
||||
var resolved = new Dictionary<ushort, ResolvedPolygon>
|
||||
{
|
||||
[0x0004] = new ResolvedPolygon
|
||||
{
|
||||
Id = 0x0004,
|
||||
NumPoints = 3,
|
||||
SidesType = CullMode.Clockwise,
|
||||
// Downward-facing floor at object-Z=1.5 (in local frame the
|
||||
// cottage floor sits 1.5m above the building origin).
|
||||
Plane = new Plane(new Vector3(0, 0, -1), 1.5f),
|
||||
Vertices = new[]
|
||||
{
|
||||
new Vector3(-6.2f, 7.6f, 1.5f),
|
||||
new Vector3(-10f, 7.6f, 1.5f),
|
||||
new Vector3(-10f, 2.8f, 1.5f),
|
||||
},
|
||||
},
|
||||
[0x0008] = new ResolvedPolygon
|
||||
{
|
||||
Id = 0x0008,
|
||||
NumPoints = 4,
|
||||
SidesType = CullMode.Clockwise,
|
||||
Plane = new Plane(new Vector3(0f, -0.7190f, 0.6950f), -0.1007f),
|
||||
Vertices = new[]
|
||||
{
|
||||
new Vector3( 0.8f, -1.59f, -1.5f),
|
||||
new Vector3( 0.8f, 1.31f, 1.5f),
|
||||
new Vector3(-0.8f, 1.31f, 1.5f),
|
||||
new Vector3(-0.8f, -1.59f, -1.5f),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
var leaf = new PhysicsBSPNode
|
||||
{
|
||||
Type = BSPNodeType.Leaf,
|
||||
BoundingSphere = new Sphere
|
||||
{
|
||||
Origin = new Vector3(-5f, 4f, 0f),
|
||||
Radius = 14f,
|
||||
},
|
||||
};
|
||||
leaf.Polygons.Add(0x0004);
|
||||
leaf.Polygons.Add(0x0008);
|
||||
|
||||
return new GfxObjPhysics
|
||||
{
|
||||
BSP = new PhysicsBSPTree { Root = leaf },
|
||||
PhysicsPolygons = new Dictionary<ushort, Polygon>(),
|
||||
Vertices = new VertexArray(),
|
||||
Resolved = resolved,
|
||||
BoundingSphere = leaf.BoundingSphere,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using AcDream.Core.Physics;
|
||||
using DatReaderWriter.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Xunit;
|
||||
|
||||
|
|
@ -141,4 +142,27 @@ public class PhysicsDiagnosticsTests
|
|||
PhysicsDiagnostics.ProbeStepWalkEnabled = initial;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// ProbeDumpGfxObjs — parallel of ProbeDumpCells (A6.P3 #98, evening v2).
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void ProbeDumpGfxObjs_EnabledTracksIdSetNonEmpty()
|
||||
{
|
||||
var initial = PhysicsDiagnostics.ProbeDumpGfxObjIds;
|
||||
try
|
||||
{
|
||||
PhysicsDiagnostics.ProbeDumpGfxObjIds = new HashSet<uint>();
|
||||
Assert.False(PhysicsDiagnostics.ProbeDumpGfxObjsEnabled);
|
||||
|
||||
PhysicsDiagnostics.ProbeDumpGfxObjIds = new HashSet<uint> { 0x01000A2Bu };
|
||||
Assert.True(PhysicsDiagnostics.ProbeDumpGfxObjsEnabled);
|
||||
Assert.Contains(0x01000A2Bu, PhysicsDiagnostics.ProbeDumpGfxObjIds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PhysicsDiagnostics.ProbeDumpGfxObjIds = initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue