diff --git a/cmake/FetchDependencies.cmake b/cmake/FetchDependencies.cmake index f05529f..4dd118c 100644 --- a/cmake/FetchDependencies.cmake +++ b/cmake/FetchDependencies.cmake @@ -41,3 +41,9 @@ check_and_download("https://gitlab.com/libeigen/eigen/-/archive/5.0.0/eigen-5.0. # 6) Boost check_and_download("https://archives.boost.io/release/1.90.0/source/boost_1_90_0.zip" "boost_1_90_0.zip" "boost_1_90_0" "boost") + +# 7) FreeType +check_and_download("https://download.savannah.gnu.org/releases/freetype/freetype-2.14.1.tar.gz" "freetype-2.14.1.tar.gz" "freetype-2.14.1" "CMakeLists.txt") + +# 8) SDL_ttf +check_and_download("https://github.com/libsdl-org/SDL_ttf/archive/refs/tags/release-2.24.0.zip" "release-2.24.0.zip" "SDL_ttf-release-2.24.0" "CMakeLists.txt") diff --git a/cmake/ThirdParty.cmake b/cmake/ThirdParty.cmake index ea8ce95..99bc399 100644 --- a/cmake/ThirdParty.cmake +++ b/cmake/ThirdParty.cmake @@ -314,7 +314,215 @@ set_target_properties(libzip_external_lib PROPERTIES ) # =========================================== -# 5) Eigen (5.0.0.zip → eigen-5.0.0) - HEADER-ONLY +# 5) FreeType (2.14.1) - dependency for SDL_ttf +# =========================================== +set(FREETYPE_SRC_DIR "${THIRDPARTY_DIR}/freetype-2.14.1") +set(FREETYPE_BASE_DIR "${FREETYPE_SRC_DIR}/install") + +set(_have_freetype TRUE) +foreach(cfg IN LISTS BUILD_CONFIGS) + if(NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetype.lib" AND + NOT EXISTS "${FREETYPE_BASE_DIR}-${cfg}/lib/freetyped.lib") + set(_have_freetype FALSE) + endif() +endforeach() + +if(NOT _have_freetype) + foreach(cfg IN LISTS BUILD_CONFIGS) + log("Configuring FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + -G "${CMAKE_GENERATOR}" + -S "${FREETYPE_SRC_DIR}" + -B "${FREETYPE_SRC_DIR}/build-${cfg}" + -DCMAKE_INSTALL_PREFIX=${FREETYPE_BASE_DIR}-${cfg} + -DCMAKE_PREFIX_PATH="${ZLIB_INSTALL_DIR};${LIBPNG_INSTALL_DIR}" + -DCMAKE_DISABLE_FIND_PACKAGE_HarfBuzz=TRUE + -DCMAKE_DISABLE_FIND_PACKAGE_BZip2=TRUE + -DFT_DISABLE_BROTLI=ON + -DBUILD_SHARED_LIBS=OFF + RESULT_VARIABLE _ft_cfg_res + ) + if(NOT _ft_cfg_res EQUAL 0) + message(FATAL_ERROR "FreeType configure failed for ${cfg}") + endif() + + log("Building FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --build "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ft_build_res + ) + if(NOT _ft_build_res EQUAL 0) + message(FATAL_ERROR "FreeType build failed for ${cfg}") + endif() + + log("Installing FreeType (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --install "${FREETYPE_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ft_inst_res + ) + if(NOT _ft_inst_res EQUAL 0) + message(FATAL_ERROR "FreeType install failed for ${cfg}") + endif() + endforeach() +endif() + +set(_ft_debug_lib "") +foreach(cand + "${FREETYPE_BASE_DIR}-Debug/lib/freetyped.lib" + "${FREETYPE_BASE_DIR}-Debug/lib/freetype.lib" +) + if(EXISTS "${cand}") + set(_ft_debug_lib "${cand}") + break() + endif() +endforeach() + +set(_ft_release_lib "") +foreach(cand + "${FREETYPE_BASE_DIR}-Release/lib/freetype.lib" +) + if(EXISTS "${cand}") + set(_ft_release_lib "${cand}") + break() + endif() +endforeach() + +if(_ft_debug_lib STREQUAL "" OR _ft_release_lib STREQUAL "") + message(FATAL_ERROR "FreeType libs not found in ${FREETYPE_BASE_DIR}-Debug/Release") +endif() + +add_library(freetype_external_lib UNKNOWN IMPORTED GLOBAL) +set_target_properties(freetype_external_lib PROPERTIES + IMPORTED_LOCATION_DEBUG "${_ft_debug_lib}" + IMPORTED_LOCATION_RELEASE "${_ft_release_lib}" + INTERFACE_INCLUDE_DIRECTORIES + "$,${FREETYPE_BASE_DIR}-Debug/include,${FREETYPE_BASE_DIR}-Release/include>" +) + +# =========================================== +# 6) SDL_ttf (2.24.0) +# =========================================== +set(SDL2TTF_SRC_DIR "${THIRDPARTY_DIR}/SDL_ttf-release-2.24.0") +set(SDL2TTF_BASE_DIR "${SDL2TTF_SRC_DIR}/install") + +set(_have_sdl2ttf TRUE) +foreach(cfg IN LISTS BUILD_CONFIGS) + if(NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttf.lib" AND + NOT EXISTS "${SDL2TTF_BASE_DIR}-${cfg}/lib/SDL2_ttfd.lib") + set(_have_sdl2ttf FALSE) + endif() +endforeach() + +if(NOT _have_sdl2ttf) + foreach(cfg IN LISTS BUILD_CONFIGS) + + if(cfg STREQUAL "Debug") + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2d.lib") + else() + set(_SDL2_LIB "${SDL2_INSTALL_DIR}/lib/SDL2.lib") + endif() + + set(_FT_PREFIX "${FREETYPE_BASE_DIR}-${cfg}") + + set(_FT_LIB "") + foreach(cand + "${_FT_PREFIX}/lib/freetyped.lib" + "${_FT_PREFIX}/lib/freetype.lib" + ) + if(EXISTS "${cand}") + set(_FT_LIB "${cand}") + break() + endif() + endforeach() + + if(_FT_LIB STREQUAL "") + message(FATAL_ERROR "FreeType library not found for ${cfg}") + endif() + + log("Configuring SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + -G "${CMAKE_GENERATOR}" + -S "${SDL2TTF_SRC_DIR}" + -B "${SDL2TTF_SRC_DIR}/build-${cfg}" + -DCMAKE_INSTALL_PREFIX=${SDL2TTF_BASE_DIR}-${cfg} + -DCMAKE_PREFIX_PATH=${_FT_PREFIX};${SDL2_INSTALL_DIR} + -DSDL2_LIBRARY=${_SDL2_LIB} + -DSDL2_INCLUDE_DIR=${SDL2_INSTALL_DIR}/include/SDL2 + -DFREETYPE_LIBRARY=${_FT_LIB} + -DFREETYPE_INCLUDE_DIR=${_FT_PREFIX}/include + -DFREETYPE_INCLUDE_DIRS=${_FT_PREFIX}/include + -DSDL2TTF_VENDORED=OFF + -DSDL2TTF_SAMPLES=OFF + RESULT_VARIABLE _ttf_cfg_res + ) + if(NOT _ttf_cfg_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf configure failed for ${cfg}") + endif() + + log("Building SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --build "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ttf_build_res + ) + if(NOT _ttf_build_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf build failed for ${cfg}") + endif() + + log("Installing SDL_ttf (${cfg}) ...") + execute_process( + COMMAND ${CMAKE_COMMAND} + --install "${SDL2TTF_SRC_DIR}/build-${cfg}" --config ${cfg} + RESULT_VARIABLE _ttf_inst_res + ) + if(NOT _ttf_inst_res EQUAL 0) + message(FATAL_ERROR "SDL_ttf install failed for ${cfg}") + endif() + + endforeach() +endif() + +set(_ttf_debug_lib "") +foreach(cand + "${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttfd.lib" + "${SDL2TTF_BASE_DIR}-Debug/lib/SDL2_ttf.lib" +) + if(EXISTS "${cand}") + set(_ttf_debug_lib "${cand}") + break() + endif() +endforeach() + +set(_ttf_release_lib "") +foreach(cand + "${SDL2TTF_BASE_DIR}-Release/lib/SDL2_ttf.lib" +) + if(EXISTS "${cand}") + set(_ttf_release_lib "${cand}") + break() + endif() +endforeach() + +if(_ttf_debug_lib STREQUAL "" OR _ttf_release_lib STREQUAL "") + message(FATAL_ERROR "SDL_ttf libs not found in install-Debug / install-Release") +endif() + +add_library(SDL2_ttf_external_lib UNKNOWN IMPORTED GLOBAL) +set_target_properties(SDL2_ttf_external_lib PROPERTIES + IMPORTED_LOCATION_DEBUG "${_ttf_debug_lib}" + IMPORTED_LOCATION_RELEASE "${_ttf_release_lib}" + INTERFACE_INCLUDE_DIRECTORIES + "$,${SDL2TTF_BASE_DIR}-Debug/include,${SDL2TTF_BASE_DIR}-Release/include>;$,${SDL2TTF_BASE_DIR}-Debug/include/SDL2,${SDL2TTF_BASE_DIR}-Release/include/SDL2>" + INTERFACE_LINK_LIBRARIES + "SDL2_external_lib;freetype_external_lib" +) + +# =========================================== +# 7) Eigen (5.0.0.zip → eigen-5.0.0) - HEADER-ONLY # =========================================== set(EIGEN_SRC_DIR "${THIRDPARTY_DIR}/eigen-5.0.0") @@ -324,7 +532,7 @@ if(NOT TARGET eigen_external_lib) endif() # =========================================== -# 6) Boost (1.90.0) - HEADER-ONLY +# 8) Boost (1.90.0) - HEADER-ONLY # =========================================== set(BOOST_VERSION "1.90.0") set(BOOST_ARCHIVE_NAME "boost_1_90_0.zip") diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 0f68441..36e6ee7 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 ) @@ -83,6 +91,8 @@ target_link_libraries(space-game001 PRIVATE libpng_external_lib zlib_external_lib libzip_external_lib + freetype_external_lib + SDL2_ttf_external_lib eigen_external_lib boost_external_lib ) @@ -106,6 +116,8 @@ if (WIN32) set(ZLIB_DLL_SRC "$,${ZLIB_INSTALL_DIR}/bin/zlibd.dll,${ZLIB_INSTALL_DIR}/bin/zlib.dll>") set(ZLIB_DLL_DST "$,$/zlibd.dll,$/zlib.dll>") + + set(SDL2TTF_DLL_SRC "$,${SDL2TTF_BASE_DIR}-Debug/bin/SDL2_ttfd.dll,${SDL2TTF_BASE_DIR}-Release/bin/SDL2_ttf.dll>") add_custom_command(TARGET space-game001 POST_BUILD @@ -124,6 +136,10 @@ if (WIN32) COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ZLIB_DLL_SRC}" "${ZLIB_DLL_DST}" + + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${SDL2TTF_DLL_SRC}" + "$" ) endif() diff --git a/resources/config/explosion_config.json b/resources/config/explosion_config.json index 2e461e8..fc0a2bc 100644 --- a/resources/config/explosion_config.json +++ b/resources/config/explosion_config.json @@ -9,7 +9,7 @@ "lifeTimeRange": [200.0, 800.0], "emissionRate": 50.0, "maxParticles": 5, - "particleSize": 0.5, + "particleSize": 2, "biasX": 0.1, "shaderProgramName": "default" } \ No newline at end of file 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 479121f..f4a569a 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,6 +1,7 @@ #include "Game.h" #include "AnimatedModel.h" #include "BoneAnimatedModel.h" +#include "planet/PlanetData.h" #include "utils/Utils.h" #include "render/OpenGlExtensions.h" #include @@ -13,6 +14,12 @@ #include #endif +#ifdef NETWORK +#include "network/WebSocketClient.h" +#else +#include "network/LocalClient.h" +#endif + namespace ZL { #ifdef EMSCRIPTEN @@ -133,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); @@ -172,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); @@ -283,7 +237,10 @@ 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; + } }); // Добавляем джойстик для управления кораблём @@ -321,7 +278,7 @@ namespace ZL //spaceshipTexture = std::make_unique(CreateTextureDataFromPng("resources/DefaultMaterial_BaseColor_shine.png", CONST_ZIP_FILE)); //spaceshipBase = LoadFromTextFile02("resources/spaceship006.txt", CONST_ZIP_FILE); //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); - + //spaceshipTexture = std::make_unique(CreateTextureDataFromPng("./resources/cap_D.png", CONST_ZIP_FILE)); //spaceshipBase = LoadFromTextFile02("./resources/spaceship006x.txt", CONST_ZIP_FILE); //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); @@ -332,9 +289,11 @@ namespace ZL //spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); //spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 }); + + spaceshipBase.Move(Vector3f{ -0.52998, 0, -10 }); + //spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); - //spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI / 2.0, Eigen::Vector3f::UnitY())).toRotationMatrix()); spaceship.AssignFrom(spaceshipBase); spaceship.RefreshVBO(); @@ -344,16 +303,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); @@ -363,25 +314,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) @@ -420,7 +370,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(); @@ -537,7 +487,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); @@ -595,7 +545,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; @@ -612,42 +562,144 @@ 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)); - // Управление кораблём через джойстик + int discreteAngle = -1; + float discreteMag = 0; + auto joystick = uiManager.findJoystick("shipJoystick"); if (joystick && joystick->isActive) { float joyX = joystick->getDirectionX(); // -1..1 float joyY = joystick->getDirectionY(); // -1..1 float magnitude = joystick->getMagnitude(); // 0..1 + discreteMag = roundf(magnitude * 10) * 0.1f; + + float angleY = std::atan2(-joyX, -joyY); + + discreteAngle = static_cast(angleY * 180.0f / M_PI); + if (discreteAngle < 0) discreteAngle += 360; + + } + + + + +//<<<<<<< HEAD + // Управление кораблём через джойстик + /*auto joystick = uiManager.findJoystick("shipJoystick"); + if (joystick && joystick->isActive) { + float joyX = joystick->getDirectionX(); // -1..1 + float joyY = joystick->getDirectionY(); // -1..1 + float magnitude = joystick->getMagnitude(); // 0..1 + if (magnitude > 0.1f) { // Скорость пропорциональна отклонению джойстика - Environment::shipVelocity = magnitude * 500.0f * static_cast(delta) / 100.0f; + Environment::shipState.velocity = magnitude * 500.0f * static_cast(delta) / 100.0f; // Направление джойстика относительно камеры // joyY отрицательный = вперёд, joyX = влево/вправо @@ -658,10 +710,10 @@ namespace ZL Matrix3f localRotMat = localRotation.toRotationMatrix(); // Преобразуем в мировую ориентацию: камера * локальный_поворот - shipWorldOrientation = Environment::shipMatrix * localRotMat; + shipWorldOrientation = Environment::shipState.rotation * localRotMat; } else { - Environment::shipVelocity = 0.0f; + Environment::shipState.velocity = 0.0f; } } else if (Environment::tapDownHold && !uiManager.isUiInteraction()) @@ -682,8 +734,8 @@ namespace ZL Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, rotationDirection)); Matrix3f rotateMat = rotateQuat.toRotationMatrix(); - Environment::shipMatrix = Environment::shipMatrix * rotateMat; - Environment::inverseShipMatrix = Environment::shipMatrix.inverse(); + Environment::shipState.rotation = Environment::shipState.rotation * rotateMat; + Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); // плавное вращение (ВАЖНО!) Environment::tapDownStartPos = Environment::tapDownCurrentPos; @@ -691,15 +743,53 @@ namespace ZL } } + float discreteMag; + int discreteAngle; - if (std::fabs(Environment::shipVelocity) > 0.01f) - { - // Корабль двигается вперёд в направлении своей ориентации - Vector3f localForward = Vector3f{ 0, 0, -1 }; - Vector3f worldMove = shipWorldOrientation * localForward * Environment::shipVelocity * static_cast(delta) / 1000.f; + if (Environment::tapDownHold) { + float diffx = Environment::tapDownCurrentPos(0) - Environment::tapDownStartPos(0); + float diffy = Environment::tapDownCurrentPos(1) - Environment::tapDownStartPos(1); - Environment::shipPosition = Environment::shipPosition + worldMove; + 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 + { + discreteAngle = -1; + discreteMag = 0.0f; + } } + else + { + discreteAngle = -1; + discreteMag = 0.0f; +>>>>>>> main*/ + //} + + + 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()) { @@ -711,7 +801,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); } @@ -737,22 +827,31 @@ namespace ZL projectileEmitter.update(static_cast(delta)); explosionEmitter.update(static_cast(delta)); + if (showExplosion) { + uint64_t now = SDL_GetTicks64(); + if (lastExplosionTime != 0 && now - lastExplosionTime >= explosionDurationMs) { + showExplosion = false; + explosionEmitter.setEmissionPoints(std::vector()); + explosionEmitter.setUseWorldSpace(false); + } + } if (shipAlive) { - float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipPosition); + float distToSurface = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distToSurface <= 0.0f) { - Vector3f localForward = { 0,0,-1 }; - Vector3f worldForward = (Environment::shipMatrix * localForward).normalized(); - const float backDistance = 400.0f; - Environment::shipPosition = Environment::shipPosition - worldForward * backDistance; + + Vector3f dir = (Environment::shipState.position - PlanetData::PLANET_CENTER_OFFSET).normalized(); + Vector3f collisionPoint = PlanetData::PLANET_CENTER_OFFSET + dir * PlanetData::PLANET_RADIUS; + 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(false); - explosionEmitter.setEmissionPoints(std::vector{ Vector3f{ 0.0f,0.0f,0.0f } }); + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ collisionPoint }); explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n"; @@ -764,8 +863,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::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 = DEFAULT_ZOOM; + Environment::tapDownHold = false; + uiManager.popMenu(); std::cerr << "Game restarted\n"; }); @@ -781,12 +886,115 @@ namespace ZL } } } + else { + bool stoneCollided = false; + int collidedTriIdx = -1; + Vector3f collidedStonePos = Vector3f{ 0.0f, 0.0f, 0.0f }; + float collidedStoneRadius = 0.0f; + + for (int triIdx : planetObject.triangleIndicesToDraw) { + if (triIdx < 0 || triIdx >= static_cast(planetObject.planetStones.allInstances.size())) + continue; + + if (planetObject.planetStones.statuses.size() <= static_cast(triIdx)) + continue; + + if (planetObject.planetStones.statuses[triIdx] != ChunkStatus::Live) + continue; + + const auto& instances = planetObject.planetStones.allInstances[triIdx]; + for (const auto& inst : instances) { + + Vector3f stoneWorld = inst.position; + 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; + float thresh = shipCollisionRadius + stoneRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + stoneCollided = true; + collidedTriIdx = triIdx; + collidedStonePos = stoneWorld; + collidedStoneRadius = stoneRadius; + break; + } + } + + if (stoneCollided) break; + } + + if (stoneCollided) { + Vector3f away = (Environment::shipState.position - collidedStonePos); + if (away.squaredNorm() <= 1e-6f) { + away = Vector3f{ 0.0f, 1.0f, 0.0f }; + } + away.normalize(); + + Environment::shipState.position = collidedStonePos + away * (collidedStoneRadius + shipCollisionRadius + 0.1f); + + shipAlive = false; + gameOver = true; + Environment::shipState.velocity = 0.0f; + showExplosion = true; + + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ collidedStonePos }); + explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); + + std::cerr << "GAME OVER: collision with stone on triangle " << collidedTriIdx << std::endl; + + if (collidedTriIdx >= 0 && collidedTriIdx < static_cast(planetObject.stonesToRender.size())) { + planetObject.stonesToRender[collidedTriIdx].data.PositionData.clear(); + planetObject.stonesToRender[collidedTriIdx].vao.reset(); + planetObject.stonesToRender[collidedTriIdx].positionVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].normalVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].tangentVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].binormalVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].colorVBO.reset(); + planetObject.stonesToRender[collidedTriIdx].texCoordVBO.reset(); + } + if (collidedTriIdx >= 0 && collidedTriIdx < static_cast(planetObject.planetStones.statuses.size())) { + planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty; + } + + if (!uiGameOverShown) { + if (uiManager.pushMenuFromFile("resources/config/game_over.json", this->renderer, CONST_ZIP_FILE)) { + uiManager.setButtonCallback("restartButton", [this](const std::string& name) { + this->shipAlive = true; + this->gameOver = false; + this->uiGameOverShown = false; + this->showExplosion = false; + this->explosionEmitter.setEmissionPoints(std::vector()); + 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 = DEFAULT_ZOOM; + Environment::tapDownHold = false; + uiManager.popMenu(); + std::cerr << "Game restarted\n"; + }); + + uiManager.setButtonCallback("gameOverExitButton", [this](const std::string& name) { + Environment::exitGameLoop = true; + }); + + uiGameOverShown = true; + } + else { + std::cerr << "Failed to load game_over.json\n"; + } + } + } + } } 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; @@ -797,17 +1005,48 @@ 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 }); explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); std::cerr << "Box destroyed at index " << i << std::endl; } } + + const float projectileHitRadius = 1.5f; + for (auto& p : projectiles) { + if (!p || !p->isActive()) continue; + Vector3f ppos = p->getPosition(); + 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 }; + Vector3f dd = ppos - boxWorld; + float thresh = boxCollisionRadius + projectileHitRadius; + if (dd.squaredNorm() <= thresh * thresh) { + boxAlive[i] = false; + boxRenderArr[i].data.PositionData.clear(); + boxRenderArr[i].vao.reset(); + boxRenderArr[i].positionVBO.reset(); + boxRenderArr[i].texCoordVBO.reset(); + + showExplosion = true; + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ boxWorld }); + explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); + + p->deactivate(); + std::cerr << "Box destroyed by projectile at index " << i << std::endl; + break; + } + } + } + uiManager.update(static_cast(delta)); - //#endif lastTickCount = newTickCount; } } @@ -822,14 +1061,12 @@ namespace ZL const float lifeMs = 5000.0f; const float size = 0.5f; - // Снаряды летят в направлении корабля (shipWorldOrientation) - Vector3f localForward = { 0, 0, -1 }; - Vector3f worldForward = (shipWorldOrientation * localForward).normalized(); + Vector3f localForward = { 0,0,-1 }; + Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); for (const auto& lo : localOffsets) { - // Позиция снаряда относительно корабля - Vector3f worldPos = Environment::shipPosition + shipWorldOrientation * 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()) { @@ -888,31 +1125,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) { @@ -928,6 +1140,7 @@ namespace ZL handleMotion(mx, my); } + /* if (event.type == SDL_MOUSEWHEEL) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { @@ -944,27 +1157,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) @@ -994,6 +1194,7 @@ namespace ZL isDraggingCamera = true; } } + void Game::handleUp(int mx, int my) { int uiX = mx; @@ -1006,6 +1207,7 @@ namespace ZL uiManager.onMouseUp(uiX, uiY); // Сбрасываем состояние если отпустили джойстик + /* if (wasJoystickActive) { Environment::shipVelocity = 0.0f; shipMoveLockActive = false; @@ -1024,7 +1226,7 @@ namespace ZL isDraggingShip = false; isDraggingCamera = false; - } + }*/ } void Game::handleMotion(int mx, int my) @@ -1040,5 +1242,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 52c9e27..2d6e22d 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; @@ -87,7 +93,6 @@ namespace ZL { int maxProjectiles = 32; std::vector shipLocalEmissionPoints; - bool isDraggingShip = false; bool isDraggingCamera = false; Matrix3f rotateShipMat = Matrix3f::Identity(); // Локальный поворот от джойстика @@ -95,14 +100,15 @@ namespace ZL { bool shipMoveLockActive = false; Matrix3f lockedCameraMat = Matrix3f::Identity(); - bool shipAlive = true; bool gameOver = false; std::vector boxAlive; - float shipCollisionRadius = 3.5f; + float shipCollisionRadius = 15.0f; float boxCollisionRadius = 2.0f; bool uiGameOverShown = false; bool showExplosion = false; + uint64_t lastExplosionTime = 0; + const uint64_t explosionDurationMs = 500; }; diff --git a/src/Projectile.h b/src/Projectile.h index 1a5a62f..872177f 100644 --- a/src/Projectile.h +++ b/src/Projectile.h @@ -18,7 +18,7 @@ namespace ZL { bool isActive() const { return active; } Vector3f getPosition() const { return pos; } - + void deactivate() { active = false; } private: Vector3f pos; Vector3f vel; 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; + } };