#include "GameConfig.h" #include "PlanetObject.h" #include #include #include "render/OpenGlExtensions.h" #include "Environment.h" #include "StoneObject.h" #include "utils/TaskManager.h" #include namespace ZL { #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, Camera& iCamera) : renderer(iRenderer), taskManager(iTaskManager), mainThreadHandler(iMainThreadHandler), camera(iCamera) { } void PlanetObject::init() { // 1. Инициализируем данные (генерация мешей) planetData.init(); // 2. Забираем данные для VBO // Берем максимальный LOD для начальной отрисовки int lodIndex = planetData.getMaxLodIndex(); planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; 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(lodIndex)); //stonesToRender = planetStones.inflate(planetStones.allInstances.size()); stonesToRender.resize(planetStones.allInstances.size()); planetStones.initStatuses(); stoneToBake = planetStones.inflateOne(0, 0.75); } 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 idx : newIndices) { if (planetStones.statuses[idx] == ChunkStatus::Empty) { planetStones.statuses[idx] = ChunkStatus::Generating; // Запускаем задачу в фоне taskManager.EnqueueBackgroundTask([this, idx]() { // Имитация тяжелой работы (генерация меша) // Тут мы просто копируем базовый камень + скейл/поворот VertexDataStruct newData = planetStones.inflateOneDataOnly(idx, 0.75f); // 0.75f - scaleModifier // Когда готово — перекидываем в главный поток для загрузки в GPU mainThreadHandler.EnqueueMainThreadTask([this, idx, data = std::move(newData)]() mutable { // Проверяем, всё ли еще актуален этот чанк (вдруг игрок улетел) // Но пока упростим: всегда грузим, а чистильщик (пункт 4) потом удалит если что if (planetStones.statuses[idx] == ChunkStatus::Generating) { stonesToRender[idx].data = std::move(data); stonesToRender[idx].RefreshVBO(); planetStones.statuses[idx] = ChunkStatus::Live; } }); }); } } // 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 defaultShaderName2 = "planetBake"; static const std::string vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vTexCoordName); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).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; 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.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::draw(Renderer& renderer) { { if (stoneMapFB == nullptr) { stoneMapFB = std::make_unique(512, 512); } stoneMapFB->Bind(); bakeStoneTexture(renderer); stoneMapFB->Unbind(); stoneMapFB->GenerateMipmaps(); } glViewport(0, 0, Environment::width, Environment::height); //-------------------------- drawPlanet(renderer); #if ENABLE_STONES //drawStones(renderer); #endif //drawAtmosphere(renderer); } void PlanetObject::drawPlanet(Renderer& renderer) { static const std::string defaultShaderName = "planetLand"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray("vTangent"); renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; renderer.PushPerspectiveProjectionMatrix( 1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar ); // View from Camera renderer.PushMatrix(); renderer.LoadIdentity(); renderer.PushSpecialMatrix(camera.getViewMatrix()); // Planet at world origin (если у тебя есть PLANET_CENTER_OFFSET — добавь Translate туда) // renderer.TranslateMatrix(PlanetData::PLANET_CENTER_OFFSET); renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("BakedTexture", 1); // Камера (для освещения) — именно позиция камеры, а не shipState.position Eigen::Vector3f camPos = camera.getPosition(); renderer.RenderUniform3fv("uViewPos", camPos.data()); renderer.RenderUniform1f("uHeightScale", 0.0f); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); // special matrix renderer.PopMatrix(); // original push renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray("vTangent"); renderer.DisableVertexAttribArray("vBinormal"); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::drawStones(Renderer& renderer) { static const std::string defaultShaderName2 = "planetStone"; static const std::string vPositionName = "vPosition"; static const std::string vColorName = "vColor"; static const std::string vNormalName = "vNormal"; static const std::string vTexCoordName = "vTexCoord"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i("Texture", 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; renderer.PushPerspectiveProjectionMatrix( 1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar ); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.PushSpecialMatrix(camera.getViewMatrix()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); Eigen::Vector3f camPos = camera.getPosition(); renderer.RenderUniform3fv("uViewPos", camPos.data()); Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data()); Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); for (int i : triangleIndicesToDraw) { if (planetStones.statuses[i] == ChunkStatus::Live) { if (!stonesToRender[i].data.PositionData.empty()) { renderer.DrawVertexRenderStruct(stonesToRender[i]); } } } CheckGlError(); glDisable(GL_BLEND); glDisable(GL_CULL_FACE); renderer.PopMatrix(); // special renderer.PopMatrix(); // original renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::drawAtmosphere(Renderer& renderer) { static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string vPositionName = "vPosition"; static const std::string vNormalName = "vNormal"; glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); 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; } renderer.PushPerspectiveProjectionMatrix( 1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), currentZNear, currentZFar ); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.PushSpecialMatrix(camera.getViewMatrix()); const Eigen::Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data()); Eigen::Vector3f color = { 0.0f, 0.5f, 1.0f }; renderer.RenderUniform3fv("uColor", color.data()); renderer.RenderUniform3fv("uSkyColor", color.data()); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); Eigen::Vector3f worldLightDir = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized(); renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data()); // Свет в view-space: берём матрицу вида (верхний левый 3x3) Eigen::Matrix3f viewRot = camera.getViewMatrix().block<3, 3>(0, 0); Eigen::Vector3f viewLightDir = (viewRot * worldLightDir).normalized(); Eigen::Vector3f lightToSource = -viewLightDir; renderer.RenderUniform3fv("uLightDirView", lightToSource.data()); Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized(); renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data()); Eigen::Vector3f sunDirWorld = worldLightDir; float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f); renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE); renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); // special renderer.PopMatrix(); // original renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { return planetData.distanceToPlanetSurfaceFast(viewerPosition); } } // namespace ZL