Add enemy target brackets + off-screen arrows

This commit is contained in:
vottozi 2026-02-19 01:13:08 +06:00
parent b56fafa0e0
commit 528c94e921
2 changed files with 296 additions and 3 deletions

View File

@ -174,6 +174,25 @@ namespace ZL
return true; return true;
} }
bool Game::projectToNDC(const Vector3f& world, float& ndcX, float& ndcY, float& ndcZ, float& clipW) const
{
float aspect = static_cast<float>(Environment::width) / static_cast<float>(Environment::height);
Eigen::Matrix4f V = makeViewMatrix_FromYourCamera();
Eigen::Matrix4f P = makePerspective(1.0f / 1.5f, aspect, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR);
Eigen::Vector4f w(world.x(), world.y(), world.z(), 1.0f);
Eigen::Vector4f clip = P * V * w;
clipW = clip.w();
if (std::abs(clipW) < 1e-6f) return false;
Eigen::Vector3f ndc = clip.head<3>() / clipW;
ndcX = ndc.x();
ndcY = ndc.y();
ndcZ = ndc.z();
return true;
}
void Game::drawBoxesLabels() void Game::drawBoxesLabels()
{ {
if (!textRenderer) return; if (!textRenderer) return;
@ -205,11 +224,9 @@ namespace ZL
float uiX = sx; float uiX = sx;
float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy) float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy)
// Можно делать масштаб по дальности: чем дальше — тем меньше.
// depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
float dist = (Environment::shipState.position - boxWorld).norm(); float dist = (Environment::shipState.position - boxWorld).norm();
float scaleRaw = 120.0f / (dist + 1.0f); float scaleRaw = 120.0f / (dist + 1.0f);
float scale = std::round(scaleRaw * 10.0f) / 10.0f; float scale = std::round(scaleRaw * 10.0f) / 10.0f; // округление до 0.1
scale = std::clamp(scale, 0.6f, 1.2f); scale = std::clamp(scale, 0.6f, 1.2f);
textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true); textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
@ -738,6 +755,7 @@ namespace ZL
drawBoxesLabels(); drawBoxesLabels();
drawUI(); drawUI();
drawTargetHud();
CheckGlError(); CheckGlError();
} }
@ -877,6 +895,270 @@ namespace ZL
#endif #endif
} }
int Game::pickTargetId() const
{
int bestId = -1;
constexpr float INF_F = 1e30f;
float bestDistSq = INF_F;
for (auto const& [id, st] : remotePlayerStates) {
if (deadRemotePlayers.count(id)) continue;
float d2 = (Environment::shipState.position - st.position).squaredNorm();
if (d2 < bestDistSq) {
bestDistSq = d2;
bestId = id;
}
}
return bestId;
}
static VertexDataStruct MakeColoredRect2D(float cx, float cy, float hw, float hh, float z,
const Eigen::Vector4f& rgba)
{
VertexDataStruct v;
// 2 triangles
Vector3f p1{ cx - hw, cy - hh, z };
Vector3f p2{ cx - hw, cy + hh, z };
Vector3f p3{ cx + hw, cy + hh, z };
Vector3f p4{ cx + hw, cy - hh, z };
v.PositionData = { p1, p2, p3, p3, p4, p1 };
// defaultColor shader likely uses vColor (vec3), но нам нужен alpha.
// У тебя в Renderer есть RenderUniform4fv, но шейдер может брать vColor.
// Поэтому: сделаем ColorData vec3, а alpha дадим через uniform uColor, если есть.
// Если в defaultColor нет uniform uColor — тогда alpha будет 1.0.
// Для совместимости: кладём RGB, alpha будем задавать uniform'ом отдельно.
Vector3f rgb{ rgba.x(), rgba.y(), rgba.z() };
v.ColorData = { rgb, rgb, rgb, rgb, rgb, rgb };
return v;
}
void Game::drawTargetHud()
{
if (!textRenderer) return;
// 1) выбираем цель
int targetIdNow = pickTargetId();
if (targetIdNow < 0) {
trackedTargetId = -1;
targetAcquireAnim = 0.f;
targetWasVisible = false;
return;
}
// если цель сменилась — сброс анимации “схлопывания”
if (trackedTargetId != targetIdNow) {
trackedTargetId = targetIdNow;
targetAcquireAnim = 0.0f;
targetWasVisible = false;
}
const ClientState& st = remotePlayerStates.at(trackedTargetId);
Vector3f shipWorld = st.position;
// 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<float>(SDL_GetTicks64()) * 0.001f;
// 4) Настройки стиля (как X3)
Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный
float thickness = 2.0f; // толщина линий (px)
float z = 0.0f; // 2D слой
// 5) Если цель в кадре: рисуем скобки
if (onScreen)
{
// перевод NDC -> экран (в пикселях)
float sx = (ndcX * 0.5f + 0.5f) * Environment::width;
float sy = (ndcY * 0.5f + 0.5f) * Environment::height;
// анимация “снаружи внутрь”
// targetAcquireAnim растёт к 1, быстро (похоже на захват)
float dt = 1.0f / 60.0f; // у тебя нет dt в draw, берём константу, выглядит норм
targetAcquireAnim = min(1.0f, targetAcquireAnim + dt * 6.5f);
// базовый размер рамки в зависимости от дистанции (как у лейблов)
float size = 220.0f / (dist * 0.01f + 1.0f); // подстройка
size = std::clamp(size, 35.0f, 120.0f); // min/max
// “схлопывание”: сначала больше, потом ближе к кораблю
// expand 1.6 -> 1.0
float expand = 1.6f - 0.6f * targetAcquireAnim;
float half = size * expand;
float cornerLen = max(10.0f, half * 0.35f);
// точки углов
float left = sx - half;
float right = sx + half;
float bottom = sy - half;
float top = sy + half;
// рисуем 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);
};
// включаем 2D режим
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
renderer.PushMatrix();
renderer.LoadIdentity();
// верх-лево: горизонт + вертикаль
drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen);
// верх-право
drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness);
drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen);
// низ-лево
drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness);
drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen);
// низ-право
drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness);
drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
targetWasVisible = true;
return;
}
// 6) Если цель offscreen: рисуем стрелку на краю
// dir: куда “смотреть” в NDC
float dirX = ndcX;
float dirY = ndcY;
// если позади камеры — разворачиваем направление
if (behind) {
dirX = -dirX;
dirY = -dirY;
}
float len = std::sqrt(dirX * dirX + dirY * dirY);
if (len < 1e-5f) return;
dirX /= len;
dirY /= len;
// пересечение луча с прямоугольником [-1..1] с отступом
float marginNdc = 0.08f;
float maxX = 1.0f - marginNdc;
float maxY = 1.0f - marginNdc;
float tx = (std::abs(dirX) < 1e-6f) ? 1e9f : (maxX / std::abs(dirX));
float ty = (std::abs(dirY) < 1e-6f) ? 1e9f : (maxY / std::abs(dirY));
float k = min(tx, ty);
float edgeNdcX = dirX * k;
float edgeNdcY = dirY * k;
float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::width;
float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::height;
// лёгкая анимация “зова”: смещение по направлению
float bob = std::sin(t * 6.0f) * 6.0f;
edgeX += dirX * bob;
edgeY += dirY * bob;
// стрелка как треугольник + маленький “хвост”
float arrowLen = 26.0f;
float arrowWid = 14.0f;
// перпендикуляр
float px = -dirY;
float py = dirX;
Vector3f tip{ edgeX + dirX * arrowLen, edgeY + dirY * arrowLen, z };
Vector3f left{ edgeX + px * (arrowWid * 0.5f), edgeY + py * (arrowWid * 0.5f), z };
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 };
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);
};
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
renderer.shaderManager.PushShader("defaultColor");
renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f);
renderer.PushMatrix();
renderer.LoadIdentity();
// треугольник-стрелка
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);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.shaderManager.PopShader();
// дистанция рядом со стрелкой
// (у тебя ещё будет “статично под прицелом” — это просто другой TextView / drawText)
{
std::string d = std::to_string((int)dist) + "m";
float tx = edgeX + px * 18.0f;
float ty = edgeY + py * 18.0f;
textRenderer->drawText(d, tx, ty, 0.6f, true, { 1.f, 0.f, 0.f, 1.f });
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
targetWasVisible = false;
}
void Game::processTickCount() { void Game::processTickCount() {
if (lastTickCount == 0) { if (lastTickCount == 0) {

View File

@ -133,7 +133,18 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers; std::unordered_set<int> deadRemotePlayers;
// --- Target HUD (brackets + offscreen arrow) ---
int trackedTargetId = -1;
bool targetWasVisible = false;
float targetAcquireAnim = 0.0f; // 0..1 схлопывание (0 = далеко, 1 = на месте)
// временный меш для HUD (будем перезаливать VBO маленькими порциями)
VertexRenderStruct hudTempMesh;
// helpers
bool projectToNDC(const Vector3f& world, float& ndcX, float& ndcY, float& ndcZ, float& clipW) const;
void drawTargetHud(); // рисует рамку или стрелку
int pickTargetId() const; // выбирает цель (пока: ближайший живой удаленный игрок)
}; };