#include "PlanetObject.h" #include #include #include "OpenGlExtensions.h" #include "Environment.h" namespace ZL { struct Triangle { std::array data; Triangle(Vector3f p1, Vector3f p2, Vector3f p3) : data{ p1, p2, p3 } { } }; std::vector subdivideTriangles(const std::vector& inputTriangles, PerlinNoise& perlin) { std::vector output; output.reserve(inputTriangles.size() * 4); for (const auto& t : inputTriangles) { Vector3f a = t.data[0]; Vector3f b = t.data[1]; Vector3f c = t.data[2]; // 1. Вычисляем "сырые" середины Vector3f m_ab = (a + b) * 0.5f; Vector3f m_bc = (b + c) * 0.5f; Vector3f m_ac = (a + c) * 0.5f; // 2. Нормализуем их (получаем идеальную сферу радиуса 1) m_ab = m_ab.normalized(); m_bc = m_bc.normalized(); m_ac = m_ac.normalized(); // 3. ПРИМЕНЯЕМ ШУМ: Смещаем точку по радиусу m_ab = m_ab * perlin.getSurfaceHeight(m_ab); m_bc = m_bc * perlin.getSurfaceHeight(m_bc); m_ac = m_ac * perlin.getSurfaceHeight(m_ac); // 4. Формируем новые треугольники output.emplace_back(a, m_ab, m_ac); output.emplace_back(m_ab, b, m_bc); output.emplace_back(m_ac, m_bc, c); output.emplace_back(m_ab, m_bc, m_ac); } return output; } Vector3f calculateSurfaceNormal(Vector3f p_sphere, PerlinNoise& perlin) { // 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(); } VertexDataStruct trianglesToVertices(const std::vector& triangles, PerlinNoise& perlin) { VertexDataStruct buffer; buffer.PositionData.reserve(triangles.size() * 3); buffer.NormalData.reserve(triangles.size() * 3); for (const auto& t : triangles) { // Проходим по всем 3 вершинам треугольника for (int i = 0; i < 3; i++) { // p_geometry - это уже точка на поверхности (с шумом) Vector3f p_geometry = t.data[i]; // Нам нужно восстановить направление от центра к этой точке, // чтобы передать его в функцию расчета нормали. // Так как (0,0,0) - центр, то normalize(p) даст нам направление. Vector3f p_dir = p_geometry.normalized(); // Считаем аналитическую нормаль для этой конкретной точки Vector3f normal = calculateSurfaceNormal(p_dir, perlin); buffer.PositionData.push_back({ p_geometry }); //buffer.NormalData.push_back({ normal }); //buffer.TexCoordData.push_back({ 0.0f, 0.0f }); } } return buffer; } // Генерация геометрии октаэдра с дублированием вершин для Flat Shading VertexDataStruct generateOctahedron(PerlinNoise& perlin) { VertexDataStruct buffer; std::vector v = { Triangle{ { 0.0f, 1.0f, 0.0f}, // Top { 0.0f, 0.0f, 1.0f}, // Front { 1.0f, 0.0f, 0.0f}, // Right }, Triangle{ { 0.0f, 1.0f, 0.0f}, // Top { 1.0f, 0.0f, 0.0f}, // Right { 0.0f, 0.0f, -1.0f}, // Back }, Triangle{ { 0.0f, 1.0f, 0.0f}, // Top { 0.0f, 0.0f, -1.0f}, // Back {-1.0f, 0.0f, 0.0f}, // Left }, Triangle{ { 0.0f, 1.0f, 0.0f}, // Top {-1.0f, 0.0f, 0.0f}, // Left { 0.0f, 0.0f, 1.0f}, // Front }, Triangle{ { 0.0f, -1.0f, 0.0f}, // Bottom { 1.0f, 0.0f, 0.0f}, // Right { 0.0f, 0.0f, 1.0f}, // Front }, Triangle{ { 0.0f, -1.0f, 0.0f}, // Bottom { 0.0f, 0.0f, 1.0f}, // Front {-1.0f, 0.0f, 0.0f}, // Left }, Triangle{ { 0.0f, -1.0f, 0.0f}, // Bottom {-1.0f, 0.0f, 0.0f}, // Left { 0.0f, 0.0f, -1.0f}, // Back }, Triangle{ { 0.0f, -1.0f, 0.0f}, // Bottom { 0.0f, 0.0f, -1.0f}, // Back { 1.0f, 0.0f, 0.0f}, // Right } }; v = subdivideTriangles(v, perlin); v = subdivideTriangles(v, perlin); v = subdivideTriangles(v, perlin); v = subdivideTriangles(v, perlin); v = subdivideTriangles(v, perlin); for (int i = 0; i < v.size(); i++) { Vector3f p1 = v[i].data[0]; Vector3f p2 = v[i].data[1]; Vector3f p3 = v[i].data[2]; // Считаем нормаль грани через векторное произведение Vector3f edge1 = p2 - p1; Vector3f edge2 = p3 - p1; Vector3f normal = (edge1.cross(edge2)).normalized(); // Дублируем нормаль для всех трех вершин треугольника (Flat shading) buffer.PositionData.push_back({ p1 }); buffer.PositionData.push_back({ p2 }); buffer.PositionData.push_back({ p3 }); /*buffer.NormalData.push_back({normal}); buffer.NormalData.push_back({ normal }); buffer.NormalData.push_back({ normal }); buffer.TexCoordData.push_back({ 0.0f, 0.0f }); buffer.TexCoordData.push_back({ 0.0f, 0.0f }); buffer.TexCoordData.push_back({ 0.0f, 0.0f });*/ } return buffer; } VertexDataStruct generateSphere(int subdivisions, PerlinNoise& perlin) { // 1. Исходный октаэдр std::vector geometry = { Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // Top-Front-Right Triangle{{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Top-Right-Back Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // Top-Back-Left Triangle{{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Top-Left-Front Triangle{{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Bottom-Right-Front Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // Bottom-Front-Left Triangle{{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Bottom-Left-Back Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}} // Bottom-Back-Right }; // 2. ПРИМЕНЯЕМ ШУМ К ИСХОДНЫМ ВЕРШИНАМ // Проходимся по всем треугольникам и всем вершинам в них for (auto& t : geometry) { for (int i = 0; i < 3; i++) { // Нормализуем (на всякий случай, хотя у октаэдра они и так норм) Vector3f dir = t.data[i].normalized(); // Применяем высоту t.data[i] = dir * perlin.getSurfaceHeight(dir); } } // 3. Разбиваем N раз (новые вершины будут получать шум внутри функции) for (int i = 0; i < subdivisions; i++) { geometry = subdivideTriangles(geometry, perlin); } // 4. Генерируем нормали // Благодаря тому, что мы реально сдвигали вершины, Flat Shading нормали // рассчитаются правильно относительно нового рельефа. return trianglesToVertices(geometry, perlin); } PlanetObject::PlanetObject() { } void PlanetObject::init() { planetMesh = generateSphere(7, perlin); planetMesh.Scale(100.f); planetMesh.Move({ 0,0,-200 }); planetRenderStruct.data = planetMesh; planetRenderStruct.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 vPositionName = "vPosition"; renderer.shaderManager.PushShader(defaultShaderName); renderer.EnableVertexAttribArray(vPositionName); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, static_cast(Environment::width) / static_cast(Environment::height), 1, 1000); renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); renderer.DrawVertexRenderStruct(planetRenderStruct); CheckGlError(); renderer.PopMatrix(); renderer.PopProjectionMatrix(); renderer.DisableVertexAttribArray(vPositionName); renderer.shaderManager.PopShader(); CheckGlError(); } void PlanetObject::update(float deltaTimeMs) { } } // namespace ZL