diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index e14bd56..752552c 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -103,10 +103,10 @@ namespace ZL { }); uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); - /*if (newVel > 2) + if (newVel > 2) { newVel = 2; - }*/ + } if (newVel != Environment::shipState.selectedVelocity) { onVelocityChanged(newVel); diff --git a/src/Space.cpp b/src/Space.cpp index f69e7d0..8483030 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -696,13 +696,15 @@ namespace ZL int Space::pickTargetId() const { int bestId = -1; - constexpr float INF_F = 1e30f; - float bestDistSq = INF_F; + float bestDistSq = 1e30f; for (auto const& [id, st] : remotePlayerStates) { if (deadRemotePlayers.count(id)) continue; float d2 = (Environment::shipState.position - st.position).squaredNorm(); + + if (d2 > TARGET_MAX_DIST_SQ) continue; // слишком далеко + if (d2 < bestDistSq) { bestDistSq = d2; bestId = id; @@ -711,6 +713,95 @@ namespace ZL 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, const Eigen::Vector4f& rgba) { @@ -725,11 +816,16 @@ namespace ZL // defaultColor shader likely uses vColor (vec3), но нам нужен alpha. // У тебя в Renderer есть RenderUniform4fv, но шейдер может брать vColor. - // Поэтому: сделаем ColorData vec3, а alpha дадим через uniform uColor, если есть. - // Если в defaultColor нет uniform uColor — тогда alpha будет 1.0. - // Для совместимости: кладём RGB, alpha будем задавать uniform'ом отдельно. + // Поэтому: сделаем ColorData vec3, а alpha будем задавать uniform'ом отдельно. Vector3f rgb{ rgba.x(), rgba.y(), rgba.z() }; v.ColorData = { rgb, rgb, rgb, rgb, rgb, rgb }; + + // defaultColor vertex shader expects vNormal and vTexCoord; provide valid values + // so WebGL/GLSL doesn't get NaN from normalize(vec3(0,0,0)). + const Vector3f n{ 0.f, 0.f, 1.f }; + v.NormalData = { n, n, n, n, n, n }; + const Vector2f uv{ 0.f, 0.f }; + v.TexCoordData = { uv, uv, uv, uv, uv, uv }; return v; } @@ -756,6 +852,54 @@ namespace ZL const ClientState& st = remotePlayerStates.at(trackedTargetId); 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); + + // + небольшой запас 10–20% чтобы не моргало на границе + 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) проекция float ndcX, ndcY, ndcZ, clipW; if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return; @@ -774,9 +918,9 @@ namespace ZL // time for arrow bob float t = static_cast(SDL_GetTicks64()) * 0.001f; - // 4) Настройки стиля (как X3) + // 4) Настройки стиля Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный - float thickness = 2.0f; // толщина линий (px) + float thickness = 10.0f; // толщина линий (px) float z = 0.0f; // 2D слой // 5) Если цель в кадре: рисуем скобки @@ -820,12 +964,39 @@ namespace ZL glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glClear(GL_DEPTH_BUFFER_BIT); renderer.shaderManager.PushShader("defaultColor"); renderer.PushProjectionMatrix((float)Environment::width, (float)Environment::height, 0.f, 1.f); renderer.PushMatrix(); renderer.LoadIdentity(); + renderer.EnableVertexAttribArray("vPosition"); + + // рисуем кружок упреждения (только если есть решение) + 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, top - cornerLen * 0.5f, thickness, cornerLen); @@ -842,6 +1013,9 @@ namespace ZL drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness); drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen); + renderer.DisableVertexAttribArray("vPosition"); + + renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.shaderManager.PopShader(); @@ -907,6 +1081,11 @@ namespace ZL 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); }; @@ -1255,7 +1434,7 @@ namespace ZL }; const float projectileSpeed = 60.0f; - const float lifeMs = 5000.0f; + const float lifeMs = 50000.0f; const float size = 0.5f; Vector3f localForward = { 0,0,-1 }; diff --git a/src/Space.h b/src/Space.h index b1f8fed..9e97eeb 100644 --- a/src/Space.h +++ b/src/Space.h @@ -120,6 +120,9 @@ namespace ZL { std::unordered_set 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) --- int trackedTargetId = -1; bool targetWasVisible = false; diff --git a/src/network/WebSocketClientEmscripten.cpp b/src/network/WebSocketClientEmscripten.cpp index 9534cf0..98044e9 100644 --- a/src/network/WebSocketClientEmscripten.cpp +++ b/src/network/WebSocketClientEmscripten.cpp @@ -7,8 +7,8 @@ namespace ZL { void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) { // Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost - std::string url = "ws://" + host + ":" + std::to_string(port); - //std::string url = "wss://api.spacegame.fishrungames.com"; + //std::string url = "ws://" + host + ":" + std::to_string(port); + std::string url = "wss://api.spacegame.fishrungames.com"; EmscriptenWebSocketCreateAttributes attr = { url.c_str(), diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index a11ed19..721ae54 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -895,41 +895,65 @@ namespace ZL { static const std::string vColor("vColor"); static const std::string vTexCoord("vTexCoord"); static const std::string vPosition("vPosition"); - - //glBindVertexArray(VertexRenderStruct.vao->getBuffer()); - //Check if main thread, check if data is not empty... + // On WebGL (and when not using VAO), vertex attribute arrays must be explicitly + // enabled before drawing. Desktop with VAO can rely on stored state; WebGL cannot. if (VertexRenderStruct.data.NormalData.size() > 0) { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.normalVBO->getBuffer()); VertexAttribPointer3fv(vNormal, 0, NULL); + EnableVertexAttribArray(vNormal); + } + else + { + DisableVertexAttribArray(vNormal); } if (VertexRenderStruct.data.TangentData.size() > 0) { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.tangentVBO->getBuffer()); VertexAttribPointer3fv(vTangent, 0, NULL); + EnableVertexAttribArray(vTangent); + } + else + { + DisableVertexAttribArray(vTangent); } if (VertexRenderStruct.data.BinormalData.size() > 0) { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.binormalVBO->getBuffer()); VertexAttribPointer3fv(vBinormal, 0, NULL); + EnableVertexAttribArray(vBinormal); + } + else + { + DisableVertexAttribArray(vBinormal); } if (VertexRenderStruct.data.ColorData.size() > 0) { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.colorVBO->getBuffer()); VertexAttribPointer3fv(vColor, 0, NULL); + EnableVertexAttribArray(vColor); + } + else + { + DisableVertexAttribArray(vColor); } if (VertexRenderStruct.data.TexCoordData.size() > 0) { glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.texCoordVBO->getBuffer()); VertexAttribPointer2fv(vTexCoord, 0, NULL); + EnableVertexAttribArray(vTexCoord); + } + else + { + DisableVertexAttribArray(vTexCoord); } glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer()); VertexAttribPointer3fv(vPosition, 0, NULL); + EnableVertexAttribArray(vPosition); glDrawArrays(GL_TRIANGLES, 0, static_cast(VertexRenderStruct.data.PositionData.size())); - } void worldToScreenCoordinates(Vector3f objectPos,