diff --git a/src/Game.cpp b/src/Game.cpp index 82306df..bf44c38 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -117,8 +117,9 @@ namespace ZL , glContext(nullptr) , newTickCount(0) , lastTickCount(0) - , planetObject(renderer, taskManager, mainThreadHandler) + , planetObject(renderer, taskManager, mainThreadHandler, camera) { + Environment::shipState.selectedVelocity = 0; projectiles.reserve(maxProjectiles); for (int i = 0; i < maxProjectiles; ++i) { projectiles.emplace_back(std::make_unique()); @@ -237,12 +238,13 @@ namespace ZL } }); - uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { - int newVel = roundf(value * 10); - if (newVel != Environment::shipState.selectedVelocity) { - newShipVelocity = newVel; - } - }); + //uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { + // int newVel = roundf(value * 10); + // if (newVel != Environment::shipState.selectedVelocity) { + // newShipVelocity = newVel; + // } + // }); + // Добавляем джойстик для управления кораблём // centerX=150, centerY=150 (от левого нижнего угла в UI координатах) @@ -345,17 +347,16 @@ namespace ZL // Для скайбокса берем только вращение от камеры Matrix4f view = camera.getViewMatrix(); - view.block<3, 1>(0, 3) = Vector3f::Zero(); // Убираем смещение + view.block<3, 1>(0, 3) = Vector3f::Zero(); renderer.PushSpecialMatrix(view); - Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - Matrix3f viewMatrix = Environment::inverseShipMatrix; - Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized(); - // Передаем вектор НА источник света + Matrix3f viewRot = view.block<3, 3>(0, 0); // world->view rotation + Vector3f viewLightDir = (viewRot * worldLightDir).normalized(); Vector3f lightToSource = -viewLightDir; + renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); // 2. Базовый цвет атмосферы (голубой) @@ -416,16 +417,28 @@ namespace ZL renderer.LoadIdentity(); renderer.PushSpecialMatrix(camera.getViewMatrix()); - + + // --- ship model matrix --- renderer.PushMatrix(); renderer.TranslateMatrix(Environment::shipState.position); - renderer.RotateMatrix(shipWorldOrientation); + renderer.RotateMatrix(Environment::shipState.rotation); if (shipAlive) { glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); renderer.DrawVertexRenderStruct(spaceship); } - renderer.PopMatrix(); + + // Эмиттеры рисуем в той же матрице корабля + if (shipAlive) { + renderer.PushMatrix(); + renderer.TranslateMatrix({ 0, 0, 16 }); + sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); + renderer.PopMatrix(); + } + + renderer.PopMatrix(); // --- end ship model matrix --- + + glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -542,8 +555,6 @@ namespace ZL glViewport(0, 0, Environment::width, Environment::height); - glViewport(0, 0, Environment::width, Environment::height); - CheckGlError(); @@ -584,13 +595,11 @@ namespace ZL } void Game::drawRemoteShips() { - // Используем те же константы имен для шейдеров, что и в drawShip static const std::string defaultShaderName = "default"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; - // Активируем шейдер и текстуру (предполагаем, что меш у всех одинаковый) renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); @@ -601,45 +610,32 @@ namespace ZL static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); - // Биндим текстуру корабля один раз для всех удаленных игроков (оптимизация батчинга) glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); auto now = std::chrono::system_clock::now(); - - //Apply server delay: now -= std::chrono::milliseconds(CLIENT_DELAY); latestRemotePlayers = networkClient->getRemotePlayers(); - // Итерируемся по актуальным данным из extrapolateRemotePlayers - for (auto const& [id, remotePlayer] : latestRemotePlayers) { + renderer.PushMatrix(); + renderer.LoadIdentity(); + renderer.PushSpecialMatrix(camera.getViewMatrix()); - if (!remotePlayer.canFetchClientStateAtTime(now)) - { - continue; - } + for (auto const& [id, remotePlayer] : latestRemotePlayers) { + if (!remotePlayer.canFetchClientStateAtTime(now)) continue; ClientState playerState = remotePlayer.fetchClientStateAtTime(now); - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.PushSpecialMatrix(camera.getViewMatrix()); - - renderer.PushMatrix(); - // Позиция удаленного игрока в мире renderer.TranslateMatrix(playerState.position); - - // 3. Поворот врага renderer.RotateMatrix(playerState.rotation); - renderer.DrawVertexRenderStruct(spaceship); renderer.PopMatrix(); - - renderer.PopMatrix(); - renderer.PopMatrix(); } + renderer.PopMatrix(); // pop special matrix stack (view) + renderer.PopMatrix(); // pop identity push + renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); @@ -648,6 +644,7 @@ namespace ZL CheckGlError(); } + void Game::processTickCount() { if (lastTickCount == 0) { @@ -673,85 +670,80 @@ namespace ZL auto now_ms = newTickCount; - sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); + // ------------------------ + // Input -> ControlState (joystick) + // ------------------------ - // Управление: Джойстик (движение) vs Камера (орбита) - float discreteMag = 0.0f; int discreteAngle = -1; - // 1. Joystick Logic (Movement) auto joystick = uiManager.findJoystick("shipJoystick"); - if (isUsingJoystick && joystick && joystick->isActive) { - float joyX = joystick->getDirectionX(); - float joyY = joystick->getDirectionY(); + bool joystickActive = isUsingJoystick && joystick && joystick->isActive; + + if (joystickActive) { + float joyX = -joystick->getDirectionX(); // -1..1 + float joyY = joystick->getDirectionY(); // -1..1 (вверх обычно отрицательный) float magnitude = joystick->getMagnitude(); - if (magnitude > 0.1f) { - // Movement logic - Environment::shipState.velocity = 0.0f; + const float deadzone = 0.1f; + if (magnitude > deadzone) { - float moveSpeed = 0.5f; + // forward/right из ТЕКУЩЕГО camYaw (без lock) + Vector3f forward(-sinf(camYaw), 0.0f, -cosf(camYaw)); + Vector3f right(cosf(camYaw), 0.0f, -sinf(camYaw)); - // Local movement vector - Vector3f localMove(joyX, 0.0f, joyY); - - // Transform to world space using current ship rotation - Vector3f worldMove = Environment::shipState.rotation * localMove; + // joyY: если вверх = отрицательный, то "- forward * joyY" даёт движение вперёд при joyY<0 + Vector3f worldMove = right * joyX - forward * joyY; - // Apply to position - Environment::shipState.position += worldMove * moveSpeed * static_cast(delta); + if (worldMove.squaredNorm() > 1e-6f) { + worldMove.normalize(); - // Calculate discrete values for network - // Angle 0-359 - float radians = atan2f(joyY, joyX); - discreteAngle = static_cast(radians * 180.0f / M_PI); - if (discreteAngle < 0) discreteAngle += 360; + float ang = atan2f(worldMove.x(), worldMove.z()); // 0 -> +Z + int a = (int)std::round(ang * 180.0f / (float)M_PI); + if (a < 0) a += 360; + a %= 360; - // Magnitude 0.0-1.0 - discreteMag = (std::min)(magnitude, 1.0f); - discreteMag = std::round(discreteMag * 10.0f) / 10.0f; + discreteAngle = a; + + discreteMag = (std::min)(magnitude, 1.0f); + discreteMag = std::round(discreteMag * 10.0f) / 10.0f; + } } } - // 2. Camera Drag Logic - else if (isDraggingCamera && Environment::tapDownHold && !uiManager.isUiInteraction()) { - float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); - float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); - if (std::abs(diffx) > 1.0f || std::abs(diffy) > 1.0f) { - float sensitivity = 0.01f; // Radians per pixel - - camYaw -= diffx * sensitivity; // Yaw: Horizontal drag - camPitch -= diffy * sensitivity; // Pitch: Vertical drag - - // Clamp pitch to avoid flipping - // -80 to 80 degrees in radians - float pitchLimit = 80.0f * static_cast(M_PI) / 180.0f; - if (camPitch > pitchLimit) camPitch = pitchLimit; - if (camPitch < -pitchLimit) camPitch = -pitchLimit; - - // Reset for incremental update - Environment::tapDownStartPos = Environment::tapDownCurrentPos; - } - } // Network Update - if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { - Environment::shipState.discreteAngle = discreteAngle; - Environment::shipState.discreteMag = discreteMag; + bool changed = false; + if (discreteAngle != Environment::shipState.discreteAngle) { + Environment::shipState.discreteAngle = discreteAngle; + changed = true; + } + if (discreteMag != Environment::shipState.discreteMag) { + Environment::shipState.discreteMag = discreteMag; + changed = true; + } + + // slider value применяем здесь (ты раньше только newShipVelocity менял) + //int newVelInt = (int)newShipVelocity; + //if (newVelInt != Environment::shipState.selectedVelocity) { + // Environment::shipState.selectedVelocity = newVelInt; + // changed = true; + //} + + if (changed) { std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); networkClient->Send(msg); - //std::cout << "Sending: " << msg << std::endl; } + Environment::shipState.simulate_physics(delta); Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); @@ -1049,8 +1041,8 @@ namespace ZL glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - drawScene(); processTickCount(); + drawScene(); SDL_GL_SwapWindow(ZL::Environment::window); } @@ -1179,9 +1171,25 @@ namespace ZL uiManager.onMouseMove(uiX, uiY); if (isDraggingCamera && !uiManager.isUiInteraction()) { - Environment::tapDownCurrentPos(0) = static_cast(mx); - Environment::tapDownCurrentPos(1) = static_cast(my); + float curX = static_cast(mx); + float curY = static_cast(my); + + float dx = curX - Environment::tapDownStartPos(0); + float dy = curY - Environment::tapDownStartPos(1); + + const float sens = 0.005f; // rad per pixel + camYaw -= dx * sens; + camPitch -= dy * sens; + + float pitchLimit = 80.0f * static_cast(M_PI) / 180.0f; + if (camPitch > pitchLimit) camPitch = pitchLimit; + if (camPitch < -pitchLimit) camPitch = -pitchLimit; + + // обновляем стартовую точку, чтобы drag был плавный + Environment::tapDownStartPos(0) = curX; + Environment::tapDownStartPos(1) = curY; } + } /* diff --git a/src/Game.h b/src/Game.h index c3aed2a..69aae20 100644 --- a/src/Game.h +++ b/src/Game.h @@ -66,7 +66,7 @@ namespace ZL { std::unordered_map latestRemotePlayers; - float newShipVelocity = 0; + //float newShipVelocity = 0; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; @@ -102,6 +102,12 @@ namespace ZL { float camYaw = 0.0f; float camPitch = 0.0f; + // Movement direction locking + bool isMoving = false; // Is joystick movement active + float moveYawLocked = 0.0f; // Locked camera yaw at movement start + Vector3f moveForwardLocked = Vector3f::UnitZ(); // Locked forward direction (world space) + Vector3f moveRightLocked = Vector3f::UnitX(); // Locked right direction (world space) + Matrix3f rotateShipMat = Matrix3f::Identity(); // Локальный поворот от джойстика Matrix3f shipWorldOrientation = Matrix3f::Identity(); // Ориентация корабля в мире (независимо от камеры) bool shipMoveLockActive = false; @@ -117,6 +123,10 @@ namespace ZL { uint64_t lastExplosionTime = 0; const uint64_t explosionDurationMs = 500; + float camDistance = 0.0f; // будет равен Environment::zoom + float camHeight = 6.0f; // высота над кораблём + + }; diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp index 12438a1..60bd1e0 100644 --- a/src/network/ClientState.cpp +++ b/src/network/ClientState.cpp @@ -1,224 +1,227 @@ #include "ClientState.h" +#include // std::min/max/clamp +#include // sinf/cosf/atan2f +// ------------------------------------------------------------ +// New control model: +// +// discreteAngle = heading in WORLD XZ plane (0..359) +// discreteMag = throttle 0..1 (joystick magnitude) +// selectedVelocity = optional 0..10 (for compatibility) +// +// Movement is along heading direction. +// Ship rotates to face heading direction (optional). +// ------------------------------------------------------------ -void ClientState::simulate_physics(size_t delta) { - if (discreteMag > 0.01f) - { - float rad = static_cast(discreteAngle) * static_cast(M_PI) / 180.0f; - - // Целевая угловая скорость (дискретная сила определяет модуль вектора) - // Вектор {cos, sin, 0} дает нам направление отклонения джойстика - Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f); - Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag; - - Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity; - float diffLen = diffVel.norm(); - - if (diffLen > 0.0001f) { - // Вычисляем, на сколько мы можем изменить скорость в этом кадре - float maxChange = ANGULAR_ACCEL * static_cast(delta); - - if (diffLen <= maxChange) { - // Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель - currentAngularVelocity = targetAngularVelocity; - } - - else { - // Линейно двигаемся в сторону целевого вектора - currentAngularVelocity += (diffVel / diffLen) * maxChange; - } - } - } - else - { - float currentSpeed = currentAngularVelocity.norm(); - - if (currentSpeed > 0.0001f) { - float drop = ANGULAR_ACCEL * static_cast(delta); - if (currentSpeed <= drop) { - currentAngularVelocity = Eigen::Vector3f::Zero(); - } - else { - // Уменьшаем модуль вектора, сохраняя направление - currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop; - } - } - } - - float speedScale = currentAngularVelocity.norm(); - if (speedScale > 0.0001f) { - // Коэффициент чувствительности вращения - - float deltaAlpha = speedScale * static_cast(delta) * ROTATION_SENSITIVITY; - - Eigen::Vector3f axis = currentAngularVelocity.normalized(); - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis)); - - rotation = rotation * rotateQuat.toRotationMatrix(); - } - - - // 4. Линейное изменение линейной скорости - float shipDesiredVelocity = selectedVelocity * 100.f; - - if (velocity < shipDesiredVelocity) - { - velocity += delta * SHIP_ACCEL; - if (velocity > shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - else if (velocity > shipDesiredVelocity) - { - velocity -= delta * SHIP_ACCEL; - if (velocity < shipDesiredVelocity) - { - velocity = shipDesiredVelocity; - } - } - - if (fabs(velocity) > 0.01f) - { - Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f }; - Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection; - position = position + velocityDirectionAdjusted; - } +static inline float DegToRad(float deg) { + return deg * static_cast(M_PI) / 180.0f; } -void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) { - - // 2. Вычисляем задержку - long long deltaMs = 0; - if (nowTime > lastUpdateServerTime) { - deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); - } - - // 3. Защита от слишком больших скачков (Clamp) - // Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков - long long final_lag_ms = deltaMs;//min(deltaMs, 500ll); - - if (final_lag_ms > 0) { - // Доматываем симуляцию на величину задержки - // Мы предполагаем, что за это время параметры управления не менялись - simulate_physics(final_lag_ms); - } +static inline float Clamp01(float v) { + return (std::max)(0.0f, (std::min)(1.0f, v)); } -void ClientState::handle_full_sync(const std::vector& parts, int startFrom) { - // Позиция - position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; +static inline float Approach(float current, float target, float maxDelta) { + if (current < target) return (std::min)(target, current + maxDelta); + if (current > target) return (std::max)(target, current - maxDelta); + return current; +} - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - rotation = q.toRotationMatrix(); +void ClientState::simulate_physics(size_t deltaMs) +{ + // dt in seconds + const float dt = static_cast(deltaMs) / 1000.0f; - currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - velocity = std::stof(parts[startFrom + 10]); - selectedVelocity = std::stoi(parts[startFrom + 11]); - discreteMag = std::stof(parts[startFrom + 12]); - discreteAngle = std::stoi(parts[startFrom + 13]); + // ---------- SETTINGS (tweak these) ---------- + // Max speed in your world units/sec. + // Choose something that makes sense vs projectileSpeed (60 in Game.cpp). + const float MAX_SPEED = 80.0f; + + // Accel/decel in units/sec^2 + const float ACCEL = 120.0f; + const float DECEL = 160.0f; + + // Rotation smoothing factor (0..1 per frame). Uses your constant as "per ms". + // With ROTATION_SENSITIVITY=0.002 and delta=10ms -> 0.02 (nice). + const float ROT_T = std::clamp(ROTATION_SENSITIVITY * static_cast(deltaMs), 0.0f, 1.0f); + // ------------------------------------------- + + // We keep this for protocol compatibility, but it no longer affects anything. + currentAngularVelocity = Eigen::Vector3f::Zero(); + + // Input validity + const bool hasInput = (discreteAngle >= 0) && (discreteMag > 0.01f); + + // Speed command: + // - Prefer discreteMag (joystick magnitude) + // - Also accept selectedVelocity (0..10) for compatibility + float speed01_fromMag = Clamp01(discreteMag); + float speed01_fromSel = Clamp01(static_cast(selectedVelocity) / 10.0f); + float speed01 = (std::max)(speed01_fromMag, speed01_fromSel); + + float targetSpeed = hasInput ? (speed01 * MAX_SPEED) : 0.0f; + + // Smooth speed (accelerate/decelerate) + if (targetSpeed > velocity) velocity = Approach(velocity, targetSpeed, ACCEL * dt); + else velocity = Approach(velocity, targetSpeed, DECEL * dt); + + // If no direction input — only тормозим + if (!hasInput) { + return; + } + + // Heading direction in world XZ plane: + // IMPORTANT: your Game.cpp must set discreteAngle consistently with: + // ang = atan2(worldMove.x, worldMove.z) + // So we reconstruct as: + // x = sin(rad), z = cos(rad) + const float rad = DegToRad(static_cast(discreteAngle)); + + Eigen::Vector3f moveDirWorld( + sinf(rad), + 0.0f, + cosf(rad) + ); + + if (moveDirWorld.squaredNorm() > 1e-6f) { + moveDirWorld.normalize(); + } + else { + return; + } + + // Move in world + position += moveDirWorld * (velocity * dt); + + // Rotate ship to face movement direction (optional). + // Ship local forward is (0,0,-1) (as in your fireProjectiles). + // We need yaw so that rotation * (0,0,-1) == moveDirWorld. + // That yaw is: yaw = atan2(-dir.x, -dir.z) + { + float desiredYaw = atan2f(-moveDirWorld.x(), -moveDirWorld.z()); + + Eigen::Quaternionf qCur(rotation); + Eigen::Quaternionf qTar(Eigen::AngleAxisf(desiredYaw, Eigen::Vector3f::UnitY())); + + Eigen::Quaternionf qNew = qCur.slerp(ROT_T, qTar).normalized(); + rotation = qNew.toRotationMatrix(); + } +} + +void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) +{ + long long deltaMs = 0; + if (nowTime > lastUpdateServerTime) { + deltaMs = std::chrono::duration_cast(nowTime - lastUpdateServerTime).count(); + } + + long long final_lag_ms = deltaMs; // можно clamp, если нужно + if (final_lag_ms > 0) { + simulate_physics(static_cast(final_lag_ms)); + } +} + +void ClientState::handle_full_sync(const std::vector& parts, int startFrom) +{ + position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + rotation = q.toRotationMatrix(); + + // For compatibility: keep reading it, but we won't use it anymore + currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + + velocity = std::stof(parts[startFrom + 10]); + selectedVelocity = std::stoi(parts[startFrom + 11]); + discreteMag = std::stof(parts[startFrom + 12]); + discreteAngle = std::stoi(parts[startFrom + 13]); } std::string ClientState::formPingMessageContent() { - Eigen::Quaternionf q(rotation); + Eigen::Quaternionf q(rotation); - std::string pingMsg = std::to_string(position.x()) + ":" - + std::to_string(position.y()) + ":" - + std::to_string(position.z()) + ":" - + std::to_string(q.w()) + ":" - + std::to_string(q.x()) + ":" - + std::to_string(q.y()) + ":" - + std::to_string(q.z()) + ":" - + std::to_string(currentAngularVelocity.x()) + ":" - + std::to_string(currentAngularVelocity.y()) + ":" - + std::to_string(currentAngularVelocity.z()) + ":" - + std::to_string(velocity) + ":" - + std::to_string(selectedVelocity) + ":" - + std::to_string(discreteMag) + ":" // Используем те же static переменные из блока ROT - + std::to_string(discreteAngle); + std::string pingMsg = + std::to_string(position.x()) + ":" + + std::to_string(position.y()) + ":" + + std::to_string(position.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + ":" + + std::to_string(currentAngularVelocity.x()) + ":" + + std::to_string(currentAngularVelocity.y()) + ":" + + std::to_string(currentAngularVelocity.z()) + ":" + + std::to_string(velocity) + ":" + + std::to_string(selectedVelocity) + ":" + + std::to_string(discreteMag) + ":" + + std::to_string(discreteAngle); - return pingMsg; + return pingMsg; } - void ClientStateInterval::add_state(const ClientState& state) { - auto nowTime = std::chrono::system_clock::now(); + auto nowTime = std::chrono::system_clock::now(); - if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime) - { - timedStates[timedStates.size() - 1] = state; - } - else - { - timedStates.push_back(state); - } + if (!timedStates.empty() && timedStates.back().lastUpdateServerTime == state.lastUpdateServerTime) { + timedStates.back() = state; + } + else { + timedStates.push_back(state); + } - auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); - - while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time) - { - timedStates.erase(timedStates.begin()); - } + auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); + while (!timedStates.empty() && timedStates.front().lastUpdateServerTime < cutoff_time) { + timedStates.erase(timedStates.begin()); + } } bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { - if (timedStates.empty()) - { - return false; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - return false; - } - - return true; + if (timedStates.empty()) return false; + if (timedStates.front().lastUpdateServerTime > targetTime) return false; + return true; } -ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { +ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const +{ + if (timedStates.empty()) { + throw std::runtime_error("No timed client states available"); + } + if (timedStates.front().lastUpdateServerTime > targetTime) { + throw std::runtime_error("Found time but it is in future"); + } - ClientState closestState; + ClientState closestState; - if (timedStates.empty()) - { - throw std::runtime_error("No timed client states available"); - return closestState; - } - if (timedStates[0].lastUpdateServerTime > targetTime) - { - throw std::runtime_error("Found time but it is in future"); - return closestState; - } - if (timedStates.size() == 1) - { - closestState = timedStates[0]; - closestState.apply_lag_compensation(targetTime); - return closestState; - } + if (timedStates.size() == 1) { + closestState = timedStates[0]; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + for (size_t i = 0; i + 1 < timedStates.size(); ++i) { + const auto& earlierState = timedStates[i]; + const auto& laterState = timedStates[i + 1]; - for (size_t i = 0; i < timedStates.size() - 1; ++i) - { - const auto& earlierState = timedStates[i]; - const auto& laterState = timedStates[i + 1]; - if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime) - { - closestState = earlierState; - closestState.apply_lag_compensation(targetTime); - return closestState; - } - } + if (earlierState.lastUpdateServerTime <= targetTime && + laterState.lastUpdateServerTime >= targetTime) + { + closestState = earlierState; + closestState.apply_lag_compensation(targetTime); + return closestState; + } + } - closestState = timedStates[timedStates.size() - 1]; - closestState.apply_lag_compensation(targetTime); - return closestState; + closestState = timedStates.back(); + closestState.apply_lag_compensation(targetTime); + return closestState; } diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index cf5c374..9fef645 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -6,7 +6,7 @@ #include "Environment.h" #include "StoneObject.h" #include "utils/TaskManager.h" - +#include namespace ZL { #if defined EMSCRIPTEN || defined __ANDROID__ @@ -58,10 +58,11 @@ namespace ZL { return rot; } - PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler) + PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, Camera& iCamera) : renderer(iRenderer), taskManager(iTaskManager), - mainThreadHandler(iMainThreadHandler) + mainThreadHandler(iMainThreadHandler), + camera(iCamera) { } @@ -278,11 +279,10 @@ namespace ZL { drawPlanet(renderer); #if ENABLE_STONES - drawStones(renderer); + //drawStones(renderer); #endif - drawAtmosphere(renderer); + //drawAtmosphere(renderer); } - void PlanetObject::drawPlanet(Renderer& renderer) { static const std::string defaultShaderName = "planetLand"; @@ -291,10 +291,9 @@ namespace ZL { static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName); - + renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); @@ -302,79 +301,69 @@ namespace ZL { renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); + renderer.PushPerspectiveProjectionMatrix( + 1.0 / 1.5, + static_cast(Environment::width) / static_cast(Environment::height), + currentZNear, currentZFar + ); + + // View from Camera renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); + renderer.PushSpecialMatrix(camera.getViewMatrix()); - renderer.TranslateMatrix(-Environment::shipState.position); + // Planet at world origin (если у тебя есть PLANET_CENTER_OFFSET — добавь Translate туда) + // renderer.TranslateMatrix(PlanetData::PLANET_CENTER_OFFSET); - const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); - renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("BakedTexture", 1); + // Камера (для освещения) — именно позиция камеры, а не shipState.position + Eigen::Vector3f camPos = camera.getPosition(); + renderer.RenderUniform3fv("uViewPos", camPos.data()); - Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник - Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании - - // Позиция камеры (корабля) в мире - renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); - - //renderer.RenderUniform1f("uHeightScale", 0.08f); renderer.RenderUniform1f("uHeightScale", 0.0f); - renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); - // Направление на солнце в мировом пространстве - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); + Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - // Направление от центра планеты к игроку для расчета дня/ночи - Vector3f playerDirWorld = Environment::shipState.position.normalized(); + Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); - // Тот же фактор освещенности игрока - float playerLightFactor = playerDirWorld.dot(-sunDirWorld); - playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); + float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); + glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); - //glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); - renderer.PopMatrix(); + renderer.PopMatrix(); // special matrix + renderer.PopMatrix(); // original push + renderer.PopProjectionMatrix(); + renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray("vTangent"); renderer.DisableVertexAttribArray("vBinormal"); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); + renderer.shaderManager.PopShader(); CheckGlError(); - - } - void PlanetObject::drawStones(Renderer& renderer) { static const std::string defaultShaderName2 = "planetStone"; @@ -382,86 +371,81 @@ namespace ZL { static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; - static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); - renderer.RenderUniform1i(textureUniformName, 0); + renderer.RenderUniform1i("Texture", 0); + renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + renderer.PushPerspectiveProjectionMatrix( + 1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); + currentZNear, currentZFar + ); renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); + renderer.PushSpecialMatrix(camera.getViewMatrix()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); - renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); - //std::cout << "uViewPos" << Environment::shipState.position << std::endl; - // PlanetObject.cpp, метод drawStones - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); + + Eigen::Vector3f camPos = camera.getPosition(); + renderer.RenderUniform3fv("uViewPos", camPos.data()); + + Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); + Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); + float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - + glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); for (int i : triangleIndicesToDraw) { - // КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: - // Проверяем, что данные не просто существуют, а загружены в GPU if (planetStones.statuses[i] == ChunkStatus::Live) { - // Дополнительная проверка на наличие данных (на всякий случай) - if (stonesToRender[i].data.PositionData.size() > 0) { + if (!stonesToRender[i].data.PositionData.empty()) { renderer.DrawVertexRenderStruct(stonesToRender[i]); } } - // Если статус Generating или Empty — мы просто пропускаем этот индекс. - // Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO. } CheckGlError(); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); - renderer.PopMatrix(); + renderer.PopMatrix(); // special + renderer.PopMatrix(); // original renderer.PopProjectionMatrix(); + renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); + renderer.shaderManager.PopShader(); CheckGlError(); - - glClear(GL_DEPTH_BUFFER_BIT); } - void PlanetObject::drawAtmosphere(Renderer& renderer) { + void PlanetObject::drawAtmosphere(Renderer& renderer) + { static const std::string defaultShaderName = "defaultAtmosphere"; - //static const std::string defaultShaderName = "defaultColor"; static const std::string vPositionName = "vPosition"; static const std::string vNormalName = "vNormal"; - //glClear(GL_DEPTH_BUFFER_BIT); + glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); @@ -473,90 +457,65 @@ namespace ZL { float currentZNear = zRange.first; float currentZFar = zRange.second; - if (currentZNear < 200) - { + if (currentZNear < 200) { currentZNear = 200; currentZFar = currentZNear * 100; } - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + renderer.PushPerspectiveProjectionMatrix( + 1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); + currentZNear, currentZFar + ); renderer.PushMatrix(); renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - - const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); - - Vector3f color = { 0.0, 0.5, 1.0 }; - - renderer.RenderUniform3fv("uColor", color.data()); + renderer.PushSpecialMatrix(camera.getViewMatrix()); + const Eigen::Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); + Eigen::Vector3f color = { 0.0f, 0.5f, 1.0f }; + renderer.RenderUniform3fv("uColor", color.data()); + renderer.RenderUniform3fv("uSkyColor", color.data()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); - // В начале drawAtmosphere или как uniform - //float time = SDL_GetTicks() / 1000.0f; - //renderer.RenderUniform1f("uTime", time); - - // В методе PlanetObject::drawAtmosphere - Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - - // Получаем текущую матрицу вида (ModelView без трансляции объекта) - // В вашем движке это Environment::inverseShipMatrix - Matrix3f viewMatrix2 = Environment::inverseShipMatrix; - - // Трансформируем вектор света в пространство вида - Vector3f viewLightDir = viewMatrix2 * worldLightDir; - - // Передаем инвертированный вектор (направление НА источник) - Vector3f lightToSource = -viewLightDir.normalized(); - renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); - - // В методе Game::drawCubemap - //renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f); - // Направление света в мировом пространстве для освещения облаков - //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); + Eigen::Vector3f worldLightDir = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); - // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); + // Свет в view-space: берём матрицу вида (верхний левый 3x3) + Eigen::Matrix3f viewRot = camera.getViewMatrix().block<3, 3>(0, 0); + Eigen::Vector3f viewLightDir = (viewRot * worldLightDir).normalized(); + Eigen::Vector3f lightToSource = -viewLightDir; + renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); - // Насколько игрок на свету - float playerLightFactor = playerDirWorld.dot(-sunDirWorld); - playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог - - // 2. ОБЯЗАТЕЛЬНО передаем в шейдер - renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - - // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) - renderer.RenderUniform3fv("uSkyColor", color.data()); - - //Vector3f playerDirWorld = Environment::shipState.position.normalized(); + Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); + Eigen::Vector3f sunDirWorld = worldLightDir; + float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); + renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); + glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения + glBlendFunc(GL_SRC_ALPHA, GL_ONE); renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); + glDisable(GL_BLEND); glDepthMask(GL_TRUE); - renderer.PopMatrix(); + + renderer.PopMatrix(); // special + renderer.PopMatrix(); // original renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); + renderer.shaderManager.PopShader(); CheckGlError(); - } + float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { return planetData.distanceToPlanetSurfaceFast(viewerPosition); diff --git a/src/planet/PlanetObject.h b/src/planet/PlanetObject.h index 6db50c0..8412b2f 100644 --- a/src/planet/PlanetObject.h +++ b/src/planet/PlanetObject.h @@ -18,7 +18,7 @@ #include "render/FrameBuffer.h" #include #include - +#include "render/Camera.h" namespace ZL { class TaskManager; class MainThreadHandler; @@ -47,8 +47,9 @@ namespace ZL { Renderer& renderer; TaskManager& taskManager; MainThreadHandler& mainThreadHandler; + Camera& camera; public: - PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler); + PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, Camera& iCamera); void init(); void update(float deltaTimeMs); diff --git a/src/render/Camera.cpp b/src/render/Camera.cpp index 347beb9..f04b8a5 100644 --- a/src/render/Camera.cpp +++ b/src/render/Camera.cpp @@ -1,4 +1,5 @@ #include "render/Camera.h" +#include namespace ZL { @@ -6,52 +7,49 @@ namespace ZL { using Eigen::Matrix3f; using Eigen::Matrix4f; - Camera::Camera() + Camera::Camera() : position(Vector3f::Zero()) + , target(Vector3f::Zero()) , rotation(Matrix3f::Identity()) { } - void Camera::follow(const Vector3f& targetPos, const Matrix3f& targetRot, float distance, float height) - { - Vector3f offset(0.0f, height, distance); - Vector3f globalOffset = targetRot * offset; - - position = targetPos + globalOffset; - rotation = targetRot; - } - void Camera::followOrbit(const Vector3f& targetPos, float yaw, float pitch, float distance, float height) { - // Convert yaw/pitch to rotation matrix - // Yaw (Y-axis), Pitch (X-axis) - - Eigen::AngleAxisf yawRot(yaw, Vector3f::UnitY()); - Eigen::AngleAxisf pitchRot(pitch, Vector3f::UnitX()); - - // Combine rotations: Yaw * Pitch (order matters) - Eigen::Quaternionf q = yawRot * pitchRot; - rotation = q.toRotationMatrix(); + target = targetPos; - Vector3f offset(0.0f, height, distance); - Vector3f globalOffset = rotation * offset; + float cosP = std::cos(pitch); + float sinP = std::sin(pitch); + float cosY = std::cos(yaw); + float sinY = std::sin(yaw); - position = targetPos + globalOffset; + Vector3f offset; + offset.x() = distance * sinY * cosP; + offset.y() = height + distance * sinP; + offset.z() = distance * cosY * cosP; + + position = target + offset; + + // Build camera basis to look at target + Vector3f forward = (target - position).normalized(); + Vector3f worldUp(0.0f, 1.0f, 0.0f); + + Vector3f right = worldUp.cross(forward).normalized(); + Vector3f up = forward.cross(right); + + rotation.col(0) = right; + rotation.col(1) = up; + rotation.col(2) = -forward; // OpenGL convention } Matrix4f Camera::getViewMatrix() const { - // View Matrix = (Translate * Rotate)^-1 - // = Rotate^-1 * Translate^-1 - Matrix3f R_inv = rotation.transpose(); - Vector3f T_inv = -position; - + Matrix4f view = Matrix4f::Identity(); - - view.block<3,3>(0,0) = R_inv; - view.block<3,1>(0,3) = R_inv * T_inv; - + view.block<3, 3>(0, 0) = R_inv; + view.block<3, 1>(0, 3) = R_inv * (-position); return view; } -} + +} // namespace ZL diff --git a/src/render/Camera.h b/src/render/Camera.h index 09c37ff..9199ea7 100644 --- a/src/render/Camera.h +++ b/src/render/Camera.h @@ -7,19 +7,17 @@ namespace ZL { public: Camera(); - // Обновление позиции камеры (например, слежение за кораблем) - void follow(const Eigen::Vector3f& targetPos, const Eigen::Matrix3f& targetRot, float distance, float height); - - // Follow target with orbital rotation (yaw/pitch) independent of target rotation + // Орбитальная камера вокруг targetPos (yaw вокруг Y, pitch вокруг X) void followOrbit(const Eigen::Vector3f& targetPos, float yaw, float pitch, float distance, float height); - // Получить матрицу вида (View Matrix) для шейдера Eigen::Matrix4f getViewMatrix() const; Eigen::Vector3f getPosition() const { return position; } + Eigen::Vector3f getTarget() const { return target; } private: Eigen::Vector3f position; - Eigen::Matrix3f rotation; // Ориентация камеры + Eigen::Vector3f target; + Eigen::Matrix3f rotation; // columns: right, up, -forward }; }