space-game001/PlanetObject.cpp
2025-12-12 23:53:34 +03:00

292 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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