refactor(render): SweepEye returns (Eye, ViewerCellId) — surface the swept viewer cell
The camera spring-arm sweep already resolves the collided eye's cell (ResolveResult.CellId = sp.CurCellId = retail viewer_cell = sphere_path.curr_cell, update_viewer pc:92871). Return it from SweepEye so the render can root on the viewer cell (Phase W single-viewpoint V1, Task 1). Pure plumbing — behavior unchanged; callers extract .Eye. 174 App tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b7375c6563
commit
832001d289
6 changed files with 28 additions and 14 deletions
|
|
@ -2,9 +2,17 @@ using System.Numerics;
|
||||||
|
|
||||||
namespace AcDream.App.Rendering;
|
namespace AcDream.App.Rendering;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Result of a camera spring-arm sweep: the collided eye position AND the cell the swept
|
||||||
|
/// viewer-sphere ended in (retail <c>viewer_cell = sphere_path.curr_cell</c>, update_viewer
|
||||||
|
/// pc:92871). The cell is graph-tracked by the transition — no AABB, no grace frames — so it
|
||||||
|
/// is the robust per-frame "which cell is the camera in?" answer that roots the render.
|
||||||
|
/// </summary>
|
||||||
|
public readonly record struct CameraSweepResult(Vector3 Eye, uint ViewerCellId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sweeps a small sphere from the camera pivot (player head) toward the
|
/// Sweeps a small sphere from the camera pivot (player head) toward the
|
||||||
/// desired eye and returns the stopped (non-penetrating) eye. The seam that
|
/// desired eye and returns the stopped (non-penetrating) eye + its cell. The seam that
|
||||||
/// lets <see cref="RetailChaseCamera"/> collide its eye without depending on
|
/// lets <see cref="RetailChaseCamera"/> collide its eye without depending on
|
||||||
/// the physics engine directly (and stay unit-testable with a fake).
|
/// the physics engine directly (and stay unit-testable with a fake).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -13,8 +21,9 @@ public interface ICameraCollisionProbe
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Roll a collision sphere from <paramref name="pivot"/> to
|
/// Roll a collision sphere from <paramref name="pivot"/> to
|
||||||
/// <paramref name="desiredEye"/>; return the position it reaches without
|
/// <paramref name="desiredEye"/>; return the position it reaches without
|
||||||
/// penetrating geometry. Returns <paramref name="desiredEye"/> unchanged
|
/// penetrating geometry AND the cell it ended in. Returns
|
||||||
|
/// <paramref name="desiredEye"/> + <paramref name="cellId"/> unchanged
|
||||||
/// when nothing blocks the path or when <paramref name="cellId"/> is 0.
|
/// when nothing blocks the path or when <paramref name="cellId"/> is 0.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId);
|
CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,10 @@ public sealed class PhysicsCameraCollisionProbe : ICameraCollisionProbe
|
||||||
|
|
||||||
public PhysicsCameraCollisionProbe(PhysicsEngine physics) => _physics = physics;
|
public PhysicsCameraCollisionProbe(PhysicsEngine physics) => _physics = physics;
|
||||||
|
|
||||||
public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
||||||
{
|
{
|
||||||
// No starting cell → nothing to sweep against; keep the desired eye.
|
// No starting cell → nothing to sweep against; keep the desired eye + cell.
|
||||||
if (cellId == 0) return desiredEye;
|
if (cellId == 0) return new CameraSweepResult(desiredEye, cellId);
|
||||||
|
|
||||||
// SpherePath.InitPath puts sphere0's center at pathPos + (0,0,radius)
|
// SpherePath.InitPath puts sphere0's center at pathPos + (0,0,radius)
|
||||||
// (the player foot-capsule convention). Retail's viewer_sphere center is
|
// (the player foot-capsule convention). Retail's viewer_sphere center is
|
||||||
|
|
@ -80,7 +80,10 @@ public sealed class PhysicsCameraCollisionProbe : ICameraCollisionProbe
|
||||||
$"collNormValid={r.CollisionNormalValid}");
|
$"collNormValid={r.CollisionNormalValid}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return eye;
|
// Phase W single-viewpoint V1 (2026-06-03): surface the swept cell (r.CellId =
|
||||||
|
// sp.CurCellId) as the viewer cell — retail viewer_cell = sphere_path.curr_cell
|
||||||
|
// (update_viewer pc:92871). Graph-tracked, no AABB/grace → the U.4c flap source is gone.
|
||||||
|
return new CameraSweepResult(eye, r.CellId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Eye/pivot point → InitPath path point (subtract the sphere-center offset).</summary>
|
/// <summary>Eye/pivot point → InitPath path point (subtract the sphere-center offset).</summary>
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ public sealed class RetailChaseCamera : ICamera
|
||||||
// leave _dampedEye as the clean, uncollided sought position.
|
// leave _dampedEye as the clean, uncollided sought position.
|
||||||
Vector3 publishedEye = _dampedEye;
|
Vector3 publishedEye = _dampedEye;
|
||||||
if (CameraDiagnostics.CollideCamera && CollisionProbe is not null)
|
if (CameraDiagnostics.CollideCamera && CollisionProbe is not null)
|
||||||
publishedEye = CollisionProbe.SweepEye(pivotWorld, _dampedEye, cellId, selfEntityId);
|
publishedEye = CollisionProbe.SweepEye(pivotWorld, _dampedEye, cellId, selfEntityId).Eye;
|
||||||
|
|
||||||
// 6. Publish renderer surface (from the collided eye; rotation stays the
|
// 6. Publish renderer surface (from the collided eye; rotation stays the
|
||||||
// smoothly-damped look direction toward the pivot).
|
// smoothly-damped look direction toward the pivot).
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ public class CameraCollisionIndoorTests
|
||||||
pivot: PivotWorld,
|
pivot: PivotWorld,
|
||||||
desiredEye: DesiredEye,
|
desiredEye: DesiredEye,
|
||||||
cellId: IndoorCellId,
|
cellId: IndoorCellId,
|
||||||
selfEntityId: 0u);
|
selfEntityId: 0u).Eye;
|
||||||
|
|
||||||
// The eye should be stopped before the exterior wall at Y=4.0.
|
// The eye should be stopped before the exterior wall at Y=4.0.
|
||||||
// Expected stopped eye Y ≈ 4.0 - ViewerSphereRadius = 3.7.
|
// Expected stopped eye Y ≈ 4.0 - ViewerSphereRadius = 3.7.
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ public class PhysicsCameraCollisionProbeTests
|
||||||
|
|
||||||
var result = probe.SweepEye(pivot, eye, cellId: 0, selfEntityId: 0);
|
var result = probe.SweepEye(pivot, eye, cellId: 0, selfEntityId: 0);
|
||||||
|
|
||||||
Assert.Equal(eye, result);
|
Assert.Equal(eye, result.Eye);
|
||||||
|
Assert.Equal(0u, result.ViewerCellId); // cellId==0 → returned unchanged
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -452,10 +452,11 @@ public class RetailChaseCameraTests
|
||||||
{
|
{
|
||||||
public int Calls;
|
public int Calls;
|
||||||
public Vector3 ReturnEye;
|
public Vector3 ReturnEye;
|
||||||
public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
public uint ReturnCell;
|
||||||
|
public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
||||||
{
|
{
|
||||||
Calls++;
|
Calls++;
|
||||||
return ReturnEye;
|
return new CameraSweepResult(ReturnEye, ReturnCell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -538,10 +539,10 @@ public class RetailChaseCameraTests
|
||||||
{
|
{
|
||||||
public int Calls;
|
public int Calls;
|
||||||
public Vector3 ClampEye;
|
public Vector3 ClampEye;
|
||||||
public Vector3 SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
public CameraSweepResult SweepEye(Vector3 pivot, Vector3 desiredEye, uint cellId, uint selfEntityId)
|
||||||
{
|
{
|
||||||
Calls++;
|
Calls++;
|
||||||
return Calls == 1 ? ClampEye : desiredEye;
|
return new CameraSweepResult(Calls == 1 ? ClampEye : desiredEye, cellId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue