#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.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 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 > 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[currentLod]; // Работаем с текущим активным 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[currentLod]; 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]]; } } } } }