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>
This commit is contained in:
parent
0f82a08f0a
commit
146a963aeb
26 changed files with 1302 additions and 1340 deletions
113
src/AcDream.App/Rendering/Selection/RetailSelectionScene.cs
Normal file
113
src/AcDream.App/Rendering/Selection/RetailSelectionScene.cs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering.Selection;
|
||||
|
||||
/// <summary>
|
||||
/// Render-thread owner of the last complete set of visible selectable parts.
|
||||
/// The renderer builds one frame while input queries the previously completed
|
||||
/// frame, avoiding partial visibility state during multi-slice portal drawing.
|
||||
/// </summary>
|
||||
internal sealed class RetailSelectionScene : IRetailSelectionRenderSink
|
||||
{
|
||||
private readonly RetailSelectionGeometryCache _geometry;
|
||||
private List<RetailSelectionPart> _building = new();
|
||||
private List<RetailSelectionPart> _published = new();
|
||||
private readonly HashSet<PartKey> _buildingKeys = new();
|
||||
private HashSet<uint> _buildingGuids = new();
|
||||
private HashSet<uint> _publishedGuids = new();
|
||||
private FrustumPlanes? _viewFrustum;
|
||||
|
||||
private readonly record struct PartKey(uint LocalEntityId, int PartIndex, uint GfxObjId);
|
||||
|
||||
public RetailSelectionScene(RetailSelectionGeometryCache geometry)
|
||||
=> _geometry = geometry ?? throw new ArgumentNullException(nameof(geometry));
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
_building.Clear();
|
||||
_buildingKeys.Clear();
|
||||
_buildingGuids.Clear();
|
||||
_viewFrustum = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supplies retail DrawMesh's current view-cone gate. Animated entities
|
||||
/// deliberately bypass acdream's coarse entity-AABB CPU cull, so this
|
||||
/// per-part drawing sphere check is the load-bearing retail equivalent.
|
||||
/// </summary>
|
||||
public void SetViewFrustum(FrustumPlanes viewFrustum)
|
||||
=> _viewFrustum = viewFrustum;
|
||||
|
||||
public void AddVisiblePart(
|
||||
WorldEntity entity,
|
||||
int partIndex,
|
||||
uint gfxObjId,
|
||||
Matrix4x4 partWorld)
|
||||
{
|
||||
if (entity.ServerGuid == 0u)
|
||||
return;
|
||||
|
||||
if (!_buildingKeys.Add(new PartKey(entity.Id, partIndex, gfxObjId)))
|
||||
return;
|
||||
RetailSelectionMesh? mesh = _geometry.Resolve(gfxObjId);
|
||||
if (mesh is null)
|
||||
return;
|
||||
if (_viewFrustum is not { } frustum
|
||||
|| !DrawingSphereIntersectsFrustum(mesh, partWorld, frustum))
|
||||
return;
|
||||
|
||||
_building.Add(new RetailSelectionPart(
|
||||
entity.ServerGuid,
|
||||
partIndex,
|
||||
partWorld,
|
||||
mesh));
|
||||
_buildingGuids.Add(entity.ServerGuid);
|
||||
}
|
||||
|
||||
public void CompleteFrame()
|
||||
{
|
||||
(_published, _building) = (_building, _published);
|
||||
(_publishedGuids, _buildingGuids) = (_buildingGuids, _publishedGuids);
|
||||
}
|
||||
|
||||
public uint? Pick(
|
||||
float mouseX,
|
||||
float mouseY,
|
||||
Vector2 viewport,
|
||||
Matrix4x4 view,
|
||||
Matrix4x4 projection,
|
||||
uint skipServerGuid)
|
||||
{
|
||||
if (viewport.X <= 0f || viewport.Y <= 0f)
|
||||
return null;
|
||||
var ray = WorldPicker.BuildRay(
|
||||
mouseX, mouseY, viewport.X, viewport.Y, view, projection);
|
||||
return RetailWorldPicker.Pick(
|
||||
ray.Origin, ray.Direction, _published, skipServerGuid)?.ServerGuid;
|
||||
}
|
||||
|
||||
public bool WasVisible(uint serverGuid) => _publishedGuids.Contains(serverGuid);
|
||||
|
||||
internal static bool DrawingSphereIntersectsFrustum(
|
||||
RetailSelectionMesh mesh,
|
||||
Matrix4x4 localToWorld,
|
||||
FrustumPlanes frustum)
|
||||
{
|
||||
Vector3 center = Vector3.Transform(mesh.SphereCenter, localToWorld);
|
||||
float scaleX = new Vector3(localToWorld.M11, localToWorld.M12, localToWorld.M13).Length();
|
||||
float scaleY = new Vector3(localToWorld.M21, localToWorld.M22, localToWorld.M23).Length();
|
||||
float scaleZ = new Vector3(localToWorld.M31, localToWorld.M32, localToWorld.M33).Length();
|
||||
float radius = mesh.SphereRadius * MathF.Max(scaleX, MathF.Max(scaleY, scaleZ));
|
||||
return TestPlane(frustum.Left, center, radius)
|
||||
&& TestPlane(frustum.Right, center, radius)
|
||||
&& TestPlane(frustum.Bottom, center, radius)
|
||||
&& TestPlane(frustum.Top, center, radius)
|
||||
&& TestPlane(frustum.Near, center, radius)
|
||||
&& TestPlane(frustum.Far, center, radius);
|
||||
}
|
||||
|
||||
private static bool TestPlane(Vector4 plane, Vector3 center, float radius)
|
||||
=> plane.X * center.X + plane.Y * center.Y + plane.Z * center.Z + plane.W >= -radius;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue