#include "PlanetData.h" #include #include #include #include namespace ZL { 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.025f); planetMeshLods[i].Scale(PLANET_RADIUS); planetMeshLods[i].Move(PLANET_CENTER_OFFSET); } for (int i = 0; i < planetMeshLodsNoDist.size(); i++) { planetMeshLodsNoDist[i] = generateSphere(i, 0); planetMeshLodsNoDist[i].Scale(PLANET_RADIUS); planetMeshLodsNoDist[i].Move(PLANET_CENTER_OFFSET); } planetAtmosphereLod = generateSphere(5, 0); planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03); planetAtmosphereLod.Move(PLANET_CENTER_OFFSET); } const LodLevel& PlanetData::getLodLevel(int level) const { return planetMeshLods.at(level); } const LodLevel& PlanetData::getLodLevelNoDist(int level) const { return planetMeshLodsNoDist.at(level); } 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 { currentZNear = 16; currentZFar = 5000.f; } return { currentZNear, currentZFar }; } float PlanetData::distanceToPlanetSurface(const Vector3f& viewerPosition) { Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET; // Используем getTrianglesUnderCamera для поиска // ВНИМАНИЕ: Здесь рекурсия логики. Метод getTrianglesUnderCamera использует внутри viewerPosition. // Чтобы не дублировать код, перепишем логику поиска здесь или будем использовать уже найденный ранее. // Для простоты оставим логику как в оригинале, но адаптированную. // ВАЖНО: getTrianglesUnderCamera - это "тяжелая" функция. В оригинале она вызывалась внутри distanceToPlanetSurface. // Но она требует LOD. Используем MAX LOD для точности. // Рекурсивный поиск треугольников под камерой. // Здесь нам нужно реализовать аналог triangleUnderCamera, но не зависящий от глобального состояния. // В оригинале функция triangleUnderCamera была рекурсивной. // Ниже я реализую итеративный или рекурсивный подход внутри getTrianglesUnderCamera. // Временное решение: полная копия логики поиска треугольника // Но нам нужен доступ к методу. std::vector targetTriangles = getTrianglesUnderCamera(viewerPosition); if (targetTriangles.empty()) { return (shipLocalPosition.length() - PLANET_RADIUS); } float lowestDistance; int tri_index = targetTriangles[0]; const auto& posData = planetMeshLods[currentLod].vertexData.PositionData; size_t data_index = tri_index * 3; if (data_index + 2 >= posData.size()) { return (shipLocalPosition.length() - PLANET_RADIUS); } const Vector3f& A = posData[data_index]; const Vector3f& B = posData[data_index + 1]; const Vector3f& C = posData[data_index + 2]; Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C); Vector3f P_closest = P_proj; lowestDistance = (shipLocalPosition - P_closest).length(); if (targetTriangles.size() <= 1) { return lowestDistance; } else { for (int i = 0; i < targetTriangles.size(); i++) { int tri_index = targetTriangles[i]; const auto& posData = planetMeshLods[currentLod].vertexData.PositionData; size_t data_index = tri_index * 3; if (data_index + 2 >= posData.size()) { return (shipLocalPosition.length() - PLANET_RADIUS); } const Vector3f& A = posData[data_index]; const Vector3f& B = posData[data_index + 1]; const Vector3f& C = posData[data_index + 2]; Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C); Vector3f P_closest = P_proj; if (lowestDistance < (shipLocalPosition - P_closest).length()) { lowestDistance = (shipLocalPosition - P_closest).length(); } } return lowestDistance; } } // --- Реализация getTrianglesUnderCamera (бывшая triangleUnderCamera) --- // Вспомогательная рекурсивная функция, скрытая от публичного API static std::vector recursiveTriangleSearch(int lod, const Vector3f& pos, const std::array& meshes) { std::vector r; // Логика уровня 0 (базовый октаэдр) if (lod == 0) { if (pos.v[1] >= 0) { if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(0); if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(1); if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(2); if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(3); } if (pos.v[1] <= 0) { if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(4); if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(5); if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(6); if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(7); } } else { // Рекурсивный шаг std::vector r0 = recursiveTriangleSearch(lod - 1, pos, meshes); for (int tri0 : r0) { Vector3f a = meshes[lod - 1].vertexData.PositionData[tri0 * 3]; Vector3f b = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 1]; Vector3f c = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 2]; std::vector result = PlanetData::find_sub_triangle_spherical(a, b, c, pos); for (int trix : result) { r.push_back(tri0 * 4 + trix); } } } return r; } std::vector PlanetData::getTrianglesUnderCamera(const Vector3f& viewerPosition) { // Вызываем рекурсию с текущим LOD return recursiveTriangleSearch(currentLod, viewerPosition, planetMeshLods); } // --- Остальные методы (check_spherical_side, generateSphere и т.д.) --- // (Копируйте реализацию из старого PlanetObject.cpp, // заменяя обращения к Environment::shipPosition на аргументы, если нужно, // и используя this->planetMeshLods) float PlanetData::check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon) { Vector3f N_plane = V1.cross(V2); float sign_P = P.dot(N_plane); float sign_V3 = V3.dot(N_plane); return sign_P * sign_V3; } bool PlanetData::is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon) { if (check_spherical_side(V1, V2, P, V3, epsilon) < -epsilon) return false; if (check_spherical_side(V2, V3, P, V1, epsilon) < -epsilon) return false; if (check_spherical_side(V3, V1, P, V2, epsilon) < -epsilon) return false; return true; } std::vector PlanetData::find_sub_triangle_spherical(const Vector3f& a_orig, const Vector3f& b_orig, const Vector3f& c_orig, const Vector3f& px_orig) { const float EPSILON = 1e-6f; std::vector result; const Vector3f a = a_orig.normalized(); const Vector3f b = b_orig.normalized(); const Vector3f c = c_orig.normalized(); const Vector3f pxx_normalized = px_orig.normalized(); const Vector3f m_ab = ((a + b) * 0.5f).normalized(); const Vector3f m_bc = ((b + c) * 0.5f).normalized(); const Vector3f m_ac = ((a + c) * 0.5f).normalized(); if (is_inside_spherical_triangle(pxx_normalized, a, m_ab, m_ac, EPSILON)) result.push_back(0); if (is_inside_spherical_triangle(pxx_normalized, m_ab, b, m_bc, EPSILON)) result.push_back(1); if (is_inside_spherical_triangle(pxx_normalized, m_ac, m_bc, c, EPSILON)) result.push_back(2); if (is_inside_spherical_triangle(pxx_normalized, m_ab, m_bc, m_ac, EPSILON)) result.push_back(3); 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); // 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::trianglesToVertices(const std::vector& geometry) { LodLevel result; result.triangles = geometry; size_t vertexCount = geometry.size() * 3; result.vertexData.PositionData.reserve(vertexCount); result.vertexData.NormalData.reserve(vertexCount); result.vertexData.TexCoordData.reserve(vertexCount); result.vertexData.TangentData.reserve(vertexCount); // Добавляем резерв result.vertexData.BinormalData.reserve(vertexCount); result.VertexIDs.reserve(vertexCount); const std::array triangleUVs = { Vector2f(0.5f, 1.0f), Vector2f(0.0f, 0.0f), Vector2f(1.0f, 0.0f) }; for (const auto& t : geometry) { // --- Вычисляем локальный базис треугольника (как в 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) { result.vertexData.PositionData.push_back(t.data[i]); result.vertexData.NormalData.push_back(z_axis); // Плоская нормаль грани для Parallax result.vertexData.TexCoordData.push_back(triangleUVs[i]); // Записываем вычисленный базис в каждую вершину треугольника result.vertexData.TangentData.push_back(x_axis); result.vertexData.BinormalData.push_back(y_axis); result.VertexIDs.push_back(t.ids[i]); } } return result; } LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) { // 1. Исходный октаэдр и присвоение ID std::vector geometry = { // Верхняя полусфера (Y > 0) {{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // 0 {{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, // 1 {{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 2 {{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // 3 // Нижняя полусфера (Y < 0) {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // 4 {{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 5 {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // 6 {{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}} // 7 }; // Присвоение ID исходным вершинам for (auto& t : geometry) { for (int i = 0; i < 3; i++) { // Используем map для получения ID по чистым координатам (норм. == чистые) t.ids[i] = initialVertexMap[t.data[i].normalized()]; } } // 3. Разбиваем N раз (в subdivideTriangles генерируются ID новых вершин) for (int i = 0; i < subdivisions; i++) { geometry = subdivideTriangles(geometry, noiseCoeff); } // 4. Генерируем PositionData, NormalData и VertexIDs LodLevel lodLevel = trianglesToVertices(geometry); // 5. Создание V2T-Map на основе VertexIDs (ТОПОЛОГИЧЕСКИЙ КЛЮЧ) V2TMap v2tMap; const auto& finalVertexIDs = lodLevel.VertexIDs; size_t num_triangles = geometry.size(); for (size_t i = 0; i < num_triangles; ++i) { for (int j = 0; j < 3; ++j) { VertexID v_id = finalVertexIDs[i * 3 + j]; // Если ключ уже есть, просто добавляем индекс v2tMap[v_id].push_back((int)i); } } lodLevel.v2tMap = v2tMap; // 6. Применение финального шума (если вы хотели, чтобы шум был только здесь) // Здесь мы должны были бы применить шум к buffer.PositionData, // но в вашем исходном коде шум применялся ранее. // Предполагаем, что шум будет применен здесь (иначе v2tMap не будет соответствовать). // ВОССТАНОВИМ ШАГ 2, но для финальной геометрии: for (size_t i = 0; i < lodLevel.vertexData.PositionData.size(); i++) { Vector3f dir = lodLevel.vertexData.PositionData[i].normalized(); lodLevel.vertexData.PositionData[i] = dir * perlin.getSurfaceHeight(dir, noiseCoeff); } // 7. Генерация ColorData lodLevel.vertexData.ColorData.reserve(geometry.size() * 3); const Vector3f baseColor = { 0.498f, 0.416f, 0.0f }; const float colorFrequency = 5.0f; const float colorAmplitude = 0.2f; const Vector3f offsetR = { 0.1f, 0.2f, 0.3f }; const Vector3f offsetG = { 0.5f, 0.4f, 0.6f }; const Vector3f offsetB = { 0.9f, 0.8f, 0.7f }; for (size_t i = 0; i < geometry.size(); i++) { for (int j = 0; j < 3; j++) { // Используем нормализованный вектор из PositionData (который равен NormalData) Vector3f dir = lodLevel.vertexData.NormalData[i * 3 + j]; // Вычисление цветового шума float noiseR = colorPerlin.noise( (dir.v[0] + offsetR.v[0]) * colorFrequency, (dir.v[1] + offsetR.v[1]) * colorFrequency, (dir.v[2] + offsetR.v[2]) * colorFrequency ); // ... (аналогично для noiseG и noiseB) // Здесь мы используем заглушки, так как нет полного определения PerlinNoise float noiseG = 0.0f; float noiseB = 0.0f; Vector3f colorOffset = { noiseR * colorAmplitude, noiseG * colorAmplitude, noiseB * colorAmplitude }; Vector3f finalColor = baseColor + colorOffset; lodLevel.vertexData.ColorData.push_back(finalColor); } } return lodLevel; } // Пример findNeighbors: std::vector PlanetData::findNeighbors(int index, int lod) { const V2TMap& v2tMap = planetMeshLods[lod].v2tMap; const auto& vertexIDs = planetMeshLods[lod].VertexIDs; if ((index * 3 + 2) >= vertexIDs.size()) return {}; std::set neighbors; for (int i = 0; i < 3; ++i) { VertexID v_id = vertexIDs[index * 3 + i]; auto it = v2tMap.find(v_id); if (it != v2tMap.end()) { for (int tri_index : it->second) { if (tri_index != index) { neighbors.insert(tri_index); } } } } return std::vector(neighbors.begin(), neighbors.end()); } }