diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0f68441..06e1ed3 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -50,6 +50,13 @@ add_executable(space-game001 ../src/UiManager.h ../src/Projectile.h ../src/Projectile.cpp + ../src/network/NetworkInterface.h + ../src/network/LocalClient.h + ../src/network/LocalClient.cpp + ../src/network/ClientState.h + ../src/network/ClientState.cpp + ../src/network/WebSocketClient.h + ../src/network/WebSocketClient.cpp ) # Установка проекта по умолчанию для Visual Studio @@ -71,6 +78,7 @@ target_compile_definitions(space-game001 PRIVATE WIN32_LEAN_AND_MEAN PNG_ENABLED SDL_MAIN_HANDLED +# NETWORK # SIMPLIFIED ) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt new file mode 100644 index 0000000..5e12ff8 --- /dev/null +++ b/server/CMakeLists.txt @@ -0,0 +1,47 @@ +cmake_minimum_required(VERSION 3.15) +project(SpaceGameServer) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Подключаем зависимости нашего движка +include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/ThirdParty.cmake) + +# Настройка флагов для тяжелых шаблонов Boost +if (MSVC) + add_compile_options(/bigobj) +endif() + +# Добавляем скомпилированные компоненты Boost через относительные пути +# CMake сам создаст цели boost_system и др. +add_subdirectory("${BOOST_SRC_DIR}/libs/system" boost-system-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/assert" boost-assert-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/config" boost-config-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/throw_exception" boost-throw_exception-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/variant2" boost-variant2-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/mp11" boost-mp11-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/winapi" boost-winapi-build EXCLUDE_FROM_ALL) +add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_ALL) +# EXCLUDE_FROM_ALL гарантирует, что мы собираем только то, что линкуем + +# Исполняемый файл сервера +add_executable(Server +server.cpp +../src/network/ClientState.h +) + +target_include_directories(Server PRIVATE ${BOOST_SRC_DIR}) + +# Линковка +target_link_libraries(Server + PRIVATE + boost_system # Скомпилированная часть для error_code + eigen_external_lib # Если планируешь использовать математику на сервере +) + +if(WIN32) + target_link_libraries(Server PRIVATE ws2_32 mswsock) +endif() + +# Дополнительный макрос, чтобы Asio знал, что мы работаем без устаревших функций +target_compile_definitions(Server PRIVATE BOOST_ASIO_NO_DEPRECATED) \ No newline at end of file diff --git a/server/server.cpp b/server/server.cpp new file mode 100644 index 0000000..1853f88 --- /dev/null +++ b/server/server.cpp @@ -0,0 +1,182 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define _USE_MATH_DEFINES +#include +#include "../src/network/ClientState.h" + +// Вспомогательный split +std::vector split(const std::string& s, char delimiter) { + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; +} + +namespace beast = boost::beast; +namespace http = beast::http; +namespace websocket = beast::websocket; +namespace net = boost::asio; +using tcp = net::ip::tcp; + +class Session; + +std::vector> g_sessions; +std::mutex g_sessions_mutex; + +class Session : public std::enable_shared_from_this { + websocket::stream ws_; + beast::flat_buffer buffer_; + int id_; + + ClientStateInterval timedClientStates; + + void process_message(const std::string& msg) { + auto now_server = std::chrono::system_clock::now(); + + auto parts = split(msg, ':'); + + if (parts.size() < 16) + { + throw std::runtime_error("Unknown message type received, too small"); + } + + uint64_t clientTimestamp = std::stoull(parts[1]); + + ClientState receivedState; + + receivedState.id = id_; + + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; + receivedState.lastUpdateServerTime = uptime_timepoint; + + if (parts[0] == "UPD") { + receivedState.handle_full_sync(parts, 2); + retranslateMessage(msg); + } + else + { + throw std::runtime_error("Unknown message type received: " + parts[0]); + } + + timedClientStates.add_state(receivedState); + } + + void retranslateMessage(const std::string& msg) + { + std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg; + + std::lock_guard lock(g_sessions_mutex); + for (auto& session : g_sessions) { + if (session->get_id() != id_) { // Не шлем отправителю + session->send_message(event_msg); + } + } + } + + +public: + explicit Session(tcp::socket&& socket, int id) + : ws_(std::move(socket)), id_(id) { + } + + void init() + { + } + + void run() { + + { + std::lock_guard lock(g_sessions_mutex); + g_sessions.push_back(shared_from_this()); + } + + ws_.async_accept([self = shared_from_this()](beast::error_code ec) { + if (ec) return; + std::cout << "Client " << self->id_ << " connected\n"; + self->init(); + self->send_message("ID:" + std::to_string(self->id_)); + self->do_read(); + }); + } + + void send_message(std::string msg) { + auto ss = std::make_shared(std::move(msg)); + ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); + } + + int get_id() const { + return id_; + } + +private: + void do_read() { + ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { + if (ec) + { + std::lock_guard lock(g_sessions_mutex); + g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), + [self](const std::shared_ptr& session) { + return session.get() == self.get(); + }), g_sessions.end()); + return; + } + + std::string msg = beast::buffers_to_string(self->buffer_.data()); + self->process_message(msg); + + self->buffer_.consume(self->buffer_.size()); + self->do_read(); + }); + } +}; + +void update_world(net::steady_timer& timer, net::io_context& ioc) { + + // TODO: Renew game state + + timer.expires_after(std::chrono::milliseconds(50)); + timer.async_wait([&](const boost::system::error_code& ec) { + if (!ec) update_world(timer, ioc); + }); +} + +int main() { + try { + net::io_context ioc; + tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} }; + int next_id = 1000; + + std::cout << "Server started on port 8080...\n"; + + auto do_accept = [&](auto& self_fn) -> void { + acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) { + if (!ec) { + std::make_shared(std::move(socket), next_id++)->run(); + } + self_fn(self_fn); + }); + }; + + + net::steady_timer timer(ioc); + update_world(timer, ioc); + + do_accept(do_accept); + ioc.run(); + } + catch (std::exception const& e) { + std::cerr << "Error: " << e.what() << std::endl; + } + return 0; +} \ No newline at end of file diff --git a/src/Environment.cpp b/src/Environment.cpp index 5f2bf6a..1fec925 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -10,26 +10,20 @@ #endif namespace ZL { + + int Environment::windowHeaderHeight = 0; int Environment::width = 0; int Environment::height = 0; -float Environment::zoom = 36.f; +float Environment::zoom = DEFAULT_ZOOM; -bool Environment::leftPressed = false; -bool Environment::rightPressed = false; -bool Environment::upPressed = false; -bool Environment::downPressed = false; - -bool Environment::settings_inverseVertical = false; - SDL_Window* Environment::window = nullptr; bool Environment::showMouse = false; bool Environment::exitGameLoop = false; -Eigen::Matrix3f Environment::shipMatrix = Eigen::Matrix3f::Identity(); Eigen::Matrix3f Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); @@ -37,10 +31,7 @@ bool Environment::tapDownHold = false; Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 }; Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 }; -Eigen::Vector3f Environment::shipPosition = {0,0,45000.f}; - -float Environment::shipVelocity = 0.f; - +ClientState Environment::shipState; const float Environment::CONST_Z_NEAR = 5.f; const float Environment::CONST_Z_FAR = 5000.f; diff --git a/src/Environment.h b/src/Environment.h index 0e01ee1..5d3c1ca 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -6,9 +6,12 @@ #include "render/OpenGlExtensions.h" #endif #include +#include "network/ClientState.h" namespace ZL { + constexpr float DEFAULT_ZOOM = 36.f; + class Environment { public: static int windowHeaderHeight; @@ -16,14 +19,6 @@ public: static int height; static float zoom; - static bool leftPressed; - static bool rightPressed; - static bool upPressed; - static bool downPressed; - - static bool settings_inverseVertical; - - static Eigen::Matrix3f shipMatrix; static Eigen::Matrix3f inverseShipMatrix; static SDL_Window* window; @@ -31,13 +26,11 @@ public: static bool showMouse; static bool exitGameLoop; - static bool tapDownHold; static Eigen::Vector2f tapDownStartPos; static Eigen::Vector2f tapDownCurrentPos; - static Eigen::Vector3f shipPosition; - static float shipVelocity; + static ClientState shipState; static const float CONST_Z_NEAR; static const float CONST_Z_FAR; diff --git a/src/Game.cpp b/src/Game.cpp index 46af558..7afcadb 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -14,6 +14,12 @@ #include #endif +#ifdef NETWORK +#include "network/WebSocketClient.h" +#else +#include "network/LocalClient.h" +#endif + namespace ZL { #ifdef EMSCRIPTEN @@ -134,33 +140,6 @@ namespace ZL ZL::BindOpenGlFunctions(); ZL::CheckGlError(); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "Game", "Start for Android"); - - const char* testFiles[] = { - "resources/shaders/default.vertex", - "resources/shaders/default_web.fragment", - nullptr - }; - - for (int i = 0; testFiles[i] != nullptr; i++) { - SDL_RWops* file = SDL_RWFromFile(testFiles[i], "rb"); - if (file) { - Sint64 size = SDL_RWsize(file); - __android_log_print(ANDROID_LOG_INFO, "Game", "Found: %s (size: %lld)", testFiles[i], size); - SDL_RWclose(file); - } - else { - __android_log_print(ANDROID_LOG_WARN, "Game", "Not found: %s (SDL error: %s)", - testFiles[i], SDL_GetError()); - } - } - - __android_log_print(ANDROID_LOG_ERROR, "ShaderManager", - "Step 1xxxxxxx"); -#endif - // Initialize renderer - #ifndef SIMPLIFIED renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); @@ -173,37 +152,11 @@ namespace ZL #else renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "ShaderManager", - "Step 2xxxxxxx"); -#endif renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 2"); -#endif renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 3"); -#endif - renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 4"); -#endif renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 5"); -#endif renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "ShaderManager", - "Step 1"); -#endif - #endif bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); @@ -284,16 +237,12 @@ namespace ZL }); uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { - Environment::shipVelocity = value * 1000.f; + int newVel = roundf(value * 10); + if (newVel != Environment::shipState.selectedVelocity) { + newShipVelocity = newVel; + } }); - /*uiManager.setSliderCallback("musicVolumeSlider", [this](const std::string& name, float value) { - std::cerr << "Music volume slider changed to: " << value << std::endl; - musicVolume = value; - Environment::shipVelocity = musicVolume * 20.0f; - }); - //#endif*/ - cubemapTexture = std::make_shared( std::array{ CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), @@ -325,10 +274,6 @@ namespace ZL spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); - //spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 }); - //spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 }); - //spaceshipBase.Move(Vector3f{ -0.52998, 0, 10 }); - spaceship.AssignFrom(spaceshipBase); spaceship.RefreshVBO(); @@ -338,16 +283,8 @@ namespace ZL boxTexture = std::make_unique(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE)); boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE); - std::cout << "Init step 1 " << std::endl; - boxCoordsArr = generateRandomBoxCoords(50); - - std::cout << "Init step 2 " << std::endl; - boxRenderArr.resize(boxCoordsArr.size()); - - std::cout << "Init step 3x " << std::endl; - for (int i = 0; i < boxCoordsArr.size(); i++) { boxRenderArr[i].AssignFrom(boxBase); @@ -357,25 +294,24 @@ namespace ZL boxAlive.resize(boxCoordsArr.size(), true); - std::cout << "Init step 4 " << std::endl; - if (!cfgLoaded) { throw std::runtime_error("Failed to load spark emitter config file!"); } - std::cout << "Init step 5 " << std::endl; renderer.InitOpenGL(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - std::cout << "Init step 6 " << std::endl; planetObject.init(); - std::cout << "Init step 7 " << std::endl; - //rockTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", CONST_ZIP_FILE)); - - std::cout << "Init step 8 " << std::endl; +#ifdef NETWORK + networkClient = std::make_unique(taskManager.getIOContext()); + networkClient->Connect("127.0.0.1", 8080); +#else + networkClient = std::make_unique(); + networkClient->Connect("", 0); +#endif } void Game::drawCubemap(float skyPercent) @@ -414,7 +350,7 @@ namespace ZL // 1. Вектор направления от центра планеты к игроку (в мировых координатах) // Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter) - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); // 2. Направление света в мировом пространстве //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); @@ -526,7 +462,7 @@ namespace ZL renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix({ 0.f, 0.f, 45000.f }); renderer.TranslateMatrix(boxCoordsArr[i].pos); renderer.RotateMatrix(boxCoordsArr[i].m); @@ -584,7 +520,7 @@ namespace ZL CheckGlError(); float skyPercent = 0.0; - float distance = planetObject.distanceToPlanetSurface(Environment::shipPosition); + float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distance > 1500.f) { skyPercent = 0.0f; @@ -601,65 +537,179 @@ namespace ZL drawCubemap(skyPercent); planetObject.draw(renderer); - if (planetObject.distanceToPlanetSurface(Environment::shipPosition) > 100.f) + if (planetObject.distanceToPlanetSurface(Environment::shipState.position) > 100.f) { glClear(GL_DEPTH_BUFFER_BIT); } drawShip(); + drawRemoteShips(); drawBoxes(); drawUI(); CheckGlError(); } + 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); + + renderer.EnableVertexAttribArray(vPositionName); + renderer.EnableVertexAttribArray(vTexCoordName); + + renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, + 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) { + + if (!remotePlayer.canFetchClientStateAtTime(now)) + { + continue; + } + + ClientState playerState = remotePlayer.fetchClientStateAtTime(now); + + + renderer.PushMatrix(); + renderer.LoadIdentity(); + + renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); + renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset + renderer.RotateMatrix(Environment::inverseShipMatrix); + renderer.TranslateMatrix(-Environment::shipState.position); + + + Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition; + renderer.TranslateMatrix(relativePos); + + // 3. Поворот врага + renderer.RotateMatrix(playerState.rotation); + + renderer.DrawVertexRenderStruct(spaceship); + renderer.PopMatrix(); + } + + renderer.PopProjectionMatrix(); + renderer.DisableVertexAttribArray(vPositionName); + renderer.DisableVertexAttribArray(vTexCoordName); + renderer.shaderManager.PopShader(); + + CheckGlError(); + } + void Game::processTickCount() { if (lastTickCount == 0) { - lastTickCount = SDL_GetTicks64(); + //lastTickCount = SDL_GetTicks64(); + lastTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); return; } - newTickCount = SDL_GetTicks64(); + //newTickCount = SDL_GetTicks64(); + newTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) { - size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ? - CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount; + + size_t delta = newTickCount - lastTickCount; + if (delta > CONST_MAX_TIME_INTERVAL) + { + throw std::runtime_error("Synchronization is lost"); + } + + auto now_ms = newTickCount; sparkEmitter.update(static_cast(delta)); planetObject.update(static_cast(delta)); - if (Environment::tapDownHold) { + static float pingTimer = 0.0f; + pingTimer += delta; + if (pingTimer >= 1000.0f) { + std::string pingMsg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); + networkClient->Send(pingMsg); + std::cout << "Sending: " << pingMsg << std::endl; + pingTimer = 0.0f; + } + + //Handle input: + + if (newShipVelocity != Environment::shipState.selectedVelocity) + { + Environment::shipState.selectedVelocity = newShipVelocity; + + std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent(); + networkClient->Send(msg); + } + + float discreteMag; + int discreteAngle; + + if (Environment::tapDownHold) { float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); - if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold + float rawMag = sqrtf(diffx * diffx + diffy * diffy); + float maxRadius = 200.0f; // Максимальный вынос джойстика + + if (rawMag > 10.0f) { // Мертвая зона + // 1. Дискретизируем отклонение (0.0 - 1.0 с шагом 0.1) + float normalizedMag = min(rawMag / maxRadius, 1.0f); + discreteMag = std::round(normalizedMag * 10.0f) / 10.0f; + + // 2. Дискретизируем угол (0-359 градусов) + // atan2 возвращает радианы, переводим в градусы + float radians = atan2f(diffy, diffx); + discreteAngle = static_cast(radians * 180.0f / M_PI); + if (discreteAngle < 0) discreteAngle += 360; + + } + else { - - float rotationPower = sqrtf(diffx * diffx + diffy * diffy); - float deltaAlpha = rotationPower * delta * static_cast(M_PI) / 500000.f; - - Eigen::Vector3f rotationDirection(diffy, diffx, 0.0f); - rotationDirection.normalize(); // Eigen-way нормализация - - // Создаем кватернион через AngleAxis - // Конструктор принимает (угол_в_радианах, ось_вращения) - Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, rotationDirection)); - - Matrix3f rotateMat = rotateQuat.toRotationMatrix(); - - Environment::shipMatrix = Environment::shipMatrix * rotateMat; - Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); - + discreteAngle = -1; + discreteMag = 0.0f; } } - - if (fabs(Environment::shipVelocity) > 0.01f) + else { - Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f }; - Vector3f velocityDirectionAdjusted = Environment::shipMatrix * velocityDirection; - Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; + discreteAngle = -1; + discreteMag = 0.0f; } + + if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { + Environment::shipState.discreteAngle = discreteAngle; + Environment::shipState.discreteMag = discreteMag; + + 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(); + for (auto& p : projectiles) { if (p && p->isActive()) { p->update(static_cast(delta), renderer); @@ -670,7 +720,7 @@ namespace ZL for (const auto& p : projectiles) { if (p && p->isActive()) { Vector3f worldPos = p->getPosition(); - Vector3f rel = worldPos - Environment::shipPosition; + Vector3f rel = worldPos - Environment::shipState.position; Vector3f camPos = Environment::inverseShipMatrix * rel; projCameraPoints.push_back(camPos); } @@ -705,15 +755,16 @@ namespace ZL } } if (shipAlive) { - float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipPosition); + float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distToSurface <= 0.0f) { - Vector3f dir = (Environment::shipPosition - PlanetData::PLANET_CENTER_OFFSET).normalized(); + + Vector3f dir = (Environment::shipState.position - PlanetData::PLANET_CENTER_OFFSET).normalized(); Vector3f collisionPoint = PlanetData::PLANET_CENTER_OFFSET + dir * PlanetData::PLANET_RADIUS; - Environment::shipPosition = PlanetData::PLANET_CENTER_OFFSET + dir * (PlanetData::PLANET_RADIUS + shipCollisionRadius + 0.1f); + Environment::shipState.position = PlanetData::PLANET_CENTER_OFFSET + dir * (PlanetData::PLANET_RADIUS + shipCollisionRadius + 0.1f); shipAlive = false; gameOver = true; - Environment::shipVelocity = 0.0f; + Environment::shipState.velocity = 0.0f; showExplosion = true; explosionEmitter.setUseWorldSpace(true); @@ -731,12 +782,14 @@ namespace ZL this->uiGameOverShown = false; this->showExplosion = false; this->explosionEmitter.setEmissionPoints(std::vector()); - Environment::shipPosition = Vector3f{ 0, 0, 45000.f }; - Environment::shipVelocity = 0.0f; - Environment::shipMatrix = Eigen::Matrix3f::Identity(); + + Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; + Environment::shipState.velocity = 0.0f; + Environment::shipState.rotation = Eigen::Matrix3f::Identity(); Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); - Environment::zoom = 18.f; + Environment::zoom = DEFAULT_ZOOM; Environment::tapDownHold = false; + uiManager.popMenu(); std::cerr << "Game restarted\n"; }); @@ -772,7 +825,7 @@ namespace ZL for (const auto& inst : instances) { Vector3f stoneWorld = inst.position; - Vector3f diff = Environment::shipPosition - stoneWorld; + Vector3f diff = Environment::shipState.position - stoneWorld; float maxScale = (std::max)({ inst.scale(0), inst.scale(1), inst.scale(2) }); float stoneRadius = StoneParams::BASE_SCALE * maxScale * 0.9f; @@ -791,17 +844,17 @@ namespace ZL } if (stoneCollided) { - Vector3f away = (Environment::shipPosition - collidedStonePos); + Vector3f away = (Environment::shipState.position - collidedStonePos); if (away.squaredNorm() <= 1e-6f) { away = Vector3f{ 0.0f, 1.0f, 0.0f }; } away.normalize(); - Environment::shipPosition = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f); + Environment::shipState.position = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f); shipAlive = false; gameOver = true; - Environment::shipVelocity = 0.0f; + Environment::shipState.velocity = 0.0f; showExplosion = true; explosionEmitter.setUseWorldSpace(true); @@ -833,11 +886,11 @@ namespace ZL this->uiGameOverShown = false; this->showExplosion = false; this->explosionEmitter.setEmissionPoints(std::vector()); - Environment::shipPosition = Vector3f{ 0, 0, 45000.f }; - Environment::shipVelocity = 0.0f; - Environment::shipMatrix = Eigen::Matrix3f::Identity(); + Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; + Environment::shipState.velocity = 0.0f; + Environment::shipState.rotation = Eigen::Matrix3f::Identity(); Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); - Environment::zoom = 18.f; + Environment::zoom = DEFAULT_ZOOM; Environment::tapDownHold = false; uiManager.popMenu(); std::cerr << "Game restarted\n"; @@ -860,7 +913,7 @@ namespace ZL for (int i = 0; i < boxCoordsArr.size(); ++i) { if (!boxAlive[i]) continue; Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f }; - Vector3f diff = Environment::shipPosition - boxWorld; + Vector3f diff = Environment::shipState.position - boxWorld; float thresh = shipCollisionRadius + boxCollisionRadius; if (diff.squaredNorm() <= thresh * thresh) { boxAlive[i] = false; @@ -871,7 +924,7 @@ namespace ZL boxRenderArr[i].texCoordVBO.reset(); showExplosion = true; - Vector3f rel = boxWorld - Environment::shipPosition; + Vector3f rel = boxWorld - Environment::shipState.position; Vector3f camPos = Environment::inverseShipMatrix * rel; explosionEmitter.setUseWorldSpace(true); explosionEmitter.setEmissionPoints(std::vector{ boxWorld }); @@ -886,7 +939,7 @@ namespace ZL for (auto& p : projectiles) { if (!p || !p->isActive()) continue; Vector3f ppos = p->getPosition(); - Vector3f projInBoxSpace = Environment::inverseShipMatrix * (ppos - Environment::shipPosition); + Vector3f projInBoxSpace = Environment::inverseShipMatrix * (ppos - Environment::shipState.position); for (int i = 0; i < boxCoordsArr.size(); ++i) { if (!boxAlive[i]) continue; Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 6.0f, 45000.0f }; @@ -913,7 +966,6 @@ namespace ZL } uiManager.update(static_cast(delta)); - //#endif lastTickCount = newTickCount; } } @@ -929,11 +981,11 @@ namespace ZL const float size = 0.5f; Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); + Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); for (const auto& lo : localOffsets) { - Vector3f worldPos = Environment::shipPosition + Environment::shipMatrix * lo; - Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipVelocity); + Vector3f worldPos = Environment::shipState.position + Environment::shipState.rotation * lo; + Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipState.velocity); for (auto& p : projectiles) { if (!p->isActive()) { @@ -992,31 +1044,6 @@ namespace ZL if (event.type == SDL_MOUSEBUTTONDOWN) { int mx = event.button.x; int my = event.button.y; - - uiManager.onMouseDown(mx, my); - - bool uiHandled = false; - - for (const auto& button : uiManager.findButton("") ? std::vector>{} : std::vector>{}) { - (void)button; - } - - auto pressedSlider = [&]() -> std::shared_ptr { - for (const auto& slider : uiManager.findSlider("") ? std::vector>{} : std::vector>{}) { - (void)slider; - } - return nullptr; - }(); - - if (!uiManager.isUiInteraction()) { - Environment::tapDownHold = true; - - Environment::tapDownStartPos(0) = mx; - Environment::tapDownStartPos(1) = my; - - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = my; - } handleDown(mx, my); } if (event.type == SDL_MOUSEBUTTONUP) { @@ -1032,6 +1059,7 @@ namespace ZL handleMotion(mx, my); } + /* if (event.type == SDL_MOUSEWHEEL) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { @@ -1048,27 +1076,14 @@ namespace ZL { if (event.key.keysym.sym == SDLK_i) { - Environment::shipVelocity += 500.f; + } - if (event.key.keysym.sym == SDLK_k) - { - Environment::shipVelocity -= 500.f; - } - if (event.key.keysym.sym == SDLK_o) - { - Environment::shipVelocity += 50.f; - //x = x + 2.0; - } - if (event.key.keysym.sym == SDLK_l) - { - Environment::shipVelocity -= 50.f; - //x = x - 2.0; - } - } + }*/ #endif } render(); mainThreadHandler.processMainThreadTasks(); + networkClient->Poll(); } void Game::handleDown(int mx, int my) @@ -1101,6 +1116,7 @@ namespace ZL Environment::tapDownCurrentPos(1) = my; } } + void Game::handleUp(int mx, int my) { int uiX = mx; @@ -1126,5 +1142,28 @@ namespace ZL } } + /* + std::string Game::formPingMessageContent() + { + Eigen::Quaternionf q(Environment::shipMatrix); + + std::string pingMsg = std::to_string(Environment::shipPosition.x()) + ":" + + std::to_string(Environment::shipPosition.y()) + ":" + + std::to_string(Environment::shipPosition.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()) + ":" + + std::to_string(Environment::currentAngularVelocity.x()) + ":" + + std::to_string(Environment::currentAngularVelocity.y()) + ":" + + std::to_string(Environment::currentAngularVelocity.z()) + ":" + + std::to_string(Environment::shipVelocity) + ":" + + std::to_string(Environment::shipSelectedVelocity) + ":" + + std::to_string(Environment::lastSentMagnitude) + ":" // Используем те же static переменные из блока ROT + + std::to_string(Environment::lastSentAngle); + + return pingMsg; + }*/ + } // namespace ZL diff --git a/src/Game.h b/src/Game.h index 9a9d45d..597c223 100644 --- a/src/Game.h +++ b/src/Game.h @@ -8,6 +8,7 @@ #include "UiManager.h" #include "Projectile.h" #include "utils/TaskManager.h" +#include "network/NetworkInterface.h" #include namespace ZL { @@ -34,6 +35,8 @@ namespace ZL { Renderer renderer; TaskManager taskManager; MainThreadHandler mainThreadHandler; + + std::unique_ptr networkClient; private: void processTickCount(); void drawScene(); @@ -41,7 +44,7 @@ namespace ZL { void drawShip(); void drawBoxes(); void drawUI(); - + void drawRemoteShips(); void fireProjectiles(); void handleDown(int mx, int my); @@ -59,6 +62,9 @@ namespace ZL { std::vector boxCoordsArr; std::vector boxRenderArr; + std::unordered_map latestRemotePlayers; + + float newShipVelocity = 0; static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_MAX_TIME_INTERVAL = 1000; diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index d162675..7d317bb 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -75,7 +75,7 @@ namespace ZL { if (particle.active) { Vector3f posCam; if (useWorldSpace) { - Vector3f rel = particle.position - Environment::shipPosition; + Vector3f rel = particle.position - Environment::shipState.position; posCam = Environment::inverseShipMatrix * rel; } else { @@ -94,7 +94,7 @@ namespace ZL { const auto& particle = *particlePtr; Vector3f posCam; if (useWorldSpace) { - Vector3f rel = particle.position - Environment::shipPosition; + Vector3f rel = particle.position - Environment::shipState.position; posCam = Environment::inverseShipMatrix * rel; } else { diff --git a/src/network/ClientState.cpp b/src/network/ClientState.cpp new file mode 100644 index 0000000..12438a1 --- /dev/null +++ b/src/network/ClientState.cpp @@ -0,0 +1,224 @@ +#include "ClientState.h" + + +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; + } +} + +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); + } +} + +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(); + + 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); + + 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); + + return pingMsg; +} + + +void ClientStateInterval::add_state(const ClientState& state) +{ + 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); + } + + auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME); + + while (timedStates.size() > 0 && timedStates[0].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; +} + +ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const { + + 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; + } + + + 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; + } + } + + closestState = timedStates[timedStates.size() - 1]; + closestState.apply_lag_compensation(targetTime); + return closestState; +} diff --git a/src/network/ClientState.h b/src/network/ClientState.h new file mode 100644 index 0000000..ac28def --- /dev/null +++ b/src/network/ClientState.h @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#define _USE_MATH_DEFINES +#include +#include + + +using std::min; +using std::max; + +constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; +constexpr float SHIP_ACCEL = 1.0f * 1000.0f; +constexpr float ROTATION_SENSITIVITY = 0.002f; + +constexpr long long SERVER_DELAY = 0; //ms +constexpr long long CLIENT_DELAY = 200; //ms +constexpr long long CUTOFF_TIME = 5000; //ms + +struct ClientState { + int id = 0; + Eigen::Vector3f position = { 0, 0, 45000.0f }; + Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity(); + Eigen::Vector3f currentAngularVelocity = Eigen::Vector3f::Zero(); + float velocity = 0.0f; + int selectedVelocity = 0; + float discreteMag = 0; + int discreteAngle = -1; + + // Для расчета лага + std::chrono::system_clock::time_point lastUpdateServerTime; + + void simulate_physics(size_t delta); + + void apply_lag_compensation(std::chrono::system_clock::time_point nowTime); + + void handle_full_sync(const std::vector& parts, int startFrom); + + std::string formPingMessageContent(); +}; + +struct ClientStateInterval +{ + std::vector timedStates; + + void add_state(const ClientState& state); + + bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; + + ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const; +}; + diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp new file mode 100644 index 0000000..264f526 --- /dev/null +++ b/src/network/LocalClient.cpp @@ -0,0 +1,18 @@ +#include "LocalClient.h" +#include + + +namespace ZL { + + void LocalClient::Connect(const std::string& host, uint16_t port) { + } + + void LocalClient::Poll() { + } + + void LocalClient::Send(const std::string& message) { + + } + + +} \ No newline at end of file diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h new file mode 100644 index 0000000..55994b3 --- /dev/null +++ b/src/network/LocalClient.h @@ -0,0 +1,25 @@ +#pragma once + +// WebSocketClient.h +#include "NetworkInterface.h" +#include + +namespace ZL { + class LocalClient : public INetworkClient { + private: + std::queue messageQueue; + public: + void Connect(const std::string& host, uint16_t port) override; + + void Poll() override; + + void Send(const std::string& message) override; + + bool IsConnected() const override { return true; } + int GetClientId() const { return 1; } + + std::unordered_map getRemotePlayers() override { + return std::unordered_map(); + } + }; +} \ No newline at end of file diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h new file mode 100644 index 0000000..4873f07 --- /dev/null +++ b/src/network/NetworkInterface.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include "ClientState.h" + +// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений +namespace ZL { + + class INetworkClient { + public: + virtual ~INetworkClient() = default; + virtual void Connect(const std::string& host, uint16_t port) = 0; + virtual void Send(const std::string& message) = 0; + virtual bool IsConnected() const = 0; + virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов + virtual std::unordered_map getRemotePlayers() = 0; + }; +} diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp new file mode 100644 index 0000000..ce9dd3e --- /dev/null +++ b/src/network/WebSocketClient.cpp @@ -0,0 +1,175 @@ +#ifdef NETWORK + +#include "WebSocketClient.h" +#include +#include + +// Вспомогательный split +std::vector split(const std::string& s, char delimiter) { + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; +} + +namespace ZL { + + void WebSocketClient::Connect(const std::string& host, uint16_t port) { + try { + boost::asio::ip::tcp::resolver resolver(ioc_); + auto const results = resolver.resolve(host, std::to_string(port)); + + ws_ = std::make_unique>(ioc_); + + // Выполняем синхронный коннект и handshake для простоты старта + boost::beast::get_lowest_layer(*ws_).connect(results); + ws_->handshake(host, "/"); + + connected = true; + + // Запускаем асинхронное чтение в пуле потоков TaskManager + startAsyncRead(); + + } + catch (std::exception& e) { + std::cerr << "Network Error: " << e.what() << std::endl; + } + } + + void WebSocketClient::startAsyncRead() { + ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { + if (!ec) { + std::string msg = boost::beast::buffers_to_string(buffer_.data()); + buffer_.consume(bytes); + processIncomingMessage(msg); + startAsyncRead(); + } + else { + connected = false; + } + }); + } + + void WebSocketClient::processIncomingMessage(const std::string& msg) { + // Логика парсинга... + if (msg.rfind("ID:", 0) == 0) { + clientId = std::stoi(msg.substr(3)); + } + + // Безопасно кладем в очередь для главного потока + std::lock_guard lock(queueMutex); + messageQueue.push(msg); + } + + void WebSocketClient::Poll() { + std::lock_guard lock(queueMutex); + + while (!messageQueue.empty()) { + + auto nowTime = std::chrono::system_clock::now(); + + //Apply server delay: + nowTime -= std::chrono::milliseconds(CLIENT_DELAY); + + auto now_ms = std::chrono::duration_cast( + nowTime.time_since_epoch() + ).count(); + + + + std::string msg = messageQueue.front(); + messageQueue.pop(); + + if (msg.rfind("EVENT:", 0) == 0) { + auto parts = split(msg, ':'); + if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... + + int remoteId = std::stoi(parts[1]); + std::string subType = parts[2]; + uint64_t sentTime = std::stoull(parts[3]); + + ClientState remoteState; + remoteState.id = remoteId; + + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(sentTime)) }; + remoteState.lastUpdateServerTime = uptime_timepoint; + + if (subType == "UPD") { + int startFrom = 4; + remoteState.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])); + remoteState.rotation = q.toRotationMatrix(); + + remoteState.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + remoteState.velocity = std::stof(parts[startFrom + 10]); + remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); + remoteState.discreteMag = std::stof(parts[startFrom + 12]); + remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); + } + else + { + throw std::runtime_error("Unknown EVENT subtype: " + subType); + } + + { + std::lock_guard pLock(playersMutex); + auto& rp = remotePlayers[remoteId]; + + rp.add_state(remoteState); + } + } + + } + } + void WebSocketClient::Send(const std::string& message) { + if (!connected) return; + + auto ss = std::make_shared(message); + + std::lock_guard lock(writeMutex_); + writeQueue_.push(ss); + + // Если сейчас ничего не записывается, инициируем первую запись + if (!isWriting_) { + doWrite(); + } + } + + void WebSocketClient::doWrite() { + // Эта функция всегда вызывается под мьютексом или из колбэка + if (writeQueue_.empty()) { + isWriting_ = false; + return; + } + + isWriting_ = true; + auto message = writeQueue_.front(); + + // Захватываем self (shared_from_this), чтобы объект не удалился во время записи + ws_->async_write( + boost::asio::buffer(*message), + [this, message](boost::beast::error_code ec, std::size_t) { + if (ec) { + connected = false; + return; + } + + std::lock_guard lock(writeMutex_); + writeQueue_.pop(); // Удаляем отправленное сообщение + doWrite(); // Проверяем следующее + } + ); + } +} + +#endif diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h new file mode 100644 index 0000000..d8b108f --- /dev/null +++ b/src/network/WebSocketClient.h @@ -0,0 +1,59 @@ +#pragma once + +#ifdef NETWORK + +// WebSocketClient.h +#include "NetworkInterface.h" +#include +#include +#include +#include +#include + +namespace ZL { + + class WebSocketClient : public INetworkClient { + private: + // Переиспользуем io_context из TaskManager + boost::asio::io_context& ioc_; + + // Объекты переехали в члены класса + std::unique_ptr> ws_; + boost::beast::flat_buffer buffer_; + + std::queue messageQueue; + std::mutex queueMutex; // Защита для messageQueue + + std::queue> writeQueue_; + bool isWriting_ = false; + std::mutex writeMutex_; // Отдельный мьютекс для очереди записи + + bool connected = false; + int clientId = -1; + + std::unordered_map remotePlayers; + std::mutex playersMutex; + + void startAsyncRead(); + void processIncomingMessage(const std::string& msg); + + public: + explicit WebSocketClient(boost::asio::io_context& ioc) : ioc_(ioc) {} + + void Connect(const std::string& host, uint16_t port) override; + + void Poll() override; + + void Send(const std::string& message) override; + void doWrite(); + + bool IsConnected() const override { return connected; } + int GetClientId() const { return clientId; } + + std::unordered_map getRemotePlayers() override { + std::lock_guard lock(playersMutex); + return remotePlayers; + } + }; +} +#endif diff --git a/src/planet/PlanetData.cpp b/src/planet/PlanetData.cpp index d4f2f02..a1a1254 100644 --- a/src/planet/PlanetData.cpp +++ b/src/planet/PlanetData.cpp @@ -1,4 +1,4 @@ -#include "PlanetData.h" +#include "PlanetData.h" #include #include #include @@ -10,13 +10,13 @@ namespace ZL { const float PlanetData::PLANET_RADIUS = 20000.f; const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; - // --- ( PlanetObject.cpp) --- + // --- Константы диапазонов (перенесены из PlanetObject.cpp) --- VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) { return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1; } - // () + // Вспомогательная функция для проекции (локальная) static Vector3f projectPointOnPlane(const Vector3f& P, const Vector3f& A, const Vector3f& B, const Vector3f& C) { Vector3f AB = B + A * (-1.0f); Vector3f AC = C + A * (-1.0f); @@ -138,18 +138,18 @@ namespace ZL { std::vector PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) { - const LodLevel& finalLod = planetMeshLods[currentLod]; // LOD + const LodLevel& finalLod = planetMeshLods[currentLod]; // Работаем с текущим активным LOD Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized(); int bestTriangle = -1; float maxDot = -1.0f; - // 1: "" - // , N- - // LOD0, . - // ( 5-6 ) + // Шаг 1: Быстрый поиск ближайшего треугольника по "центроиду" + // Чтобы не проверять все, можно проверять каждый N-й или использовать + // предварительно вычисленные центры для LOD0, чтобы сузить круг. + // Но для надежности пройдемся по массиву (для 5-6 подразделений это быстро) for (int i = 0; i < (int)finalLod.triangles.size(); ++i) { - // + // Вычисляем примерное направление на треугольник Vector3f triDir = (finalLod.triangles[i].data[0] + finalLod.triangles[i].data[1] + finalLod.triangles[i].data[2]).normalized(); @@ -172,22 +172,22 @@ namespace ZL { float currentDist = shipLocal.norm(); Vector3f targetDir = shipLocal.normalized(); - // ( / ) - // , . + // Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка) + // Подбери это значение так, чтобы камни вокруг корабля всегда были видны. const float desiredCoverageRadius = 3000.0f; - // . - // (currentDist ), - // , . + // Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния. + // Чем мы дальше (currentDist больше), тем меньше должен быть угол отклонения + // от нормали, чтобы захватить ту же площадь. float angle = atan2(desiredCoverageRadius, currentDist); float searchThreshold = cos(angle); - // , + // Ограничитель, чтобы не захватить всю планету или вообще ничего searchThreshold = std::clamp(searchThreshold, 0.90f, 0.9999f); std::vector result; for (int i = 0; i < (int)finalLod.triangles.size(); ++i) { - // ( LodLevel ) + // Используем центроид (можно кэшировать в LodLevel для скорости) Vector3f triDir = (finalLod.triangles[i].data[0] + finalLod.triangles[i].data[1] + finalLod.triangles[i].data[2]).normalized(); @@ -205,7 +205,7 @@ namespace ZL { std::vector output; for (const auto& t : input) { - // ID + // Вершины и их ID const Vector3f& a = t.data[0]; const Vector3f& b = t.data[1]; const Vector3f& c = t.data[2]; @@ -213,7 +213,7 @@ namespace ZL { const VertexID& id_b = t.ids[1]; const VertexID& id_c = t.ids[2]; - // 1. () + // 1. Вычисляем середины (координаты) Vector3f m_ab = ((a + b) * 0.5f).normalized(); Vector3f m_bc = ((b + c) * 0.5f).normalized(); Vector3f m_ac = ((a + c) * 0.5f).normalized(); @@ -226,12 +226,12 @@ namespace ZL { Vector3f pm_bc = m_bc; Vector3f pm_ac = m_ac; - // 2. ID + // 2. Вычисляем ID новых вершин VertexID id_mab = generateEdgeID(id_a, id_b); VertexID id_mbc = generateEdgeID(id_b, id_c); VertexID id_mac = generateEdgeID(id_a, id_c); - // 3. 4 + // 3. Формируем 4 новых треугольника output.emplace_back(Triangle{ {a, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0 output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1 output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2 @@ -282,31 +282,31 @@ namespace ZL { Vector2f(1.0f, 0.0f) }; - const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // - const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // + const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый + const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый - const float colorFrequency = 4.0f; // + const float colorFrequency = 4.0f; // Масштаб пятен for (const auto& t : lod.triangles) { - // --- ( GetRotationForTriangle) --- + // --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) --- Vector3f vA = t.data[0]; Vector3f vB = t.data[1]; Vector3f vC = t.data[2]; - Vector3f x_axis = (vC - vB).normalized(); // U + Vector3f x_axis = (vC - vB).normalized(); // Направление U Vector3f edge1 = vB - vA; Vector3f edge2 = vC - vA; - Vector3f z_axis = edge1.cross(edge2).normalized(); // + Vector3f z_axis = edge1.cross(edge2).normalized(); // Нормаль плоскости - // ( ) + // Проверка направления нормали наружу (от центра планеты) Vector3f centerToTri = (vA + vB + vC).normalized(); if (z_axis.dot(centerToTri) < 0) { z_axis = z_axis * -1.0f; } - Vector3f y_axis = z_axis.cross(x_axis).normalized(); // V + Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Направление V for (int i = 0; i < 3; ++i) { lod.vertexData.PositionData.push_back(t.data[i]); @@ -315,7 +315,7 @@ namespace ZL { lod.vertexData.TangentData.push_back(x_axis); lod.vertexData.BinormalData.push_back(y_axis); - // + // Используем один шум для выбора между розовым и желтым Vector3f dir = t.data[i].normalized(); float blendFactor = colorPerlin.noise( dir(0) * colorFrequency, @@ -323,10 +323,10 @@ namespace ZL { dir(2) * colorFrequency ); - // [-1, 1] [0, 1] + // Приводим шум из диапазона [-1, 1] в [0, 1] blendFactor = blendFactor * 0.5f + 0.5f; - // + // Линейная интерполяция между двумя цветами Vector3f finalColor; finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish); @@ -339,17 +339,17 @@ namespace ZL { LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) { const float t = (1.0f + std::sqrt(5.0f)) / 2.0f; - // 12 + // 12 базовых вершин икосаэдра std::vector icosaVertices = { {-1, t, 0}, { 1, t, 0}, {-1, -t, 0}, { 1, -t, 0}, { 0, -1, t}, { 0, 1, t}, { 0, -1, -t}, { 0, 1, -t}, { t, 0, -1}, { t, 0, 1}, {-t, 0, -1}, {-t, 0, 1} }; - // + // Нормализуем вершины for (auto& v : icosaVertices) v = v.normalized(); - // 20 + // 20 граней икосаэдра struct IndexedTri { int v1, v2, v3; }; std::vector faces = { {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, @@ -365,7 +365,7 @@ namespace ZL { tri.data[1] = icosaVertices[f.v2]; tri.data[2] = icosaVertices[f.v3]; - // ID ( ) + // Генерируем ID для базовых вершин (можно использовать их координаты) for (int i = 0; i < 3; ++i) { tri.ids[i] = std::to_string(tri.data[i](0)) + "_" + std::to_string(tri.data[i](1)) + "_" + @@ -374,15 +374,15 @@ namespace ZL { geometry.push_back(tri); } - // 3. N + // 3. Разбиваем N раз for (int i = 0; i < subdivisions; i++) { - geometry = subdivideTriangles(geometry, 0.0f); // + geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем } - // 4. LodLevel (v2tMap) + // 4. Создаем LodLevel и заполняем топологию (v2tMap) LodLevel lodLevel = createLodLevel(geometry); - // v2tMap ( ) + // Пересобираем v2tMap (она критична для релаксации) lodLevel.v2tMap.clear(); for (size_t i = 0; i < geometry.size(); ++i) { for (int j = 0; j < 3; ++j) { @@ -390,12 +390,12 @@ namespace ZL { } } - // 5. (Lloyd-like) - // 5-10 + // 5. Применяем итеративную релаксацию (Lloyd-like) + // 5-10 итераций достаточно для отличной сетки applySphericalRelaxation(lodLevel, 8); - // 6. - // ... ( Perlin) + // 6. Накладываем шум и обновляем атрибуты + // ... (твой код наложения шума через Perlin) recalculateMeshAttributes(lodLevel); return lodLevel; @@ -410,7 +410,7 @@ namespace ZL { for (auto const& [vID, connectedTris] : lod.v2tMap) { Vector3f centroid(0, 0, 0); - // + // Находим среднюю точку среди центров всех соседних треугольников for (int triIdx : connectedTris) { const auto& tri = lod.triangles[triIdx]; Vector3f faceCenter = (tri.data[0] + tri.data[1] + tri.data[2]) * (1.0f / 3.0f); @@ -419,11 +419,11 @@ namespace ZL { centroid = centroid * (1.0f / (float)connectedTris.size()); - // + // Проецируем обратно на единичную сферу newPositions[vID] = centroid.normalized(); } - // + // Синхронизируем данные в треугольниках for (auto& tri : lod.triangles) { for (int i = 0; i < 3; ++i) { tri.data[i] = newPositions[tri.ids[i]]; diff --git a/src/planet/PlanetData.h b/src/planet/PlanetData.h index 8c875da..9430239 100644 --- a/src/planet/PlanetData.h +++ b/src/planet/PlanetData.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "utils/Perlin.h" #include "render/Renderer.h" @@ -16,7 +16,7 @@ namespace ZL { struct Vector3fComparator { bool operator()(const Eigen::Vector3f& a, const Eigen::Vector3f& b) const { - // (x, y, z) + // Лексикографическое сравнение (x, затем y, затем z) if (a.x() != b.x()) return a.x() < b.x(); if (a.y() != b.y()) return a.y() < b.y(); return a.z() < b.z(); @@ -87,11 +87,11 @@ namespace ZL { std::array planetMeshLods; LodLevel planetAtmosphereLod; - int currentLod; // + int currentLod; // Логический текущий уровень детализации //std::map initialVertexMap; - // + // Внутренние методы генерации std::vector subdivideTriangles(const std::vector& inputTriangles, float noiseCoeff); LodLevel createLodLevel(const std::vector& triangles); void recalculateMeshAttributes(LodLevel& lod); @@ -101,17 +101,17 @@ namespace ZL { void init(); - // ( ) + // Методы доступа к данным (для рендерера) const LodLevel& getLodLevel(int level) const; const LodLevel& getAtmosphereLod() const; int getCurrentLodIndex() const; int getMaxLodIndex() const; - // + // Логика std::pair calculateZRange(float distanceToSurface); float distanceToPlanetSurfaceFast(const Vector3f& viewerPosition); - // , + // Возвращает индексы треугольников, видимых камерой std::vector getBestTriangleUnderCamera(const Vector3f& viewerPosition); std::vector getTrianglesUnderCameraNew2(const Vector3f& viewerPosition); diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index 9048b6c..1216607 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -101,15 +101,15 @@ namespace ZL { // 1. Проверка порога движения (оптимизация из текущего кода) float movementThreshold = 1.0f; - if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold + if ((Environment::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold && !triangleIndicesToDraw.empty()) { //processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач return; } - lastUpdatePos = Environment::shipPosition; + lastUpdatePos = Environment::shipState.position; // 2. Получаем список видимых треугольников - auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition); + auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position); std::sort(newIndices.begin(), newIndices.end()); // 3. Анализируем, что нужно загрузить @@ -305,7 +305,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -320,7 +320,7 @@ namespace ZL { renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); @@ -332,7 +332,7 @@ namespace ZL { Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании // Позиция камеры (корабля) в мире - renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); + renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); //renderer.RenderUniform1f("uHeightScale", 0.08f); renderer.RenderUniform1f("uHeightScale", 0.0f); @@ -345,7 +345,7 @@ namespace ZL { renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); // Направление от центра планеты к игроку для расчета дня/ночи - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); // Тот же фактор освещенности игрока @@ -394,7 +394,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vTexCoordName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -409,17 +409,17 @@ namespace ZL { renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); - renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data()); - //std::cout << "uViewPos" << Environment::shipPosition << std::endl; + 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(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); @@ -479,7 +479,7 @@ namespace ZL { renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); float currentZNear = zRange.first; float currentZFar = zRange.second; @@ -499,7 +499,7 @@ namespace ZL { renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix(-Environment::shipState.position); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); @@ -536,7 +536,7 @@ namespace ZL { renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) - Vector3f playerDirWorld = Environment::shipPosition.normalized(); + Vector3f playerDirWorld = Environment::shipState.position.normalized(); Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // Насколько игрок на свету @@ -549,7 +549,7 @@ namespace ZL { // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) renderer.RenderUniform3fv("uSkyColor", color.data()); - //Vector3f playerDirWorld = Environment::shipPosition.normalized(); + //Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); glEnable(GL_BLEND); diff --git a/src/planet/StoneObject.h b/src/planet/StoneObject.h index a2218b4..6827a74 100644 --- a/src/planet/StoneObject.h +++ b/src/planet/StoneObject.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "PlanetData.h" @@ -6,11 +6,11 @@ namespace ZL { struct StoneParams { - static const float BASE_SCALE; // - static const float MIN_AXIS_SCALE; // / - static const float MAX_AXIS_SCALE; // / - static const float MIN_PERTURBATION; // - static const float MAX_PERTURBATION; // + static const float BASE_SCALE; // Общий размер камня + static const float MIN_AXIS_SCALE; // Минимальное растяжение/сжатие по оси + static const float MAX_AXIS_SCALE; // Максимальное растяжение/сжатие по оси + static const float MIN_PERTURBATION; // Минимальное радиальное возмущение вершины + static const float MAX_PERTURBATION; // Максимальное радиальное возмущение вершины static const int STONES_PER_TRIANGLE; }; @@ -23,19 +23,19 @@ namespace ZL { }; enum class ChunkStatus { - Empty, // - Generating, // TaskManager (CPU) - ReadyToUpload, // , - Live // GPU + Empty, // Данных нет + Generating, // Задача в TaskManager (CPU) + ReadyToUpload, // Данные в памяти, ждут очереди в главный поток + Live // Загружено в GPU и готово к отрисовке }; struct StoneGroup { - // mesh.PositionData inflate() + // mesh.PositionData и прочие будут заполняться в inflate() VertexDataStruct mesh; std::vector> allInstances; - // + // Очищает старую геометрию и генерирует новую для указанных индексов std::vector inflate(int count); VertexRenderStruct inflateOne(int index, float scaleModifier); @@ -43,13 +43,13 @@ namespace ZL { std::vector statuses; - // + // Инициализация статусов при создании группы void initStatuses() { statuses.assign(allInstances.size(), ChunkStatus::Empty); } }; - // , + // Теперь возвращает заготовку со всеми параметрами, но без тяжелого меша StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& lodLevel); Triangle createLocalTriangle(const Triangle& sampleTri); diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h index f1c5eb3..66423f8 100644 --- a/src/utils/TaskManager.h +++ b/src/utils/TaskManager.h @@ -24,6 +24,11 @@ namespace ZL { // Graceful shutdown ~TaskManager(); + + boost::asio::io_context& getIOContext() + { + return ioContext; + } };