From 423d8b8f5208d3d541af8a3f2c4670179ff63fc6 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Mon, 9 Nov 2026 19:56:28 +0600 Subject: [PATCH] Fixing script --- src/Location.cpp | 63 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/src/Location.cpp b/src/Location.cpp index b798204..adc775d 100644 --- a/src/Location.cpp +++ b/src/Location.cpp @@ -890,14 +890,16 @@ namespace FRG } Character* Location::raycastNpcs(const Eigen::Vector3f& rayOrigin, const Eigen::Vector3f& rayDir, float maxDistance) { - // Every NPC is treated as a vertical cylinder: radius 1.0m, height 1.85m, + // Every NPC is treated as a vertical cylinder: radius 0.25m, height 1.6m, // base at npc->position (the model's foot). Intersection = circle hit in the // XZ plane, then clip the entry/exit t against the [yFoot, yFoot+height] slab. - static constexpr float NPC_CLICK_RADIUS = 1.0f; - static constexpr float NPC_CLICK_HEIGHT = 1.85f; + static constexpr float NPC_CLICK_RADIUS = 0.25f; + static constexpr float NPC_CLICK_HEIGHT = 1.6f; + static constexpr float NPC_PICK_EPSILON = 1e-6f; Character* closestNpc = nullptr; - float closestDist = maxDistance; + float closestAngularDistanceSq = (std::numeric_limits::max)(); + float closestEntryDistance = maxDistance; //logger() << "[RAYCAST_NPC] Starting raycast with " << npcs.size() << " npcs" << std::endl; @@ -908,8 +910,11 @@ namespace FRG continue; } if (!npcState->enabled) continue; - const float dx = rayOrigin.x() - npcState->position.x(); - const float dz = rayOrigin.z() - npcState->position.z(); + // Use the presentation position so the clickable volume follows the + // rendered character while it is being interpolated between ticks. + const Eigen::Vector3f npcPosition = npc->renderPosition(); + const float dx = rayOrigin.x() - npcPosition.x(); + const float dz = rayOrigin.z() - npcPosition.z(); const float a = rayDir.x() * rayDir.x() + rayDir.z() * rayDir.z(); if (a < 1e-6f) continue; // purely-vertical ray; not a real click case @@ -922,28 +927,60 @@ namespace FRG const float tNear = (-b - sqrtDisc) / (2.0f * a); const float tFar = (-b + sqrtDisc) / (2.0f * a); - float entryT = max(tNear, 0.1f); + float entryT = (std::max)(tNear, 0.1f); float exitT = tFar; // Clip against the cylinder's vertical extent. - const float yMin = npcState->position.y(); + const float yMin = npcPosition.y(); const float yMax = yMin + NPC_CLICK_HEIGHT; if (std::abs(rayDir.y()) > 1e-6f) { const float tYa = (yMin - rayOrigin.y()) / rayDir.y(); const float tYb = (yMax - rayOrigin.y()) / rayDir.y(); - entryT = max(entryT, min(tYa, tYb)); - exitT = min(exitT, max(tYa, tYb)); + entryT = (std::max)(entryT, (std::min)(tYa, tYb)); + exitT = (std::min)(exitT, (std::max)(tYa, tYb)); } else if (rayOrigin.y() < yMin || rayOrigin.y() > yMax) { continue; // horizontal ray passing above or below the cylinder } if (entryT > exitT) continue; + if (entryT > maxDistance) continue; - //logger() << "[RAYCAST_NPC] " << npc->entityId() << " hit at t=" << entryT << std::endl; + // All candidates above are under the cursor. Resolve overlaps by + // projected cursor proximity rather than by whichever cylinder is + // entered first along the ray. The normalized perpendicular distance + // is an angular/screen-space approximation that remains comparable + // for NPCs at different depths. + const Eigen::Vector3f bodyCenter = + npcPosition + Eigen::Vector3f(0.0f, NPC_CLICK_HEIGHT * 0.5f, 0.0f); + const Eigen::Vector3f toBody = bodyCenter - rayOrigin; + const float bodyDepth = toBody.dot(rayDir); + if (bodyDepth <= NPC_PICK_EPSILON) continue; + const float perpendicularDistanceSq = + (toBody - rayDir * bodyDepth).squaredNorm(); + const float angularDistanceSq = + perpendicularDistanceSq / + (std::max)(bodyDepth * bodyDepth, NPC_PICK_EPSILON); - if (entryT < closestDist) { - closestDist = entryT; + const bool betterAngularMatch = + angularDistanceSq < + closestAngularDistanceSq - NPC_PICK_EPSILON; + const bool equalAngularMatch = + std::abs(angularDistanceSq - closestAngularDistanceSq) <= + NPC_PICK_EPSILON; + const bool betterDepthTieBreak = + equalAngularMatch && + (entryT < closestEntryDistance - NPC_PICK_EPSILON); + const bool equalDepthTieBreak = + equalAngularMatch && + std::abs(entryT - closestEntryDistance) <= NPC_PICK_EPSILON; + const bool betterStableTieBreak = + equalDepthTieBreak && + (!closestNpc || npc->entityId() < closestNpc->entityId()); + + if (betterAngularMatch || betterDepthTieBreak || betterStableTieBreak) { + closestEntryDistance = entryT; + closestAngularDistanceSq = angularDistanceSq; closestNpc = npc.get(); } }