diff --git a/proj-windows/CMakeLists.txt b/proj-windows/CMakeLists.txt index 060eb41..0ec0df8 100644 --- a/proj-windows/CMakeLists.txt +++ b/proj-windows/CMakeLists.txt @@ -36,41 +36,24 @@ add_executable(space-game001 ../src/utils/Utils.h ../src/SparkEmitter.cpp ../src/SparkEmitter.h -# ../src/planet/PlanetObject.cpp -# ../src/planet/PlanetObject.h -# ../src/planet/PlanetData.cpp -# ../src/planet/PlanetData.h ../src/utils/Perlin.cpp ../src/utils/Perlin.h ../src/utils/TaskManager.cpp ../src/utils/TaskManager.h -# ../src/planet/StoneObject.cpp -# ../src/planet/StoneObject.h ../src/render/FrameBuffer.cpp ../src/render/FrameBuffer.h ../src/render/ShadowMap.cpp ../src/render/ShadowMap.h ../src/UiManager.cpp ../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 -# ../src/network/WebSocketClientBase.h -# ../src/network/WebSocketClientBase.cpp -# ../src/network/WebSocketClientEmscripten.h -# ../src/network/WebSocketClientEmscripten.cpp +# ../src/Projectile.h +# ../src/Projectile.cpp ../src/render/TextRenderer.h ../src/render/TextRenderer.cpp ../src/MenuManager.h ../src/MenuManager.cpp -# ../src/Space.h -# ../src/Space.cpp + ../src/Location.h + ../src/Location.cpp ../src/GameConstants.h ../src/GameConstants.cpp ../src/ScriptEngine.h @@ -115,8 +98,6 @@ target_compile_definitions(space-game001 PRIVATE SDL_MAIN_HANDLED # DEBUG_LIGHT # SHOW_PATH -# NETWORK -# SIMPLIFIED ) # Линкуем с SDL2main, если он вообще установлен diff --git a/src/Character.h b/src/Character.h index 7a8669b..bf50cc9 100644 --- a/src/Character.h +++ b/src/Character.h @@ -70,8 +70,8 @@ public: bool canAttack = false; Character* attackTarget = nullptr; bool isPlayer = false; - //bool useGpuSkinning = true; - bool useGpuSkinning = false; + bool useGpuSkinning = true; + //bool useGpuSkinning = false; float interactionRadius = 0.0f; diff --git a/src/Game.cpp b/src/Game.cpp index 5be8cee..a8cec04 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -1,7 +1,6 @@ #include "Game.h" #include "AnimatedModel.h" #include "BoneAnimatedModel.h" -#include "planet/PlanetData.h" #include "utils/Utils.h" #include "render/OpenGlExtensions.h" #include diff --git a/src/Game.h b/src/Game.h index 9b8ac09..8b6dec3 100644 --- a/src/Game.h +++ b/src/Game.h @@ -5,9 +5,7 @@ #include "Environment.h" #include "render/TextureManager.h" #include "SparkEmitter.h" -#include "planet/PlanetObject.h" #include "UiManager.h" -#include "Projectile.h" #include "utils/TaskManager.h" #include "items/GameObjectLoader.h" #include "items/Item.h" diff --git a/src/Location.cpp b/src/Location.cpp new file mode 100644 index 0000000..42d09d5 --- /dev/null +++ b/src/Location.cpp @@ -0,0 +1,2 @@ +#include "Location.h" + diff --git a/src/Location.h b/src/Location.h new file mode 100644 index 0000000..73b4b86 --- /dev/null +++ b/src/Location.h @@ -0,0 +1 @@ +#pragma once diff --git a/src/Space.cpp b/src/Space.cpp deleted file mode 100644 index 84de330..0000000 --- a/src/Space.cpp +++ /dev/null @@ -1,2353 +0,0 @@ -#include "Space.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 -#ifdef EMSCRIPTEN -#include "network/WebSocketClientEmscripten.h" -#else -#include "network/WebSocketClient.h" -#endif -#else -#include "network/LocalClient.h" -#endif - -#include "GameConstants.h" - -namespace ZL -{ - - extern const char* CONST_ZIP_FILE; - - extern float x; - extern float y; - extern float z; - - bool inverseVertical = true; - - 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 worldToScreen(const Vector3f& world, float& outX, float& outY, float& outDepth) - { - // Матрицы должны совпасть с 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::projectionWidth; - float sy = (ndc.y() * 0.5f + 0.5f) * Environment::projectionHeight; - - outX = sx; - outY = sy; - - // Можно отсеять те, что вне: - if (sx < -200 || sx > Environment::projectionWidth + 200) return false; - if (sy < -200 || sy > Environment::projectionHeight + 200) return false; - - return true; - } - - bool projectToNDC(const Vector3f& world, float& ndcX, float& ndcY, float& ndcZ, float& clipW) - { - 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; - - clipW = clip.w(); - if (std::abs(clipW) < 1e-6f) return false; - - Eigen::Vector3f ndc = clip.head<3>() / clipW; - ndcX = ndc.x(); - ndcY = ndc.y(); - ndcZ = ndc.z(); - return true; - } - - void Space::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) - - float dist = (Environment::shipState.position - boxWorld).norm(); - float scaleRaw = 120.0f / (dist + 1.0f); - float scale = std::round(scaleRaw * 10.0f) / 10.0f; // округление до 0.1 - scale = std::clamp(scale, 0.6f, 1.2f); - - textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true); - } - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - - - Space::Space(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, std::unique_ptr& iNetworkClient, MenuManager& iMenuManager) - : renderer(iRenderer), - taskManager(iTaskManager), - mainThreadHandler(iMainThreadHandler), - planetObject(renderer, taskManager, mainThreadHandler), - networkClient(iNetworkClient), - menuManager(iMenuManager) - { - projectiles.reserve(maxProjectiles); - for (int i = 0; i < maxProjectiles; ++i) { - projectiles.emplace_back(std::make_unique()); - } - } - - Space::~Space() { - } - - void Space::resetPlayerState() - { - shipAlive = true; - gameOver = false; - showExplosion = false; - explosionEmitter.setEmissionPoints(std::vector()); - Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; - Environment::shipState.velocity = 0.0f; - Environment::shipState.selectedVelocity = 0; - newShipVelocity = 0; - Environment::shipState.rotation = Eigen::Matrix3f::Identity(); - Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); - Environment::zoom = DEFAULT_ZOOM; - Environment::tapDownHold = false; - playerScore = 0; - - } - - void Space::updateShowPlayersButtonState() - { - bool hasAlivePlayers = false; - for (auto const& [id, _] : remotePlayerStates) { - if (!deadRemotePlayers.count(id)) { - hasAlivePlayers = true; - break; - } - } - if (hasAlivePlayers == showPlayersButtonEnabled) return; - showPlayersButtonEnabled = hasAlivePlayers; - auto btn = menuManager.uiManager.findButton("showPlayersButton"); - if (!btn) return; - btn->state = hasAlivePlayers ? ButtonState::Normal : ButtonState::Disabled; - } - - - void Space::setup() { - - menuManager.onRestartPressed = [this]() { - resetPlayerState(); - - if (networkClient) { - try { - networkClient->Send(std::string("RESPAWN")); - std::cout << "Client: Sent RESPAWN to server\n"; - } - catch (...) { - std::cerr << "Client: Failed to send RESPAWN\n"; - } - } - std::cerr << "Game restarted\n"; - }; - - menuManager.onVelocityChanged = [this](float newVelocity) { - newShipVelocity = newVelocity; - if (Environment::shipState.shipType == 0) - { - if (newVelocity > 2) - { - this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled; - this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled; - - } - else - { - this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal; - this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal; - } - } - }; - - menuManager.onFirePressed = [this]() { - firePressed = true; - }; - - menuManager.onShowPlayersPressed = [this]() { - buildAndShowPlayerList(); - }; - - menuManager.onTakeButtonPressed = [this]() { - if (Environment::shipState.shipType != 1) return; - if (!networkClient) return; - - int bestIdx = -1; - float bestDistSq = BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS; - for (size_t i = 0; i < boxCoordsArr.size(); ++i) { - if (i >= boxAlive.size() || !boxAlive[i]) continue; - Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.f, 0.f, 45000.f }; - float distSq = (Environment::shipState.position - boxWorld).squaredNorm(); - if (distSq <= bestDistSq) { - bestDistSq = distSq; - bestIdx = static_cast(i); - } - } - if (bestIdx >= 0) { - networkClient->Send("BOX_PICKUP:" + std::to_string(bestIdx)); - this->playerScore += 1; - } - }; - - menuManager.forceSetupSpaceUICallback = [this]() { - this->nearPickupBox = false; - this->showPlayersButtonEnabled = false; - - if (menuManager.uiManager.findButton("minusButton")) - { - menuManager.uiManager.findButton("minusButton")->state = ButtonState::Disabled; - } - if (menuManager.uiManager.findButton("plusButton")) - { - menuManager.uiManager.findButton("plusButton")->state = ButtonState::Normal; - } - if (Environment::shipState.shipType == 0) - { - if (menuManager.uiManager.findButton("shootButton")) - { - menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal; - } - if (menuManager.uiManager.findButton("shootButton2")) - { - menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal; - } - } - - }; - - bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); - bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE); - sparkEmitter.setIsActive(false); - sparkEmitterCargo.setIsActive(false); - - 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({ Vector3f{0,0,45000} }); - //projectileEmitter.setUseWorldSpace(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/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 }); - spaceshipBase.Scale(0.4f); - - spaceship.AssignFrom(spaceshipBase); - spaceship.RefreshVBO(); - - // Load cargo - cargoTexture = std::make_shared(CreateTextureDataFromPng("resources/Cargo_Base_color_sRGB.png", CONST_ZIP_FILE)); - cargoBase = LoadFromTextFile02("resources/cargoship001.txt", CONST_ZIP_FILE); - auto quat = Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5, Eigen::Vector3f::UnitZ())); - auto rotMatrix = quat.toRotationMatrix(); - cargoBase.RotateByMatrix(rotMatrix); - - auto quat2 = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitY())); - auto rotMatrix2 = quat2.toRotationMatrix(); - cargoBase.RotateByMatrix(rotMatrix2); - //cargoBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix()); - cargoBase.Move(Vector3f{ 0, 0, -5 }); - cargo.AssignFrom(cargoBase); - cargo.RefreshVBO(); - - //projectileTexture = std::make_shared(CreateTextureDataFromPng("resources/spark2.png", CONST_ZIP_FILE)); - - //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].RefreshVBO(); - } - - boxAlive.resize(boxCoordsArr.size(), true); -#ifdef NETWORK - std::fill(boxAlive.begin(), boxAlive.end(), false); - serverBoxesApplied = false; -#endif - ZL::CheckGlError(); - boxLabels.clear(); - boxLabels.reserve(boxCoordsArr.size()); - for (size_t i = 0; i < boxCoordsArr.size(); ++i) { - boxLabels.push_back("Box " + std::to_string(i)); - } - - if (!cfgLoaded) - { - throw std::runtime_error("Failed to load spark emitter config file!"); - } - - crosshairCfgLoaded = loadCrosshairConfig("resources/config/crosshair_config.json"); - std::cout << "[Crosshair] loaded=" << crosshairCfgLoaded - << " enabled=" << crosshairCfg.enabled - << " w=" << Environment::width << " h=" << Environment::height - << " alpha=" << crosshairCfg.alpha - << " thickness=" << crosshairCfg.thicknessPx - << " gap=" << crosshairCfg.gapPx << "\n"; - if (!crosshairCfgLoaded) { - std::cerr << "Failed to load crosshair_config.json, using defaults\n"; - } - - - - textRenderer = std::make_unique(); - if (!textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE)) { - std::cerr << "Failed to init TextRenderer\n"; - } - ZL::CheckGlError(); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - planetObject.init(); - } - - - void Space::drawCubemap(float skyPercent) - { - static const std::string envSkyShaderName = "env_sky"; - static const std::string skyPercentUniformName = "skyPercent"; - - renderer.shaderManager.PushShader(envSkyShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - renderer.RenderUniform1f(skyPercentUniformName, skyPercent); - - 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.shaderManager.PopShader(); - CheckGlError(); - } - - void Space::drawShip() - { - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - - 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.PushMatrix(); - renderer.TranslateMatrix({ 0, -6.f, 0 }); //Ship camera offset - - //renderer.RotateMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 150 / 180.f, Eigen::Vector3f::UnitY())).toRotationMatrix()); - if (shipAlive) { - if (Environment::shipState.shipType == 1 && cargoTexture) { - glBindTexture(GL_TEXTURE_2D, cargoTexture->getTexID()); - renderer.DrawVertexRenderStruct(cargo); - } - else { - glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); - renderer.DrawVertexRenderStruct(spaceship); - } - - - drawShipSparkEmitters(); - } - - renderer.PopMatrix(); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE); - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - for (const auto& p : projectiles) { - if (p && p->isActive()) { - p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); - } - } - renderer.PopMatrix(); - glDisable(GL_BLEND); - - renderer.shaderManager.PopShader(); - - if (showExplosion) { - explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false); - } - - glDisable(GL_BLEND); - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - } - - void Space::drawBoxes() - { - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - 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()); - renderer.DrawVertexRenderStruct(boxRenderArr[i]); - - renderer.PopMatrix(); - } - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - } - - void Space::drawScene() { - 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); - - prepareSparkEmittersForDraw(); - - 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); - } - - drawRemoteShips(); - drawRemoteShipsLabels(); - drawBoxes(); - drawBoxesLabels(); - drawShip(); - - drawCrosshair(); - drawTargetHud(); - CheckGlError(); - } - - void Space::drawRemoteShips() { - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - Environment::CONST_Z_NEAR, Environment::CONST_Z_FAR); - - if (!serverBoxesApplied && networkClient) { - auto sboxes = networkClient->getServerBoxes(); - auto destroyedFlags = networkClient->getServerBoxDestroyedFlags(); - if (!sboxes.empty()) { - boxCoordsArr.clear(); - boxCoordsArr.resize(sboxes.size()); - for (size_t i = 0; i < sboxes.size(); ++i) { - BoxCoords bc; - bc.pos = sboxes[i].first; - bc.m = sboxes[i].second; - boxCoordsArr[i] = 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); - - size_t n = (std::min)(destroyedFlags.size(), boxAlive.size()); - for (size_t i = 0; i < n; ++i) { - if (destroyedFlags[i]) boxAlive[i] = false; // destroyed => не рисуем - } - boxLabels.clear(); - boxLabels.resize(boxCoordsArr.size()); - for (size_t i = 0; i < boxCoordsArr.size(); ++i) { - boxLabels[i] = "Box " + std::to_string(i); - } - serverBoxesApplied = true; - } - } - - for (auto const& [id, remotePlayer] : remotePlayerStates) { - - const ClientState& playerState = remotePlayer; - if (deadRemotePlayers.count(id)) continue; - - renderer.PushMatrix(); - renderer.LoadIdentity(); - - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - - - Eigen::Vector3f relativePos = playerState.position;// -Environment::shipPosition; - renderer.TranslateMatrix(relativePos); - - renderer.RotateMatrix(playerState.rotation); - - if (playerState.shipType == 1 && cargoTexture) { - glBindTexture(GL_TEXTURE_2D, cargoTexture->getTexID()); - renderer.DrawVertexRenderStruct(cargo); - } - else { - glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID()); - renderer.DrawVertexRenderStruct(spaceship); - } - renderer.PopMatrix(); - } - - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - - CheckGlError(); - } - - void Space::drawRemoteShipsLabels() - { - if (!textRenderer) return; - - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - for (auto const& [id, remotePlayer] : remotePlayerStates) - { - if (deadRemotePlayers.count(id)) continue; - - const ClientState& st = remotePlayer; - Vector3f shipWorld = st.position; - - float distSq = (Environment::shipState.position - shipWorld).squaredNorm(); - float dist = sqrt(distSq); - float alpha = 1.0f; - 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); - - std::string displayName; - if (!st.nickname.empty() && st.nickname != "Player") { - displayName = st.nickname; - } - else { - displayName = "Player (" + std::to_string(st.id) + ")"; - } - std::string label = displayName + " " + std::to_string((int)dist) + "m"; - - textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); - textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); - } - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - - // хелпер прицела: добавляет повернутую 2D-линию в меш прицела - static void AppendRotatedRect2D( - VertexDataStruct& out, - float cx, float cy, - float length, float thickness, - float angleRad, - float z, - const Eigen::Vector3f& rgb) - { - // прямоугольник вдоль локальной оси +X: [-L/2..+L/2] и [-T/2..+T/2] - float hl = length * 0.5f; - float ht = thickness * 0.5f; - - Eigen::Vector2f p0(-hl, -ht); - Eigen::Vector2f p1(-hl, +ht); - Eigen::Vector2f p2(+hl, +ht); - Eigen::Vector2f p3(+hl, -ht); - - float c = std::cos(angleRad); - float s = std::sin(angleRad); - - auto rot = [&](const Eigen::Vector2f& p) -> Vector3f { - float rx = p.x() * c - p.y() * s; - float ry = p.x() * s + p.y() * c; - return Vector3f(cx + rx, cy + ry, z); - }; - - Vector3f v0 = rot(p0); - Vector3f v1 = rot(p1); - Vector3f v2 = rot(p2); - Vector3f v3 = rot(p3); - - // 2 треугольника - out.PositionData.push_back(v0); - out.PositionData.push_back(v1); - out.PositionData.push_back(v2); - out.PositionData.push_back(v2); - out.PositionData.push_back(v3); - out.PositionData.push_back(v0); - - for (int i = 0; i < 6; ++i) out.ColorData.push_back(rgb); - } - - bool Space::loadCrosshairConfig(const std::string& path) - { - using json = nlohmann::json; - - std::string content; - try { - if (std::string(CONST_ZIP_FILE).empty()) content = readTextFile(path); - else { - auto buf = readFileFromZIP(path, CONST_ZIP_FILE); - if (buf.empty()) return false; - content.assign(buf.begin(), buf.end()); - } - json j = json::parse(content); - - if (j.contains("enabled")) crosshairCfg.enabled = j["enabled"].get(); - - if (j.contains("referenceResolution") && j["referenceResolution"].is_array() && j["referenceResolution"].size() == 2) { - crosshairCfg.refW = j["referenceResolution"][0].get(); - crosshairCfg.refH = j["referenceResolution"][1].get(); - } - - if (j.contains("scale")) crosshairCfg.scaleMul = j["scale"].get(); - crosshairCfg.scaleMul = std::clamp(crosshairCfg.scaleMul, 0.1f, 3.0f); - - if (j.contains("color") && j["color"].is_array() && j["color"].size() == 3) { - crosshairCfg.color = Eigen::Vector3f( - j["color"][0].get(), - j["color"][1].get(), - j["color"][2].get() - ); - } - - if (j.contains("cl_crosshairalpha")) crosshairCfg.alpha = j["cl_crosshairalpha"].get(); - if (j.contains("cl_crosshairthickness")) crosshairCfg.thicknessPx = j["cl_crosshairthickness"].get(); - if (j.contains("centerGapPx")) crosshairCfg.gapPx = j["centerGapPx"].get(); - - if (j.contains("top") && j["top"].is_object()) { - auto t = j["top"]; - if (t.contains("lengthPx")) crosshairCfg.topLenPx = t["lengthPx"].get(); - if (t.contains("angleDeg")) crosshairCfg.topAngleDeg = t["angleDeg"].get(); - } - - crosshairCfg.arms.clear(); - if (j.contains("arms") && j["arms"].is_array()) { - for (auto& a : j["arms"]) { - CrosshairConfig::Arm arm; - arm.lenPx = a.value("lengthPx", 20.0f); - arm.angleDeg = a.value("angleDeg", 210.0f); - crosshairCfg.arms.push_back(arm); - } - } - else { - // дефолт - crosshairCfg.arms.push_back({ 20.0f, 210.0f }); - crosshairCfg.arms.push_back({ 20.0f, 330.0f }); - } - - // clamp - crosshairCfg.alpha = std::clamp(crosshairCfg.alpha, 0.0f, 1.0f); - crosshairCfg.thicknessPx = max(0.5f, crosshairCfg.thicknessPx); - crosshairCfg.gapPx = max(0.0f, crosshairCfg.gapPx); - - crosshairMeshValid = false; // пересобрать - return true; - } - catch (...) { - return false; - } - } - - // пересобирает mesh прицела при изменениях/ресайзе - void Space::rebuildCrosshairMeshIfNeeded() - { - if (!crosshairCfg.enabled) return; - - // если ничего не изменилось — не трогаем VBO - if (crosshairMeshValid && - crosshairLastW == Environment::projectionWidth && - crosshairLastH == Environment::projectionHeight && - std::abs(crosshairLastAlpha - crosshairCfg.alpha) < 1e-6f && - std::abs(crosshairLastThickness - crosshairCfg.thicknessPx) < 1e-6f && - std::abs(crosshairLastGap - crosshairCfg.gapPx) < 1e-6f && - std::abs(crosshairLastScaleMul - crosshairCfg.scaleMul) < 1e-6f) - { - return; - } - - crosshairLastW = Environment::projectionWidth; - crosshairLastH = Environment::projectionHeight; - crosshairLastAlpha = crosshairCfg.alpha; - crosshairLastThickness = crosshairCfg.thicknessPx; - crosshairLastGap = crosshairCfg.gapPx; - crosshairLastScaleMul = crosshairCfg.scaleMul; - - float cx = Environment::projectionWidth * 0.5f; - float cy = Environment::projectionHeight * 0.5f; - - // масштаб от reference (стандартно: по высоте) - float scale = (crosshairCfg.refH > 0) ? (Environment::projectionHeight / (float)crosshairCfg.refH) : 1.0f; - scale *= crosshairCfg.scaleMul; - - float thickness = crosshairCfg.thicknessPx * scale; - float gap = crosshairCfg.gapPx * scale; - - VertexDataStruct v; - v.PositionData.reserve(6 * (1 + (int)crosshairCfg.arms.size())); - v.ColorData.reserve(6 * (1 + (int)crosshairCfg.arms.size())); - - const float z = 0.0f; - const Eigen::Vector3f rgb = crosshairCfg.color; - - auto deg2rad = [](float d) { return d * 3.1415926535f / 180.0f; }; - - // TOP (короткая палочка сверху) - { - float len = crosshairCfg.topLenPx * scale; - float ang = deg2rad(crosshairCfg.topAngleDeg); - - // сдвигаем сегмент от центра на gap + len/2 по направлению - float dx = std::cos(ang); - float dy = std::sin(ang); - float mx = cx + dx * (gap + len * 0.5f); - float my = cy + dy * (gap + len * 0.5f); - - AppendRotatedRect2D(v, mx, my, len, thickness, ang, z, rgb); - } - - // ARMS (2 луча вниз-влево и вниз-вправо) - for (auto& a : crosshairCfg.arms) - { - float len = a.lenPx * scale; - float ang = deg2rad(a.angleDeg); - - float dx = std::cos(ang); - float dy = std::sin(ang); - float mx = cx + dx * (gap + len * 0.5f); - float my = cy + dy * (gap + len * 0.5f); - - AppendRotatedRect2D(v, mx, my, len, thickness, ang, z, rgb); - } - - crosshairMesh.AssignFrom(v); - crosshairMesh.RefreshVBO(); - crosshairMeshValid = true; - } - - void Space::drawCrosshair() - { - if (!crosshairCfg.enabled) return; - - rebuildCrosshairMeshIfNeeded(); - if (!crosshairMeshValid) return; - - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - renderer.shaderManager.PushShader("defaultColor"); - renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f); - renderer.PushMatrix(); - renderer.LoadIdentity(); - - Eigen::Vector4f uColor(crosshairCfg.color.x(), crosshairCfg.color.y(), crosshairCfg.color.z(), crosshairCfg.alpha); - renderer.RenderUniform4fv("uColor", uColor.data()); - - renderer.DrawVertexRenderStruct(crosshairMesh); - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - } - - int Space::pickTargetId() const - { - // Use manually selected target if it's still alive and in range - if (manualTrackedTargetId >= 0) { - auto it = remotePlayerStates.find(manualTrackedTargetId); - if (it != remotePlayerStates.end() && !deadRemotePlayers.count(manualTrackedTargetId)) { - float d2 = (Environment::shipState.position - it->second.position).squaredNorm(); - if (d2 <= TARGET_MAX_DIST_SQ) return manualTrackedTargetId; - } - // Target no longer valid — fall through to auto-pick - } - - int bestId = -1; - float bestDistSq = 1e30f; - - for (auto const& [id, st] : remotePlayerStates) { - if (deadRemotePlayers.count(id)) continue; - - float d2 = (Environment::shipState.position - st.position).squaredNorm(); - - if (d2 > TARGET_MAX_DIST_SQ) continue; - - if (d2 < bestDistSq) { - bestDistSq = d2; - bestId = id; - } - } - return bestId; - } - - static Vector3f ForwardFromRotation(const Matrix3f& rot) - { - Vector3f localForward(0, 0, -1); - Vector3f worldForward = rot * localForward; - float len = worldForward.norm(); - if (len > 1e-6f) worldForward /= len; - return worldForward; - } - - static bool SolveLeadInterceptTime( - const Vector3f& shooterPos, - const Vector3f& shooterVel, - const Vector3f& targetPos, - const Vector3f& targetVel, - float projectileSpeed, // muzzle speed (например 60) - float& outT) - { - Vector3f r = targetPos - shooterPos; - Vector3f v = targetVel - shooterVel; - float S = projectileSpeed; - - float a = v.dot(v) - S * S; - float b = 2.0f * r.dot(v); - float c = r.dot(r); - - // Если a почти 0 -> линейный случай - if (std::abs(a) < 1e-6f) { - if (std::abs(b) < 1e-6f) return false; // нет решения - float t = -c / b; - if (t > 0.0f) { outT = t; return true; } - return false; - } - - float disc = b * b - 4.0f * a * c; - if (disc < 0.0f) return false; - - float sqrtDisc = std::sqrt(disc); - float t1 = (-b - sqrtDisc) / (2.0f * a); - float t2 = (-b + sqrtDisc) / (2.0f * a); - - float t = 1e30f; - if (t1 > 0.0f) t = min(t, t1); - if (t2 > 0.0f) t = min(t, t2); - - if (t >= 1e29f) return false; - outT = t; - return true; - } - - static VertexDataStruct MakeRing2D( - float cx, float cy, - float innerR, float outerR, - float z, - int segments, - const Eigen::Vector4f& rgba) - { - VertexDataStruct v; - v.PositionData.reserve(segments * 6); - v.ColorData.reserve(segments * 6); - - Vector3f rgb(rgba.x(), rgba.y(), rgba.z()); - - const float twoPi = 6.28318530718f; - for (int i = 0; i < segments; ++i) { - float a0 = twoPi * (float)i / (float)segments; - float a1 = twoPi * (float)(i + 1) / (float)segments; - - float c0 = std::cos(a0), s0 = std::sin(a0); - float c1 = std::cos(a1), s1 = std::sin(a1); - - Vector3f p0i(cx + innerR * c0, cy + innerR * s0, z); - Vector3f p0o(cx + outerR * c0, cy + outerR * s0, z); - Vector3f p1i(cx + innerR * c1, cy + innerR * s1, z); - Vector3f p1o(cx + outerR * c1, cy + outerR * s1, z); - - // два треугольника (p0i,p0o,p1o) и (p0i,p1o,p1i) - v.PositionData.push_back(p0i); - v.PositionData.push_back(p0o); - v.PositionData.push_back(p1o); - - v.PositionData.push_back(p0i); - v.PositionData.push_back(p1o); - v.PositionData.push_back(p1i); - - for (int k = 0; k < 6; ++k) v.ColorData.push_back(rgb); - } - return v; - } - - static VertexDataStruct MakeColoredRect2D(float cx, float cy, float hw, float hh, float z, - const Eigen::Vector4f& rgba) - { - VertexDataStruct v; - // 2 triangles - Vector3f p1{ cx - hw, cy - hh, z }; - Vector3f p2{ cx - hw, cy + hh, z }; - Vector3f p3{ cx + hw, cy + hh, z }; - Vector3f p4{ cx + hw, cy - hh, z }; - - v.PositionData = { p1, p2, p3, p3, p4, p1 }; - - // defaultColor shader likely uses vColor (vec3), но нам нужен alpha. - // У тебя в Renderer есть RenderUniform4fv, но шейдер может брать vColor. - // Поэтому: сделаем ColorData vec3, а alpha будем задавать uniform'ом отдельно. - Vector3f rgb{ rgba.x(), rgba.y(), rgba.z() }; - v.ColorData = { rgb, rgb, rgb, rgb, rgb, rgb }; - - // defaultColor vertex shader expects vNormal and vTexCoord; provide valid values - // so WebGL/GLSL doesn't get NaN from normalize(vec3(0,0,0)). - const Vector3f n{ 0.f, 0.f, 1.f }; - v.NormalData = { n, n, n, n, n, n }; - const Vector2f uv{ 0.f, 0.f }; - v.TexCoordData = { uv, uv, uv, uv, uv, uv }; - return v; - } - - void Space::drawTargetHud() - { - if (!textRenderer) return; - - // 1) выбираем цель - int targetIdNow = pickTargetId(); - if (targetIdNow < 0) { - trackedTargetId = -1; - targetAcquireAnim = 0.f; - targetWasVisible = false; - return; - } - - // если цель сменилась — сброс анимации “схлопывания” - if (trackedTargetId != targetIdNow) { - trackedTargetId = targetIdNow; - targetAcquireAnim = 0.0f; - targetWasVisible = false; - } - - const ClientState& st = remotePlayerStates.at(trackedTargetId); - Vector3f shipWorld = st.position; - - // Lead Indicator - // скорость пули (как в fireProjectiles) - const float projectileSpeed = PROJECTILE_VELOCITY; - - // позиция вылета - Vector3f shooterPos = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0f, 0.9f - 6.0f, 5.0f }; - - // скорость цели в мире (вектор) - // Vector3f shooterVel = ForwardFromRotation(Environment::shipState.rotation) * Environment::shipState.velocity; - - float shooterSpeed = std::abs(Environment::shipState.velocity); - // В нашей физике линейная скорость корабля всегда направлена по его forward (-Z) - // Когда игрок наводится на lead indicator, forward (и скорость) становятся сонаправлены с выстрелом - // поэтому эффективная скорость снаряда в мире ≈ muzzle + shipSpeed. - const float effectiveProjectileSpeed = projectileSpeed + shooterSpeed; - Vector3f shooterVel = Vector3f::Zero(); // скорость уже учтена в effectiveProjectileSpeed - Vector3f targetVel = ForwardFromRotation(st.rotation) * st.velocity; - - // ВАЖНО: remote state берется на now_ms - CLIENT_DELAY - // Значит shipWorld - это позиция ~0.5 сек назад. - // Для корректного lead нужно предсказать положение цели на сейчас - const float clientDelaySec = (float)CLIENT_DELAY / 1000.0f; - Vector3f targetPosNow = shipWorld + targetVel * clientDelaySec; - - const float minTargetSpeed = 0.5f; // подобрать (в твоих единицах) - bool targetMoving = (targetVel.norm() > minTargetSpeed); - - // альфа круга - float leadAlpha = targetMoving ? 1.0f : 0.5f; - - Vector3f leadWorld = targetPosNow; - bool haveLead = false; - - // Дистанцию лучше считать от реальной точки вылета - float distToTarget = (shooterPos - targetPosNow).norm(); - // Максимальное время перехвата ограничиваем жизнью пули - const float projectileLifeSec = (float)PROJECTILE_LIFE / 1000.0f; - float maxLeadTime = std::clamp((distToTarget / effectiveProjectileSpeed) * 1.25f, 0.01f, projectileLifeSec * 0.98f); - - if (!targetMoving) { - // Цель стоит: рисуем lead прямо на ней, но полупрозрачный - leadWorld = targetPosNow; - haveLead = true; - } - else { - float tLead = 0.0f; - - // 1) Пытаемся “правильное” решение перехвата - bool ok = SolveLeadInterceptTime(shooterPos, shooterVel, targetPosNow, targetVel, effectiveProjectileSpeed, tLead); - - // 2) Если решения нет / оно плохое — fallback (чтобы круг не пропадал при пролёте "вбок") - // Это ключевое изменение: lead всегда будет. - if (!ok || !(tLead > 0.0f) || tLead > maxLeadTime) { - tLead = std::clamp(distToTarget / effectiveProjectileSpeed, 0.05f, maxLeadTime); - } - - leadWorld = targetPosNow + targetVel * tLead; - haveLead = true; - } - - // Проекция цели (для рамок/стрелки) - float ndcX, ndcY, ndcZ, clipW; - if (!projectToNDC(shipWorld, ndcX, ndcY, ndcZ, clipW)) return; - - bool behind = (clipW <= 0.0f); - - bool onScreen = (!behind && - ndcX >= -1.0f && ndcX <= 1.0f && - ndcY >= -1.0f && ndcY <= 1.0f); - - float dist = (Environment::shipState.position - shipWorld).norm(); - float t = static_cast(SDL_GetTicks64()) * 0.001f; - - // Проекция Lead - float leadNdcX = 0.f, leadNdcY = 0.f, leadNdcZ = 0.f, leadClipW = 0.f; - bool leadOnScreen = false; - - if (haveLead) { - if (projectToNDC(leadWorld, leadNdcX, leadNdcY, leadNdcZ, leadClipW) && leadClipW > 0.0f) { - leadOnScreen = - (leadNdcX >= -1.0f && leadNdcX <= 1.0f && - leadNdcY >= -1.0f && leadNdcY <= 1.0f); - } - } - - // Настройки HUD стилизация - Eigen::Vector4f enemyColor(1.f, 0.f, 0.f, 1.f); // красный - float thickness = 2.0f; // толщина линий (px) - float z = 0.0f; // 2D слой - - auto drawLeadRing2D = [&](float lx, float ly) - { - float distLead = (Environment::shipState.position - leadWorld).norm(); - float r = 30.0f / (distLead * 0.01f + 1.0f); - r = std::clamp(r, 6.0f, 18.0f); - - float thicknessPx = 2.5f; - float innerR = max(1.0f, r - thicknessPx); - float outerR = r + thicknessPx; - - Eigen::Vector4f leadColor = enemyColor; - leadColor.w() = leadAlpha; - - renderer.RenderUniform4fv("uColor", leadColor.data()); - VertexDataStruct ring = MakeRing2D(lx, ly, innerR, outerR, 0.0f, 32, enemyColor); - hudTempMesh.AssignFrom(ring); - renderer.DrawVertexRenderStruct(hudTempMesh); - - // вернуть цвет HUD обратно - renderer.RenderUniform4fv("uColor", enemyColor.data()); - }; - - - // Цель в кадре: рамки - if (onScreen) - { - // перевод NDC -> экран (в пикселях) - float sx = (ndcX * 0.5f + 0.5f) * Environment::projectionWidth; - float sy = (ndcY * 0.5f + 0.5f) * Environment::projectionHeight; - - // анимация “снаружи внутрь” - // targetAcquireAnim растёт к 1, быстро (похоже на захват) - float dt = 1.0f / 60.0f; // у тебя нет dt в draw, берём константу, выглядит норм - targetAcquireAnim = min(1.0f, targetAcquireAnim + dt * 6.5f); - - // базовый размер рамки в зависимости от дистанции (как у лейблов) - float size = 220.0f / (dist * 0.01f + 1.0f); // подстройка - size = std::clamp(size, 35.0f, 120.0f); // min/max - - // “схлопывание”: сначала больше, потом ближе к кораблю - // expand 1.6 -> 1.0 - float expand = 1.6f - 0.6f * targetAcquireAnim; - - float half = size * expand; - float cornerLen = max(10.0f, half * 0.35f); - - // точки углов - float left = sx - half; - float right = sx + half; - float bottom = sy - half; - float top = sy + half; - - // рисуем 8 тонких прямоугольников (2 на угол) - auto drawBar = [&](float cx, float cy, float w, float h) - { - VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; - - // включаем 2D режим - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glClear(GL_DEPTH_BUFFER_BIT); - - renderer.shaderManager.PushShader("defaultColor"); - renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f); - renderer.PushMatrix(); - renderer.LoadIdentity(); - - renderer.RenderUniform4fv("uColor", enemyColor.data()); - - // рамки - drawBar(left + cornerLen * 0.5f, top, cornerLen, thickness); - drawBar(left, top - cornerLen * 0.5f, thickness, cornerLen); - - drawBar(right - cornerLen * 0.5f, top, cornerLen, thickness); - drawBar(right, top - cornerLen * 0.5f, thickness, cornerLen); - - drawBar(left + cornerLen * 0.5f, bottom, cornerLen, thickness); - drawBar(left, bottom + cornerLen * 0.5f, thickness, cornerLen); - - drawBar(right - cornerLen * 0.5f, bottom, cornerLen, thickness); - drawBar(right, bottom + cornerLen * 0.5f, thickness, cornerLen); - - // LEAD — независимо от рамок: если его точка на экране, рисуем - if (haveLead && leadOnScreen) { - float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth; - float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight; - drawLeadRing2D(lx, ly); - } - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - - targetWasVisible = true; - return; - } - - - // Цель вне экрана: стрелка - float dirX = ndcX; - float dirY = ndcY; - - if (behind) { - dirX = -dirX; - dirY = -dirY; - } - - float len = std::sqrt(dirX * dirX + dirY * dirY); - if (len < 1e-5f) return; - dirX /= len; - dirY /= len; - - float marginNdc = 0.08f; - float maxX = 1.0f - marginNdc; - float maxY = 1.0f - marginNdc; - - float tx = (std::abs(dirX) < 1e-6f) ? 1e9f : (maxX / std::abs(dirX)); - float ty = (std::abs(dirY) < 1e-6f) ? 1e9f : (maxY / std::abs(dirY)); - float k = min(tx, ty); - - float edgeNdcX = dirX * k; - float edgeNdcY = dirY * k; - - float edgeX = (edgeNdcX * 0.5f + 0.5f) * Environment::projectionWidth; - float edgeY = (edgeNdcY * 0.5f + 0.5f) * Environment::projectionHeight; - - float bob = std::sin(t * 6.0f) * 6.0f; - edgeX += dirX * bob; - edgeY += dirY * bob; - - float arrowLen = 26.0f; - float arrowWid = 14.0f; - - float px = -dirY; - float py = dirX; - - Vector3f tip{ edgeX + dirX * arrowLen, edgeY + dirY * arrowLen, z }; - Vector3f left{ edgeX + px * (arrowWid * 0.5f), edgeY + py * (arrowWid * 0.5f), z }; - Vector3f right{ edgeX - px * (arrowWid * 0.5f), edgeY - py * (arrowWid * 0.5f), z }; - - auto drawTri = [&](const Vector3f& a, const Vector3f& b, const Vector3f& c) - { - VertexDataStruct v; - v.PositionData = { a, b, c }; - Vector3f rgb{ enemyColor.x(), enemyColor.y(), enemyColor.z() }; - v.ColorData = { rgb, rgb, rgb }; - // defaultColor vertex shader expects vNormal and vTexCoord (avoids NaN on WebGL). - const Vector3f n{ 0.f, 0.f, 1.f }; - v.NormalData = { n, n, n }; - const Vector2f uv{ 0.f, 0.f }; - v.TexCoordData = { uv, uv, uv }; - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; - - auto drawBar = [&](float cx, float cy, float w, float h) - { - VertexDataStruct v = MakeColoredRect2D(cx, cy, w * 0.5f, h * 0.5f, z, enemyColor); - hudTempMesh.AssignFrom(v); - renderer.DrawVertexRenderStruct(hudTempMesh); - }; - - glDisable(GL_DEPTH_TEST); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - renderer.shaderManager.PushShader("defaultColor"); - renderer.PushProjectionMatrix(Environment::projectionWidth, Environment::projectionHeight, 0.f, 1.f); - renderer.PushMatrix(); - renderer.LoadIdentity(); - - renderer.RenderUniform4fv("uColor", enemyColor.data()); - - // стрелка - drawTri(tip, left, right); - - float tailLen = 14.0f; - float tailX = edgeX - dirX * 6.0f; - float tailY = edgeY - dirY * 6.0f; - drawBar(tailX, tailY, max(thickness, tailLen), thickness); - - // LEAD — рисуем даже когда цель вне экрана (если lead точка на экране) - if (haveLead && leadOnScreen) { - float lx = (leadNdcX * 0.5f + 0.5f) * Environment::projectionWidth; - float ly = (leadNdcY * 0.5f + 0.5f) * Environment::projectionHeight; - drawLeadRing2D(lx, ly); - } - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - - // дистанция около стрелки - { - std::string d = std::to_string((int)dist) + "m"; - float tx = edgeX + px * 18.0f; - float ty = edgeY + py * 18.0f; - textRenderer->drawText(d, tx, ty, 0.6f, true, { 1.f, 0.f, 0.f, 1.f }); - } - - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - - targetWasVisible = false; - } - - void Space::updateSparkEmitters(float deltaMs) - { - // Local ship - SparkEmitter* sparkEmitterPtr; - if (Environment::shipState.shipType == 1) { - sparkEmitterPtr = &sparkEmitterCargo; - static std::vector emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) }; - emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 }; - emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 }; - sparkEmitterPtr->setEmissionPoints(emissionPoints); - } - else { - sparkEmitterPtr = &sparkEmitter; - static std::vector emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) }; - emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 }; - emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 }; - sparkEmitterPtr->setEmissionPoints(emissionPoints); - } - sparkEmitterPtr->setIsActive(Environment::shipState.velocity > 0.1f); - sparkEmitterPtr->update(deltaMs); - - // Remote ships - for (auto const& [id, playerState] : remotePlayerStates) { - if (deadRemotePlayers.count(id)) continue; - if (!remoteShipSparkEmitters.count(id)) { - remoteShipSparkEmitters.emplace(id, playerState.shipType == 1 ? sparkEmitterCargo : sparkEmitter); - } - auto& remEmitter = remoteShipSparkEmitters.at(id); - std::vector remEmitPts(2); - if (playerState.shipType == 1) { - remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+2.8f, 8.4f }; - remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+1.5f, 8.4f }; - } else { - remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ -0.9f, -0.2,5.6 }; - remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.9f,-0.2,5.6 }; - } - remEmitter.setEmissionPoints(remEmitPts); - remEmitter.setIsActive(playerState.velocity > 0.1f); - remEmitter.update(deltaMs); - } - } - - void Space::prepareSparkEmittersForDraw() - { - sparkEmitter.prepareForDraw(true); - sparkEmitterCargo.prepareForDraw(true); - for (auto& [id, emitter] : remoteShipSparkEmitters) { - if (!deadRemotePlayers.count(id)) emitter.prepareForDraw(true); - } - explosionEmitter.prepareForDraw(false); - for (const auto& p : projectiles) { - if (p && p->isActive()) { - p->projectileEmitter.prepareForDraw(true); - } - } - } - - void Space::drawShipSparkEmitters() - { - renderer.PushMatrix(); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - if (Environment::shipState.shipType == 1) { - sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height); - } else { - sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); - } - - for (auto& [id, emitter] : remoteShipSparkEmitters) { - if (!deadRemotePlayers.count(id)) { - - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - emitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); - renderer.PopMatrix(); - } - } - renderer.PopMatrix(); - } - - void Space::processTickCount(int64_t newTickCount, int64_t delta) { - - auto now_ms = newTickCount; - - if (firePressed) - { - firePressed = false; - if (now_ms - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { - lastProjectileFireTime = now_ms; - const float projectileSpeed = PROJECTILE_VELOCITY; - - 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 - 6.0f, 5.0f }; - - Eigen::Quaternionf q(Environment::shipState.rotation); - float speedToSend = projectileSpeed + Environment::shipState.velocity; - int shotCount = 2; - - std::string fireMsg = "FIRE:" + - std::to_string(now_ms) + ":" + - std::to_string(centerPos.x()) + ":" + - std::to_string(centerPos.y()) + ":" + - std::to_string(centerPos.z()) + ":" + - std::to_string(q.w()) + ":" + - std::to_string(q.x()) + ":" + - std::to_string(q.y()) + ":" + - std::to_string(q.z()) + ":" + - std::to_string(speedToSend) + ":" + - std::to_string(shotCount); - - networkClient->Send(fireMsg); - } - } - - - //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; - } - - long long leftoverDelta = delta; - while (leftoverDelta > 0) - { - long long miniDelta = 50; - Environment::shipState.simulate_physics(miniDelta); - leftoverDelta -= miniDelta; - } - Environment::inverseShipMatrix = Environment::shipState.rotation.inverse(); - - 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; - } - - - auto latestRemotePlayers = networkClient->getRemotePlayers(); - - std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) }; - - - for (auto const& [id, remotePlayer] : latestRemotePlayers) { - - if (networkClient && id == networkClient->GetClientId()) { - continue; - } - - if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay)) - { - continue; - } - - ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay); - - remotePlayerStates[id] = playerState; - } - - updateShowPlayersButtonState(); - updateSparkEmitters(static_cast(delta)); - - for (auto& p : projectiles) { - if (p && p->isActive()) { - p->update(static_cast(delta), renderer); - } - } - - - for (const auto& p : projectiles) { - if (p && p->isActive()) { - Vector3f worldPos = p->getPosition(); - p->projectileEmitter.resetEmissionPoints({ worldPos }); - p->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.selectedVelocity = 0; - Environment::shipState.velocity = 0.0f; - newShipVelocity = 0; - 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"; - - clearPlayerListIfVisible(); - menuManager.showGameOver(this->playerScore); - } - 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.selectedVelocity = 0; - Environment::shipState.velocity = 0.0f; - newShipVelocity = 0; - 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; - } - - clearPlayerListIfVisible(); - menuManager.showGameOver(this->playerScore); - } - } - } - - planetObject.update(static_cast(delta)); - - // update velocity text - - if (shipAlive && !gameOver) { - auto velocityTv = menuManager.uiManager.findTextView("velocityText"); - if (velocityTv) { - std::string velocityStr = "Velocity: " + std::to_string(static_cast(Environment::shipState.velocity)); - menuManager.uiManager.setText("velocityText", velocityStr); - } - - bool canPickup = false; - if (Environment::shipState.shipType == 1 && Environment::shipState.velocity < 0.1f) { - for (size_t i = 0; i < boxCoordsArr.size(); ++i) { - if (i >= boxAlive.size() || !boxAlive[i]) continue; - Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.f, 0.f, 45000.f }; - float distSq = (Environment::shipState.position - boxWorld).squaredNorm(); - if (distSq <= BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) { - canPickup = true; - break; - } - } - } - if (canPickup != nearPickupBox) { - nearPickupBox = canPickup; - if (auto btn = menuManager.uiManager.findButton("takeButton")) - btn->state = canPickup ? ButtonState::Normal : ButtonState::Disabled; - } - } - - if (playerScore != prevPlayerScore) - { - prevPlayerScore = playerScore; - menuManager.uiManager.setText("gameScoreText", "Score: " + std::to_string(playerScore)); - } - } - - void Space::fireProjectiles() { - std::vector localOffsets = { - Vector3f{ -1.5f, 0.9f - 6.f, 5.0f }, - Vector3f{ 1.5f, 0.9f - 6.f, 5.0f } - }; - - - const float projectileSpeed = PROJECTILE_VELOCITY; - const float lifeMs = PROJECTILE_LIFE; - 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); - p->projectileEmitter = SparkEmitter(projectileEmitter); - break; - } - } - } - } - - - void Space::update() { - if (networkClient) { - if (networkClient->IsConnected()) { - wasConnectedToServer = true; - } - else if (wasConnectedToServer && shipAlive && !gameOver) { - wasConnectedToServer = false; - shipAlive = false; - gameOver = true; - Environment::shipState.velocity = 0.0f; - std::cout << "Client: Lost connection to server\n"; - clearPlayerListIfVisible(); - menuManager.showConnectionLost(); - } - - auto pending = networkClient->getPendingProjectiles(); - if (!pending.empty()) { - const float projectileSpeed = PROJECTILE_VELOCITY; - const float lifeMs = PROJECTILE_LIFE; - const float size = 0.5f; - for (const auto& pi : pending) { - const std::vector localOffsets = { - Vector3f{ -1.5f, 0.9f - 6.0f, 5.0f }, - Vector3f{ 1.5f, 0.9f - 6.0f, 5.0f } - }; - - Vector3f localForward = { 0, 0, -1 }; - Vector3f worldForward = pi.rotation * localForward; - - float len = worldForward.norm(); - if (len <= 1e-6f) { - continue; - } - worldForward /= len; - - Vector3f baseVel = worldForward * pi.velocity; - - for (const auto& off : localOffsets) { - Vector3f shotPos = pi.position + (pi.rotation * off); - - for (auto& p : projectiles) { - if (!p->isActive()) { - p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer); - p->projectileEmitter = SparkEmitter(projectileEmitter); - break; - } - } - } - } - } - - // Обработка событий смерти, присланных сервером - auto deaths = networkClient->getPendingDeaths(); - if (!deaths.empty()) { - int localId = networkClient->GetClientId(); - std::cout << "Client: Received " << deaths.size() << " death events" << std::endl; - - for (const auto& d : deaths) { - std::cout << "Client: Processing death - target=" << d.targetId - << ", killer=" << d.killerId << ", pos=(" - << d.position.x() << ", " << d.position.y() << ", " << d.position.z() << ")" << std::endl; - - showExplosion = true; - explosionEmitter.setUseWorldSpace(true); - explosionEmitter.setEmissionPoints(std::vector{ d.position }); - explosionEmitter.emit(); - lastExplosionTime = SDL_GetTicks64(); - std::cout << "Client: Explosion emitted at (" << d.position.x() << ", " - << d.position.y() << ", " << d.position.z() << ")" << std::endl; - - if (d.targetId == localId) { - std::cout << "Client: Local ship destroyed!" << std::endl; - shipAlive = false; - gameOver = true; - Environment::shipState.velocity = 0.0f; - clearPlayerListIfVisible(); - menuManager.showGameOver(this->playerScore); - } - else { - deadRemotePlayers.insert(d.targetId); - if (d.targetId == manualTrackedTargetId) manualTrackedTargetId = -1; - std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl; - } - if (d.killerId == localId) { - this->playerScore += 1; - std::cout << "Client: Increased local score to " << this->playerScore << std::endl; - - } - } - rebuildPlayerListIfVisible(); - } - - auto respawns = networkClient->getPendingRespawns(); - if (!respawns.empty()) { - for (const auto& respawnId : respawns) { - deadRemotePlayers.erase(respawnId); - - auto it = remotePlayerStates.find(respawnId); - if (it != remotePlayerStates.end()) { - it->second.position = Vector3f{ 0.f, 0.f, 45000.f }; - it->second.velocity = 0.0f; - it->second.rotation = Eigen::Matrix3f::Identity(); - } - - std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl; - } - } - rebuildPlayerListIfVisible(); - - auto disconnects = networkClient->getPendingDisconnects(); - for (int pid : disconnects) { - remotePlayerStates.erase(pid); - deadRemotePlayers.erase(pid); - remoteShipSparkEmitters.erase(pid); - if (trackedTargetId == pid) { - trackedTargetId = -1; - targetAcquireAnim = 0.f; - } - if (pid == manualTrackedTargetId) manualTrackedTargetId = -1; - std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n"; - } - - rebuildPlayerListIfVisible(); - updateShowPlayersButtonState(); - auto boxDestructions = networkClient->getPendingBoxDestructions(); - if (!boxDestructions.empty()) { - std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl; - - for (const auto& destruction : boxDestructions) { - int idx = destruction.boxIndex; - - if (idx >= 0 && idx < (int)boxCoordsArr.size()) { - if (boxAlive[idx]) { - boxAlive[idx] = false; - - boxRenderArr[idx].data.PositionData.clear(); - boxRenderArr[idx].vao.reset(); - boxRenderArr[idx].positionVBO.reset(); - boxRenderArr[idx].texCoordVBO.reset(); - - showExplosion = true; - explosionEmitter.setUseWorldSpace(true); - explosionEmitter.setEmissionPoints(std::vector{ destruction.position }); - explosionEmitter.emit(); - lastExplosionTime = SDL_GetTicks64(); - - std::cout << "Game: Box " << idx << " destroyed by player " - << destruction.destroyedBy << std::endl; - } - } - } - } - } - - auto boxPickups = networkClient->getPendingBoxPickups(); - for (const auto& pickup : boxPickups) { - int idx = pickup.boxIndex; - if (idx >= 0 && idx < (int)boxCoordsArr.size() && idx < (int)boxAlive.size()) { - if (boxAlive[idx]) { - boxAlive[idx] = false; - boxRenderArr[idx].data.PositionData.clear(); - boxRenderArr[idx].vao.reset(); - boxRenderArr[idx].positionVBO.reset(); - boxRenderArr[idx].texCoordVBO.reset(); - std::cout << "Client: Box " << idx << " picked up by player " << pickup.pickedUpBy << "\n"; - } - } - } - - - auto boxRespawns = networkClient->getPendingBoxRespawns(); - for (const auto& respawn : boxRespawns) { - int idx = respawn.boxIndex; - if (idx >= 0 && idx < (int)boxCoordsArr.size()) { - boxCoordsArr[idx].pos = respawn.position; - boxCoordsArr[idx].m = respawn.rotation; - boxAlive[idx] = true; - boxRenderArr[idx].AssignFrom(boxBase); - boxRenderArr[idx].RefreshVBO(); - std::cout << "Client: Box " << idx << " respawned" << std::endl; - } - } - } - - void Space::handleDown(int mx, int my) - { - if (playerListVisible) return; - - Environment::tapDownHold = true; - - if (inverseVertical) - { - Environment::tapDownStartPos(0) = mx; - Environment::tapDownStartPos(1) = my; - - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = my; - } - else - { - Environment::tapDownStartPos(0) = mx; - Environment::tapDownStartPos(1) = -my; - - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = -my; - } - } - - void Space::handleUp(int mx, int my) - { - Environment::tapDownHold = false; - - } - - void Space::handleMotion(int mx, int my) - { - if (playerListVisible) return; - - - if (inverseVertical) - { - if (Environment::tapDownHold) { - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = my; - } - } - else - { - if (Environment::tapDownHold) { - Environment::tapDownCurrentPos(0) = mx; - Environment::tapDownCurrentPos(1) = -my; - } - } - } - - void Space::clearTextRendererCache() - { - if (textRenderer) { - textRenderer->ClearCache(); - } - } - - /* - std::string Space::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; - }*/ - - - - std::shared_ptr Space::buildPlayerListRoot() - { - const float btnW = 250; - const float btnH = 40.0f; - - // Collect alive remote players - std::vector> players; - for (auto& kv : remotePlayerStates) { - if (!deadRemotePlayers.count(kv.first)) - players.push_back({ kv.first, kv.second.nickname }); - } - - // Root: FrameLayout match_parent x match_parent - auto root = std::make_shared(); - root->name = "playerListRoot"; - root->layoutType = LayoutType::Frame; - root->width = -1.0f; // match_parent - root->height = -1.0f; - - // List container: LinearLayout vertical, centered - float listH = btnH * (float)(players.size()+1); - auto listNode = std::make_shared(); - listNode->name = "playerList"; - listNode->layoutType = LayoutType::Linear; - listNode->orientation = Orientation::Vertical; - listNode->width = btnW; - listNode->height = listH; - listNode->layoutSettings.hGravity = HorizontalGravity::Center; - listNode->layoutSettings.vGravity = VerticalGravity::Center; - - auto titleNode = std::make_shared(); - titleNode->name = "player_list_title"; - titleNode->layoutType = LayoutType::Frame; - titleNode->width = btnW; - titleNode->height = btnH; - auto titleImage = std::make_shared(); - titleImage->name = "player_list_title"; - titleImage->texNormal = std::make_unique(CreateTextureDataFromPng("resources/players_list_title.png", CONST_ZIP_FILE)); - titleImage->texPressed = titleImage->texNormal; - titleImage->texHover = titleImage->texNormal; - titleNode->button = titleImage; - - listNode->children.push_back(titleNode); - - for (auto& [pid, nick] : players) { - auto btnNode = std::make_shared(); - btnNode->name = "playerBtn_" + std::to_string(pid); - btnNode->layoutType = LayoutType::Frame; - btnNode->width = btnW; - btnNode->height = btnH; - - - auto tb = std::make_shared(); - tb->name = btnNode->name; - tb->text = nick; - tb->fontSize = 20; - tb->color = { 1.f, 1.f, 1.f, 1.f }; - tb->textCentered = true; - tb->textRenderer = std::make_unique(); - if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, CONST_ZIP_FILE)) { - std::cerr << "Failed to init TextRenderer for TextField: " << tb->name << std::endl; - } - tb->texNormal = std::make_unique(CreateTextureDataFromPng("resources/player_under.png", CONST_ZIP_FILE)); - - btnNode->textButton = tb; - /*auto button = std::make_shared(); - button->name = "Hello"; - button->texNormal = std::make_unique(CreateTextureDataFromPng("resources/loading.png", "")); - btnNode->button = button;*/ - listNode->children.push_back(btnNode); - } - - // Backdrop: invisible full-screen TextButton — placed LAST so player buttons get priority - auto backdropNode = std::make_shared(); - backdropNode->name = "playerListBackdrop"; - backdropNode->layoutType = LayoutType::Frame; - backdropNode->width = -1.0f; - backdropNode->height = -1.0f; - auto backdropTb = std::make_shared(); - backdropTb->name = "playerListBackdrop"; - backdropNode->textButton = backdropTb; - - - /* - auto backgroundNode = std::make_shared(); - backgroundNode->name = "playerListBackground"; - backgroundNode->layoutType = LayoutType::Frame; - backgroundNode->width = btnW; - backgroundNode->height = listH; - backgroundNode->layoutSettings.hGravity = HorizontalGravity::Center; - backgroundNode->layoutSettings.vGravity = VerticalGravity::Center; - auto backdropImage = std::make_shared(); - backdropImage->name = "playerListBackgroundImage"; - backdropImage->texture = std::make_unique(CreateTextureDataFromPng("resources/blue_transparent.png", "")); - backgroundNode->staticImage = backdropImage; - */ - root->children.push_back(listNode); - root->children.push_back(backdropNode); - //root->children.push_back(backgroundNode); - return root; - } - - void Space::buildAndShowPlayerList() - { - auto listRoot = buildPlayerListRoot(); - menuManager.uiManager.pushMenuFromSavedRoot(listRoot); - menuManager.uiManager.updateAllLayouts(); - playerListVisible = true; - - for (auto& kv : remotePlayerStates) { - if (deadRemotePlayers.count(kv.first)) continue; - int pid = kv.first; - std::string btnName = "playerBtn_" + std::to_string(pid); - menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) { - manualTrackedTargetId = pid; - closePlayerList(); - }); - } - - menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) { - closePlayerList(); - }); - menuManager.uiManager.setTextButtonCallback("player_list_title", [this](const std::string&) { - closePlayerList(); - }); - } - - void Space::closePlayerList() - { - menuManager.uiManager.popMenu(); - menuManager.uiManager.updateAllLayouts(); - playerListVisible = false; - } - - void Space::rebuildPlayerListIfVisible() - { - if (!playerListVisible) return; - - auto listRoot = buildPlayerListRoot(); - menuManager.uiManager.replaceRoot(listRoot); - - for (auto& kv : remotePlayerStates) { - if (deadRemotePlayers.count(kv.first)) continue; - int pid = kv.first; - std::string btnName = "playerBtn_" + std::to_string(pid); - menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) { - manualTrackedTargetId = pid; - closePlayerList(); - }); - } - - menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) { - closePlayerList(); - }); - } - - void Space::clearPlayerListIfVisible() - { - if (!playerListVisible) return; - menuManager.uiManager.clearMenuStack(); - playerListVisible = false; - } - -} // namespace ZL diff --git a/src/Space.h b/src/Space.h deleted file mode 100644 index 9651bd6..0000000 --- a/src/Space.h +++ /dev/null @@ -1,204 +0,0 @@ -#pragma once - -#include "render/Renderer.h" -#include "Environment.h" -#include "render/TextureManager.h" -#include "SparkEmitter.h" -#include "planet/PlanetObject.h" -#include "UiManager.h" -#include "Projectile.h" -#include "utils/TaskManager.h" -#include "network/NetworkInterface.h" -#include -#include -#include -#include -#include -#include "MenuManager.h" - -#include - -namespace ZL { - - - struct BoxCoords - { - Vector3f pos; - Matrix3f m; - }; - - - class Space { - public: - Space(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler, std::unique_ptr& iNetworkClient, MenuManager& iMenuManager); - ~Space(); - - void setup(); - void update(); - - Renderer& renderer; - TaskManager& taskManager; - MainThreadHandler& mainThreadHandler; - std::unique_ptr& networkClient; - MenuManager& menuManager; - - - public: - void processTickCount(int64_t newTickCount, int64_t delta); - void drawScene(); - void drawCubemap(float skyPercent); - void drawShip(); - void drawBoxes(); - void drawBoxesLabels(); - void drawRemoteShips(); - void drawRemoteShipsLabels(); - void fireProjectiles(); - - void handleDown(int mx, int my); - void handleUp(int mx, int my); - void handleMotion(int mx, int my); - - - std::vector boxCoordsArr; - std::vector boxRenderArr; - - std::vector boxLabels; - std::unique_ptr textRenderer; - - std::unordered_map remotePlayerStates; - std::unordered_map remoteShipSparkEmitters; - - float newShipVelocity = 0; - - static const size_t CONST_TIMER_INTERVAL = 10; - static const size_t CONST_MAX_TIME_INTERVAL = 1000; - - std::shared_ptr sparkTexture; - std::shared_ptr spaceshipTexture; - std::shared_ptr cubemapTexture; - VertexDataStruct spaceshipBase; - VertexRenderStruct spaceship; - - std::shared_ptr cargoTexture; - VertexDataStruct cargoBase; - VertexRenderStruct cargo; - - VertexRenderStruct cubemap; - - std::shared_ptr boxTexture; - VertexDataStruct boxBase; - - SparkEmitter sparkEmitter; - SparkEmitter sparkEmitterCargo; - SparkEmitter projectileEmitter; - SparkEmitter explosionEmitter; - PlanetObject planetObject; - - std::vector> projectiles; - std::shared_ptr projectileTexture; - float projectileCooldownMs = 500.0f; - int64_t lastProjectileFireTime = 0; - int maxProjectiles = 500; - //std::vector shipLocalEmissionPoints; - - - bool shipAlive = true; - bool gameOver = false; - bool firePressed = false; - std::vector boxAlive; - float shipCollisionRadius = 15.0f; - float boxCollisionRadius = 2.0f; - bool showExplosion = false; - uint64_t lastExplosionTime = 0; - const uint64_t explosionDurationMs = 500; - - bool serverBoxesApplied = false; - bool nearPickupBox = false; - - static constexpr float MAX_DIST_SQ = 10000.f * 10000.f; - static constexpr float FADE_START = 6000.f; - static constexpr float FADE_RANGE = 4000.f; - static constexpr float BASE_SCALE = 140.f; - static constexpr float PERSPECTIVE_K = 0.05f; // Tune - static constexpr float MIN_SCALE = 0.4f; - static constexpr float MAX_SCALE = 0.8f; - static constexpr float CLOSE_DIST = 600.0f; - - std::unordered_set deadRemotePlayers; - int playerScore = 0; - int prevPlayerScore = 0; - bool wasConnectedToServer = false; - - bool playerListVisible = false; - int manualTrackedTargetId = -1; - - static constexpr float TARGET_MAX_DIST = 50000.0f; - static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST; - - // --- Target HUD (brackets + offscreen arrow) --- - int trackedTargetId = -1; - bool targetWasVisible = false; - float targetAcquireAnim = 0.0f; // 0..1 схлопывание (0 = далеко, 1 = на месте) - - // временный меш для HUD (будем перезаливать VBO маленькими порциями) - VertexRenderStruct hudTempMesh; - - // helpers - void drawTargetHud(); // рисует рамку или стрелку - int pickTargetId() const; // ???????? ???? (????: ????????? ????? ????????? ?????) - - void resetPlayerState(); - void clearTextRendererCache(); - void updateShowPlayersButtonState(); - bool showPlayersButtonEnabled = false; - - // Player list overlay - void buildAndShowPlayerList(); - void closePlayerList(); - void rebuildPlayerListIfVisible(); - void clearPlayerListIfVisible(); - std::shared_ptr buildPlayerListRoot(); - - void updateSparkEmitters(float deltaMs); - void prepareSparkEmittersForDraw(); - void drawShipSparkEmitters(); - - // Crosshair HUD - struct CrosshairConfig { - bool enabled = true; - int refW = 1280; - int refH = 720; - - float scaleMul = 1.0f; - - Eigen::Vector3f color = { 1.f, 1.f, 1.f }; - float alpha = 1.0f; // cl_crosshairalpha - float thicknessPx = 2.0f; // cl_crosshairthickness - float gapPx = 10.0f; - - float topLenPx = 14.0f; - float topAngleDeg = 90.0f; - - struct Arm { float lenPx; float angleDeg; }; - std::vector arms; - }; - - CrosshairConfig crosshairCfg; - bool crosshairCfgLoaded = false; - - // кеш геометрии - VertexRenderStruct crosshairMesh; - bool crosshairMeshValid = false; - int crosshairLastW = 0, crosshairLastH = 0; - float crosshairLastAlpha = -1.0f; - float crosshairLastThickness = -1.0f; - float crosshairLastGap = -1.0f; - float crosshairLastScaleMul = -1.0f; - - bool loadCrosshairConfig(const std::string& path); - void rebuildCrosshairMeshIfNeeded(); - void drawCrosshair(); - }; - - -} // namespace ZL \ No newline at end of file diff --git a/src/planet/PlanetData.cpp b/src/planet/PlanetData.cpp deleted file mode 100644 index 94c1614..0000000 --- a/src/planet/PlanetData.cpp +++ /dev/null @@ -1,455 +0,0 @@ -#include "PlanetData.h" -#include -#include -#include -#include - -namespace ZL { - - Matrix3f GetRotationForTriangle(const Triangle& tri); - - const float PlanetData::PLANET_RADIUS = 20000.f; - const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; - - // --- Константы диапазонов (перенесены из 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); - Vector3f N = AB.cross(AC).normalized(); - Vector3f AP = P + A * (-1.0f); - float distance = N.dot(AP); - return P + N * (-distance); - } - - PlanetData::PlanetData() - : perlin(77777) - , colorPerlin(123123) - //, currentLod(0) - { - // currentLod = planetMeshLods.size() - 1; // Start with max LOD - /* - initialVertexMap = { - {{ 0.0f, 1.0f, 0.0f}, "A"}, - {{ 0.0f, -1.0f, 0.0f}, "B"}, - {{ 1.0f, 0.0f, 0.0f}, "C"}, - {{-1.0f, 0.0f, 0.0f}, "D"}, - {{ 0.0f, 0.0f, 1.0f}, "E"}, - {{ 0.0f, 0.0f, -1.0f}, "F"} - };*/ - } - - void PlanetData::init() { - for (int i = 0; i < planetMeshLods.size(); i++) { - //planetMeshLods[i] = generateSphere(i, 0.01f); - planetMeshLods[i] = generateSphere(i, 0.f); - planetMeshLods[i].Scale(PLANET_RADIUS); - planetMeshLods[i].Move(PLANET_CENTER_OFFSET); - } - - planetAtmosphereLod = generateSphere(5, 0); - planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03); - planetAtmosphereLod.Move(PLANET_CENTER_OFFSET); - - const auto& lodLevel = getLodLevel(); - - for (size_t i = 0; i < lodLevel.triangles.size(); i++) - { - if (i % 100 == 0) - { - PlanetCampObject campObject; - - campObject.position = (lodLevel.triangles[i].data[0] + - lodLevel.triangles[i].data[1] + - lodLevel.triangles[i].data[2]) / 3.0f; - - auto newM = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 0.5, Eigen::Vector3f::UnitX())).toRotationMatrix(); - - campObject.rotation = GetRotationForTriangle(lodLevel.triangles[i]).inverse() * newM; - campObjects.push_back(campObject); - } - } - } - - const LodLevel& PlanetData::getLodLevel() const { - return planetMeshLods.at(MAX_LOD_LEVELS-1); - } - - - const LodLevel& PlanetData::getAtmosphereLod() const { - return planetAtmosphereLod; - } - - /* - int PlanetData::getCurrentLodIndex() const { - return currentLod; - } - - int PlanetData::getMaxLodIndex() const { - return static_cast(planetMeshLods.size() - 1); - }*/ - - std::pair PlanetData::calculateZRange(float dToPlanetSurface) { - - - float currentZNear; - float currentZFar; - float alpha; - - if (dToPlanetSurface > 2000) - { - currentZNear = 1000; - currentZFar = currentZNear * 100; - } - else if (dToPlanetSurface > 1200) - { - currentZNear = 500; - currentZFar = currentZNear * 100; - } - else if (dToPlanetSurface > 650) - { - currentZNear = 250; - currentZFar = currentZNear * 100; - } - else if (dToPlanetSurface > 160) - { - currentZNear = 125; - currentZFar = currentZNear * 150; - } - else if (dToPlanetSurface > 100) - { - currentZNear = 65; - currentZFar = currentZNear * 170; - } - else if (dToPlanetSurface > 40) - { - currentZNear = 32; - currentZFar = 10000.f; - } - else if (dToPlanetSurface > 20) - { - currentZNear = 16; - currentZFar = 5000.f; - } - else if (dToPlanetSurface > 5) - { - currentZNear = 8; - currentZFar = 2500.f; - } - else - { - currentZNear = 4; - currentZFar = 1250.f; - } - - return { currentZNear, currentZFar }; - } - - float PlanetData::distanceToPlanetSurfaceFast(const Vector3f& viewerPosition) - { - Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET; - - return sqrt(shipLocalPosition.squaredNorm()) - PLANET_RADIUS; - } - - - std::vector PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) { - const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1]; // Работаем с текущим активным LOD - Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized(); - - int bestTriangle = -1; - float maxDot = -1.0f; - - // Шаг 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(); - - float dot = targetDir.dot(triDir); - if (dot > maxDot) { - maxDot = dot; - bestTriangle = i; - } - } - - if (bestTriangle == -1) return {}; - - return { bestTriangle }; - } - - std::vector PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) { - const LodLevel& finalLod = planetMeshLods[MAX_LOD_LEVELS - 1]; - Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET; - float currentDist = shipLocal.norm(); - Vector3f targetDir = shipLocal.normalized(); - - // Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка) - // Подбери это значение так, чтобы камни вокруг корабля всегда были видны. - const float desiredCoverageRadius = 3000.0f; - - // Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния. - // Чем мы дальше (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 для скорости) - Vector3f triDir = (finalLod.triangles[i].data[0] + - finalLod.triangles[i].data[1] + - finalLod.triangles[i].data[2]).normalized(); - - if (targetDir.dot(triDir) > searchThreshold) { - result.push_back(i); - } - } - - if (result.empty()) return getBestTriangleUnderCamera(viewerPosition); - return result; - } - - std::vector PlanetData::subdivideTriangles(const std::vector& input, float noiseCoeff) { - std::vector output; - - for (const auto& t : input) { - // Вершины и их ID - const Vector3f& a = t.data[0]; - const Vector3f& b = t.data[1]; - const Vector3f& c = t.data[2]; - const VertexID& id_a = t.ids[0]; - const VertexID& id_b = t.ids[1]; - const VertexID& id_c = t.ids[2]; - - // 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(); - - //Vector3f pm_ab = m_ab * perlin.getSurfaceHeight(m_ab, noiseCoeff); - //Vector3f pm_bc = m_bc * perlin.getSurfaceHeight(m_bc, noiseCoeff); - //Vector3f pm_ac = m_ac * perlin.getSurfaceHeight(m_ac, noiseCoeff); - - Vector3f pm_ab = m_ab; - Vector3f pm_bc = m_bc; - Vector3f pm_ac = m_ac; - - // 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 новых треугольника - 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 - output.emplace_back(Triangle{ {pm_ab, pm_bc, pm_ac}, {id_mab, id_mbc, id_mac} }); // 3 - } - return output; - } - - LodLevel PlanetData::createLodLevel(const std::vector& geometry) { - LodLevel result; - result.triangles = geometry; - - size_t vertexCount = geometry.size() * 3; - - result.VertexIDs.reserve(vertexCount); - - for (const auto& t : geometry) { - for (int i = 0; i < 3; ++i) { - result.VertexIDs.push_back(t.ids[i]); - } - } - return result; - } - - void PlanetData::recalculateMeshAttributes(LodLevel& lod) - { - size_t vertexCount = lod.triangles.size() * 3; - - lod.vertexData.PositionData.clear(); - lod.vertexData.NormalData.clear(); - lod.vertexData.TexCoordData.clear(); - lod.vertexData.TangentData.clear(); - lod.vertexData.BinormalData.clear(); - lod.vertexData.ColorData.clear(); - - lod.vertexData.PositionData.reserve(vertexCount); - lod.vertexData.NormalData.reserve(vertexCount); - lod.vertexData.TexCoordData.reserve(vertexCount); - lod.vertexData.TangentData.reserve(vertexCount); - lod.vertexData.BinormalData.reserve(vertexCount); - lod.vertexData.ColorData.reserve(vertexCount); - - - - const std::array triangleUVs = { - Vector2f(0.5f, 1.0f), - Vector2f(0.0f, 0.0f), - Vector2f(1.0f, 0.0f) - }; - - const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый - const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый - - const float colorFrequency = 4.0f; // Масштаб пятен - - - for (const auto& t : lod.triangles) { - // --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) --- - Vector3f vA = t.data[0]; - Vector3f vB = t.data[1]; - Vector3f vC = t.data[2]; - - Vector3f x_axis = (vC - vB).normalized(); // Направление U - - Vector3f edge1 = vB - vA; - Vector3f edge2 = vC - vA; - 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 - - for (int i = 0; i < 3; ++i) { - lod.vertexData.PositionData.push_back(t.data[i]); - lod.vertexData.NormalData.push_back(z_axis); - lod.vertexData.TexCoordData.push_back(triangleUVs[i]); - 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, - dir(1) * colorFrequency, - dir(2) * colorFrequency - ); - - // Приводим шум из диапазона [-1, 1] в [0, 1] - blendFactor = blendFactor * 0.5f + 0.5f; - - // Линейная интерполяция между двумя цветами - Vector3f finalColor; - - finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish); - - lod.vertexData.ColorData.push_back(finalColor); - } - } - } - - LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) { - const float t = (1.0f + std::sqrt(5.0f)) / 2.0f; - - // 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 граней икосаэдра - struct IndexedTri { int v1, v2, v3; }; - std::vector faces = { - {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, - {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, - {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, - {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1} - }; - - std::vector geometry; - for (auto& f : faces) { - Triangle tri; - tri.data[0] = icosaVertices[f.v1]; - tri.data[1] = icosaVertices[f.v2]; - tri.data[2] = icosaVertices[f.v3]; - - // Генерируем 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)) + "_" + - std::to_string(tri.data[i](2)); - } - geometry.push_back(tri); - } - - // 3. Разбиваем N раз - for (int i = 0; i < subdivisions; i++) { - geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем - } - - // 4. Создаем LodLevel и заполняем топологию (v2tMap) - LodLevel lodLevel = createLodLevel(geometry); - - // Пересобираем v2tMap (она критична для релаксации) - lodLevel.v2tMap.clear(); - for (size_t i = 0; i < geometry.size(); ++i) { - for (int j = 0; j < 3; ++j) { - lodLevel.v2tMap[geometry[i].ids[j]].push_back((int)i); - } - } - - // 5. Применяем итеративную релаксацию (Lloyd-like) - // 5-10 итераций достаточно для отличной сетки - applySphericalRelaxation(lodLevel, 8); - - // 6. Накладываем шум и обновляем атрибуты - // ... (твой код наложения шума через Perlin) - - recalculateMeshAttributes(lodLevel); - return lodLevel; - } - - - - void PlanetData::applySphericalRelaxation(LodLevel& lod, int iterations) { - for (int iter = 0; iter < iterations; ++iter) { - std::map newPositions; - - 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); - centroid = centroid + faceCenter; - } - - 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]]; - } - } - } - } -} \ No newline at end of file diff --git a/src/planet/PlanetData.h b/src/planet/PlanetData.h deleted file mode 100644 index 5e24d82..0000000 --- a/src/planet/PlanetData.h +++ /dev/null @@ -1,134 +0,0 @@ -#pragma once - -#include "utils/Perlin.h" -#include "render/Renderer.h" -#include -#include -#include -#include -#include - -namespace ZL { - - - using VertexID = std::string; - using V2TMap = std::map>; - - struct Vector3fComparator { - bool operator()(const Eigen::Vector3f& a, const Eigen::Vector3f& b) const { - // Лексикографическое сравнение (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(); - } - }; - - VertexID generateEdgeID(const VertexID& id1, const VertexID& id2); - - constexpr static int MAX_LOD_LEVELS = 6; - - struct Triangle - { - std::array data; - std::array ids; - - Triangle() - { - } - - Triangle(Vector3f p1, Vector3f p2, Vector3f p3) - : data{ p1, p2, p3 } - { - } - - Triangle(std::array idata, std::array iids) - : data{ idata } - , ids{ iids } - { - } - }; - - struct LodLevel - { - std::vector triangles; - VertexDataStruct vertexData; - std::vector VertexIDs; - V2TMap v2tMap; - - void Scale(float s) - { - vertexData.Scale(s); - for (auto& t : triangles) { - for (int i = 0; i < 3; i++) { - t.data[i] = t.data[i] * s; - } - } - } - void Move(Vector3f pos) - { - vertexData.Move(pos); - for (auto& t : triangles) { - for (int i = 0; i < 3; i++) { - t.data[i] = t.data[i] + pos; - } - } - } - }; - - struct PlanetCampObject - { - Vector3f position; - Matrix3f rotation; - - std::array platformPos = { - Vector3f{ 0.f, 0.f,-38.f }, - Vector3f{ 20.f, 0.f,-18.f }, - Vector3f{ 20.f, 0.f,-58.f }, - Vector3f{ -20.f, 0.f,-58.f }, - Vector3f{ -20.f, 0.f,-18.f } - }; - }; - - class PlanetData { - public: - static const float PLANET_RADIUS; - static const Vector3f PLANET_CENTER_OFFSET; - - private: - PerlinNoise perlin; - PerlinNoise colorPerlin; - - std::array planetMeshLods; - LodLevel planetAtmosphereLod; - - //int currentLod; // Логический текущий уровень детализации - - //std::map initialVertexMap; - - // Внутренние методы генерации - std::vector subdivideTriangles(const std::vector& inputTriangles, float noiseCoeff); - LodLevel createLodLevel(const std::vector& triangles); - void recalculateMeshAttributes(LodLevel& lod); - LodLevel generateSphere(int subdivisions, float noiseCoeff); - public: - PlanetData(); - - void init(); - const LodLevel& getLodLevel() const; - const LodLevel& getAtmosphereLod() const; - - // Логика - std::pair calculateZRange(float distanceToSurface); - float distanceToPlanetSurfaceFast(const Vector3f& viewerPosition); - - // Возвращает индексы треугольников, видимых камерой - std::vector getBestTriangleUnderCamera(const Vector3f& viewerPosition); - std::vector getTrianglesUnderCameraNew2(const Vector3f& viewerPosition); - - void applySphericalRelaxation(LodLevel& lod, int iterations); - - std::vector campObjects; - - }; - -} // namespace ZL \ No newline at end of file diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp deleted file mode 100644 index 9d2b2d3..0000000 --- a/src/planet/PlanetObject.cpp +++ /dev/null @@ -1,611 +0,0 @@ -#include "PlanetObject.h" -#include -#include -#include "render/OpenGlExtensions.h" -#include "Environment.h" -#include "StoneObject.h" -#include "utils/TaskManager.h" -#include "TextModel.h" -#include "GameConstants.h" - -namespace ZL { - - extern float x; - -#if defined EMSCRIPTEN || defined __ANDROID__ - using std::min; - using std::max; -#endif - - extern const char* CONST_ZIP_FILE; - - Matrix3f GetRotationForTriangle(const Triangle& tri) { - - - Vector3f vA = tri.data[0]; - Vector3f vB = tri.data[1]; - Vector3f vC = tri.data[2]; - - // 1. Вычисляем ось X (горизонталь). - Vector3f x_axis = (vC - vB).normalized(); - - - // 2. Вычисляем нормаль (ось Z). - // Порядок cross product (AB x AC) определит "лицевую" сторону. - Vector3f edge1 = vB - vA; - Vector3f edge2 = vC - vA; - Vector3f z_axis = edge1.cross(edge2).normalized(); - - // 3. Вычисляем ось Y (вертикаль). - // В ортонормированном базисе Y всегда перпендикулярна Z и X. - Vector3f y_axis = z_axis.cross(x_axis).normalized(); - - // 4. Формируем прямую матрицу поворота (Rotation/World Matrix). - Matrix3f rot; - - // Столбец 0: Ось X - rot.data()[0] = x_axis.data()[0]; - rot.data()[3] = x_axis.data()[1]; - rot.data()[6] = x_axis.data()[2]; - - // Столбец 1: Ось Y - rot.data()[1] = y_axis.data()[0]; - rot.data()[4] = y_axis.data()[1]; - rot.data()[7] = y_axis.data()[2]; - - // Столбец 2: Ось Z - rot.data()[2] = z_axis.data()[0]; - rot.data()[5] = z_axis.data()[1]; - rot.data()[8] = z_axis.data()[2]; - - return rot; - } - - PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler) - : renderer(iRenderer), - taskManager(iTaskManager), - mainThreadHandler(iMainThreadHandler) - { - } - - void PlanetObject::init() { - // 1. Инициализируем данные (генерация мешей) - planetData.init(); - - // 2. Забираем данные для VBO - // Берем максимальный LOD для начальной отрисовки - planetRenderStruct.data = planetData.getLodLevel().vertexData; - - //planetRenderStruct.data.PositionData.resize(9); - planetRenderStruct.RefreshVBO(); - - - sandTexture = std::make_unique(CreateTextureDataFromPng("resources/sand2.png", CONST_ZIP_FILE)); - stoneTexture = std::make_unique(CreateTextureDataFromPng("resources/rockdark3.png", CONST_ZIP_FILE)); - - // Атмосфера - planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; - if (planetAtmosphereRenderStruct.data.PositionData.size() > 0) - { - planetAtmosphereRenderStruct.RefreshVBO(); - } - - planetStones = CreateStoneGroupData(778, planetData.getLodLevel()); - - //stonesToRender = planetStones.inflate(planetStones.allInstances.size()); - stonesToRender.resize(planetStones.allInstances.size()); - planetStones.initStatuses(); - - stoneToBake = planetStones.inflateOne(0, 0.75); - - /* - campPlatform.data = LoadFromTextFile02("resources/platform1.txt", CONST_ZIP_FILE); - campPlatform.RefreshVBO(); - - campPlatformTexture = std::make_unique(CreateTextureDataFromPng("resources/platform_base.png", CONST_ZIP_FILE)); - */ - } - - - void PlanetObject::update(float deltaTimeMs) { - - // 1. Проверка порога движения (оптимизация из текущего кода) - float movementThreshold = 1.0f; - if ((Environment::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold - && !triangleIndicesToDraw.empty()) { - //processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач - return; - } - lastUpdatePos = Environment::shipState.position; - - // 2. Получаем список видимых треугольников - auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position); - std::sort(newIndices.begin(), newIndices.end()); - - // 3. Анализируем, что нужно загрузить - for (int triIdx : newIndices) { - if (planetStones.statuses[triIdx] == ChunkStatus::Empty) { - // Помечаем, чтобы не спамить задачами - planetStones.statuses[triIdx] = ChunkStatus::Generating; - - // Отправляем тяжелую математику в TaskManager - taskManager.EnqueueBackgroundTask([this, triIdx]() { - // Выполняется в фоновом потоке: только генерация геометрии в RAM - float scaleModifier = 1.0f; - VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier); - - // Передаем задачу на загрузку в GPU в очередь главного потока - this->mainThreadHandler.EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable { - // Проверяем актуальность: если треугольник всё еще в списке видимых - auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx); - if (it != triangleIndicesToDraw.end()) { - stonesToRender[triIdx].data = std::move(data); - stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов - planetStones.statuses[triIdx] = ChunkStatus::Live; - } - else { - // Если уже не нужен — просто сбрасываем статус - planetStones.statuses[triIdx] = ChunkStatus::Empty; - } - }); - }); - } - } - - // 4. Очистка (Unload) - // Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память - for (size_t i = 0; i < planetStones.statuses.size(); ++i) { - if (planetStones.statuses[i] == ChunkStatus::Live) { - bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i); - if (!isVisible) { - // Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers) - stonesToRender[i].data.PositionData.clear(); - stonesToRender[i].vao.reset(); - stonesToRender[i].positionVBO.reset(); - stonesToRender[i].normalVBO.reset(); - stonesToRender[i].tangentVBO.reset(); - stonesToRender[i].binormalVBO.reset(); - stonesToRender[i].colorVBO.reset(); - stonesToRender[i].texCoordVBO.reset(); - - planetStones.statuses[i] = ChunkStatus::Empty; - } - } - } - - triangleIndicesToDraw = std::move(newIndices); - - // 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.) - //processMainThreadTasks(); - } - - - void PlanetObject::bakeStoneTexture(Renderer& renderer) { - glViewport(0, 0, 512, 512); - glClearColor(0,0,0, 1.0f); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - - static const std::string planetBakeShaderName = "planetBake"; - - renderer.shaderManager.PushShader(planetBakeShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - - Triangle tr = planetData.getLodLevel().triangles[0]; - - // 1. Получаем матрицу вращения (оси в столбцах) - Matrix3f mr = GetRotationForTriangle(tr); - - // 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты - // Используем MultMatrixVector(Matrix, Vector). - // Если ваша функция считает V * M, то передайте Inverse(mr). - Vector3f rA = mr * tr.data[0]; - Vector3f rB = mr * tr.data[1]; - Vector3f rC = mr * tr.data[2]; - - // 3. Вычисляем реальные границы треугольника после поворота - float minX = min(rA(0), min(rB(0), rC(0))); - float maxX = max(rA(0), max(rB(0), rC(0))); - float minY = min(rA(1), min(rB(1), rC(1))); - float maxY = max(rA(1), max(rB(1), rC(1))); - - - // Находим центр и размеры - float width = maxX - minX; - float height = maxY - minY; - float centerX = (minX + maxX) * 0.5f; - float centerY = (minY + maxY) * 0.5f; - - //width = width * 0.995; - //height = height * 0.995; - - renderer.PushProjectionMatrix( - centerX - width*0.5, centerX + width * 0.5, - centerY - height * 0.5, centerY + height * 0.5, - 150, 200000); - - renderer.PushMatrix(); - renderer.LoadIdentity(); - // Сдвигаем камеру по Z - renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 }); - - // Применяем вращение - renderer.RotateMatrix(mr); - - - // Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения) - Vector3f planeNormal = mr.col(2); - - renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data()); - renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data()); - renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation - - - glActiveTexture(GL_TEXTURE0); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); // Отсекаем задние грани - if (stoneToBake.data.PositionData.size() > 0) - { - - glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); - renderer.DrawVertexRenderStruct(stoneToBake); - CheckGlError(); - } - glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - } - - - void PlanetObject::draw(Renderer& renderer) { - - { - if (stoneMapFB == nullptr) - { - //stoneMapFB = std::make_unique(512, 512, true); - stoneMapFB = std::make_unique(512, 512); - } - stoneMapFB->Bind(); - - bakeStoneTexture(renderer); - - stoneMapFB->Unbind(); - - stoneMapFB->GenerateMipmaps(); - - } - - glViewport(0, 0, Environment::width, Environment::height); - //-------------------------- - - - drawPlanet(renderer); - drawStones(renderer); - //drawCamp(renderer); - glClear(GL_DEPTH_BUFFER_BIT); - drawAtmosphere(renderer); - } - - void PlanetObject::drawPlanet(Renderer& renderer) - { - static const std::string planetLandShaderName = "planetLand"; - - static const std::string textureUniformName = "Texture"; - - renderer.shaderManager.PushShader(planetLandShaderName); - - - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); - auto zRange = planetData.calculateZRange(dist); - const float currentZNear = zRange.first; - const float currentZFar = zRange.second; - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); - - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - - renderer.TranslateMatrix(-Environment::shipState.position); - - const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); - - renderer.RenderUniform1i(textureUniformName, 0); - renderer.RenderUniform1i("BakedTexture", 1); - - Triangle tr = planetData.getLodLevel().triangles[0]; // Берем базовый треугольник - Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании - - // Позиция камеры (корабля) в мире - renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); - - //renderer.RenderUniform1f("uHeightScale", 0.08f); - renderer.RenderUniform1f("uHeightScale", 0.0f); - - renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); - renderer.RenderUniform1f("uCurrentZFar", currentZFar); - - // Направление на солнце в мировом пространстве - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - - // Направление от центра планеты к игроку для расчета дня/ночи - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); - - // Тот же фактор освещенности игрока - float playerLightFactor = playerDirWorld.dot(-sunDirWorld); - playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); - renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - - - glActiveTexture(GL_TEXTURE1); - glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); - //glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); - - renderer.DrawVertexRenderStruct(planetRenderStruct); - CheckGlError(); - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - - - } - - void PlanetObject::drawStones(Renderer& renderer) - { - static const std::string planetStoneShaderName = "planetStone"; - - renderer.shaderManager.PushShader(planetStoneShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); - auto zRange = planetData.calculateZRange(dist); - const float currentZNear = zRange.first; - const float currentZFar = zRange.second; - - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); - - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - - renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); - renderer.RenderUniform1f("uCurrentZFar", currentZFar); - renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); - //std::cout << "uViewPos" << Environment::shipState.position << std::endl; - // PlanetObject.cpp, метод drawStones - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); - renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); - - /* - for (int i : triangleIndicesToDraw) - //for (int i = 0; i < stonesToRender.size(); i++) - { - if (stonesToRender[i].data.PositionData.size() > 0) - { - renderer.DrawVertexRenderStruct(stonesToRender[i]); - } - }*/ - for (int i : triangleIndicesToDraw) { - // КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ: - // Проверяем, что данные не просто существуют, а загружены в GPU - if (planetStones.statuses[i] == ChunkStatus::Live) { - // Дополнительная проверка на наличие данных (на всякий случай) - if (stonesToRender[i].data.PositionData.size() > 0) { - renderer.DrawVertexRenderStruct(stonesToRender[i]); - } - } - // Если статус Generating или Empty — мы просто пропускаем этот индекс. - // Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO. - } - - CheckGlError(); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - - - } - - void PlanetObject::drawAtmosphere(Renderer& renderer) { - static const std::string defaultShaderName = "defaultAtmosphere"; - //glClear(GL_DEPTH_BUFFER_BIT); - glDepthMask(GL_FALSE); - - renderer.shaderManager.PushShader(defaultShaderName); - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); - auto zRange = planetData.calculateZRange(dist); - float currentZNear = zRange.first; - float currentZFar = zRange.second; - - if (currentZNear < 200) - { - currentZNear = 200; - currentZFar = currentZNear * 100; - } - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); - - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - - const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); - - Vector3f color = { 0.0, 0.5, 1.0 }; - - renderer.RenderUniform3fv("uColor", color.data()); - - renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); - - renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); - - // В начале drawAtmosphere или как uniform - //float time = SDL_GetTicks() / 1000.0f; - //renderer.RenderUniform1f("uTime", time); - - // В методе PlanetObject::drawAtmosphere - Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - - // Получаем текущую матрицу вида (ModelView без трансляции объекта) - // В вашем движке это Environment::inverseShipMatrix - Matrix3f viewMatrix2 = Environment::inverseShipMatrix; - - // Трансформируем вектор света в пространство вида - Vector3f viewLightDir = viewMatrix2 * worldLightDir; - - // Передаем инвертированный вектор (направление НА источник) - Vector3f lightToSource = -viewLightDir.normalized(); - renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); - - // В методе Game::drawCubemap - //renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f); - // Направление света в мировом пространстве для освещения облаков - //Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); - - // 1. Рассчитываем uPlayerLightFactor (как в Game.cpp) - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - - // Насколько игрок на свету - float playerLightFactor = playerDirWorld.dot(-sunDirWorld); - playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог - - // 2. ОБЯЗАТЕЛЬНО передаем в шейдер - renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - - // 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было) - renderer.RenderUniform3fv("uSkyColor", color.data()); - - //Vector3f playerDirWorld = Environment::shipState.position.normalized(); - renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения - - renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); - glDisable(GL_BLEND); - glDepthMask(GL_TRUE); - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - - } - - void PlanetObject::drawCamp(Renderer& renderer) - { - renderer.shaderManager.PushShader(defaultShaderName); - renderer.RenderUniform1i(textureUniformName, 0); - - float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); - auto zRange = planetData.calculateZRange(dist); - const float currentZNear = zRange.first; - const float currentZFar = zRange.second; - - - // 2. Применяем динамическую матрицу проекции - renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, - static_cast(Environment::width) / static_cast(Environment::height), - currentZNear, currentZFar); - - renderer.PushMatrix(); - renderer.LoadIdentity(); - renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); - renderer.RotateMatrix(Environment::inverseShipMatrix); - renderer.TranslateMatrix(-Environment::shipState.position); - - renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); - renderer.RenderUniform1f("uCurrentZFar", currentZFar); - renderer.RenderUniform3fv("uViewPos", Environment::shipState.position.data()); - //std::cout << "uViewPos" << Environment::shipState.position << std::endl; - // PlanetObject.cpp, метод drawStones - Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized(); - renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); - - Vector3f playerDirWorld = Environment::shipState.position.normalized(); - float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); - renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); - - glEnable(GL_CULL_FACE); - glCullFace(GL_BACK); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - for (int i = 0; i < planetData.campObjects.size(); i++) - { - - renderer.PushMatrix(); - for (int j = 0; j < 5; j++) - { - renderer.PushMatrix(); - renderer.TranslateMatrix(planetData.campObjects[i].position); - renderer.RotateMatrix(planetData.campObjects[i].rotation); - renderer.ScaleMatrix(Vector3f{ 2.0f, 2.0f, 2.0f }); - renderer.TranslateMatrix(planetData.campObjects[i].platformPos[j]); - glBindTexture(GL_TEXTURE_2D, campPlatformTexture->getTexID()); - renderer.DrawVertexRenderStruct(campPlatform); - renderer.PopMatrix(); - } - renderer.PopMatrix(); - - } - - CheckGlError(); - glDisable(GL_BLEND); - glDisable(GL_CULL_FACE); - - renderer.PopMatrix(); - renderer.PopProjectionMatrix(); - renderer.shaderManager.PopShader(); - CheckGlError(); - - glClear(GL_DEPTH_BUFFER_BIT); - } - - float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) - { - return planetData.distanceToPlanetSurfaceFast(viewerPosition); - } - - - -} // namespace ZL \ No newline at end of file diff --git a/src/planet/PlanetObject.h b/src/planet/PlanetObject.h deleted file mode 100644 index d566062..0000000 --- a/src/planet/PlanetObject.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include "render/Renderer.h" -#include "render/TextureManager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "utils/Perlin.h" -#include "PlanetData.h" -#include "StoneObject.h" -#include "render/FrameBuffer.h" -#include -#include - -namespace ZL { - class TaskManager; - class MainThreadHandler; - - class PlanetObject { - public: - PlanetData planetData; - - // Данные только для рендеринга (OpenGL specific) - VertexRenderStruct planetRenderStruct; - VertexRenderStruct planetAtmosphereRenderStruct; - StoneGroup planetStones; - std::vector stonesToRender; - VertexRenderStruct stoneToBake; - - std::vector triangleIndicesToDraw; - - std::shared_ptr sandTexture; - std::shared_ptr stoneTexture; - - std::unique_ptr stoneMapFB; - - - VertexRenderStruct campPlatform; - std::shared_ptr campPlatformTexture; - - Vector3f lastUpdatePos; - - // External items, set outside - Renderer& renderer; - TaskManager& taskManager; - MainThreadHandler& mainThreadHandler; - public: - PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler); - - void init(); - void update(float deltaTimeMs); - void bakeStoneTexture(Renderer& renderer); - void draw(Renderer& renderer); - void drawStones(Renderer& renderer); - void drawPlanet(Renderer& renderer); - void drawAtmosphere(Renderer& renderer); - void drawCamp(Renderer& renderer); - - float distanceToPlanetSurface(const Vector3f& viewerPosition); - }; - -} // namespace ZL \ No newline at end of file diff --git a/src/planet/StoneObject.cpp b/src/planet/StoneObject.cpp deleted file mode 100644 index babd177..0000000 --- a/src/planet/StoneObject.cpp +++ /dev/null @@ -1,354 +0,0 @@ -#include "StoneObject.h" - -#include "utils/Utils.h" -#include -#include -#include "render/Renderer.h" -#include "PlanetData.h" - -namespace ZL { - -#if defined EMSCRIPTEN || defined __ANDROID__ - using std::min; - using std::max; -#endif - - - - const float StoneParams::BASE_SCALE = 10.0f; // Общий размер камня - const float StoneParams::MIN_AXIS_SCALE = 1.0f; // Минимальное растяжение/сжатие по оси - const float StoneParams::MAX_AXIS_SCALE = 1.0f; // Максимальное растяжение/сжатие по оси - const float StoneParams::MIN_PERTURBATION = 0.0f; // Минимальное радиальное возмущение вершины - const float StoneParams::MAX_PERTURBATION = 0.0f; // Максимальное радиальное возмущение вершины - const int StoneParams::STONES_PER_TRIANGLE = 40; - - - - // Вспомогательная функция для получения случайного числа в диапазоне [min, max] - float getRandomFloat(std::mt19937& gen, float min, float max) { - std::uniform_real_distribution<> distrib(min, max); - return static_cast(distrib(gen)); - } - - // Вспомогательная функция для генерации случайной точки на треугольнике - // Использует барицентрические координаты - Vector3f GetRandomPointOnTriangle(const Triangle& t, std::mt19937& engine) { - std::uniform_real_distribution<> distrib(0.0f, 1.0f); - - float r1 = getRandomFloat(engine, 0.0f, 1.0f); - float r2 = getRandomFloat(engine, 0.0f, 1.0f); - - // Преобразование r1, r2 для получения равномерного распределения - float a = 1.0f - std::sqrt(r1); - float b = std::sqrt(r1) * r2; - float c = 1.0f - a - b; // c = sqrt(r1) * (1 - r2) - - // Барицентрические координаты - // P = a*p1 + b*p2 + c*p3 - Vector3f p1_term = t.data[0] * a; - Vector3f p2_term = t.data[1] * b; - Vector3f p3_term = t.data[2] * c; - - return p1_term + p2_term + p3_term; - } - - - // Икосаэдр (на основе золотого сечения phi) - // Координаты могут быть вычислены заранее для константного икосаэдра. - // Здесь только объявление, чтобы показать идею. - - VertexDataStruct CreateBaseConvexPolyhedron(uint64_t seed) { - - // const size_t SUBDIVISION_LEVEL = 1; // Уровень подразделения (для более круглого камня, пока опустим) - - std::mt19937 engine(static_cast(seed)); - - // Золотое сечение - const float t = (1.0f + std::sqrt(5.0f)) / 2.0f; - - // 12 вершин икосаэдра - std::vector initialVertices = { - { -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 } - }; - - // 20 треугольных граней (индексы вершин) - std::vector> faces = { - // 5 треугольников вокруг вершины 0 - {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11}, - // 5 смежных полос - {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8}, - // 5 треугольников вокруг вершины 3 - {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9}, - // 5 смежных полос - {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1} - }; - - // 1. Нормализация и Возмущение (Perturbation) - for (Vector3f& v : initialVertices) { - v = v.normalized() * StoneParams::BASE_SCALE; // Нормализация к сфере радиуса BASE_SCALE - - // Радиальное возмущение: - float perturbation = getRandomFloat(engine, StoneParams::MIN_PERTURBATION, StoneParams::MAX_PERTURBATION); - v = v * (1.0f + perturbation); - } - - // 2. Трансформация (Масштабирование и Поворот) - - // Случайные масштабы по осям - Vector3f scaleFactors = { - getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE), - getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE), - getRandomFloat(engine, StoneParams::MIN_AXIS_SCALE, StoneParams::MAX_AXIS_SCALE) - }; - - // Применяем масштабирование - for (Vector3f& v : initialVertices) { - v(0) *= scaleFactors(0); - v(1) *= scaleFactors(1); - v(2) *= scaleFactors(2); - } - - /* - // Случайный поворот (например, вокруг трех осей) - Vector4f qx = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitX()));//QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f)); - Vector4f qy = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitY()));//QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f)); - Vector4f qz = Eigen::Quaternionf(Eigen::AngleAxisf(getRandomFloat(engine, 0.0f, 360.0f), Eigen::Vector3f::UnitZ()));//QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f)); - Vector4f qFinal = slerp(qx, qy, 0.5f); // Простой пример комбинирования - qFinal = slerp(qFinal, qz, 0.5f).normalized(); - Matrix3f rotationMatrix = QuatToMatrix(qFinal); - - for (Vector3f& v : initialVertices) { - v = MultMatrixVector(rotationMatrix, v); - }*/ - - // 3. Сглаженные Нормали и Формирование Mesh - VertexDataStruct result; - // Карта для накопления нормалей по уникальным позициям вершин - // (Требует определенного оператора < для Vector3f в ZLMath.h, который у вас есть) - std::map smoothNormalsMap; - - // Предварительное заполнение карты нормалями - for (const auto& face : faces) { - Vector3f p1 = initialVertices[face[0]]; - Vector3f p2 = initialVertices[face[1]]; - Vector3f p3 = initialVertices[face[2]]; - - // Нормаль грани: (p2 - p1) x (p3 - p1) - Vector3f faceNormal = (p2 - p1).cross(p3 - p1).normalized(); - - smoothNormalsMap[p1] = smoothNormalsMap[p1] + faceNormal; - smoothNormalsMap[p2] = smoothNormalsMap[p2] + faceNormal; - smoothNormalsMap[p3] = smoothNormalsMap[p3] + faceNormal; - } - - // Нормализация накопленных нормалей - for (auto& pair : smoothNormalsMap) { - pair.second = pair.second.normalized(); - } - - - // 4. Финальное заполнение VertexDataStruct и Текстурные Координаты - for (const auto& face : faces) { - Vector3f p1 = initialVertices[face[0]]; - Vector3f p2 = initialVertices[face[1]]; - Vector3f p3 = initialVertices[face[2]]; - - // Позиции - result.PositionData.push_back(p1); - result.PositionData.push_back(p2); - result.PositionData.push_back(p3); - - // Сглаженные Нормали (из карты) - result.NormalData.push_back(smoothNormalsMap[p1]); - result.NormalData.push_back(smoothNormalsMap[p2]); - result.NormalData.push_back(smoothNormalsMap[p3]); - - // Текстурные Координаты (Планарная проекция на плоскость грани) - // p1 -> (0, 0), p2 -> (L_12, 0), p3 -> (L_13 * cos(angle), L_13 * sin(angle)) - // Где L_xy - длина вектора, angle - угол между p2-p1 и p3-p1 - - Vector3f uAxis = (p2 - p1).normalized(); - Vector3f vRaw = p3 - p1; - - // Проекция vRaw на uAxis - float uProjLen = vRaw.dot(uAxis); - - // Вектор V перпендикулярный U: vRaw - uProj - Vector3f vAxisRaw = vRaw - (uAxis * uProjLen); - - // Длина оси V - float vLen = vAxisRaw.norm(); - - // Нормализованная ось V - Vector3f vAxis = vAxisRaw.normalized(); - - // Координаты (u, v) для p1, p2, p3 относительно p1 - Vector2f uv1 = { 0.0f, 0.0f }; - Vector2f uv2 = { (p2 - p1).norm(), 0.0f }; // p2-p1 вдоль оси U - Vector2f uv3 = { uProjLen, vLen }; // p3-p1: u-компонента = uProjLen, v-компонента = vLen - - // Находим максимальный размер, чтобы масштабировать в [0, 1] - float maxUV = max(uv2(0), max(uv3(0), uv3(1))); - - if (maxUV > 0.000001f) { - // Масштабируем: - result.TexCoordData.push_back(uv1); - result.TexCoordData.push_back(uv2 * (1.f / maxUV)); - result.TexCoordData.push_back(uv3 * (1.f / maxUV)); - } - else { - // Предотвращение деления на ноль для вырожденных граней - result.TexCoordData.push_back({ 0.0f, 0.0f }); - result.TexCoordData.push_back({ 0.0f, 0.0f }); - result.TexCoordData.push_back({ 0.0f, 0.0f }); - } - } - - return result; - } - - Triangle createLocalTriangle(const Triangle& sampleTri) - { - // Находим центр в 3D - Vector3f center = (sampleTri.data[0] + sampleTri.data[1] + sampleTri.data[2]) * (1.0f / 3.0f); - - // Строим базис самого треугольника - // vY направляем на 0-ю вершину (как в вашем Special расчете) - Vector3f vY = (sampleTri.data[0] - center).normalized(); - // Временный X для расчета нормали - Vector3f vX_temp = (sampleTri.data[1] - sampleTri.data[2]).normalized(); - // Чистая нормаль - Vector3f vZ = vX_temp.cross(vY).normalized(); - // Чистый X, перпендикулярный Y и Z - Vector3f vX = vY.cross(vZ).normalized(); - - // Переводим 3D точки в этот 2D базис (Z зануляется сам собой) - auto toLocal = [&](const Vector3f& p) { - Vector3f d = p - center; - return Vector3f{ d.dot(vX), d.dot(vY), 0.0f }; - }; - - Triangle local; - local.data[0] = toLocal(sampleTri.data[0]); - local.data[1] = toLocal(sampleTri.data[1]); - local.data[2] = toLocal(sampleTri.data[2]); - return local; - } - - StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& planetLodLevel) { - StoneGroup group; - group.allInstances.resize(planetLodLevel.triangles.size()); - - for (size_t tIdx = 0; tIdx < planetLodLevel.triangles.size(); ++tIdx) { - const Triangle& tri = planetLodLevel.triangles[tIdx]; - std::mt19937 engine(static_cast(globalSeed)); - - for (int i = 0; i < StoneParams::STONES_PER_TRIANGLE; ++i) { - StoneInstance instance; - instance.seed = globalSeed;// + tIdx * 1000 + i; - instance.position = GetRandomPointOnTriangle(tri, engine); - - float SCALE_MIN = 0.75f; - float SCALE_MAX = 2.5f; - - instance.scale = { - getRandomFloat(engine, SCALE_MIN, SCALE_MAX), - getRandomFloat(engine, SCALE_MIN, SCALE_MAX), - getRandomFloat(engine, SCALE_MIN, SCALE_MAX) - }; - - /* - if (tIdx == 0) - { - instance.scale = instance.scale * 0.7f; - }*/ - - /* - Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f)); - Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f)); - Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f)); - instance.rotation = slerp(slerp(qx, qy, 0.5f), qz, 0.5f).normalized(); - */ - instance.rotation = Vector4f(0.f, 0.f, 0.f, 1.f); - group.allInstances[tIdx].push_back(instance); - } - } - return group; - } - - std::vector StoneGroup::inflate(int count) - { - std::vector result; - result.resize(count); - - for (int tIdx = 0; tIdx < count; tIdx++) - { - result[tIdx] = inflateOne(tIdx, 1.0f); - } - - return result; - } - - VertexRenderStruct StoneGroup::inflateOne(int index, float scaleModifier) - { - static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); - - VertexRenderStruct result; - - - for (const auto& inst : allInstances[index]) { - Matrix3f rotMat = inst.rotation.toRotationMatrix(); - - for (size_t j = 0; j < baseStone.PositionData.size(); ++j) { - Vector3f p = baseStone.PositionData[j]; - Vector3f n = baseStone.NormalData[j]; - - p(0) *= inst.scale(0) * scaleModifier; - p(1) *= inst.scale(1) * scaleModifier; - p(2) *= inst.scale(2) * scaleModifier; - - result.data.PositionData.push_back(rotMat * p + inst.position); - result.data.NormalData.push_back(rotMat * n); - result.data.TexCoordData.push_back(baseStone.TexCoordData[j]); - - } - - result.RefreshVBO(); - } - - - return result; - } - - VertexDataStruct StoneGroup::inflateOneDataOnly(int index, float scaleModifier) - { - static VertexDataStruct baseStone = CreateBaseConvexPolyhedron(1337); - - VertexDataStruct result; - - - for (const auto& inst : allInstances[index]) { - Matrix3f rotMat = inst.rotation.toRotationMatrix(); - - for (size_t j = 0; j < baseStone.PositionData.size(); ++j) { - Vector3f p = baseStone.PositionData[j]; - Vector3f n = baseStone.NormalData[j]; - - p(0) *= inst.scale(0) * scaleModifier; - p(1) *= inst.scale(1) * scaleModifier; - p(2) *= inst.scale(2) * scaleModifier; - - result.PositionData.push_back(rotMat * p + inst.position); - result.NormalData.push_back(rotMat * n); - result.TexCoordData.push_back(baseStone.TexCoordData[j]); - - } - } - - - return result; - } - -} // namespace ZL diff --git a/src/planet/StoneObject.h b/src/planet/StoneObject.h deleted file mode 100644 index 6827a74..0000000 --- a/src/planet/StoneObject.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once -#include "render/Renderer.h" -#include "PlanetData.h" - -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 int STONES_PER_TRIANGLE; - - }; - - struct StoneInstance { - uint64_t seed; - Vector3f position; - Vector3f scale; - Eigen::Quaternionf rotation; - }; - - enum class ChunkStatus { - Empty, // Данных нет - Generating, // Задача в TaskManager (CPU) - ReadyToUpload, // Данные в памяти, ждут очереди в главный поток - Live // Загружено в GPU и готово к отрисовке - }; - - struct StoneGroup { - // mesh.PositionData и прочие будут заполняться в inflate() - VertexDataStruct mesh; - - std::vector> allInstances; - - // Очищает старую геометрию и генерирует новую для указанных индексов - std::vector inflate(int count); - - VertexRenderStruct inflateOne(int index, float scaleModifier); - VertexDataStruct inflateOneDataOnly(int index, float scaleModifier); - - std::vector statuses; - - // Инициализация статусов при создании группы - void initStatuses() { - statuses.assign(allInstances.size(), ChunkStatus::Empty); - } - }; - - // Теперь возвращает заготовку со всеми параметрами, но без тяжелого меша - StoneGroup CreateStoneGroupData(uint64_t globalSeed, const LodLevel& lodLevel); - - Triangle createLocalTriangle(const Triangle& sampleTri); - -} // namespace ZL