#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) --- static constexpr float FAR_Z_NEAR = 2000.0f; static constexpr float FAR_Z_FAR = 200000.0f; static constexpr float TRANSITION_FAR_START = 3000.0f; static constexpr float MIDDLE_Z_NEAR = 500.f; static constexpr float MIDDLE_Z_FAR = 100000.f; static constexpr float TRANSITION_MIDDLE_START = 500.f; static constexpr float NEAR_Z_NEAR = 100.0f; static constexpr float NEAR_Z_FAR = 20000.0f; static constexpr float TRANSITION_NEAR_END = 100.f; static constexpr float SUPER_NEAR_Z_NEAR = 5.0f; static constexpr float SUPER_NEAR_Z_FAR = 5000.0f; static constexpr float TRANSITION_SUPER_NEAR_END = 30.f; 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); 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 LodLevel& PlanetData::getLodLevel(int level) const { return planetMeshLods.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 >= TRANSITION_FAR_START) { currentZNear = FAR_Z_NEAR; currentZFar = FAR_Z_FAR; } else if (dToPlanetSurface > TRANSITION_MIDDLE_START) { const float transitionLength = TRANSITION_FAR_START - TRANSITION_MIDDLE_START; float normalizedDist = (dToPlanetSurface - TRANSITION_MIDDLE_START) / transitionLength; alpha = 1.0f - normalizedDist; currentZNear = FAR_Z_NEAR * (1.0f - alpha) + MIDDLE_Z_NEAR * alpha; currentZFar = FAR_Z_FAR * (1.0f - alpha) + MIDDLE_Z_FAR * alpha; } else if (dToPlanetSurface > TRANSITION_NEAR_END) { const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END; float normalizedDist = (dToPlanetSurface - TRANSITION_NEAR_END) / transitionLength; alpha = 1.0f - normalizedDist; currentZNear = MIDDLE_Z_NEAR * (1.0f - alpha) + NEAR_Z_NEAR * alpha; currentZFar = MIDDLE_Z_FAR * (1.0f - alpha) + NEAR_Z_FAR * alpha; } else if (dToPlanetSurface > TRANSITION_SUPER_NEAR_END) { const float transitionLength = TRANSITION_NEAR_END - TRANSITION_SUPER_NEAR_END; float normalizedDist = (dToPlanetSurface - TRANSITION_SUPER_NEAR_END) / transitionLength; alpha = 1.0f - normalizedDist; currentZNear = NEAR_Z_NEAR * (1.0f - alpha) + SUPER_NEAR_Z_NEAR * alpha; currentZFar = NEAR_Z_FAR * (1.0f - alpha) + SUPER_NEAR_Z_FAR * alpha; } else { currentZNear = SUPER_NEAR_Z_NEAR; currentZFar = SUPER_NEAR_Z_FAR; } 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); } 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; return (shipLocalPosition - P_closest).length(); } // --- Реализация 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; } // ... Реализации subdivideTriangles, calculateSurfaceNormal, trianglesToVertices, generateSphere, findNeighbors // Перенесите их из PlanetObject.cpp "как есть", добавив класс PlanetData:: перед именем. // Убедитесь, что используете perlin и planetMeshLods из this. std::vector PlanetData::subdivideTriangles(const std::vector& input) { 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(); // 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, m_ab, m_ac}, {id_a, id_mab, id_mac} }); // 0 output.emplace_back(Triangle{ {m_ab, b, m_bc}, {id_mab, id_b, id_mbc} }); // 1 output.emplace_back(Triangle{ {m_ac, m_bc, c}, {id_mac, id_mbc, id_c} }); // 2 output.emplace_back(Triangle{ {m_ab, m_bc, m_ac}, {id_mab, id_mbc, id_mac} }); // 3 } return output; } Vector3f PlanetData::calculateSurfaceNormal(Vector3f p_sphere, float noiseCoeff) { // p_sphere - это нормализованный вектор (точка на идеальной сфере) float theta = 0.01f; // Шаг для "щупанья" соседей (epsilon) // Нам нужно найти два вектора, касательных к сфере в точке p_sphere. // Для этого берем любой вектор (например UP), делаем Cross Product, чтобы получить касательную. // Если p_sphere совпадает с UP, берем RIGHT. Vector3f up = Vector3f(0.0f, 1.0f, 0.0f); if (abs(p_sphere.dot(up)) > 0.99f) { up = Vector3f(1.0f, 0.0f, 0.0f); } Vector3f tangentX = (up.cross(p_sphere)).normalized(); Vector3f tangentY = (p_sphere.cross(tangentX)).normalized(); // Точки на идеальной сфере со смещением Vector3f p0_dir = p_sphere; Vector3f p1_dir = (p_sphere + tangentX * theta).normalized(); Vector3f p2_dir = (p_sphere + tangentY * theta).normalized(); // Реальные точки на искаженной поверхности // p = dir * height(dir) Vector3f p0 = p0_dir * perlin.getSurfaceHeight(p0_dir, noiseCoeff); Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir, noiseCoeff); Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir, noiseCoeff); // Вектора от центральной точки к соседям Vector3f v1 = p1 - p0; Vector3f v2 = p2 - p0; // Нормаль - это перпендикуляр к этим двум векторам // Порядок (v2, v1) или (v1, v2) зависит от системы координат, // здесь подбираем так, чтобы нормаль смотрела наружу. return (-v2.cross(v1)).normalized(); } LodLevel PlanetData::trianglesToVertices(const std::vector& geometry) { LodLevel result; result.triangles = geometry; result.vertexData.PositionData.reserve(geometry.size() * 3); result.vertexData.NormalData.reserve(geometry.size() * 3); result.vertexData.TexCoordData.reserve(geometry.size() * 3); // <-- РЕЗЕРВИРУЕМ //buffer.TexCoord3Data.reserve(triangles.size() * 3); // <-- РЕЗЕРВИРУЕМ // Стандартные UV-координаты для покрытия одного треугольника // Покрывает текстурой всю грань. const std::array triangleUVs = { Vector2f(0.0f, 0.0f), Vector2f(1.0f, 0.0f), Vector2f(0.0f, 1.0f) }; result.VertexIDs.reserve(geometry.size() * 3); // Заполняем ID здесь for (const auto& t : geometry) { for (int i = 0; i < 3; ++i) { // Заполняем PositionData result.vertexData.PositionData.push_back(t.data[i]); // Заполняем NormalData (нормаль = нормализованная позиция на сфере) result.vertexData.NormalData.push_back(t.data[i].normalized()); result.vertexData.TexCoordData.push_back(triangleUVs[i]); // Заполняем VertexIDs result.VertexIDs.push_back(t.ids[i]); } } return result; } LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) { // 1. Исходный октаэдр и присвоение ID std::vector geometry = { {{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // 0 {{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 1 {{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // 2 {{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // 3 {{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // 4 {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // 5 {{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // 6 {{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}} // 7 }; // Присвоение ID исходным вершинам for (auto& t : geometry) { for (int i = 0; i < 3; i++) { // Используем map для получения ID по чистым координатам (норм. == чистые) t.ids[i] = initialVertexMap[t.data[i].normalized()]; } } // 2. ПРИМЕНЯЕМ ШУМ К ИСХОДНЫМ ВЕРШИНАМ // ВАЖНО: Мы применяем шум ПОСЛЕ нормализации, но перед разбиением. // Если вы хотите, чтобы шум был применен только к конечным вершинам, // переместите этот блок после шага 3. Оставим, как в вашем коде. // **ПРИМЕЧАНИЕ:** Если шум применен сейчас, то вершины на L>0 будут иметь // координаты (m_ab = (a_noisy + b_noisy)*0.5). // Если вы хотите, чтобы только финальные вершины имели шум, пропустите этот блок. // В текущей задаче это не критично, так как мы используем ТОЛЬКО VertexID. // 3. Разбиваем N раз (в subdivideTriangles генерируются ID новых вершин) for (int i = 0; i < subdivisions; i++) { geometry = subdivideTriangles(geometry); } // 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); // Обратите внимание: NormalData остается (dir), как в вашем коде lodLevel.vertexData.NormalData[i] = dir; } // 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()); } // ... И остальные методы генерации ... }