checkpoint(render): preserve pre-overhaul investigation state

This commit is contained in:
Erik 2026-09-01 18:04:24 +02:00
parent e880860291
commit b3b7d922f1
45 changed files with 3168 additions and 619 deletions

View file

@ -43,6 +43,16 @@ else if (args.Length > 0 && string.Equals(args[0], "gfxobj", StringComparison.Or
foreach (var gfxObjId in ids)
DumpGfxObj(dats, gfxObjId);
}
else if (args.Length > 0 && string.Equals(args[0], "scan-drawing-spheres", StringComparison.OrdinalIgnoreCase))
{
return AuditDrawingSpheres(dats);
}
else if (args.Length > 0 && string.Equals(args[0], "setup", StringComparison.OrdinalIgnoreCase))
{
var ids = args.Skip(1).Select(ParseHex).ToArray();
foreach (var setupId in ids)
DumpSetup(dats, setupId);
}
else
{
var ids = args.Length == 0
@ -57,6 +67,76 @@ else
return 0;
static int AuditDrawingSpheres(DatCollection dats)
{
const float absoluteTolerance = 1e-4f;
long objects = 0;
long withDrawingSphere = 0;
long withoutRenderableVertices = 0;
long invalid = 0;
var failures = new List<(uint Id, int RenderVertices, float Radius, float RequiredRadius, Vector3 Center)>();
foreach (uint gfxObjId in dats.GetAllIdsOfType<GfxObj>().OrderBy(id => id))
{
GfxObj? gfxObj = dats.Get<GfxObj>(gfxObjId);
if (gfxObj is null)
continue;
objects++;
if (gfxObj.DrawingBSP?.Root?.BoundingSphere is not { } sphere)
continue;
withDrawingSphere++;
var renderVertexIds = gfxObj.Polygons.Values
.SelectMany(poly => poly.VertexIds)
.Distinct()
.ToArray();
if (renderVertexIds.Length == 0)
{
withoutRenderableVertices++;
continue;
}
float requiredRadius = 0f;
foreach (var vertexId in renderVertexIds)
{
if (!gfxObj.VertexArray.Vertices.TryGetValue((ushort)vertexId, out var vertex))
continue;
requiredRadius = MathF.Max(requiredRadius, Vector3.Distance(sphere.Origin, vertex.Origin));
}
float tolerance = MathF.Max(absoluteTolerance, MathF.Abs(sphere.Radius) * 1e-5f);
if (!float.IsFinite(sphere.Radius)
|| sphere.Radius < 0f
|| requiredRadius > sphere.Radius + tolerance)
{
invalid++;
failures.Add((gfxObjId, renderVertexIds.Length, sphere.Radius, requiredRadius, sphere.Origin));
}
}
Console.WriteLine(
$"gfxobjs={objects} withDrawingSphere={withDrawingSphere} "
+ $"withoutRenderableVertices={withoutRenderableVertices} invalid={invalid}");
foreach (var failure in failures
.OrderByDescending(entry => entry.Radius > 0f
? entry.RequiredRadius / entry.Radius
: float.PositiveInfinity)
.ThenBy(entry => entry.Id)
.Take(100))
{
float ratio = failure.Radius > 0f
? failure.RequiredRadius / failure.Radius
: float.PositiveInfinity;
Console.WriteLine(
$"0x{failure.Id:X8} renderVerts={failure.RenderVertices} "
+ $"sphere=({failure.Center.X:R},{failure.Center.Y:R},{failure.Center.Z:R};r={failure.Radius:R}) "
+ $"required={failure.RequiredRadius:R} ratio={ratio:R}");
}
return 0;
}
static int AuditCellWindingCatalog(DatCollection dats)
{
long environments = 0;
@ -213,7 +293,22 @@ static void DumpCell(DatCollection dats, uint envCellId)
Console.WriteLine(
$"environment=0x{envId:X8} cellStruct={envCell.CellStructure} " +
$"surfaces={envCell.Surfaces.Count} verts={cellStruct.VertexArray.Vertices.Count} polys={cellStruct.Polygons.Count}");
$"surfaces={envCell.Surfaces.Count} verts={cellStruct.VertexArray.Vertices.Count} polys={cellStruct.Polygons.Count} " +
$"pos=({envCell.Position.Origin.X:R},{envCell.Position.Origin.Y:R},{envCell.Position.Origin.Z:R}) " +
$"quat=({envCell.Position.Orientation.W:R},{envCell.Position.Orientation.X:R}," +
$"{envCell.Position.Orientation.Y:R},{envCell.Position.Orientation.Z:R})");
Console.WriteLine(
"visible=[" + string.Join(",", envCell.VisibleCells.Select(
id => $"0x{((envCellId & 0xFFFF0000u) | id):X8}")) + "]");
for (int staticIndex = 0; staticIndex < envCell.StaticObjects.Count; staticIndex++)
{
var staticObject = envCell.StaticObjects[staticIndex];
Console.WriteLine(
$"static[{staticIndex}] model=0x{staticObject.Id:X8} " +
$"pos=({staticObject.Frame.Origin.X:R},{staticObject.Frame.Origin.Y:R},{staticObject.Frame.Origin.Z:R}) " +
$"quat=({staticObject.Frame.Orientation.W:R},{staticObject.Frame.Orientation.X:R}," +
$"{staticObject.Frame.Orientation.Y:R},{staticObject.Frame.Orientation.Z:R})");
}
int posSides = 0;
int negSides = 0;
@ -240,6 +335,22 @@ static void DumpCell(DatCollection dats, uint envCellId)
var normal = ComputeNormal(cellStruct, poly);
var uvRange = UvRange(cellStruct, poly);
var localMin = new Vector3(float.MaxValue);
var localMax = new Vector3(float.MinValue);
foreach (int vertexId in poly.VertexIds)
{
if (!TryGetOrigin(cellStruct, checked((ushort)vertexId), out Vector3 vertex))
continue;
localMin = Vector3.Min(localMin, vertex);
localMax = Vector3.Max(localMax, vertex);
}
Matrix4x4 cellTransform =
Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation)
* Matrix4x4.CreateTranslation(envCell.Position.Origin);
Vector3 worldA = Vector3.Transform(localMin, cellTransform);
Vector3 worldB = Vector3.Transform(localMax, cellTransform);
Vector3 worldMin = Vector3.Min(worldA, worldB);
Vector3 worldMax = Vector3.Max(worldA, worldB);
string planeHint = Math.Abs(normal.Z) > 0.9f
? normal.Z > 0 ? "floor/up" : "ceiling/down"
: Math.Abs(normal.Z) > 0.15f ? "slope" : "wall";
@ -253,7 +364,9 @@ static void DumpCell(DatCollection dats, uint envCellId)
$"poly=0x{polyId:X4} pts={poly.VertexIds.Count} n=({normal.X:F3},{normal.Y:F3},{normal.Z:F3}) {planeHint,-12} " +
$"stip={poly.Stippling} sides={poly.SidesType} pos={poly.PosSurface}->{posSurf} neg={poly.NegSurface}->{negSurf} " +
$"emitPos={emitPos} emitNeg={emitNeg} posUv={poly.PosUVIndices?.Count ?? 0} negUv={poly.NegUVIndices?.Count ?? 0} " +
$"uv=({uvRange.Min.X:F3},{uvRange.Min.Y:F3})..({uvRange.Max.X:F3},{uvRange.Max.Y:F3})");
$"uv=({uvRange.Min.X:F3},{uvRange.Min.Y:F3})..({uvRange.Max.X:F3},{uvRange.Max.Y:F3}) " +
$"local=({localMin.X:F3},{localMin.Y:F3},{localMin.Z:F3})..({localMax.X:F3},{localMax.Y:F3},{localMax.Z:F3}) " +
$"world=({worldMin.X:F3},{worldMin.Y:F3},{worldMin.Z:F3})..({worldMax.X:F3},{worldMax.Y:F3},{worldMax.Z:F3})");
}
Console.WriteLine(
@ -568,6 +681,46 @@ static void DumpGfxObj(DatCollection dats, uint gfxObjId)
Console.WriteLine(
$"bbox min=({min.X:F2},{min.Y:F2},{min.Z:F2}) max=({max.X:F2},{max.Y:F2},{max.Z:F2}) " +
$"size=({max.X - min.X:F2},{max.Y - min.Y:F2},{max.Z - min.Z:F2})");
Console.WriteLine(
$"bboxExact min=({min.X:R},{min.Y:R},{min.Z:R}) " +
$"max=({max.X:R},{max.Y:R},{max.Z:R})");
if (g.DrawingBSP?.Root?.BoundingSphere is { } drawingSphere)
{
float requiredRadius = 0f;
foreach (var vertexId in g.Polygons.Values.SelectMany(poly => poly.VertexIds).Distinct())
{
if (g.VertexArray.Vertices.TryGetValue((ushort)vertexId, out var vertex))
requiredRadius = MathF.Max(requiredRadius, Vector3.Distance(drawingSphere.Origin, vertex.Origin));
}
Console.WriteLine(
$"drawingSphere=({drawingSphere.Origin.X:R},{drawingSphere.Origin.Y:R}," +
$"{drawingSphere.Origin.Z:R};r={drawingSphere.Radius:R}) " +
$"requiredForRenderVertices={requiredRadius:R} " +
$"ratio={(drawingSphere.Radius > 0f ? requiredRadius / drawingSphere.Radius : float.PositiveInfinity):R}");
DumpDrawingBsp(g.DrawingBSP.Root, depth: 0);
}
if (g.PhysicsBSP?.Root?.BoundingSphere is { } physicsSphere)
{
Console.WriteLine(
$"physicsSphere=({physicsSphere.Origin.X:R},{physicsSphere.Origin.Y:R}," +
$"{physicsSphere.Origin.Z:R};r={physicsSphere.Radius:R})");
}
if (g.Flags.HasFlag(GfxObjFlags.HasDIDDegrade) && g.DIDDegrade != 0)
{
GfxObjDegradeInfo? degrade = dats.Get<GfxObjDegradeInfo>(g.DIDDegrade);
Console.WriteLine(
$"degrade=0x{g.DIDDegrade:X8} entries={degrade?.Degrades.Count ?? 0}");
if (degrade is not null)
{
for (int i = 0; i < degrade.Degrades.Count; i++)
{
var level = degrade.Degrades[i];
Console.WriteLine(
$" level[{i}] gfx=0x{(uint)level.Id:X8} min={level.MinDist:R} " +
$"ideal={level.IdealDist:R} max={level.MaxDist:R} mode={level.DegradeMode}");
}
}
}
Console.WriteLine(
$"classify: walls={walls} (outwardFacing={outwardWalls} inwardFacing={inwardWalls}) " +
$"floors={floors} ceilings={ceilings} slopes={slopes}");
@ -595,6 +748,76 @@ static void DumpGfxObj(DatCollection dats, uint gfxObjId)
Console.WriteLine();
}
static void DumpDrawingBsp(
DatReaderWriter.Types.DrawingBSPNode? node,
int depth)
{
if (node is null)
return;
string indent = new(' ', depth * 2);
var sphere = node.BoundingSphere;
string polygons = node.Polygons is null
? "[]"
: "[" + string.Join(",", node.Polygons.Select(id => $"0x{id:X4}")) + "]";
Console.WriteLine(
$"{indent}drawingNode type={node.GetType().Name} "
+ $"sphere=({sphere.Origin.X:R},{sphere.Origin.Y:R},{sphere.Origin.Z:R};r={sphere.Radius:R}) "
+ $"polys={polygons} portals={node.Portals?.Count ?? 0}");
DumpDrawingBsp(node.PosNode, depth + 1);
DumpDrawingBsp(node.NegNode, depth + 1);
}
static void DumpSetup(DatCollection dats, uint setupId)
{
Console.WriteLine($"=== Setup 0x{setupId:X8} ===");
var setup = dats.Get<Setup>(setupId);
if (setup is null)
{
Console.WriteLine("missing Setup");
return;
}
Console.WriteLine(
$"flags={setup.Flags} radius={setup.Radius:R} height={setup.Height:R} " +
$"parts={setup.Parts.Count} cylspheres={setup.CylSpheres.Count} spheres={setup.Spheres.Count}");
for (int i = 0; i < setup.CylSpheres.Count; i++)
{
var cylinder = setup.CylSpheres[i];
Console.WriteLine(
$"cyl[{i}] origin=({cylinder.Origin.X:R},{cylinder.Origin.Y:R},{cylinder.Origin.Z:R}) " +
$"radius={cylinder.Radius:R} height={cylinder.Height:R}");
}
for (int i = 0; i < setup.Spheres.Count; i++)
{
var sphere = setup.Spheres[i];
Console.WriteLine(
$"sphere[{i}] origin=({sphere.Origin.X:R},{sphere.Origin.Y:R},{sphere.Origin.Z:R}) " +
$"radius={sphere.Radius:R}");
}
for (int i = 0; i < setup.Parts.Count; i++)
{
uint partId = setup.Parts[i];
GfxObj? part = dats.Get<GfxObj>(partId);
Console.WriteLine(
$"part[{i}] gfx=0x{partId:X8} flags={(part is null ? "missing" : part.Flags)} " +
$"physics={(part?.PhysicsBSP?.Root is null ? 0 : 1)} " +
$"visualVerts={part?.VertexArray?.Vertices.Count ?? 0} visualPolys={part?.Polygons?.Count ?? 0}");
}
foreach (var (placement, frames) in setup.PlacementFrames)
{
Console.WriteLine($"placement={placement} frames={frames.Frames.Count}");
for (int i = 0; i < frames.Frames.Count; i++)
{
var frame = frames.Frames[i];
Console.WriteLine(
$" frame[{i}] pos=({frame.Origin.X:R},{frame.Origin.Y:R},{frame.Origin.Z:R}) " +
$"quat=({frame.Orientation.W:R},{frame.Orientation.X:R}," +
$"{frame.Orientation.Y:R},{frame.Orientation.Z:R})");
}
}
}
static Vector3 ComputeNormalG(GfxObj g, DatReaderWriter.Types.Polygon poly)
{
if (poly.VertexIds.Count < 3) return Vector3.Zero;