From 2f50dc152255f613ae3834252f19f042353c1752 Mon Sep 17 00:00:00 2001 From: vottozi Date: Wed, 4 Mar 2026 01:42:57 +0600 Subject: [PATCH] fix Lead Indicator rendering when target off-screen --- src/Space.cpp | 233 +++++++++++++++++++------------------------------- 1 file changed, 89 insertions(+), 144 deletions(-) diff --git a/src/Space.cpp b/src/Space.cpp index 9ec49f2..cd968aa 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -25,13 +25,6 @@ #include "network/LocalClient.h" #endif -// --- TEMP DEBUG SHOT LOG --- -#define DEBUG_SHOTLOG 1 -#if DEBUG_SHOTLOG -static uint64_t g_lastShotLogMs = 0; -static uint64_t g_lastAimLogMs = 0; -#endif - namespace ZL { @@ -1153,16 +1146,16 @@ namespace ZL // Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity; float shooterSpeed = std::abs(Environment::shipState.velocity); - // В нашей физике линейная скорость корабля всегда направлена по его forward (-Z). - // Когда игрок "наводится" на lead, forward (и скорость) становятся сонаправлены с выстрелом, + // В нашей физике линейная скорость корабля всегда направлена по его forward (-Z) + // Когда игрок наводится на lead indicator, forward (и скорость) становятся сонаправлены с выстрелом // поэтому эффективная скорость снаряда в мире ≈ muzzle + shipSpeed. const float effectiveProjectileSpeed = projectileSpeed + shooterSpeed; Vector3f shooterVel = Vector3f::Zero(); // скорость уже учтена в effectiveProjectileSpeed Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity; - // --- ВАЖНО: у нас remote state берется на now_ms - CLIENT_DELAY + // ВАЖНО: remote state берется на now_ms - CLIENT_DELAY // Значит shipWorld - это позиция ~0.5 сек назад. - // Для корректного lead нужно предсказать положение цели на "сейчас". + // Для корректного lead нужно предсказать положение цели на сейчас const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f; Vector3f targetPosNow = shipWorld + targetVel * clientDelaySec; @@ -1172,7 +1165,6 @@ namespace ZL // альфа круга float leadAlpha = targetMoving ? 1.0f : 0.5f; - // Vector3f leadWorld = shipWorld; Vector3f leadWorld = targetPosNow; bool haveLead = false; @@ -1203,30 +1195,60 @@ namespace ZL haveLead = true; } - // 2) проекция + // Проекция цели (для рамок/стрелки) float ndcX, ndcY, ndcZ, clipW; if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return; - // behind camera? bool behind = (clipW <= 0.0f); - // on-screen check (NDC) bool onScreen = (!behind && ndcX >= -1.0f && ndcX <= 1.0f && ndcY >= -1.0f && ndcY <= 1.0f); - // 3) расстояние float dist = (Environment::shipState.position - shipWorld).norm(); - - // time for arrow bob float t = static_cast(SDL_GetTicks64()) * 0.001f; - // 4) Настройки стиля + // Проекция Lead + float leadNdcX = 0.f, leadNdcY = 0.f, leadNdcZ = 0.f, leadClipW = 0.f; + bool leadOnScreen = false; + + if (haveLead) { + if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) { + leadOnScreen = + (leadNdcX >= -1.0f && leadNdcX <= 1.0f && + leadNdcY >= -1.0f && leadNdcY <= 1.0f); + } + } + + // Настройки HUD стилизация Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный float thickness = 2.0f; // толщина линий (px) float z = 0.0f; // 2D слой - // 5) Если цель в кадре: рисуем скобки + auto drawLeadRing2D = [&](float lx, float ly) + { + float distLead = (Environment::shipState.position - leadWorld).norm(); + float r = 30.0f / (distLead * 0.01f + 1.0f); + r = std::clamp(r, 6.0f, 18.0f); + + float thicknessPx = 2.5f; + float innerR = max(1.0f, r - thicknessPx); + float outerR = r + thicknessPx; + + Eigen::Vector4f leadColor = enemyColor; + leadColor.w() = leadAlpha; + + renderer.RenderUniform4fv("uColor", leadColor.data()); + VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor); + hudTempMesh.AssignFrom(ring); + renderer.DrawVertexRenderStruct(hudTempMesh); + + // вернуть цвет HUD обратно + renderer.RenderUniform4fv("uColor", enemyColor.data()); + }; + + + // Цель в кадре: рамки if (onScreen) { // перевод NDC -> экран (в пикселях) @@ -1257,11 +1279,11 @@ namespace ZL // рисуем 8 тонких прямоугольников (2 на угол) auto drawBar = [&](float cx, float cy, float w, float h) - { - VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; + { + VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); + hudTempMesh.AssignFrom(v); + renderer.DrawVertexRenderStruct(hudTempMesh); + }; // включаем 2D режим glDisable(GL_DEPTH_TEST); @@ -1275,40 +1297,9 @@ namespace ZL renderer.LoadIdentity(); renderer.EnableVertexAttribArray("vPosition"); + renderer.RenderUniform4fv("uColor", enemyColor.data()); - Eigen::Vector4f hudColor = enemyColor; - renderer.RenderUniform4fv("uColor", hudColor.data()); - - - if (haveLead) { - float leadNdcX, leadNdcY, leadNdcZ, leadClipW; - if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) { - if (leadNdcX >= -1 && leadNdcX <= 1 && leadNdcY >= -1 && leadNdcY <= 1) { - float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth; - float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight; - - float distLead = (Environment::shipState.position - leadWorld).norm(); - float r = 30.0f / (distLead * 0.01f + 1.0f); - r = std::clamp(r, 6.0f, 18.0f); - - float thicknessPx = 2.5f; - float innerR = max(1.0f, r - thicknessPx); - float outerR = r + thicknessPx; - Eigen::Vector4f leadColor = enemyColor; - leadColor.w() = leadAlpha; - renderer.RenderUniform4fv("uColor", leadColor.data()); - - VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor); - hudTempMesh.AssignFrom(ring); - renderer.DrawVertexRenderStruct(hudTempMesh); - - renderer.RenderUniform4fv("uColor", hudColor.data()); - } - } - } - - renderer.EnableVertexAttribArray("vPosition"); - + // рамки drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness); drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen); @@ -1321,9 +1312,14 @@ namespace ZL drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness); drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen); + // LEAD — независимо от рамок: если его точка на экране, рисуем + if (haveLead && leadOnScreen) { + float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth; + float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight; + drawLeadRing2D(lx, ly); + } + renderer.DisableVertexAttribArray("vPosition"); - - renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); @@ -1335,6 +1331,8 @@ namespace ZL return; } + + // Цель вне экрана: стрелка float dirX = ndcX; float dirY = ndcY; @@ -1377,26 +1375,26 @@ namespace ZL Vector3f right{ edgeX - px * (arrowWid * 0.5f), edgeY - py * (arrowWid * 0.5f), z }; auto drawTri = [&](const Vector3f& a, const Vector3f& b, const Vector3f& c) - { - VertexDataStruct v; - v.PositionData = { a, b, c }; - Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() }; - v.ColorData = { rgb, rgb, rgb }; - // defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL). - const Vector3f n{ 0.f, 0.f, 1.f }; - v.NormalData = { n, n, n }; - const Vector2f uv{ 0.f, 0.f }; - v.TexCoordData = { uv, uv, uv }; - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; + { + VertexDataStruct v; + v.PositionData = { a, b, c }; + Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() }; + v.ColorData = { rgb, rgb, rgb }; + // defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL). + const Vector3f n{ 0.f, 0.f, 1.f }; + v.NormalData = { n, n, n }; + const Vector2f uv{ 0.f, 0.f }; + v.TexCoordData = { uv, uv, uv }; + hudTempMesh.AssignFrom(v); + renderer.DrawVertexRenderStruct(hudTempMesh); + }; auto drawBar = [&](float cx, float cy, float w, float h) - { - VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; + { + VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); + hudTempMesh.AssignFrom(v); + renderer.DrawVertexRenderStruct(hudTempMesh); + }; glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); @@ -1407,18 +1405,30 @@ namespace ZL renderer.PushMatrix(); renderer.LoadIdentity(); + renderer.EnableVertexAttribArray("vPosition"); + renderer.RenderUniform4fv("uColor", enemyColor.data()); + + // стрелка drawTri(tip, left, right); float tailLen = 14.0f; float tailX = edgeX - dirX * 6.0f; float tailY = edgeY - dirY * 6.0f; - drawBar(tailX, tailY, max(thickness, tailLen), thickness); + // LEAD — рисуем даже когда цель вне экрана (если lead точка на экране) + if (haveLead && leadOnScreen) { + float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth; + float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight; + drawLeadRing2D(lx, ly); + } + + renderer.DisableVertexAttribArray("vPosition"); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); + // дистанция около стрелки { std::string d = std::to_string((int)dist) + "m"; float tx = edgeX + px * 18.0f; @@ -1444,7 +1454,6 @@ namespace ZL firePressed = false; if (now_ms - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { lastProjectileFireTime = now_ms; - // const float projectileSpeed = 250.0f; const float projectileSpeed = PROJECTILE_VELOCITY; this->fireProjectiles(); @@ -1455,70 +1464,6 @@ namespace ZL Eigen::Vector3f centerPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0, 0.9f - 6.0f, 5.0f }; -#if DEBUG_SHOTLOG - // лог не чаще чем раз в 250мс, чтобы не заспамить консоль - if (now_ms - g_lastShotLogMs > 250) { - g_lastShotLogMs = now_ms; - - // 1) кого мы сейчас таргетим (как в HUD) - int tid = pickTargetId(); - if (tid >= 0 && remotePlayerStates.count(tid)) { - const ClientState& st = remotePlayerStates.at(tid); - - // shooterPos ДОЛЖЕН совпадать с тем, что используется в lead (HUD) - Vector3f shooterPos = Environment::shipState.position - + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f }; - - Vector3f targetPos = st.position; - Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity; - - const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f; - Vector3f targetPosNow = targetPos + targetVel * clientDelaySec; - - float shooterSpeed = std::abs(Environment::shipState.velocity); - float effSpeed = PROJECTILE_VELOCITY + shooterSpeed; - - float dist = (shooterPos - targetPosNow).norm(); - float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f; - float tStraight = dist / max(1e-3f, effSpeed); - - // попытка solve lead - float tLead = 0.0f; - bool ok = SolveLeadInterceptTime(shooterPos, Vector3f::Zero(), targetPosNow, targetVel, effSpeed, tLead); - - // clamp как у HUD - float maxLeadTime = std::clamp(tStraight * 1.25f, 0.01f, projectileLifeSec * 0.98f); - if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) { - tLead = std::clamp(tStraight, 0.05f, maxLeadTime); - } - - Vector3f leadWorld = targetPosNow + targetVel * tLead; - - std::cout - << "\n[SHOTLOG] now_ms=" << now_ms - << " tid=" << tid - << " dist=" << dist - << " tStraight=" << tStraight - << " tLead=" << tLead - << " lifeSec=" << projectileLifeSec - << " effSpeed=" << effSpeed - << " shipSpeed=" << shooterSpeed - << "\n shooterPos=(" << shooterPos.x() << "," << shooterPos.y() << "," << shooterPos.z() << ")" - << "\n centerPos (sent)=(" << centerPos.x() << "," << centerPos.y() << "," << centerPos.z() << ")" - << "\n targetPos(raw)=(" << targetPos.x() << "," << targetPos.y() << "," << targetPos.z() << ")" - << "\n targetPosNow =(" << targetPosNow.x() << "," << targetPosNow.y() << "," << targetPosNow.z() << ")" - << "\n targetVel=(" << targetVel.x() << "," << targetVel.y() << "," << targetVel.z() << ")" - << "\n leadWorld=(" << leadWorld.x() << "," << leadWorld.y() << "," << leadWorld.z() << ")" - << "\n okSolve=" << (ok ? "true" : "false") - << "\n WILL_REACH=" << ((tStraight <= projectileLifeSec) ? "YES" : "NO (life too short)") - << std::endl; - } - else { - std::cout << "\n[SHOTLOG] now_ms=" << now_ms << " no target\n"; - } - } -#endif - Eigen::Quaternionf q(Environment::shipState.rotation); float speedToSend = projectileSpeed + Environment::shipState.velocity; int shotCount = 2;