932 lines
37 KiB
C++
932 lines
37 KiB
C++
#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> 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<int> 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<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);
|
||
|
||
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<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.vertexData.TexCoordData.reserve(geometry.size() * 3); // <-- РЕЗЕРВИРУЕМ
|
||
//buffer.TexCoord3Data.reserve(triangles.size() * 3); // <-- РЕЗЕРВИРУЕМ
|
||
|
||
// Стандартные UV-координаты для покрытия одного треугольника
|
||
// Покрывает текстурой всю грань.
|
||
const std::array<Vector2f, 3> 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<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
|