#include "PlanetObject.h" #include #include #include "OpenGlExtensions.h" #include "Environment.h" namespace ZL { static constexpr float PLANET_RADIUS = 20000.f; static Vector3f PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; //static Vector3f PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, -45000.f }; // --- 1. Дальний диапазон (FAR) --- static constexpr float FAR_Z_NEAR = 2000.0f; static constexpr float FAR_Z_FAR = 200000.0f; // Дистанция, где НАЧИНАЕТСЯ переход FAR -> MIDDLE static constexpr float TRANSITION_FAR_START = 3000.0f; // --- 2. Средний диапазон (MIDDLE) --- static constexpr float MIDDLE_Z_NEAR = 500.f; static constexpr float MIDDLE_Z_FAR = 50000.f; // Дистанция, где ЗАВЕРШАЕТСЯ переход FAR -> MIDDLE и НАЧИНАЕТСЯ MIDDLE -> NEAR static constexpr float TRANSITION_MIDDLE_START = 500.f; // --- 3. Ближний диапазон (NEAR) --- // Новые константы для максимальной точности static constexpr float NEAR_Z_NEAR = 100.0f; static constexpr float NEAR_Z_FAR = 10000.0f; // Дистанция, где ЗАВЕРШАЕТСЯ переход MIDDLE -> NEAR static constexpr float TRANSITION_NEAR_END = 100.f; // --- 3. Ближний диапазон (NEAR) --- // Новые константы для максимальной точности static constexpr float SUPER_NEAR_Z_NEAR = 5.0f; static constexpr float SUPER_NEAR_Z_FAR = 5000.0f; // Дистанция, где ЗАВЕРШАЕТСЯ переход MIDDLE -> NEAR static constexpr float TRANSITION_SUPER_NEAR_END = 10.f; VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) { // Канонический вид для ребра V1-V2: (min(ID1, ID2) + "_" + max(ID1, ID2)) return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1; } std::pair PlanetObject::calculateZRange(const Vector3f& shipPosition) { // 1. Вычисление расстояния до поверхности планеты const float dToPlanetSurface = distanceToPlanetSurface(); std::cout << "distanceToPlanetSurface " << dToPlanetSurface << std::endl; float currentZNear; float currentZFar; float alpha; // Коэффициент интерполяции для текущего сегмента // Диапазон I: Далеко (FAR) -> Средне (MIDDLE) if (dToPlanetSurface >= TRANSITION_FAR_START) { // Полностью дальний диапазон currentZNear = FAR_Z_NEAR; currentZFar = FAR_Z_FAR; } else if (dToPlanetSurface > TRANSITION_MIDDLE_START) { // Плавный переход от FAR к MIDDLE const float transitionLength = TRANSITION_FAR_START - TRANSITION_MIDDLE_START; // Нормализация расстояния, 0 при TRANSITION_FAR_START (Далеко), 1 при TRANSITION_MIDDLE_START (Близко) float normalizedDist = (dToPlanetSurface - TRANSITION_MIDDLE_START) / transitionLength; alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) // Интерполяция: FAR * (1-alpha) + MIDDLE * alpha currentZNear = FAR_Z_NEAR * (1.0f - alpha) + MIDDLE_Z_NEAR * alpha; currentZFar = FAR_Z_FAR * (1.0f - alpha) + MIDDLE_Z_FAR * alpha; // Диапазон II: Средне (MIDDLE) -> Близко (NEAR) } else if (dToPlanetSurface > TRANSITION_NEAR_END) { // Плавный переход от MIDDLE к NEAR const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END; // Нормализация расстояния, 0 при TRANSITION_MIDDLE_START (Далеко), 1 при TRANSITION_NEAR_END (Близко) float normalizedDist = (dToPlanetSurface - TRANSITION_NEAR_END) / transitionLength; alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) // Интерполяция: MIDDLE * (1-alpha) + NEAR * alpha 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) { // Плавный переход от MIDDLE к NEAR const float transitionLength = TRANSITION_NEAR_END - TRANSITION_SUPER_NEAR_END; // Нормализация расстояния, 0 при TRANSITION_MIDDLE_START (Далеко), 1 при TRANSITION_NEAR_END (Близко) float normalizedDist = (dToPlanetSurface - TRANSITION_SUPER_NEAR_END) / transitionLength; alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) // Интерполяция: MIDDLE * (1-alpha) + NEAR * alpha 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 { // Полностью ближний диапазон (distancdToPlanetSurfaceeToPlanetSurface <= TRANSITION_NEAR_END) currentZNear = SUPER_NEAR_Z_NEAR; currentZFar = SUPER_NEAR_Z_FAR; } return { currentZNear, currentZFar }; } PerlinNoise::PerlinNoise() { p.resize(256); std::iota(p.begin(), p.end(), 0); // Перемешиваем для случайности (можно задать seed) std::default_random_engine engine(77777); std::shuffle(p.begin(), p.end(), engine); p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения } PerlinNoise::PerlinNoise(uint64_t seed) { p.resize(256); std::iota(p.begin(), p.end(), 0); // Перемешиваем для случайности (используем переданный seed) std::default_random_engine engine(seed); // <-- Использование сида std::shuffle(p.begin(), p.end(), engine); p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения } float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float PerlinNoise::lerp(float t, float a, float b) { return a + t * (b - a); } float PerlinNoise::grad(int hash, float x, float y, float z) { int h = hash & 15; float u = h < 8 ? x : y; float v = h < 4 ? y : (h == 12 || h == 14 ? x : z); return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); } float PerlinNoise::noise(float x, float y, float z) { int X = (int)floor(x) & 255; int Y = (int)floor(y) & 255; int Z = (int)floor(z) & 255; x -= floor(x); y -= floor(y); z -= floor(z); float u = fade(x); float v = fade(y); float w = fade(z); int A = p[X] + Y, AA = p[A] + Z, AB = p[A + 1] + Z; int B = p[X + 1] + Y, BA = p[B] + Z, BB = p[B + 1] + Z; return lerp(w, lerp(v, lerp(u, grad(p[AA], x, y, z), grad(p[BA], x - 1, y, z)), lerp(u, grad(p[AB], x, y - 1, z), grad(p[BB], x - 1, y - 1, z))), lerp(v, lerp(u, grad(p[AA + 1], x, y, z - 1), grad(p[BA + 1], x - 1, y, z - 1)), lerp(u, grad(p[AB + 1], x, y - 1, z - 1), grad(p[BB + 1], x - 1, y - 1, z - 1)))); } float PerlinNoise::getSurfaceHeight(Vector3f pos) { // Частота шума (чем больше, тем больше "холмов") float frequency = 7.0f; // Получаем значение шума (обычно от -1 до 1) float noiseValue = noise(pos.v[0] * frequency, pos.v[1] * frequency, pos.v[2] * frequency); // Переводим из диапазона [-1, 1] в [0, 1] //noiseValue = (noiseValue + 1.0f) * 0.5f; // Масштабируем: хотим отклонение от 1.0 до 1.1 // Значит амплитуда = 0.1 //float height = 1.0f; // * 0.2 даст вариацию высоты float height = 1.0f - (noiseValue * 0.02f); // * 0.2 даст вариацию высоты return height; } bool PlanetObject::planetIsFar() { const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET; const float distanceToPlanetCenter = (planetWorldPosition - Environment::shipPosition).length(); const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS; return distanceToPlanetSurface > 0.1*TRANSITION_MIDDLE_START; } /** * @brief Находит ближайшую точку на плоскости, содержащей треугольник (A, B, C). * Это проекция точки P на плоскость. * @return Точка на плоскости (но не обязательно внутри треугольника). */ 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); // Расстояние (со знаком) от P до плоскости: d = N . AP float distance = N.dot(AP); // Проекция: P_proj = P - d * N Vector3f P_proj = P + N * (-distance); return P_proj; } float PlanetObject::distanceToPlanetSurface() { // 1. Смещение позиции корабля в локальные координаты планеты const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET; Vector3f shipLocalPosition = Environment::shipPosition - planetWorldPosition; // 2. Находим индекс треугольника, над которым находится корабль // Используем максимальный LOD для наивысшей точности std::vector targetTriangles = triangleUnderCamera(currentLod); if (targetTriangles.empty()) { // Если не удалось найти треугольник (например, ошибка в triangleUnderCamera // или корабль очень далеко от сетки), возвращаем старую оценку. return (shipLocalPosition.length() - PLANET_RADIUS); } // Берем первый найденный треугольник (в пограничных случаях будет выбрана одна сторона) int tri_index = targetTriangles[0]; // 3. Извлекаем вершины const auto& posData = planetMeshLods[currentLod].vertexData.PositionData; // Индексы в плоском массиве 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]; // 4. Находим ближайшую точку на плоскости треугольника Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C); // 5. Проверяем, находится ли P_proj внутри треугольника (используя барицентрические координаты) // ВАЖНОЕ УПРОЩЕНИЕ: В контексте геосферы, если корабль находится "над" треугольником // (что гарантируется triangleUnderCamera), ближайшая точка на плоскости (P_proj) // почти всегда будет лежать внутри треугольника A, B, C. // Если P_ship находится очень далеко от поверхности (что обычно так), // P_proj почти точно совпадет с P_closest. // Для высокой точности, нам бы понадобилась полная функция closestPointOnTriangle, // но для оценки расстояния в LOD-системах, где P_ship находится далеко от граней, // P_closest = P_proj — это приемлемая аппроксимация. // P_closest = closestPointOnTriangle(shipLocalPosition, A, B, C); Vector3f P_closest = P_proj; // Используем аппроксимацию P_proj // 6. Вычисляем точное расстояние // Расстояние = |shipLocalPosition - P_closest| float exactDistance = (shipLocalPosition - P_closest).length(); return exactDistance; } PlanetObject::PlanetObject() : perlin(77777) , colorPerlin(123123) { } void PlanetObject::init() { for (int i = 0; i < planetMeshLods.size(); i++) { planetMeshLods[i] = generateSphere(i); planetMeshLods[i].vertexData.Scale(PLANET_RADIUS); planetMeshLods[i].vertexData.Move(PLANET_CENTER_OFFSET); } planetRenderStruct.data = planetMeshLods[currentLod].vertexData; planetRenderStruct.RefreshVBO(); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand.png", "")); //sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", "")); /* planetAtmosphere.data = generateSphereOld(5); planetAtmosphere.data.Scale(PLANET_RADIUS*1.03); planetAtmosphere.data.Move(PLANET_CENTER_OFFSET); planetAtmosphere.RefreshVBO();*/ } void PlanetObject::prepareDrawData() { if (!drawDataDirty) return; drawDataDirty = false; } void PlanetObject::draw(Renderer& renderer) { prepareDrawData(); static const std::string defaultShaderName = "defaultColor"; static const std::string defaultShaderName2 = "defaultColor2"; 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 vTexCoord3Name = "vTexCoord3"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(defaultShaderName); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); renderer.EnableVertexAttribArray(vColorName); renderer.EnableVertexAttribArray(vNormalName); renderer.EnableVertexAttribArray(vTexCoordName); //renderer.EnableVertexAttribArray(vTexCoord3Name); const auto zRange = calculateZRange(Environment::shipPosition); 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 lightDir_World = Vector3f(1.0f, 0.0f, -1.0f).normalized(); // В OpenGL/шейдерах удобнее работать с вектором, указывающим ОТ источника к поверхности. Vector3f lightDirection_World = -lightDir_World; // Вектор, направленный от источника Vector3f lightDirection_View; lightDirection_View.v[0] = viewMatrix.m[0] * lightDirection_World.v[0] + viewMatrix.m[4] * lightDirection_World.v[1] + viewMatrix.m[8] * lightDirection_World.v[2]; lightDirection_View.v[1] = viewMatrix.m[1] * lightDirection_World.v[0] + viewMatrix.m[5] * lightDirection_World.v[1] + viewMatrix.m[9] * lightDirection_World.v[2]; lightDirection_View.v[2] = viewMatrix.m[2] * lightDirection_World.v[0] + viewMatrix.m[6] * lightDirection_World.v[1] + viewMatrix.m[10] * lightDirection_World.v[2]; lightDirection_View = lightDirection_View.normalized(); // Нормализуем на всякий случай // Установка uniform-переменной // Предполагается, что RenderUniform3fv определена в Renderer.h renderer.RenderUniform3fv("uLightDirection", &lightDirection_View.v[0]); renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, &viewMatrix.m[0]); float dist = distanceToPlanetSurface(); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); renderer.RenderUniform1f("uCurrentZFar", currentZFar); //glEnable(GL_BLEND); //glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); renderer.DrawVertexRenderStruct(planetRenderStruct); //glDisable(GL_BLEND); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); //renderer.DisableVertexAttribArray(vTexCoord3Name); renderer.DisableVertexAttribArray(vTexCoordName); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vColorName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); glClear(GL_DEPTH_BUFFER_BIT); //-------------------------- if (planetRenderRedStruct.data.PositionData.size() > 0) { renderer.shaderManager.PushShader(defaultShaderName2); renderer.RenderUniform1i(textureUniformName, 0); renderer.EnableVertexAttribArray(vPositionName); // 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); glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID()); Vector3f color1 = { 1.0, 0.0, 0.0 }; Vector3f color2 = { 1.0, 1.0, 0.0 }; renderer.RenderUniform3fv("uColor", &color1.v[0]); renderer.DrawVertexRenderStruct(planetRenderRedStruct); renderer.RenderUniform3fv("uColor", &color2.v[0]); renderer.DrawVertexRenderStruct(planetRenderYellowStruct); //glDisable(GL_BLEND); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } //drawAtmosphere(renderer); } 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); const auto zRange = calculateZRange(Environment::shipPosition); 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]); float dist = distanceToPlanetSurface(); renderer.RenderUniform1f("uDistanceToPlanetSurface", dist); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.DrawVertexRenderStruct(planetAtmosphere); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vNormalName); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::update(float deltaTimeMs) { auto lr = triangleUnderCamera(currentLod); planetRenderRedStruct.data.PositionData.clear(); planetRenderYellowStruct.data.PositionData.clear(); std::set usedYellow; for (int i : lr) { planetRenderRedStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[i * 3]); planetRenderRedStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[i * 3 + 1]); planetRenderRedStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[i * 3 + 2]); usedYellow.insert(i); } planetRenderRedStruct.RefreshVBO(); for (int i : lr) { auto neighbors = findNeighbors(i, currentLod); for (int n : neighbors) { if (usedYellow.count(n) == 0) { usedYellow.insert(n); planetRenderYellowStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[n * 3]); planetRenderYellowStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[n * 3 + 1]); planetRenderYellowStruct.data.PositionData.push_back(planetMeshLods[currentLod].vertexData.PositionData[n * 3 + 2]); } } } planetRenderYellowStruct.RefreshVBO(); } std::vector PlanetObject::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 PlanetObject::calculateSurfaceNormal(Vector3f p_sphere) { // 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); Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir); Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir); // Вектора от центральной точки к соседям Vector3f v1 = p1 - p0; Vector3f v2 = p2 - p0; // Нормаль - это перпендикуляр к этим двум векторам // Порядок (v2, v1) или (v1, v2) зависит от системы координат, // здесь подбираем так, чтобы нормаль смотрела наружу. return (-v2.cross(v1)).normalized(); } LodLevel PlanetObject::trianglesToVertices(const std::vector& geometry) { LodLevel result; 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 PlanetObject::generateSphere(int subdivisions) { // 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); // Обратите внимание: 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; } std::vector PlanetObject::findNeighbors(int index, int lod) { // Проверка lod опущена для краткости... // Получаем V2T-Map и VertexIDs для текущего LOD const V2TMap& v2tMap = planetMeshLods[lod].v2tMap; const auto& vertexIDs = planetMeshLods[lod].VertexIDs; if ((index * 3 + 2) >= vertexIDs.size()) return {}; std::set neighbors; // 1. Проходим по 3 топологическим ID вершин искомого треугольника for (int i = 0; i < 3; ++i) { VertexID v_id = vertexIDs[index * 3 + i]; // 2. Ищем этот ID в Map auto it = v2tMap.find(v_id); if (it != v2tMap.end()) { // 3. Добавляем все треугольники, связанные с v_id for (int tri_index : it->second) { if (tri_index != index) { neighbors.insert(tri_index); } } } } // Иерархические соседи (родители/дети) можно добавить по желанию // ... return std::vector(neighbors.begin(), neighbors.end()); } float check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon = 1e-6f) { // Нормаль к плоскости, проходящей через O, V1 и V2 Vector3f N_plane = V1.cross(V2); // Расстояние P до плоскости V1V2 (со знаком) float sign_P = P.dot(N_plane); // Расстояние V3 до плоскости V1V2 (со знаком) float sign_V3 = V3.dot(N_plane); // Если знаки совпадают, P находится на той же стороне, что и V3. // Возвращаем произведение знаков. return sign_P * sign_V3; } /** * @brief Проверяет, находится ли точка P внутри сферического треугольника (V1, V2, V3). * @param P Проверяемая точка (нормализованная). * @param V1, V2, V3 Нормализованные вершины треугольника. * @return true, если внутри или на границе. */ bool is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon) { // Точка P должна быть на одной стороне от всех трех граней относительно третьей вершины. // 1. Проверка относительно V1V2 (эталон V3) if (check_spherical_side(V1, V2, P, V3, epsilon) < -epsilon) return false; // 2. Проверка относительно V2V3 (эталон V1) if (check_spherical_side(V2, V3, P, V1, epsilon) < -epsilon) return false; // 3. Проверка относительно V3V1 (эталон V2) if (check_spherical_side(V3, V1, P, V2, epsilon) < -epsilon) return false; return true; } std::vector 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; // 1. Нормализация всех вершин и проверяемой точки для работы на сфере (радиус 1) const Vector3f a = a_orig.normalized(); const Vector3f b = b_orig.normalized(); const Vector3f c = c_orig.normalized(); // Точка pxx должна быть спроецирована на сферу. const Vector3f pxx_normalized = px_orig.normalized(); // 2. Вычисляем нормализованные середины (вершины внутренних треугольников) 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(); // 3. Определяем 4 подтреугольника // Delta_0: (a, m_ab, m_ac) if (is_inside_spherical_triangle(pxx_normalized, a, m_ab, m_ac, EPSILON)) { result.push_back(0); } // Delta_1: (m_ab, b, m_bc) if (is_inside_spherical_triangle(pxx_normalized, m_ab, b, m_bc, EPSILON)) { result.push_back(1); } // Delta_2: (m_ac, m_bc, c) if (is_inside_spherical_triangle(pxx_normalized, m_ac, m_bc, c, EPSILON)) { result.push_back(2); } // Delta_3: (m_ab, m_bc, m_ac) - Внутренний if (is_inside_spherical_triangle(pxx_normalized, m_ab, m_bc, m_ac, EPSILON)) { result.push_back(3); } // Если исходный pxx точно лежит на луче из O(0,0,0) и внутри исходного треугольника, // то pxx_normalized гарантированно попадет в один или несколько (на границе) // из 4х подтреугольников. return result; } std::vector PlanetObject::triangleUnderCamera(int lod) { std::vector r; if (lod == 0) { if (Environment::shipPosition.v[1] >= 0) { if (Environment::shipPosition.v[0] >= 0 && Environment::shipPosition.v[2] >= 0) { r.push_back(0); } if (Environment::shipPosition.v[0] >= 0 && Environment::shipPosition.v[2] <= 0) { r.push_back(1); } if (Environment::shipPosition.v[0] <= 0 && Environment::shipPosition.v[2] <= 0) { r.push_back(2); } if (Environment::shipPosition.v[0] <= 0 && Environment::shipPosition.v[2] >= 0) { r.push_back(3); } } if (Environment::shipPosition.v[1] <= 0) { if (Environment::shipPosition.v[0] >= 0 && Environment::shipPosition.v[2] >= 0) { r.push_back(4); } if (Environment::shipPosition.v[0] <= 0 && Environment::shipPosition.v[2] >= 0) { r.push_back(5); } if (Environment::shipPosition.v[0] <= 0 && Environment::shipPosition.v[2] <= 0) { r.push_back(6); } if (Environment::shipPosition.v[0] >= 0 && Environment::shipPosition.v[2] <= 0) { r.push_back(7); } } } else if (lod >= 1) { std::vector r0 = triangleUnderCamera(lod-1); for (int tri0 : r0) { Vector3f a = planetMeshLods[lod - 1].vertexData.PositionData[tri0 * 3]; Vector3f b = planetMeshLods[lod - 1].vertexData.PositionData[tri0 * 3+1]; Vector3f c = planetMeshLods[lod - 1].vertexData.PositionData[tri0 * 3+2]; std::vector result = find_sub_triangle_spherical(a, b, c, Environment::shipPosition); if (result.size() == 0) { std::cout << "Error!" << std::endl; } for (int trix : result) { r.push_back(tri0 * 4 + trix); } } } return r; } } // namespace ZL