#include "Game.h" #include "AnimatedModel.h" #include "BoneAnimatedModel.h" #include "planet/PlanetData.h" #include "utils/Utils.h" #include "render/OpenGlExtensions.h" #include #include "render/TextureManager.h" #include "TextModel.h" #include #include #include #include #ifdef __ANDROID__ #include #endif #ifdef NETWORK #include "network/WebSocketClient.h" #else #include "network/LocalClient.h" #endif namespace ZL { #ifdef EMSCRIPTEN const char* CONST_ZIP_FILE = "resources.zip"; #else const char* CONST_ZIP_FILE = ""; #endif static bool g_exitBgAnimating = false; float x = 0; Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen) { std::normal_distribution<> distrib(0.0, 1.0); Eigen::Quaternionf randomQuat = { (float)distrib(gen), (float)distrib(gen), (float)distrib(gen), (float)distrib(gen) }; return randomQuat.normalized(); } std::vector generateRandomBoxCoords(int N) { const float MIN_DISTANCE = 3.0f; const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; const float MIN_COORD = -100.0f; const float MAX_COORD = 100.0f; const int MAX_ATTEMPTS = 1000; std::vector boxCoordsArr; std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD); int generatedCount = 0; while (generatedCount < N) { bool accepted = false; int attempts = 0; while (!accepted && attempts < MAX_ATTEMPTS) { Vector3f newPos( (float)distrib(gen), (float)distrib(gen), (float)distrib(gen) ); accepted = true; for (const auto& existingBox : boxCoordsArr) { Vector3f diff = newPos - existingBox.pos; float distanceSquared = diff.squaredNorm(); if (distanceSquared < MIN_DISTANCE_SQUARED) { accepted = false; break; } } if (accepted) { Eigen::Quaternionf randomQuat = generateRandomQuaternion(gen); Matrix3f randomMatrix = randomQuat.toRotationMatrix(); boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix }); generatedCount++; } attempts++; } if (!accepted) { break; } } return boxCoordsArr; } static Eigen::Matrix4f makeViewMatrix_FromYourCamera() { Eigen::Matrix4f Tz = Eigen::Matrix4f::Identity(); Tz(2, 3) = -1.0f * ZL::Environment::zoom; Eigen::Matrix4f R = Eigen::Matrix4f::Identity(); R.block<3, 3>(0, 0) = ZL::Environment::inverseShipMatrix; Eigen::Matrix4f Tship = Eigen::Matrix4f::Identity(); Tship(0, 3) = -ZL::Environment::shipState.position.x(); Tship(1, 3) = -ZL::Environment::shipState.position.y(); Tship(2, 3) = -ZL::Environment::shipState.position.z(); return Tz * R * Tship; } static Eigen::Matrix4f makePerspective(float fovyRadians, float aspect, float zNear, float zFar) { // Стандартная перспектива float f = 1.0f / std::tan(fovyRadians * 0.5f); Eigen::Matrix4f P = Eigen::Matrix4f::Zero(); P(0, 0) = f / aspect; P(1, 1) = f; P(2, 2) = (zFar + zNear) / (zNear - zFar); P(2, 3) = (2.0f * zFar * zNear) / (zNear - zFar); P(3, 2) = -1.0f; return P; } bool Game::worldToScreen(const Vector3f& world, float& outX, float& outY, float& outDepth) const { // Матрицы должны совпасть с drawBoxes/drawShip по смыслу float aspect = static_cast(Environment::width) / static_cast(Environment::height); Eigen::Matrix4f V = makeViewMatrix_FromYourCamera(); Eigen::Matrix4f P = makePerspective(1.0f / 1.5f, aspect, Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); Eigen::Vector4f w(world.x(), world.y(), world.z(), 1.0f); Eigen::Vector4f clip = P * V * w; if (clip.w() <= 0.0001f) return false; // позади камеры Eigen::Vector3f ndc = clip.head<3>() / clip.w(); // [-1..1] outDepth = ndc.z(); // В пределах экрана? // (можно оставить, можно клампить) float sx = (ndc.x() * 0.5f + 0.5f) * Environment::width; float sy = (ndc.y() * 0.5f + 0.5f) * Environment::height; outX = sx; outY = sy; // Можно отсеять те, что вне: if (sx < -200 || sx > Environment::width + 200) return false; if (sy < -200 || sy > Environment::height + 200) return false; return true; } void Game::drawBoxesLabels() { if (!textRenderer) return; // Текст рисуем как 2D поверх всего 3D, но ДО drawUI или после — как хочешь. // Чтобы подписи были поверх — делай после drawBoxes и до drawUI (как мы и вставили). glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (size_t i = 0; i < boxCoordsArr.size(); ++i) { if (i >= boxAlive.size() || !boxAlive[i]) continue; if (i >= boxLabels.size()) continue; // ВАЖНО: твои боксы рисуются с Translate({0,0,45000}) + pos Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f }; // Чуть выше бокса по Y (или по Z — как нравится) Vector3f labelWorld = boxWorld + Vector3f{ 0.0f, 2.2f, 0.0f }; float sx, sy, depth; if (!worldToScreen(labelWorld, sx, sy, depth)) continue; // В твоей UI-системе Y обычно перевёрнут (ты делаешь uiY = height - my). // Наш worldToScreen отдаёт Y в системе "низ=0, верх=height" (NDC->screen). // Чтобы совпало с твоей UI-логикой, перевернём: float uiX = sx; float uiY = sy; // если окажется вверх ногами — замени на (Environment::height - sy) // Можно делать масштаб по дальности: чем дальше — тем меньше. // depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию: float dist = (Environment::shipState.position - boxWorld).norm(); float scale = std::clamp(120.0f / (dist + 1.0f), 0.6f, 1.2f); textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true); } glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); } Game::Game() : window(nullptr) , glContext(nullptr) , newTickCount(0) , lastTickCount(0) , planetObject(renderer, taskManager, mainThreadHandler) { projectiles.reserve(maxProjectiles); for (int i = 0; i < maxProjectiles; ++i) { projectiles.emplace_back(std::make_unique()); } } Game::~Game() { if (glContext) { SDL_GL_DeleteContext(glContext); } if (window) { SDL_DestroyWindow(window); } SDL_Quit(); } void Game::setup() { glContext = SDL_GL_CreateContext(ZL::Environment::window); ZL::BindOpenGlFunctions(); ZL::CheckGlError(); //#ifndef SIMPLIFIED renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/defaultAtmosphere.vertex", "resources/shaders/defaultAtmosphere_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/planet_bake.vertex", "resources/shaders/planet_bake_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE); /*#else renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); #endif*/ bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE); bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE); explosionEmitter.setEmissionPoints(std::vector()); projectileEmitter.setEmissionPoints(std::vector()); uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE); std::function loadGameplayUI; loadGameplayUI = [this]() { uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE); uiManager.startAnimationOnNode("backgroundNode", "bgScroll"); static bool isExitButtonAnimating = false; uiManager.setAnimationCallback("settingsButton", "buttonsExit", [this]() { std::cerr << "Settings button animation finished -> переход в настройки" << std::endl; if (uiManager.pushMenuFromFile("resources/config/settings.json", this->renderer, CONST_ZIP_FILE)) { uiManager.setButtonCallback("Opt1", [this](const std::string& n) { std::cerr << "Opt1 pressed: " << n << std::endl; }); uiManager.setButtonCallback("Opt2", [this](const std::string& n) { std::cerr << "Opt2 pressed: " << n << std::endl; }); uiManager.setButtonCallback("backButton", [this](const std::string& n) { uiManager.stopAllAnimations(); uiManager.popMenu(); }); } else { std::cerr << "Failed to open settings menu after animations" << std::endl; } }); uiManager.setAnimationCallback("exitButton", "bgScroll", []() { std::cerr << "Exit button bgScroll animation finished" << std::endl; g_exitBgAnimating = false; }); uiManager.setButtonCallback("playButton", [this](const std::string& name) { std::cerr << "Play button pressed: " << name << std::endl; }); uiManager.setButtonCallback("settingsButton", [this](const std::string& name) { std::cerr << "Settings button pressed: " << name << std::endl; uiManager.startAnimationOnNode("playButton", "buttonsExit"); uiManager.startAnimationOnNode("settingsButton", "buttonsExit"); uiManager.startAnimationOnNode("exitButton", "buttonsExit"); }); uiManager.setButtonCallback("exitButton", [this](const std::string& name) { std::cerr << "Exit button pressed: " << name << std::endl; if (!g_exitBgAnimating) { std::cerr << "start repeat anim bgScroll on exitButton" << std::endl; g_exitBgAnimating = true; uiManager.startAnimationOnNode("exitButton", "bgScroll"); } else { std::cerr << "stop repeat anim bgScroll on exitButton" << std::endl; g_exitBgAnimating = false; uiManager.stopAnimationOnNode("exitButton", "bgScroll"); auto exitButton = uiManager.findButton("exitButton"); if (exitButton) { exitButton->animOffsetX = 0.0f; exitButton->animOffsetY = 0.0f; exitButton->animScaleX = 1.0f; exitButton->animScaleY = 1.0f; exitButton->buildMesh(); } } }); uiManager.setButtonCallback("shootButton", [this](const std::string& name) { uint64_t now = SDL_GetTicks64(); if (now - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { lastProjectileFireTime = now; this->fireProjectiles(); Eigen::Vector3f localForward = { 0, 0, -1 }; Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); Eigen::Vector3f centerPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f }; std::string fireMsg = "FIRE:" + std::to_string(now) + ":" + std::to_string(centerPos.x()) + ":" + std::to_string(centerPos.y()) + ":" + std::to_string(centerPos.z()) + ":" + std::to_string(worldForward.x()) + ":" + std::to_string(worldForward.y()) + ":" + std::to_string(worldForward.z()) + ":" + "2"; networkClient->Send(fireMsg); } }); uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); if (newVel != Environment::shipState.selectedVelocity) { newShipVelocity = newVel; } }); }; uiManager.setButtonCallback("singleButton", [loadGameplayUI](const std::string& name) { std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n"; loadGameplayUI(); }); uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) { std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n"; loadGameplayUI(); }); uiManager.setButtonCallback("exitButton", [](const std::string& name) { std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n"; Environment::exitGameLoop = true; }); cubemapTexture = std::make_shared( std::array{ CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE) }); cubemap.data = ZL::CreateCubemap(500); cubemap.RefreshVBO(); //Load texture //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()); spaceshipTexture = std::make_unique(CreateTextureDataFromPng("resources/MainCharacter_Base_color_sRGB.png", CONST_ZIP_FILE)); spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE); 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 }); spaceship.AssignFrom(spaceshipBase); spaceship.RefreshVBO(); //Boxes boxTexture = std::make_unique(CreateTextureDataFromPng("resources/box/box.png", CONST_ZIP_FILE)); boxBase = LoadFromTextFile02("resources/box/box.txt", CONST_ZIP_FILE); boxCoordsArr = generateRandomBoxCoords(50); boxRenderArr.resize(boxCoordsArr.size()); for (int i = 0; i < boxCoordsArr.size(); i++) { boxRenderArr[i].AssignFrom(boxBase); //boxRenderArr[i].data = CreateBaseConvexPolyhedron(1999); boxRenderArr[i].RefreshVBO(); } boxAlive.resize(boxCoordsArr.size(), true); textRenderer = std::make_unique(); textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32); boxLabels.clear(); boxLabels.reserve(boxCoordsArr.size()); for (size_t i = 0; i < boxCoordsArr.size(); ++i) { boxLabels.push_back("Box " + std::to_string(i + 1)); } if (!cfgLoaded) { throw std::runtime_error("Failed to load spark emitter config file!"); } renderer.InitOpenGL(); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); planetObject.init(); #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) { static const std::string defaultShaderName = "default"; static const std::string envShaderName = "env_sky"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; static const std::string skyPercentUniformName = "skyPercent"; renderer.shaderManager.PushShader(envShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.RenderUniform1f(skyPercentUniformName, skyPercent); renderer.EnableVertexAttribArray(vPositionName); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.RotateMatrix(Environment::inverseShipMatrix); Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); Matrix3f viewMatrix = Environment::inverseShipMatrix; Vector3f viewLightDir = (viewMatrix * worldLightDir).normalized(); // Передаем вектор НА источник света Vector3f lightToSource = -viewLightDir; renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); // 2. Базовый цвет атмосферы (голубой) Vector3f skyColor = { 0.0f, 0.5f, 1.0f }; renderer.RenderUniform3fv("uSkyColor", skyColor.data()); // 1. Вектор направления от центра планеты к игроку (в мировых координатах) // Предполагаем, что планета в (0,0,0). Если нет, то (shipPosition - planetCenter) Vector3f playerDirWorld = Environment::shipState.position.normalized(); // 2. Направление света в мировом пространстве //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); // 3. Считаем глобальную освещенность для игрока (насколько он на свету) // Это одно число для всего кадра float playerLightFactor = playerDirWorld.dot(-worldLightDir); // Ограничиваем и делаем переход мягче playerLightFactor = std::clamp((playerLightFactor + 0.2f) / 1.2f, 0.0f, 1.0f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); renderer.RenderUniform1f("skyPercent", skyPercent); CheckGlError(); glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID()); renderer.DrawVertexRenderStruct(cubemap); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void Game::drawShip() { static const std::string defaultShaderName = "default"; static const std::string envShaderName = "env"; 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); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset if (shipAlive) { glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); renderer.DrawVertexRenderStruct(spaceship); } glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); for (const auto& p : projectiles) { if (p && p->isActive()) { p->draw(renderer); } } if (shipAlive) { renderer.PushMatrix(); renderer.TranslateMatrix({ 0, 0, 16 }); sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); renderer.PopMatrix(); projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); } if (showExplosion) { explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); } glDisable(GL_BLEND); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); CheckGlError(); } void Game::drawBoxes() { static const std::string defaultShaderName = "default"; static const std::string envShaderName = "env"; 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); for (int i = 0; i < boxCoordsArr.size(); i++) { if (!boxAlive[i]) continue; renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipState.position); renderer.TranslateMatrix({ 0.f, 0.f, 45000.f }); renderer.TranslateMatrix(boxCoordsArr[i].pos); renderer.RotateMatrix(boxCoordsArr[i].m); glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID()); //glBindTexture(GL_TEXTURE_2D, rockTexture->getTexID()); renderer.DrawVertexRenderStruct(boxRenderArr[i]); renderer.PopMatrix(); } renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); CheckGlError(); } void Game::drawUI() { static const std::string defaultShaderName = "default"; static const std::string envShaderName = "env"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; glClear(GL_DEPTH_BUFFER_BIT); renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); glEnable(GL_BLEND); uiManager.draw(renderer); glDisable(GL_BLEND); renderer.shaderManager.PopShader(); CheckGlError(); } void Game::drawScene() { static const std::string defaultShaderName = "default"; static const std::string envShaderName = "env"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); glViewport(0, 0, Environment::width, Environment::height); CheckGlError(); float skyPercent = 0.0; float distance = planetObject.distanceToPlanetSurface(Environment::shipState.position); if (distance > 1500.f) { skyPercent = 0.0f; } else if (distance < 800.f) { skyPercent = 1.0f; } else { skyPercent = (1500.f - distance) / (1500.f - 800.f); } drawCubemap(skyPercent); planetObject.draw(renderer); if (planetObject.distanceToPlanetSurface(Environment::shipState.position) > 100.f) { glClear(GL_DEPTH_BUFFER_BIT); } drawShip(); drawRemoteShips(); drawRemoteShipsLabels(); drawBoxes(); drawBoxesLabels(); 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(); // Если сервер прислал коробки, применяем их однократно вместо локальной генерации if (!serverBoxesApplied && networkClient) { auto sboxes = networkClient->getServerBoxes(); if (!sboxes.empty()) { boxCoordsArr.clear(); for (auto& b : sboxes) { BoxCoords bc; bc.pos = b.first; bc.m = b.second; boxCoordsArr.push_back(bc); } boxRenderArr.resize(boxCoordsArr.size()); for (int i = 0; i < (int)boxCoordsArr.size(); ++i) { boxRenderArr[i].AssignFrom(boxBase); boxRenderArr[i].RefreshVBO(); } boxAlive.assign(boxCoordsArr.size(), true); serverBoxesApplied = true; } } // Итерируемся по актуальным данным из 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::drawRemoteShipsLabels() { if (!textRenderer) return; #ifdef NETWORK // 2D поверх 3D glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Берем удаленных игроков latestRemotePlayers = networkClient->getRemotePlayers(); auto now = std::chrono::system_clock::now(); now -= std::chrono::milliseconds(CLIENT_DELAY); for (auto const& [id, remotePlayer] : latestRemotePlayers) { if (!remotePlayer.canFetchClientStateAtTime(now)) continue; ClientState st = remotePlayer.fetchClientStateAtTime(now); // Позиция корабля в мире Vector3f shipWorld = st.position; float distSq = (Environment::shipState.position - shipWorld).squaredNorm(); /*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма continue;*/ float dist = sqrt(distSq); float alpha = 1.0f; // постоянная видимость /*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма if (alpha < 0.01f) continue; */ Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты float sx, sy, depth; if (!worldToScreen(labelWorld, sx, sy, depth)) continue; float uiX = sx, uiY = sy; //float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); float scale; if (dist > CLOSE_DIST) { scale = 0.4f; } else { float t = 1.0f - (dist / CLOSE_DIST); scale = 0.4f + (MAX_SCALE - 0.4f) * t; } scale = std::clamp(scale, MIN_SCALE, MAX_SCALE); // Дефолтный лейбл std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; // TODO: nickname sync textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, {0.f, 0.f, 0.f, alpha}); // color param textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); } glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); #endif } void Game::processTickCount() { if (lastTickCount == 0) { //lastTickCount = SDL_GetTicks64(); lastTickCount = std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ).count(); return; } //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; 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)); 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); 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; } 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); } } std::vector projCameraPoints; for (const auto& p : projectiles) { if (p && p->isActive()) { Vector3f worldPos = p->getPosition(); Vector3f rel = worldPos - Environment::shipState.position; Vector3f camPos = Environment::inverseShipMatrix * rel; projCameraPoints.push_back(camPos); } } if (!projCameraPoints.empty()) { projectileEmitter.setEmissionPoints(projCameraPoints); projectileEmitter.emit(); } else { projectileEmitter.setEmissionPoints(std::vector()); } std::vector shipCameraPoints; for (const auto& lp : shipLocalEmissionPoints) { Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f }; shipCameraPoints.push_back(adjusted); } if (!shipCameraPoints.empty()) { sparkEmitter.setEmissionPoints(shipCameraPoints); } sparkEmitter.update(static_cast(delta)); 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::shipState.position); if (distToSurface <= 0.0f) { 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::shipState.velocity = 0.0f; showExplosion = true; 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"; 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"; } } } 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::shipState.position - boxWorld; float thresh = shipCollisionRadius + boxCollisionRadius; if (diff.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; 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)); lastTickCount = newTickCount; } } void Game::fireProjectiles() { std::vector localOffsets = { Vector3f{ -1.5f, 0.9f, 5.0f }, Vector3f{ 1.5f, 0.9f, 5.0f } }; const float projectileSpeed = 60.0f; const float lifeMs = 5000.0f; const float size = 0.5f; Vector3f localForward = { 0,0,-1 }; Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); for (const auto& lo : localOffsets) { Vector3f worldPos = Environment::shipState.position + Environment::shipState.rotation * lo; Vector3f worldVel = worldForward * (projectileSpeed + Environment::shipState.velocity); for (auto& p : projectiles) { if (!p->isActive()) { p->init(worldPos, worldVel, lifeMs, size, projectileTexture, renderer); break; } } } } void Game::render() { SDL_GL_MakeCurrent(ZL::Environment::window, glContext); ZL::CheckGlError(); glClearColor(0.0f, 1.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); drawScene(); processTickCount(); SDL_GL_SwapWindow(ZL::Environment::window); } void Game::update() { SDL_Event event; while (SDL_PollEvent(&event)) { if (event.type == SDL_QUIT) { Environment::exitGameLoop = true; } #ifdef __ANDROID__ if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { Environment::exitGameLoop = true; } #endif #ifdef __ANDROID__ if (event.type == SDL_FINGERDOWN) { // Координаты Finger в SDL нормализованы от 0.0 до 1.0 int mx = static_cast(event.tfinger.x * Environment::width); int my = static_cast(event.tfinger.y * Environment::height); handleDown(mx, my); } else if (event.type == SDL_FINGERUP) { int mx = static_cast(event.tfinger.x * Environment::width); int my = static_cast(event.tfinger.y * Environment::height); handleUp(mx, my); } else if (event.type == SDL_FINGERMOTION) { int mx = static_cast(event.tfinger.x * Environment::width); int my = static_cast(event.tfinger.y * Environment::height); handleMotion(mx, my); } #else if (event.type == SDL_MOUSEBUTTONDOWN) { int mx = event.button.x; int my = event.button.y; handleDown(mx, my); } if (event.type == SDL_MOUSEBUTTONUP) { int mx = event.button.x; int my = event.button.y; handleUp(mx, my); } if (event.type == SDL_MOUSEMOTION) { int mx = event.motion.x; int my = event.motion.y; handleMotion(mx, my); } /* if (event.type == SDL_MOUSEWHEEL) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { Environment::zoom -= zoomstep; } else if (event.wheel.y < 0) { Environment::zoom += zoomstep; } if (Environment::zoom < zoomstep) { Environment::zoom = zoomstep; } } if (event.type == SDL_KEYUP) { if (event.key.keysym.sym == SDLK_i) { } }*/ #endif } render(); mainThreadHandler.processMainThreadTasks(); networkClient->Poll(); if (networkClient) { auto pending = networkClient->getPendingProjectiles(); if (!pending.empty()) { const float projectileSpeed = 60.0f; const float lifeMs = 5000.0f; const float size = 0.5f; auto remotePlayersSnapshot = networkClient->getRemotePlayers(); for (const auto& pi : pending) { Eigen::Vector3f dir = pi.direction; float len = dir.norm(); if (len <= 1e-6f) continue; dir /= len; Eigen::Matrix3f shooterRot = Eigen::Matrix3f::Identity(); float shooterVel = 0.0f; auto it = remotePlayersSnapshot.find(pi.shooterId); if (it != remotePlayersSnapshot.end()) { std::chrono::system_clock::time_point pktTime{ std::chrono::milliseconds(pi.clientTime) }; if (it->second.canFetchClientStateAtTime(pktTime)) { ClientState shooterState = it->second.fetchClientStateAtTime(pktTime); shooterRot = shooterState.rotation; shooterVel = shooterState.velocity; } } float speedWithOwner = projectileSpeed + shooterVel; Eigen::Vector3f baseVel = dir * speedWithOwner; int shotCount = 2; std::vector localOffsets = { {-1.5f, 0.9f, 5.0f}, { 1.5f, 0.9f, 5.0f} }; for (int i = 0; i < shotCount; ++i) { Eigen::Vector3f rotatedOffset = shooterRot * localOffsets[i]; Eigen::Vector3f shotPos = pi.position + rotatedOffset; for (auto& p : projectiles) { if (!p->isActive()) { p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer); break; } } } } } } } void Game::handleDown(int mx, int my) { int uiX = mx; int uiY = Environment::height - my; uiManager.onMouseDown(uiX, uiY); 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; } } void Game::handleUp(int mx, int my) { int uiX = mx; int uiY = Environment::height - my; uiManager.onMouseUp(uiX, uiY); if (!uiManager.isUiInteraction()) { Environment::tapDownHold = false; } } void Game::handleMotion(int mx, int my) { int uiX = mx; int uiY = Environment::height - my; uiManager.onMouseMove(uiX, uiY); if (Environment::tapDownHold && !uiManager.isUiInteraction()) { Environment::tapDownCurrentPos(0) = mx; Environment::tapDownCurrentPos(1) = my; } } /* 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