add lead indicator

This commit is contained in:
vottozi 2026-02-23 14:43:23 +06:00
parent 84006380db
commit fa3a4c66c9
2 changed files with 169 additions and 4 deletions

View File

@ -696,13 +696,15 @@ namespace ZL
int Space::pickTargetId() const int Space::pickTargetId() const
{ {
int bestId = -1; int bestId = -1;
constexpr float INF_F = 1e30f; float bestDistSq = 1e30f;
float bestDistSq = INF_F;
for (auto const& [id, st] : remotePlayerStates) { for (auto const& [id, st] : remotePlayerStates) {
if (deadRemotePlayers.count(id)) continue; if (deadRemotePlayers.count(id)) continue;
float d2 = (Environment::shipState.position - st.position).squaredNorm(); float d2 = (Environment::shipState.position - st.position).squaredNorm();
if (d2 > TARGET_MAX_DIST_SQ) continue; // слишком далеко
if (d2 < bestDistSq) { if (d2 < bestDistSq) {
bestDistSq = d2; bestDistSq = d2;
bestId = id; bestId = id;
@ -711,6 +713,95 @@ namespace ZL
return bestId; return bestId;
} }
static Vector3f ForwardFromRotation(const Matrix3f& rot)
{
Vector3f localForward(0, 0, -1);
Vector3f worldForward = rot * localForward;
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
return worldForward;
}
static bool SolveLeadInterceptTime(
const Vector3f& shooterPos,
const Vector3f& shooterVel,
const Vector3f& targetPos,
const Vector3f& targetVel,
float projectileSpeed, // muzzle speed (например 60)
float& outT)
{
Vector3f r = targetPos - shooterPos;
Vector3f v = targetVel - shooterVel;
float S = projectileSpeed;
float a = v.dot(v) - S * S;
float b = 2.0f * r.dot(v);
float c = r.dot(r);
// Если a почти 0 -> линейный случай
if (std::abs(a) < 1e-6f) {
if (std::abs(b) < 1e-6f) return false; // нет решения
float t = -c / b;
if (t > 0.0f) { outT = t; return true; }
return false;
}
float disc = b * b - 4.0f * a * c;
if (disc < 0.0f) return false;
float sqrtDisc = std::sqrt(disc);
float t1 = (-b - sqrtDisc) / (2.0f * a);
float t2 = (-b + sqrtDisc) / (2.0f * a);
float t = 1e30f;
if (t1 > 0.0f) t = min(t, t1);
if (t2 > 0.0f) t = min(t, t2);
if (t >= 1e29f) return false;
outT = t;
return true;
}
static VertexDataStruct MakeRing2D(
float cx, float cy,
float innerR, float outerR,
float z,
int segments,
const Eigen::Vector4f& rgba)
{
VertexDataStruct v;
v.PositionData.reserve(segments * 6);
v.ColorData.reserve(segments * 6);
Vector3f rgb(rgba.x(), rgba.y(), rgba.z());
const float twoPi = 6.28318530718f;
for (int i = 0; i < segments; ++i) {
float a0 = twoPi * (float)i / (float)segments;
float a1 = twoPi * (float)(i + 1) / (float)segments;
float c0 = std::cos(a0), s0 = std::sin(a0);
float c1 = std::cos(a1), s1 = std::sin(a1);
Vector3f p0i(cx + innerR * c0, cy + innerR * s0, z);
Vector3f p0o(cx + outerR * c0, cy + outerR * s0, z);
Vector3f p1i(cx + innerR * c1, cy + innerR * s1, z);
Vector3f p1o(cx + outerR * c1, cy + outerR * s1, z);
// два треугольника (p0i,p0o,p1o) и (p0i,p1o,p1i)
v.PositionData.push_back(p0i);
v.PositionData.push_back(p0o);
v.PositionData.push_back(p1o);
v.PositionData.push_back(p0i);
v.PositionData.push_back(p1o);
v.PositionData.push_back(p1i);
for (int k = 0; k < 6; ++k) v.ColorData.push_back(rgb);
}
return v;
}
static VertexDataStruct MakeColoredRect2D(float cx, float cy, float hw, float hh, float z, static VertexDataStruct MakeColoredRect2D(float cx, float cy, float hw, float hh, float z,
const Eigen::Vector4f& rgba) const Eigen::Vector4f& rgba)
{ {
@ -756,6 +847,54 @@ namespace ZL
const ClientState& st = remotePlayerStates.at(trackedTargetId); const ClientState& st = remotePlayerStates.at(trackedTargetId);
Vector3f shipWorld = st.position; Vector3f shipWorld = st.position;
// Lead Indicator
// скорость пули (как в fireProjectiles)
const float projectileSpeed = 60.0f;
// позиция вылета
Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f };
// скорость цели в мире (вектор)
Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity;
Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity;
// условие "если враг не движется — круг не рисуем"
const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах)
bool targetMoving = (targetVel.norm() > minTargetSpeed);
Vector3f leadWorld = shipWorld;
bool haveLead = false;
//if (targetMoving) {
// float tLead = 0.0f;
// if (SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead)) {
// // ограничим случаи, чтобы круг не улетал далеко
// if (tLead > 0.0f && tLead < 8.0f) {
// // подобрать максимум (сек)
// leadWorld = shipWorld + targetVel * tLead;
// haveLead = true;
// }
// }
//}
if (targetMoving) {
float tLead = 0.0f;
float distToTarget = (Environment::shipState.position - shipWorld).norm();
const float leadMaxDist = 2500.0f; // максимум
float allowedDist = min(distToTarget, leadMaxDist);
// + небольшой запас 1020% чтобы не моргало на границе
const float maxLeadTime = (allowedDist / projectileSpeed) * 1.2f;
if (SolveLeadInterceptTime(shooterPos, shooterVel, shipWorld, targetVel, projectileSpeed, tLead)) {
if (tLead > 0.0f && tLead < maxLeadTime) {
leadWorld = shipWorld + targetVel * tLead;
haveLead = true;
}
}
}
// 2) проекция // 2) проекция
float ndcX, ndcY, ndcZ, clipW; float ndcX, ndcY, ndcZ, clipW;
if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return; if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return;
@ -774,7 +913,7 @@ namespace ZL
// time for arrow bob // time for arrow bob
float t = static_cast<float>(SDL_GetTicks64()) * 0.001f; float t = static_cast<float>(SDL_GetTicks64()) * 0.001f;
// 4) Настройки стиля (как X3) // 4) Настройки стиля
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
float thickness = 2.0f; // толщина линий (px) float thickness = 2.0f; // толщина линий (px)
float z = 0.0f; // 2D слой float z = 0.0f; // 2D слой
@ -826,6 +965,29 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.LoadIdentity(); renderer.LoadIdentity();
// рисуем кружок упреждения (только если есть решение)
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::width;
float ly = (leadNdcY * 0.5f + 0.5f) * Environment::height;
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;
VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor);
hudTempMesh.AssignFrom(ring);
renderer.DrawVertexRenderStruct(hudTempMesh);
}
}
}
// верх-лево: горизонт + вертикаль // верх-лево: горизонт + вертикаль
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness); drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen); drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
@ -1255,7 +1417,7 @@ namespace ZL
}; };
const float projectileSpeed = 60.0f; const float projectileSpeed = 60.0f;
const float lifeMs = 5000.0f; const float lifeMs = 50000.0f;
const float size = 0.5f; const float size = 0.5f;
Vector3f localForward = { 0,0,-1 }; Vector3f localForward = { 0,0,-1 };

View File

@ -120,6 +120,9 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers; std::unordered_set<int> deadRemotePlayers;
static constexpr float TARGET_MAX_DIST = 50000.0f;
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
// --- Target HUD (brackets + offscreen arrow) --- // --- Target HUD (brackets + offscreen arrow) ---
int trackedTargetId = -1; int trackedTargetId = -1;
bool targetWasVisible = false; bool targetWasVisible = false;