space-game001/PlanetObject.cpp
2025-12-14 17:45:29 +03:00

853 lines
33 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 {
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<float, float> calculateZRange(const Vector3f& shipPosition) {
// 1. Вычисление расстояния до поверхности планеты
const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET;
const float distanceToPlanetCenter = (planetWorldPosition - shipPosition).length();
const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS;
std::cout << "distanceToPlanetSurface " << distanceToPlanetSurface << std::endl;
float currentZNear;
float currentZFar;
float alpha; // Коэффициент интерполяции для текущего сегмента
// Диапазон I: Далеко (FAR) -> Средне (MIDDLE)
if (distanceToPlanetSurface >= TRANSITION_FAR_START) {
// Полностью дальний диапазон
currentZNear = FAR_Z_NEAR;
currentZFar = FAR_Z_FAR;
}
else if (distanceToPlanetSurface > TRANSITION_MIDDLE_START) {
// Плавный переход от FAR к MIDDLE
const float transitionLength = TRANSITION_FAR_START - TRANSITION_MIDDLE_START;
// Нормализация расстояния, 0 при TRANSITION_FAR_START (Далеко), 1 при TRANSITION_MIDDLE_START (Близко)
float normalizedDist = (distanceToPlanetSurface - 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 (distanceToPlanetSurface > TRANSITION_NEAR_END) {
// Плавный переход от MIDDLE к NEAR
const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END;
// Нормализация расстояния, 0 при TRANSITION_MIDDLE_START (Далеко), 1 при TRANSITION_NEAR_END (Близко)
float normalizedDist = (distanceToPlanetSurface - 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 (distanceToPlanetSurface > 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 = (distanceToPlanetSurface - 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 {
// Полностью ближний диапазон (distanceToPlanetSurface <= 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.01f); // * 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;
}
float PlanetObject::distanceToPlanetSurface()
{
const Vector3f planetWorldPosition = PLANET_CENTER_OFFSET;
const float distanceToPlanetCenter = (planetWorldPosition - Environment::shipPosition).length();
const float distanceToPlanetSurface = distanceToPlanetCenter - PLANET_RADIUS;
return distanceToPlanetSurface;
}
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<Texture>(CreateTextureDataFromPng("./resources/sand.png", ""));
//sandTexture = std::make_unique<Texture>(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<float>(Environment::width) / static_cast<float>(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<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
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<float>(Environment::width) / static_cast<float>(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<int> 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<Triangle> PlanetObject::subdivideTriangles(const std::vector<Triangle>& input) {
std::vector<Triangle> 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<Triangle>& geometry) {
LodLevel result;
result.vertexData.PositionData.reserve(geometry.size() * 3);
result.vertexData.NormalData.reserve(geometry.size() * 3);
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());
// Заполняем VertexIDs
result.VertexIDs.push_back(t.ids[i]);
}
}
return result;
}
LodLevel PlanetObject::generateSphere(int subdivisions) {
// 1. Исходный октаэдр и присвоение ID
std::vector<Triangle> 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<int> 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<int> 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<int>(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<int> 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<int> 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<int> PlanetObject::triangleUnderCamera(int lod)
{
std::vector<int> 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<int> 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<int> 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