fix(B.4b): WorldPicker.Pick — handle inside-sphere origin + document normalize contract
Code review flagged two latent correctness bugs in Pick: 1. The single t = -b - sqrt(d) intersection skipped entities whose 5m bounding sphere contained the ray origin. Realistic at point-blank range — if the player stands within ~5m of a door, the near-plane sits inside the door's bounding sphere and the door becomes unpickable. Standard fix: when t_near < 0 fall through to t_far = -b + sqrt(d) (the sphere exit point). 2. The discriminant formula assumes |direction| = 1. BuildRay currently normalizes so the assumption holds at the wire, but the contract wasn't documented. Added an explicit <param name="direction"> note. New test Pick_RayOriginInsideEntitySphere_StillReturnsServerGuid covers the inside-sphere case. Suite: 9/9 WorldPicker tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
221b64186d
commit
5821bdc9ea
2 changed files with 31 additions and 2 deletions
|
|
@ -60,6 +60,13 @@ public static class WorldPicker
|
|||
/// using a fixed 5m sphere radius. Returns the <see cref="WorldEntity.ServerGuid"/>
|
||||
/// of the closest hit within <paramref name="maxDistance"/>, or null on miss.
|
||||
/// </summary>
|
||||
/// <param name="direction">
|
||||
/// World-space ray direction. <b>Must be normalized</b> — the geometric
|
||||
/// ray-sphere formula simplifies <c>a = dot(direction, direction)</c> to
|
||||
/// <c>1</c>; non-unit input produces an undocumented <c>t</c>-scale that
|
||||
/// makes <c>maxDistance</c> compare against ray-parameter units instead
|
||||
/// of world meters.
|
||||
/// </param>
|
||||
/// <remarks>
|
||||
/// Entities with <c>ServerGuid == 0</c> (atlas-tier scenery, dat-hydrated
|
||||
/// statics) are skipped — they have no server-side identity and can't be
|
||||
|
|
@ -94,8 +101,13 @@ public static class WorldPicker
|
|||
float d = b * b - c;
|
||||
if (d < 0f) continue;
|
||||
|
||||
float t = -b - MathF.Sqrt(d);
|
||||
if (t < 0f) continue; // ray points away or origin inside
|
||||
// Two intersection roots: t_near = -b - sqrt(d), t_far = -b + sqrt(d).
|
||||
// If t_near < 0 the ray origin is INSIDE the sphere; fall through
|
||||
// to t_far so the entity is still pickable at point-blank range.
|
||||
float sqrtD = MathF.Sqrt(d);
|
||||
float t = -b - sqrtD;
|
||||
if (t < 0f) t = -b + sqrtD; // origin inside sphere -> use far exit
|
||||
if (t < 0f) continue; // both roots negative -> sphere entirely behind ray
|
||||
if (t >= maxDistance) continue;
|
||||
if (t < bestT)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -149,4 +149,21 @@ public class WorldPickerTests
|
|||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Pick_RayOriginInsideEntitySphere_StillReturnsServerGuid()
|
||||
{
|
||||
// Player ~3m from a door -> camera near-plane sits INSIDE the door's
|
||||
// 5m bounding sphere. Naive t_near < 0 guard would skip; correct
|
||||
// behavior is to fall through to t_far (the sphere exit point).
|
||||
var entity = MakeEntity(0xABCDu, new Vector3(0, 0, -3));
|
||||
|
||||
var result = WorldPicker.Pick(
|
||||
origin: Vector3.Zero,
|
||||
direction: -Vector3.UnitZ,
|
||||
candidates: new[] { entity },
|
||||
skipServerGuid: 0u);
|
||||
|
||||
Assert.Equal(0xABCDu, result);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue