acdream/src/AcDream.Core/Selection/WorldPicker.cs
Erik 146a963aeb feat(selection): port retail polygon picking and vivid marker
Replace the projected Setup-sphere rectangle and independent physics-wall ray with retail's render-coupled picker: only visible server-object parts participate, each exact drawing sphere broad-phases the camera-eye ray, and first-in-DAT-order visual polygon hits globally outrank sphere fallbacks.

Replace the devtools-only procedural triangles with the retained gameplay VividTargetIndicator using retail client-enum surfaces 1..4, radar-blip colorization, Setup selection-sphere framing, and the exact eight-pixel viewport clamp.

Release build succeeds with zero warnings and all 5,886 tests pass with five intentional skips.

Co-authored-by: OpenAI Codex <codex@openai.com>
2026-07-17 21:32:51 +02:00

54 lines
2 KiB
C#

using System.Numerics;
namespace AcDream.Core.Selection;
/// <summary>
/// Pure mouse-pixel to world-ray conversion shared by retail selection paths.
/// </summary>
public static class WorldPicker
{
/// <summary>
/// Unprojects a pixel coordinate to a world-space ray using System.Numerics'
/// row-vector convention (<c>view * projection</c>).
/// </summary>
/// <returns>
/// The camera viewpoint and normalized direction, or two zero vectors
/// when the camera transform is singular.
/// </returns>
public static (Vector3 Origin, Vector3 Direction) BuildRay(
float mouseX,
float mouseY,
float viewportW,
float viewportH,
Matrix4x4 view,
Matrix4x4 projection)
{
float ndcX = (2f * mouseX) / viewportW - 1f;
float ndcY = 1f - (2f * mouseY) / viewportH;
Matrix4x4 vp = view * projection;
if (!Matrix4x4.Invert(vp, out Matrix4x4 invVp)
|| !Matrix4x4.Invert(view, out Matrix4x4 invView))
return (Vector3.Zero, Vector3.Zero);
Vector4 nearClip = new(ndcX, ndcY, -1f, 1f);
Vector4 farClip = new(ndcX, ndcY, 1f, 1f);
Vector4 near = Vector4.Transform(nearClip, invVp);
Vector4 far = Vector4.Transform(farClip, invVp);
if (near.W == 0f || far.W == 0f)
return (Vector3.Zero, Vector3.Zero);
Vector3 nearWorld = new Vector3(near.X, near.Y, near.Z) / near.W;
Vector3 farWorld = new Vector3(far.X, far.Y, far.Z) / far.W;
Vector3 direction = farWorld - nearWorld;
if (direction.LengthSquared() < 1e-10f)
return (Vector3.Zero, Vector3.Zero);
// Render::pick_ray @ 0x0054B610 stores a direction through the pixel;
// GfxObjUnderSelectionRay @ 0x0054C740 pairs it with Render::viewpoint,
// not the near-plane point. Inverse-view origin is that viewpoint in
// System.Numerics' row-vector convention.
Vector3 viewpoint = Vector3.Transform(Vector3.Zero, invView);
return (viewpoint, Vector3.Normalize(direction));
}
}