Add enemy target brackets + off-screen arrows
This commit is contained in:
parent
b56fafa0e0
commit
528c94e921
288
src/Game.cpp
288
src/Game.cpp
@ -174,6 +174,25 @@ namespace ZL
|
||||
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()
|
||||
{
|
||||
if (!textRenderer) return;
|
||||
@ -205,11 +224,9 @@ namespace ZL
|
||||
float uiX = sx;
|
||||
float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy)
|
||||
|
||||
// Можно делать масштаб по дальности: чем дальше — тем меньше.
|
||||
// depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
|
||||
float dist = (Environment::shipState.position - boxWorld).norm();
|
||||
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);
|
||||
|
||||
textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
|
||||
@ -738,6 +755,7 @@ namespace ZL
|
||||
drawBoxesLabels();
|
||||
|
||||
drawUI();
|
||||
drawTargetHud();
|
||||
CheckGlError();
|
||||
}
|
||||
|
||||
@ -877,6 +895,270 @@ namespace ZL
|
||||
#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() {
|
||||
|
||||
if (lastTickCount == 0) {
|
||||
|
||||
11
src/Game.h
11
src/Game.h
@ -133,7 +133,18 @@ namespace ZL {
|
||||
|
||||
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; // выбирает цель (пока: ближайший живой удаленный игрок)
|
||||
};
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user