fix(picker): lift sphere centre to mid-body so chest/head clicks hit
User reported intermittent selection — 'sometimes can be selected,
sometimes not'. Cause: WorldEntity.Position is at FEET level (Z=ground
for standing humanoids), so a 0.7m sphere centred there only covered
the lower legs. Clicks on chest (Z≈1.2m) or head (Z≈1.7m) missed
because the closest-approach distance from the cursor ray to the
feet-centered sphere exceeded the radius.
Fix:
- Sphere centre now defaults to position.Z + 0.9 m (humanoid
mid-body). New optional verticalOffsetForGuid callback overrides
per entity.
- Default radius bumped 0.7 → 1.0 m to match the new sphere
placement (1.0 m at 0.9 m height covers a 1.8 m humanoid from
shin to top-of-head).
GameWindow.PickAndStoreSelection wires the callback:
- Creatures (ItemType.Creature flag): vz = 0.9 m (humanoid centre)
- Large flat objects (BF_DOOR | BF_LIFESTONE | BF_PORTAL |
BF_CORPSE): vz = 1.0 m + radius 2.0 m (mid-door/lifestone)
- Everything else (ground items): vz = 0.2 m (just above feet)
Existing 9 WorldPicker tests still pass — their head-on ray geometry
doesn't depend on the vertical offset.
This commit is contained in:
parent
23cb1e9636
commit
1a0656a3ce
2 changed files with 51 additions and 8 deletions
|
|
@ -9009,7 +9009,28 @@ public sealed class GameWindow : IDisposable
|
||||||
const uint LargeFlatMask = 0x1000u | 0x4000u | 0x40000u | 0x2000u;
|
const uint LargeFlatMask = 0x1000u | 0x4000u | 0x40000u | 0x2000u;
|
||||||
if ((odf & LargeFlatMask) != 0) return 2.0f;
|
if ((odf & LargeFlatMask) != 0) return 2.0f;
|
||||||
}
|
}
|
||||||
return 0.7f;
|
// 1.0 m sphere centred at chest height (see
|
||||||
|
// verticalOffsetForGuid) covers a 1.8 m humanoid from
|
||||||
|
// shin to crown without overlapping neighbours.
|
||||||
|
return 1.0f;
|
||||||
|
},
|
||||||
|
verticalOffsetForGuid: g =>
|
||||||
|
{
|
||||||
|
// Lift the pick sphere to mid-body so chest/head clicks
|
||||||
|
// hit instead of missing past the top of a feet-anchored
|
||||||
|
// sphere. WorldEntity.Position is at feet level
|
||||||
|
// (Z=ground); humanoids reach ~1.8 m, items sit close
|
||||||
|
// to the ground (~0.2 m above their feet).
|
||||||
|
if (_liveEntityInfoByGuid.TryGetValue(g, out var info)
|
||||||
|
&& (info.ItemType & AcDream.Core.Items.ItemType.Creature) != 0)
|
||||||
|
return 0.9f; // humanoid mid-body
|
||||||
|
if (_lastSpawnByGuid.TryGetValue(g, out var s)
|
||||||
|
&& s.ObjectDescriptionFlags is { } odf)
|
||||||
|
{
|
||||||
|
const uint LargeFlatMask = 0x1000u | 0x4000u | 0x40000u | 0x2000u;
|
||||||
|
if ((odf & LargeFlatMask) != 0) return 1.0f; // mid-door
|
||||||
|
}
|
||||||
|
return 0.2f; // small ground item — sphere just above feet
|
||||||
});
|
});
|
||||||
|
|
||||||
if (picked is uint guid)
|
if (picked is uint guid)
|
||||||
|
|
|
||||||
|
|
@ -90,9 +90,11 @@ public static class WorldPicker
|
||||||
IEnumerable<WorldEntity> candidates,
|
IEnumerable<WorldEntity> candidates,
|
||||||
uint skipServerGuid,
|
uint skipServerGuid,
|
||||||
float maxDistance = 50f,
|
float maxDistance = 50f,
|
||||||
Func<uint, float>? radiusForGuid = null)
|
Func<uint, float>? radiusForGuid = null,
|
||||||
|
Func<uint, float>? verticalOffsetForGuid = null)
|
||||||
{
|
{
|
||||||
const float DefaultRadius = 0.7f;
|
const float DefaultRadius = 1.0f;
|
||||||
|
const float DefaultVerticalOffset = 0.9f;
|
||||||
|
|
||||||
if (direction.LengthSquared() < 1e-10f) return null;
|
if (direction.LengthSquared() < 1e-10f) return null;
|
||||||
|
|
||||||
|
|
@ -103,18 +105,38 @@ public static class WorldPicker
|
||||||
if (entity.ServerGuid == 0u) continue;
|
if (entity.ServerGuid == 0u) continue;
|
||||||
if (entity.ServerGuid == skipServerGuid) continue;
|
if (entity.ServerGuid == skipServerGuid) continue;
|
||||||
|
|
||||||
// Per-entity radius (caller-supplied) lets large flat objects
|
// Per-entity radius + vertical offset (caller-supplied).
|
||||||
// like doors, lifestones, and portals use a bigger sphere
|
//
|
||||||
// than the 0.7 m humanoid/item default — their visible
|
// <para>
|
||||||
// surface extends well beyond their origin point.
|
// <b>Vertical offset (2026-05-15).</b> WorldEntity.Position
|
||||||
|
// is at the entity's feet (Z=ground for a humanoid). User
|
||||||
|
// clicks usually land on chest/head (Z ≈ 1–1.8 m). With the
|
||||||
|
// sphere centred at feet, a chest click is 1.2 m of vertical
|
||||||
|
// distance from sphere centre — bigger than any reasonable
|
||||||
|
// body radius — so the ray misses. Lifting the sphere
|
||||||
|
// centre to mid-body fixes this: 0.9 m default for
|
||||||
|
// humanoids, smaller for items, larger for tall objects.
|
||||||
|
// </para>
|
||||||
|
//
|
||||||
|
// <para>
|
||||||
|
// <b>Radius (2026-05-15).</b> Bumped default 0.7 → 1.0 m to
|
||||||
|
// accommodate the new vertical-offset sphere placement
|
||||||
|
// (chest-height sphere centre + 1.0 m radius covers from
|
||||||
|
// shin to top-of-head for a 1.8 m humanoid).
|
||||||
|
// </para>
|
||||||
float r = radiusForGuid?.Invoke(entity.ServerGuid) ?? DefaultRadius;
|
float r = radiusForGuid?.Invoke(entity.ServerGuid) ?? DefaultRadius;
|
||||||
float r2 = r * r;
|
float r2 = r * r;
|
||||||
|
float vz = verticalOffsetForGuid?.Invoke(entity.ServerGuid) ?? DefaultVerticalOffset;
|
||||||
|
var sphereCenter = new Vector3(
|
||||||
|
entity.Position.X,
|
||||||
|
entity.Position.Y,
|
||||||
|
entity.Position.Z + vz);
|
||||||
|
|
||||||
// Geometric ray-sphere: oc = origin - center, b = dot(oc, dir),
|
// Geometric ray-sphere: oc = origin - center, b = dot(oc, dir),
|
||||||
// c = |oc|^2 - r^2, discriminant = b^2 - c. If discriminant < 0
|
// c = |oc|^2 - r^2, discriminant = b^2 - c. If discriminant < 0
|
||||||
// the ray misses the sphere. Otherwise nearest intersection is
|
// the ray misses the sphere. Otherwise nearest intersection is
|
||||||
// t = -b - sqrt(discriminant).
|
// t = -b - sqrt(discriminant).
|
||||||
var oc = origin - entity.Position;
|
var oc = origin - sphereCenter;
|
||||||
float b = Vector3.Dot(oc, direction);
|
float b = Vector3.Dot(oc, direction);
|
||||||
float c = Vector3.Dot(oc, oc) - r2;
|
float c = Vector3.Dot(oc, oc) - r2;
|
||||||
float d = b * b - c;
|
float d = b * b - c;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue