292 lines
10 KiB
C++
292 lines
10 KiB
C++
#include "PlanetObject.h"
|
||
#include <random>
|
||
#include <cmath>
|
||
#include "OpenGlExtensions.h"
|
||
#include "Environment.h"
|
||
|
||
namespace ZL {
|
||
|
||
struct Triangle
|
||
{
|
||
std::array<Vector3f, 3> data;
|
||
|
||
Triangle(Vector3f p1, Vector3f p2, Vector3f p3)
|
||
: data{ p1, p2, p3 }
|
||
{
|
||
}
|
||
};
|
||
|
||
std::vector<Triangle> subdivideTriangles(const std::vector<Triangle>& inputTriangles, PerlinNoise& perlin) {
|
||
std::vector<Triangle> 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<Triangle>& 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<Triangle> 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<Triangle> 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<float>(Environment::width) / static_cast<float>(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
|