probe(physics): ACDREAM_PROBE_SUPPORT + ACDREAM_WIRE_MESH — separate #337's three candidates
The user is wedged at the top of Neftet rock plateaus, jumps sink into the mesh, and a corpse falls straight through. ACDREAM_PROBE_REACH already ruled out its own domain: blocked=0, every candidate tested-ok. Three candidates remain — terrain support, a collision mesh not where its visual is, or the transition wedging on an unobstructed path. ACDREAM_PROBE_RESOLVE alone cannot separate them. It prints a three-value contact-plane token, no plane normal, no plane height, no terrain sample and no plane provenance, so all three produce the same line. Two additions: [support] — one line per resolve for EVERY body, not just the player. A corpse is a plain physics body with no player-specific logic, so its fall-through is the cheapest available control on "movement code vs geometry data", and it is invisible to any player-filtered probe. The line samples the outdoor terrain INDEPENDENTLY at the body's own out-XY and prints the contact plane's own height at that same XY. Two heights at one point make support=terrain / object / none a measurement rather than an inference, and cpSrc= names the site that asserted the plane so provenance and classification cross-check. [geom] — once per GfxObj that comes near a mover: the object's physics-BSP vertex cloud against its visual mesh AABB in the same local frame, through the same prepared accessors the resolver queries. verdict=coincident REFUTES the working hypothesis for that object outright; no-physics-bsp / empty-physics-bsp / displaced / extent-mismatch each name a specific data defect. Built to refute, not to confirm — two diagnoses on this defect's lineage have already been refuted by measurement. ACDREAM_WIRE_MESH upgrades the existing F2 overlay, which drew a broadphase proxy cylinder for BSP objects and so could not answer the question at all, to the real physics-BSP polygon edges (cyan) beside the visual mesh box (magenta) and the terrain surface (yellow). Own class per code-structure rule 1. The provenance latch lives on PhysicsDiagnostics, not on CollisionInfo. Two fields there first — the obvious home — broke the flat/graph differential referee and the scratch-reset poison test, both of which compare CollisionInfo member-for-member. Teaching either to skip a member is a one-line green fix that puts a permanent hole in a referee whose whole job is comparing everything. Captured as feedback_probe_state_off_compared_types. Seven tests cover the support classifier's boundaries: a wrong classifier does not fail to answer, it answers confidently wrong. Gates: Release build 0 errors; complete suite 11,225 passed / 4 skipped / 0 failed from a cleaned tree — baseline 11,218/4/0 plus exactly the seven new tests, skips unchanged. Issue #337 filed with the symptom set, what is ruled out, and a table of what each possible output means. All of this is TEMPORARY and recorded for stripping with the physics-probe family. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
13fcf38138
commit
49a7e90652
9 changed files with 1376 additions and 1 deletions
372
src/AcDream.App/Rendering/CollisionMeshWireframe.cs
Normal file
372
src/AcDream.App/Rendering/CollisionMeshWireframe.cs
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Rendering;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// #337 collision-mesh wireframe (2026-08-06 — TEMPORARY, strip with the #337
|
||||
/// probe family).
|
||||
///
|
||||
/// <para>
|
||||
/// The F2 collision overlay predating this class drew, for a BSP object, a
|
||||
/// proxy cylinder sized from the object's registered BROADPHASE radius. That
|
||||
/// answers "where does the collision system think this object roughly is" and
|
||||
/// nothing more. The open question in Neftet is a different one — whether an
|
||||
/// object's collision SURFACES are where its visual mesh is drawn — and a
|
||||
/// proxy sphere cannot answer it in either direction.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// This draws the actual geometry instead, in three colours that are meant to
|
||||
/// be read against each other:
|
||||
/// <list type="bullet">
|
||||
/// <item><b>Cyan</b> — the object's real physics-BSP polygon edges, in world
|
||||
/// space. These are the surfaces a body can stand on or be stopped by.
|
||||
/// Where the cyan mesh sits away from the rock you can see, the collision
|
||||
/// is displaced; where a visible rock has no cyan on it at all, it has no
|
||||
/// collision geometry.</item>
|
||||
/// <item><b>Magenta</b> — the same object's VISUAL mesh bounding box, from
|
||||
/// the same prepared assets the renderer draws from. It is the reference
|
||||
/// the cyan is judged against, so the comparison does not depend on the
|
||||
/// eye's guess about where the visual "really" is.</item>
|
||||
/// <item><b>Yellow</b> — the outdoor terrain surface under the player, as a
|
||||
/// grid of the physics engine's own sampled heights. If a body is resting
|
||||
/// on the yellow rather than on cyan, terrain is holding it up and the
|
||||
/// object's collision is not involved at all.</item>
|
||||
/// </list>
|
||||
/// Dim orange keeps the old broadphase proxy visible so nothing the previous
|
||||
/// overlay showed has been taken away.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Geometry is resolved through the SAME prepared collision accessors the
|
||||
/// resolver queries (<c>PhysicsDataCache.GetFlatGfxObj</c> /
|
||||
/// <c>GetVisualBounds</c>) and placed with the SAME world transform the
|
||||
/// collision probes use, so this cannot draw a shape the collision system does
|
||||
/// not actually hold. Reading the geometry by a second route is how AP-156
|
||||
/// managed to report a sphere the registry never emitted.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Pure reads. Nothing here mutates physics, registry, or render state; the
|
||||
/// caller owns the <see cref="DebugLineRenderer"/> frame.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed class CollisionMeshWireframe
|
||||
{
|
||||
// Colours, in the order the class comment lists them.
|
||||
private static readonly Vector3 PhysicsColor = new(0f, 1f, 1f);
|
||||
private static readonly Vector3 VisualColor = new(1f, 0f, 1f);
|
||||
private static readonly Vector3 TerrainColor = new(1f, 1f, 0f);
|
||||
private static readonly Vector3 BroadphaseColor = new(0.45f, 0.22f, 0f);
|
||||
|
||||
/// <summary>
|
||||
/// Per-frame line ceiling. Each line is 48 bytes in the debug renderer's
|
||||
/// ring allocation, so this caps the overlay at ~1.9 MB a frame. Landblock
|
||||
/// 0x8766 carries the largest single collision owner measured in the game
|
||||
/// (an 81-cell footprint), and an uncapped walk of it would be the one
|
||||
/// place this overlay falls over.
|
||||
/// </summary>
|
||||
private const int MaxLines = 40_000;
|
||||
|
||||
/// <summary>Per-object polygon ceiling, so one enormous formation cannot
|
||||
/// consume the whole budget and hide every other object near it.</summary>
|
||||
private const int MaxPolygonsPerObject = 4_000;
|
||||
|
||||
/// <summary>Half-width in metres of the terrain grid drawn under the
|
||||
/// player, and its sample spacing.</summary>
|
||||
private const float TerrainGridHalfWidth = 12f;
|
||||
private const float TerrainGridStep = 2f;
|
||||
|
||||
private readonly PhysicsEngine _physics;
|
||||
|
||||
public CollisionMeshWireframe(PhysicsEngine physics)
|
||||
=> _physics = physics ?? throw new ArgumentNullException(nameof(physics));
|
||||
|
||||
/// <summary>
|
||||
/// Emit the overlay for everything within
|
||||
/// <see cref="RenderingDiagnostics.CollisionMeshWireframeRadius"/> of
|
||||
/// <paramref name="centre"/>. Returns what it drew so the caller can
|
||||
/// report a capped frame rather than silently showing partial geometry.
|
||||
/// </summary>
|
||||
public CollisionMeshWireframeStats Draw(DebugLineRenderer lines, Vector3 centre)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(lines);
|
||||
|
||||
float radius = RenderingDiagnostics.CollisionMeshWireframeRadius;
|
||||
float radiusSquared = radius * radius;
|
||||
var budget = new LineBudget(MaxLines);
|
||||
|
||||
int objects = 0;
|
||||
int polygons = 0;
|
||||
int withoutGeometry = 0;
|
||||
|
||||
PhysicsDataCache? cache = _physics.DataCache;
|
||||
|
||||
foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug())
|
||||
{
|
||||
// Objects register their part ORIGIN, which for a BSP part is
|
||||
// routinely nowhere near the geometry itself (376 of the 973
|
||||
// installed physics-BSP parts sit further from their own bounding
|
||||
// centre than half their radius). Admitting on origin distance
|
||||
// ALONE would drop exactly the large displaced-centre formations
|
||||
// this overlay exists to look at, so the object's own radius is
|
||||
// added to the window.
|
||||
float reach = radius + shadow.Radius;
|
||||
if (Vector3.DistanceSquared(shadow.Position, centre) > reach * reach)
|
||||
continue;
|
||||
|
||||
objects++;
|
||||
|
||||
if (shadow.CollisionType != ShadowCollisionType.BSP)
|
||||
{
|
||||
DrawBroadphaseProxy(lines, in shadow, budget);
|
||||
continue;
|
||||
}
|
||||
|
||||
FlatGfxObjCollisionAsset? asset = cache?.GetFlatGfxObj(shadow.GfxObjId);
|
||||
int drawn = DrawPhysicsPolygons(lines, in shadow, asset, centre, radiusSquared, budget);
|
||||
polygons += drawn;
|
||||
if (drawn == 0) withoutGeometry++;
|
||||
|
||||
DrawVisualBounds(lines, in shadow, cache?.GetVisualBounds(shadow.GfxObjId), budget);
|
||||
DrawBroadphaseProxy(lines, in shadow, budget);
|
||||
}
|
||||
|
||||
DrawTerrainGrid(lines, centre, budget);
|
||||
|
||||
return new CollisionMeshWireframeStats(
|
||||
ObjectsConsidered: objects,
|
||||
PolygonsDrawn: polygons,
|
||||
ObjectsWithoutPhysicsGeometry: withoutGeometry,
|
||||
LinesDrawn: budget.Used,
|
||||
Capped: budget.Capped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walk the object's physics BSP and emit one closed edge loop per polygon
|
||||
/// the tree actually indexes. Polygons the tree does not reference are NOT
|
||||
/// drawn: no query can reach them, so showing them would overstate the
|
||||
/// collision surface. Returns the polygon count emitted.
|
||||
/// </summary>
|
||||
private static int DrawPhysicsPolygons(
|
||||
DebugLineRenderer lines,
|
||||
in ShadowEntry shadow,
|
||||
FlatGfxObjCollisionAsset? asset,
|
||||
Vector3 centre,
|
||||
float radiusSquared,
|
||||
LineBudget budget)
|
||||
{
|
||||
FlatPhysicsBsp? bsp = asset?.PhysicsBsp;
|
||||
if (bsp is not { RootIndex: >= 0 } || bsp.Nodes.Length == 0)
|
||||
return 0;
|
||||
|
||||
FlatPolygonTable table = bsp.PolygonTable;
|
||||
ImmutableArray<Vector3> vertices = table.Vertices;
|
||||
int emitted = 0;
|
||||
|
||||
foreach (FlatPhysicsBspNode node in bsp.Nodes)
|
||||
{
|
||||
FlatIndexRange indices = node.PolygonIndexRange;
|
||||
for (int i = indices.Start; i < indices.EndExclusive; i++)
|
||||
{
|
||||
if (emitted >= MaxPolygonsPerObject || budget.Exhausted)
|
||||
return emitted;
|
||||
|
||||
int polygonIndex = bsp.PolygonIndexStream[i];
|
||||
if ((uint)polygonIndex >= (uint)table.Polygons.Length) continue;
|
||||
|
||||
FlatIndexRange span = table.Polygons[polygonIndex].VertexRange;
|
||||
if (span.Count < 2) continue;
|
||||
|
||||
Vector3 first = ToWorld(vertices[span.Start], in shadow);
|
||||
// Per-polygon distance rejection, AFTER the world transform:
|
||||
// a big object admitted by the object-level window still only
|
||||
// needs the faces near the player drawn.
|
||||
if (Vector3.DistanceSquared(first, centre) > radiusSquared) continue;
|
||||
|
||||
Vector3 previous = first;
|
||||
for (int v = span.Start + 1; v < span.EndExclusive; v++)
|
||||
{
|
||||
Vector3 current = ToWorld(vertices[v], in shadow);
|
||||
if (!budget.TryAdd()) return emitted;
|
||||
lines.AddLine(previous, current, PhysicsColor);
|
||||
previous = current;
|
||||
}
|
||||
|
||||
if (span.Count > 2)
|
||||
{
|
||||
if (!budget.TryAdd()) return emitted;
|
||||
lines.AddLine(previous, first, PhysicsColor);
|
||||
}
|
||||
|
||||
emitted++;
|
||||
}
|
||||
}
|
||||
|
||||
return emitted;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The visual mesh box, placed with the SAME transform as the physics
|
||||
/// polygons above. It is drawn as the object's own rotated box (eight
|
||||
/// transformed corners, twelve edges) rather than as a world-axis-aligned
|
||||
/// box, so a rotated object's magenta lines still bound its actual visual.
|
||||
/// </summary>
|
||||
private static void DrawVisualBounds(
|
||||
DebugLineRenderer lines,
|
||||
in ShadowEntry shadow,
|
||||
GfxObjVisualBounds? visual,
|
||||
LineBudget budget)
|
||||
{
|
||||
if (visual is null || budget.Exhausted) return;
|
||||
|
||||
Vector3 min = visual.Min;
|
||||
Vector3 max = visual.Max;
|
||||
Span<Vector3> corners =
|
||||
[
|
||||
ToWorld(new Vector3(min.X, min.Y, min.Z), in shadow),
|
||||
ToWorld(new Vector3(max.X, min.Y, min.Z), in shadow),
|
||||
ToWorld(new Vector3(max.X, max.Y, min.Z), in shadow),
|
||||
ToWorld(new Vector3(min.X, max.Y, min.Z), in shadow),
|
||||
ToWorld(new Vector3(min.X, min.Y, max.Z), in shadow),
|
||||
ToWorld(new Vector3(max.X, min.Y, max.Z), in shadow),
|
||||
ToWorld(new Vector3(max.X, max.Y, max.Z), in shadow),
|
||||
ToWorld(new Vector3(min.X, max.Y, max.Z), in shadow),
|
||||
];
|
||||
|
||||
ReadOnlySpan<int> edges =
|
||||
[
|
||||
0, 1, 1, 2, 2, 3, 3, 0,
|
||||
4, 5, 5, 6, 6, 7, 7, 4,
|
||||
0, 4, 1, 5, 2, 6, 3, 7,
|
||||
];
|
||||
|
||||
for (int e = 0; e < edges.Length; e += 2)
|
||||
{
|
||||
if (!budget.TryAdd()) return;
|
||||
lines.AddLine(corners[edges[e]], corners[edges[e + 1]], VisualColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The registered broadphase shape — what the pre-#337 overlay showed, and
|
||||
/// what the collision system's reach filter measures against. Kept so this
|
||||
/// overlay is a superset of the one it replaces.
|
||||
/// </summary>
|
||||
private static void DrawBroadphaseProxy(
|
||||
DebugLineRenderer lines,
|
||||
in ShadowEntry shadow,
|
||||
LineBudget budget)
|
||||
{
|
||||
// AddCylinder emits a fixed 36 lines. Reserve them together so a
|
||||
// partial ring cannot be drawn.
|
||||
if (!budget.TryAdd(36)) return;
|
||||
|
||||
if (shadow.CollisionType == ShadowCollisionType.Cylinder)
|
||||
{
|
||||
float height = shadow.CylHeight > 0f ? shadow.CylHeight : shadow.Radius * 2f;
|
||||
lines.AddCylinder(shadow.Position, shadow.Radius, height, BroadphaseColor);
|
||||
return;
|
||||
}
|
||||
|
||||
lines.AddCylinder(
|
||||
shadow.Position - new Vector3(0f, 0f, shadow.Radius),
|
||||
shadow.Radius,
|
||||
shadow.Radius * 2f,
|
||||
BroadphaseColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The terrain surface under the player, sampled through the physics
|
||||
/// engine's own height resolver — the same numbers the resolver grounds
|
||||
/// against, not a re-derivation. Drawn as a grid rather than as the single
|
||||
/// containing triangle so the slope around the player reads at a glance.
|
||||
/// </summary>
|
||||
private void DrawTerrainGrid(DebugLineRenderer lines, Vector3 centre, LineBudget budget)
|
||||
{
|
||||
int steps = (int)(TerrainGridHalfWidth * 2f / TerrainGridStep);
|
||||
float originX = centre.X - TerrainGridHalfWidth;
|
||||
float originY = centre.Y - TerrainGridHalfWidth;
|
||||
|
||||
for (int ix = 0; ix <= steps; ix++)
|
||||
{
|
||||
for (int iy = 0; iy <= steps; iy++)
|
||||
{
|
||||
float x = originX + ix * TerrainGridStep;
|
||||
float y = originY + iy * TerrainGridStep;
|
||||
float? z = _physics.SampleTerrainZ(x, y);
|
||||
if (z is null) continue;
|
||||
|
||||
var here = new Vector3(x, y, z.Value);
|
||||
|
||||
if (ix < steps)
|
||||
{
|
||||
float nx = x + TerrainGridStep;
|
||||
if (_physics.SampleTerrainZ(nx, y) is { } nz)
|
||||
{
|
||||
if (!budget.TryAdd()) return;
|
||||
lines.AddLine(here, new Vector3(nx, y, nz), TerrainColor);
|
||||
}
|
||||
}
|
||||
|
||||
if (iy < steps)
|
||||
{
|
||||
float ny = y + TerrainGridStep;
|
||||
if (_physics.SampleTerrainZ(x, ny) is { } nz2)
|
||||
{
|
||||
if (!budget.TryAdd()) return;
|
||||
lines.AddLine(here, new Vector3(x, ny, nz2), TerrainColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The one placement formula, matching the <c>[resolve-bldg]</c> probe's
|
||||
/// world transform for a shadow part
|
||||
/// (<c>TransitionTypes.FindObjCollisionsInCell</c>): scale in the part's
|
||||
/// own frame, then rotate, then translate to the registered position.
|
||||
/// </summary>
|
||||
private static Vector3 ToWorld(Vector3 local, in ShadowEntry shadow)
|
||||
=> shadow.Position + Vector3.Transform(local * shadow.Scale, shadow.Rotation);
|
||||
|
||||
/// <summary>
|
||||
/// Mutable line counter shared across the draw. A class rather than a
|
||||
/// struct so the per-shape helpers can be static and still share it
|
||||
/// without ref-plumbing through every signature.
|
||||
/// </summary>
|
||||
private sealed class LineBudget(int limit)
|
||||
{
|
||||
public int Used { get; private set; }
|
||||
|
||||
public bool Capped { get; private set; }
|
||||
|
||||
public bool Exhausted => Used >= limit;
|
||||
|
||||
public bool TryAdd(int count = 1)
|
||||
{
|
||||
if (Used + count > limit)
|
||||
{
|
||||
Capped = true;
|
||||
return false;
|
||||
}
|
||||
Used += count;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>What one <see cref="CollisionMeshWireframe.Draw"/> emitted.
|
||||
/// <paramref name="ObjectsWithoutPhysicsGeometry"/> is the interesting one: a
|
||||
/// non-zero count means objects near the player carry no reachable collision
|
||||
/// polygons at all.</summary>
|
||||
internal readonly record struct CollisionMeshWireframeStats(
|
||||
int ObjectsConsidered,
|
||||
int PolygonsDrawn,
|
||||
int ObjectsWithoutPhysicsGeometry,
|
||||
int LinesDrawn,
|
||||
bool Capped);
|
||||
|
|
@ -64,6 +64,10 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
private readonly DebugVmRenderFactsPublisher _debugVm;
|
||||
private readonly bool _debugVmConsumerActive;
|
||||
private int _debugDrawLogCount;
|
||||
// #337 (TEMPORARY): built on first use so the ordinary overlay path and
|
||||
// every headless/no-window host pay nothing for it.
|
||||
private CollisionMeshWireframe? _meshWireframe;
|
||||
private CollisionMeshWireframeStats _lastMeshStats = new(-1, -1, -1, -1, false);
|
||||
|
||||
public WorldSceneDiagnosticsController(
|
||||
WorldRenderDiagnostics diagnostics,
|
||||
|
|
@ -215,6 +219,18 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
if (!_state.CollisionWireframesVisible || _lines is null)
|
||||
return;
|
||||
|
||||
// #337 (2026-08-06 — TEMPORARY): ACDREAM_WIRE_MESH=1 replaces the
|
||||
// broadphase-proxy overlay below with the objects' real physics-BSP
|
||||
// polygon edges beside their visual mesh boxes and the terrain surface
|
||||
// — see CollisionMeshWireframe for why the proxy cannot answer the
|
||||
// question this mode exists for. Default off; F2 keeps its old
|
||||
// behaviour otherwise.
|
||||
if (RenderingDiagnostics.CollisionMeshWireframeEnabled)
|
||||
{
|
||||
DrawCollisionMesh(in camera);
|
||||
return;
|
||||
}
|
||||
|
||||
_lines.Begin();
|
||||
int drawn = 0;
|
||||
foreach (ShadowEntry shadow in _physics.ShadowObjects.AllEntriesForDebug())
|
||||
|
|
@ -255,6 +271,50 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
|||
_lines.Flush(camera.Camera.View, camera.Projection);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #337 (2026-08-06 — TEMPORARY, strip with the probe family). Centres on
|
||||
/// the player when there is one, else on the camera, so the fly-camera
|
||||
/// mode can inspect geometry too.
|
||||
/// </summary>
|
||||
private void DrawCollisionMesh(in WorldCameraFrame camera)
|
||||
{
|
||||
Vector3 centre = _mode.IsPlayerMode && _player.Controller is { } controller
|
||||
? controller.Position
|
||||
: camera.Position;
|
||||
|
||||
_meshWireframe ??= new CollisionMeshWireframe(_physics);
|
||||
|
||||
_lines!.Begin();
|
||||
CollisionMeshWireframeStats stats = _meshWireframe.Draw(_lines, centre);
|
||||
|
||||
if (_mode.IsPlayerMode && _player.Controller is { } player)
|
||||
{
|
||||
_lines.AddCylinder(
|
||||
player.Position,
|
||||
DebugVmRenderFactsPublisher.PlayerCollisionRadius,
|
||||
1.8f,
|
||||
new Vector3(1f, 0f, 0f));
|
||||
}
|
||||
|
||||
_lines.Flush(camera.Camera.View, camera.Projection);
|
||||
|
||||
// A capped frame is showing PARTIAL geometry, which would otherwise be
|
||||
// indistinguishable from an object that has none — exactly the
|
||||
// misreading this overlay exists to prevent. Say so, throttled, rather
|
||||
// than letting the picture lie.
|
||||
if (stats != _lastMeshStats)
|
||||
{
|
||||
_lastMeshStats = stats;
|
||||
Console.WriteLine(string.Format(
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
"[wire-mesh] centre=({0:F2},{1:F2},{2:F2}) objects={3} polys={4} " +
|
||||
"noPhysicsGeometry={5} lines={6} capped={7}",
|
||||
centre.X, centre.Y, centre.Z,
|
||||
stats.ObjectsConsidered, stats.PolygonsDrawn,
|
||||
stats.ObjectsWithoutPhysicsGeometry, stats.LinesDrawn, stats.Capped));
|
||||
}
|
||||
}
|
||||
|
||||
private void LogNearbyCollisionObjects(Vector3 playerPosition, int drawn)
|
||||
{
|
||||
if (_debugDrawLogCount >= 5)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue