#include "PlanetObject.h" #include #include #include "OpenGlExtensions.h" #include "Environment.h" #include "StoneObject.h" namespace ZL { Matrix3f GetRotationForTriangle(const Triangle& tri) { // Для треугольника №0: // tri.data[0] = {0, 20000, 0} (A) // tri.data[1] = {0, 0, 20000} (B) // tri.data[2] = {20000, 0, 0} (C) Vector3f vA = tri.data[0]; Vector3f vB = tri.data[1]; Vector3f vC = tri.data[2]; // 1. Вычисляем ось X (горизонталь). // Нам нужна грань BC: от (0, 0, 20000) до (20000, 0, 0) 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.m[0] = x_axis.v[0]; rot.m[3] = x_axis.v[1]; rot.m[6] = x_axis.v[2]; // Столбец 1: Ось Y rot.m[1] = y_axis.v[0]; rot.m[4] = y_axis.v[1]; rot.m[7] = y_axis.v[2]; // Столбец 2: Ось Z rot.m[2] = z_axis.v[0]; rot.m[5] = z_axis.v[1]; rot.m[8] = z_axis.v[2]; return rot; } PlanetObject::PlanetObject() { } void PlanetObject::init() { // 1. Инициализируем данные (генерация мешей) planetData.init(); // 2. Забираем данные для VBO // Берем максимальный LOD для начальной отрисовки int lodIndex = planetData.getMaxLodIndex(); planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; planetRenderStruct.RefreshVBO(); planetRenderStructCut.data = planetData.getLodLevelNoDist(lodIndex).vertexData; planetRenderStructCut.data.PositionData.resize(3); planetRenderStructCut.RefreshVBO(); //sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand2.png", "")); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand2.png", "")); stoneTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", "")); // Атмосфера planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; planetAtmosphereRenderStruct.RefreshVBO(); planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex)); planetStones.inflate({ 0/*,1,2,3,4,5,6,7*/ }); planetStonesToBakeRenderStruct.AssignFrom(planetStones.mesh); planetStonesToBakeRenderStruct.RefreshVBO(); } void PlanetObject::prepareDrawData() { if (!drawDataDirty) return; drawDataDirty = false; } void PlanetObject::update(float deltaTimeMs) { // 1. Получаем базовые треугольники под камерой auto lr = planetData.getTrianglesUnderCamera(Environment::shipPosition); int currentLod = planetData.getCurrentLodIndex(); // Временный вектор для сбора новых индексов std::vector newIndices; std::set used; // Рекурсивно (или итеративно, как у тебя) собираем индексы видимых зон for (int i : lr) { if (used.insert(i).second) newIndices.push_back(i); auto neighbors = planetData.findNeighbors(i, currentLod); for (int n : neighbors) { if (used.insert(n).second) newIndices.push_back(n); auto neighbors2 = planetData.findNeighbors(n, currentLod); for (int n2 : neighbors2) { if (used.insert(n2).second) newIndices.push_back(n2); auto neighbors3 = planetData.findNeighbors(n, currentLod); for (int n3 : neighbors3) { if (used.insert(n3).second) newIndices.push_back(n3); } } } } // 2. Сортируем новый список, чтобы порядок не влиял на сравнение std::sort(newIndices.begin(), newIndices.end()); // 3. Сравниваем с тем, что было нарисовано в прошлый раз if (newIndices != triangleIndicesToDraw) { // Обновляем список индексов (используем move для эффективности) triangleIndicesToDraw = std::move(newIndices); // --- ОБНОВЛЯЕМ ЖЕЛТУЮ ЗОНУ (только когда изменился состав треугольников) --- const auto& fullMesh = planetData.getLodLevel(currentLod).vertexData; planetRenderYellowStruct.data.PositionData.clear(); planetRenderYellowStruct.data.TexCoordData.clear(); for (int i : triangleIndicesToDraw) { // Копируем геометрию для подсветки for (int j = 0; j < 3; ++j) { planetRenderYellowStruct.data.PositionData.push_back(fullMesh.PositionData[i * 3 + j]); planetRenderYellowStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[i * 3 + j]); } } if (planetRenderYellowStruct.data.PositionData.size() > 0) { planetRenderYellowStruct.RefreshVBO(); } // --- ОБНОВЛЯЕМ КАМНИ (через новую структуру StoneGroup) --- if (triangleIndicesToDraw.size() > 0) { planetStones.inflate(triangleIndicesToDraw); // Используем AssignFrom, он внутри сам вызывает RefreshVBO planetStonesRenderStruct.AssignFrom(planetStones.mesh); } } } void PlanetObject::bakeStoneTexture(Renderer& renderer) { glViewport(0, 0, 512, 512); glClearColor(224 / 255.f, 201 / 255.f, 167 / 255.f, 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); float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; Triangle tr = planetData.getLodLevelNoDist(planetData.getCurrentLodIndex()).triangles[0]; // 1. Получаем матрицу вращения (оси в столбцах) Matrix3f mr = GetRotationForTriangle(tr); // 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты // Используем MultMatrixVector(Matrix, Vector). // Если ваша функция считает V * M, то передайте Inverse(mr). Vector3f rA = MultMatrixVector(mr, tr.data[0]); Vector3f rB = MultMatrixVector(mr, tr.data[1]); Vector3f rC = MultMatrixVector(mr, tr.data[2]); // 3. Вычисляем реальные границы треугольника после поворота float minX = min(rA.v[0], min(rB.v[0], rC.v[0])); float maxX = max(rA.v[0], max(rB.v[0], rC.v[0])); float minY = min(rA.v[1], min(rB.v[1], rC.v[1])); float maxY = max(rA.v[1], max(rB.v[1], rC.v[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.m[2], mr.m[5], mr.m[8] }; renderer.RenderUniform3fv("uPlanePoint", &tr.data[0].v[0]); renderer.RenderUniform3fv("uPlaneNormal", &planeNormal.v[0]); renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.PushMatrix(); renderer.DrawVertexRenderStruct(planetRenderStructCut); renderer.PopMatrix(); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); // Отсекаем задние грани if (planetStonesToBakeRenderStruct.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); renderer.DrawVertexRenderStruct(planetStonesToBakeRenderStruct); CheckGlError(); } glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::draw(Renderer& renderer) { prepareDrawData(); { if (stoneMapFB == nullptr) { stoneMapFB = std::make_unique(512, 512); } stoneMapFB->Bind(); bakeStoneTexture(renderer); stoneMapFB->Unbind(); } //bakeStoneTexture(renderer); glViewport(0, 0, Environment::width, Environment::height); //-------------------------- drawPlanet(renderer); //drawYellowZone(renderer); drawStones(renderer); 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"; static const std::string textureUniformName = "Texture"; 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.distanceToPlanetSurface(Environment::shipPosition); 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::shipPosition); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); renderer.RenderUniform1i("Texture", 0); renderer.RenderUniform1i("BakedTexture", 1); Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании // Позиция камеры (корабля) в мире renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]); //renderer.RenderUniform1f("uHeightScale", 0.03f); renderer.RenderUniform1f("uHeightScale", 0.0f); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID()); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); 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"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); 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::shipPosition); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if (planetStonesRenderStruct.data.PositionData.size() > 0) { glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID()); renderer.DrawVertexRenderStruct(planetStonesRenderStruct); CheckGlError(); } glDisable(GL_BLEND); glDisable(GL_CULL_FACE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); glClear(GL_DEPTH_BUFFER_BIT); } void PlanetObject::drawYellowZone(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"; static const std::string textureUniformName = "Texture"; float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; glClear(GL_DEPTH_BUFFER_BIT); if (planetRenderYellowStruct.data.PositionData.size() > 0) { renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray("vTangent"); renderer.EnableVertexAttribArray("vBinormal"); renderer.EnableVertexAttribArray(vTexCoordName); // 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::shipPosition); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); renderer.RenderUniform3fv("uViewPos", &Environment::shipPosition.v[0]); Vector3f color2 = { 1.0, 1.0, 0.0 }; glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.RenderUniform3fv("uColor", &color2.v[0]); renderer.DrawVertexRenderStruct(planetRenderYellowStruct); //glDisable(GL_BLEND); CheckGlError(); renderer.PopMatrix(); 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::drawAtmosphere(Renderer& renderer) { static const std::string defaultShaderName = "defaultAtmosphere"; static const std::string vPositionName = "vPosition"; static const std::string vNormalName = "vNormal"; //glClear(GL_DEPTH_BUFFER_BIT); glDepthMask(GL_FALSE); renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vNormalName); float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); 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::shipPosition); const Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix(); Vector3f color = { 0.0, 0.5, 1.0 }; renderer.RenderUniform3fv("uColor", &color.v[0]); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, &viewMatrix.m[0]); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { return planetData.distanceToPlanetSurface(viewerPosition); } } // namespace ZL