space-game001/src/planet/PlanetObject.cpp
2026-01-14 13:24:07 +03:00

578 lines
22 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 "render/OpenGlExtensions.h"
#include "Environment.h"
#include "StoneObject.h"
#include "utils/TaskManager.h"
namespace ZL {
#if defined EMSCRIPTEN || defined __ANDROID__
using std::min;
using std::max;
#endif
extern const char* CONST_ZIP_FILE;
Matrix3f GetRotationForTriangle(const Triangle& tri) {
Vector3f vA = tri.data[0];
Vector3f vB = tri.data[1];
Vector3f vC = tri.data[2];
// 1. Вычисляем ось X (горизонталь).
Vector3f x_axis = (vC - vB).normalized();
// 2. Вычисляем нормаль (ось Z).
// Порядок cross product (AB x AC) определит "лицевую" сторону.
Vector3f edge1 = vB - vA;
Vector3f edge2 = vC - vA;
Vector3f z_axis = edge1.cross(edge2).normalized();
// 3. Вычисляем ось Y (вертикаль).
// В ортонормированном базисе Y всегда перпендикулярна Z и X.
Vector3f y_axis = z_axis.cross(x_axis).normalized();
// 4. Формируем прямую матрицу поворота (Rotation/World Matrix).
Matrix3f rot;
// Столбец 0: Ось X
rot.data()[0] = x_axis.data()[0];
rot.data()[3] = x_axis.data()[1];
rot.data()[6] = x_axis.data()[2];
// Столбец 1: Ось Y
rot.data()[1] = y_axis.data()[0];
rot.data()[4] = y_axis.data()[1];
rot.data()[7] = y_axis.data()[2];
// Столбец 2: Ось Z
rot.data()[2] = z_axis.data()[0];
rot.data()[5] = z_axis.data()[1];
rot.data()[8] = z_axis.data()[2];
return rot;
}
PlanetObject::PlanetObject(Renderer& iRenderer, TaskManager& iTaskManager, MainThreadHandler& iMainThreadHandler)
: renderer(iRenderer),
taskManager(iTaskManager),
mainThreadHandler(iMainThreadHandler)
{
}
void PlanetObject::init() {
// 1. Инициализируем данные (генерация мешей)
planetData.init();
// 2. Забираем данные для VBO
// Берем максимальный LOD для начальной отрисовки
int lodIndex = planetData.getMaxLodIndex();
planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData;
//planetRenderStruct.data.PositionData.resize(9);
planetRenderStruct.RefreshVBO();
sandTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/sand2.png", CONST_ZIP_FILE));
stoneTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/rockdark3.png", CONST_ZIP_FILE));
// Атмосфера
planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData;
if (planetAtmosphereRenderStruct.data.PositionData.size() > 0)
{
planetAtmosphereRenderStruct.RefreshVBO();
}
planetStones = CreateStoneGroupData(778, planetData.getLodLevel(lodIndex));
//stonesToRender = planetStones.inflate(planetStones.allInstances.size());
stonesToRender.resize(planetStones.allInstances.size());
planetStones.initStatuses();
stoneToBake = planetStones.inflateOne(0, 0.75);
}
void PlanetObject::update(float deltaTimeMs) {
// 1. Проверка порога движения (оптимизация из текущего кода)
float movementThreshold = 1.0f;
if ((Environment::shipPosition - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold
&& !triangleIndicesToDraw.empty()) {
//processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач
return;
}
lastUpdatePos = Environment::shipPosition;
// 2. Получаем список видимых треугольников
auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipPosition);
std::sort(newIndices.begin(), newIndices.end());
// 3. Анализируем, что нужно загрузить
for (int triIdx : newIndices) {
if (planetStones.statuses[triIdx] == ChunkStatus::Empty) {
// Помечаем, чтобы не спамить задачами
planetStones.statuses[triIdx] = ChunkStatus::Generating;
// Отправляем тяжелую математику в TaskManager
taskManager.EnqueueBackgroundTask([this, triIdx]() {
// Выполняется в фоновом потоке: только генерация геометрии в RAM
float scaleModifier = 1.0f;
VertexDataStruct generatedData = planetStones.inflateOneDataOnly(triIdx, scaleModifier);
// Передаем задачу на загрузку в GPU в очередь главного потока
this->mainThreadHandler.EnqueueMainThreadTask([this, triIdx, data = std::move(generatedData)]() mutable {
// Проверяем актуальность: если треугольник всё еще в списке видимых
auto it = std::find(triangleIndicesToDraw.begin(), triangleIndicesToDraw.end(), triIdx);
if (it != triangleIndicesToDraw.end()) {
stonesToRender[triIdx].data = std::move(data);
stonesToRender[triIdx].RefreshVBO(); // OpenGL вызов
planetStones.statuses[triIdx] = ChunkStatus::Live;
}
else {
// Если уже не нужен — просто сбрасываем статус
planetStones.statuses[triIdx] = ChunkStatus::Empty;
}
});
});
}
}
// 4. Очистка (Unload)
// Если статус Live, но треугольника нет в новых индексах — освобождаем GPU память
for (size_t i = 0; i < planetStones.statuses.size(); ++i) {
if (planetStones.statuses[i] == ChunkStatus::Live) {
bool isVisible = std::binary_search(newIndices.begin(), newIndices.end(), (int)i);
if (!isVisible) {
// Очищаем данные и VBO (деструкторы shared_ptr в VertexRenderStruct сделают glDeleteBuffers)
stonesToRender[i].data.PositionData.clear();
stonesToRender[i].vao.reset();
stonesToRender[i].positionVBO.reset();
stonesToRender[i].normalVBO.reset();
stonesToRender[i].tangentVBO.reset();
stonesToRender[i].binormalVBO.reset();
stonesToRender[i].colorVBO.reset();
stonesToRender[i].texCoordVBO.reset();
planetStones.statuses[i] = ChunkStatus::Empty;
}
}
}
triangleIndicesToDraw = std::move(newIndices);
// 5. Выполняем одну задачу из очереди OpenGL (RefreshVBO и т.д.)
//processMainThreadTasks();
}
void PlanetObject::bakeStoneTexture(Renderer& renderer) {
glViewport(0, 0, 512, 512);
glClearColor(0,0,0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
static const std::string defaultShaderName2 = "planetBake";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0];
// 1. Получаем матрицу вращения (оси в столбцах)
Matrix3f mr = GetRotationForTriangle(tr);
// 2. Трансформируем вершины в локальное пространство экрана, чтобы найти габариты
// Используем MultMatrixVector(Matrix, Vector).
// Если ваша функция считает V * M, то передайте Inverse(mr).
Vector3f rA = mr * tr.data[0];
Vector3f rB = mr * tr.data[1];
Vector3f rC = mr * tr.data[2];
// 3. Вычисляем реальные границы треугольника после поворота
float minX = min(rA(0), min(rB(0), rC(0)));
float maxX = max(rA(0), max(rB(0), rC(0)));
float minY = min(rA(1), min(rB(1), rC(1)));
float maxY = max(rA(1), max(rB(1), rC(1)));
// Находим центр и размеры
float width = maxX - minX;
float height = maxY - minY;
float centerX = (minX + maxX) * 0.5f;
float centerY = (minY + maxY) * 0.5f;
//width = width * 0.995;
//height = height * 0.995;
renderer.PushProjectionMatrix(
centerX - width*0.5, centerX + width * 0.5,
centerY - height * 0.5, centerY + height * 0.5,
150, 200000);
renderer.PushMatrix();
renderer.LoadIdentity();
// Сдвигаем камеру по Z
renderer.TranslateMatrix(Vector3f{ 0, 0, -45000 });
// Применяем вращение
renderer.RotateMatrix(mr);
// Извлекаем нормаль треугольника (это 3-й столбец нашей матрицы вращения)
Vector3f planeNormal = mr.col(2);
renderer.RenderUniform3fv("uPlanePoint", tr.data[0].data());
renderer.RenderUniform3fv("uPlaneNormal", planeNormal.data());
renderer.RenderUniform1f("uMaxHeight", StoneParams::BASE_SCALE * 1.1f); // Соответствует BASE_SCALE + perturbation
glActiveTexture(GL_TEXTURE0);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK); // Отсекаем задние грани
if (stoneToBake.data.PositionData.size() > 0)
{
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
renderer.DrawVertexRenderStruct(stoneToBake);
CheckGlError();
}
glDisable(GL_CULL_FACE); // Не забываем выключить, чтобы не сломать остальной рендер
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void PlanetObject::draw(Renderer& renderer) {
{
if (stoneMapFB == nullptr)
{
//stoneMapFB = std::make_unique<FrameBuffer>(512, 512, true);
stoneMapFB = std::make_unique<FrameBuffer>(512, 512);
}
stoneMapFB->Bind();
bakeStoneTexture(renderer);
stoneMapFB->Unbind();
stoneMapFB->GenerateMipmaps();
}
glViewport(0, 0, Environment::width, Environment::height);
//--------------------------
drawPlanet(renderer);
drawStones(renderer);
drawAtmosphere(renderer);
}
void PlanetObject::drawPlanet(Renderer& renderer)
{
static const std::string defaultShaderName = "planetLand";
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 textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray("vTangent");
renderer.EnableVertexAttribArray("vBinormal");
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition);
auto zRange = planetData.calculateZRange(dist);
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();
renderer.RenderUniform1i("Texture", 0);
renderer.RenderUniform1i("BakedTexture", 1);
Triangle tr = planetData.getLodLevel(planetData.getCurrentLodIndex()).triangles[0]; // Берем базовый треугольник
Matrix3f mr = GetRotationForTriangle(tr); // Та же матрица, что и при запекании
// Позиция камеры (корабля) в мире
renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data());
//renderer.RenderUniform1f("uHeightScale", 0.08f);
renderer.RenderUniform1f("uHeightScale", 0.0f);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
// Направление на солнце в мировом пространстве
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
// Направление от центра планеты к игроку для расчета дня/ночи
Vector3f playerDirWorld = Environment::shipPosition.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
// Тот же фактор освещенности игрока
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sandTexture->getTexID());
//glBindTexture(GL_TEXTURE_2D, stoneMapFB->getTextureID());
renderer.DrawVertexRenderStruct(planetRenderStruct);
CheckGlError();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray("vTangent");
renderer.DisableVertexAttribArray("vBinormal");
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void PlanetObject::drawStones(Renderer& renderer)
{
static const std::string defaultShaderName2 = "planetStone";
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 textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition);
auto zRange = planetData.calculateZRange(dist);
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);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
renderer.RenderUniform3fv("uViewPos", Environment::shipPosition.data());
//std::cout << "uViewPos" << Environment::shipPosition << std::endl;
// PlanetObject.cpp, метод drawStones
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
Vector3f playerDirWorld = Environment::shipPosition.normalized();
float playerLightFactor = max(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D, stoneTexture->getTexID());
/*
for (int i : triangleIndicesToDraw)
//for (int i = 0; i < stonesToRender.size(); i++)
{
if (stonesToRender[i].data.PositionData.size() > 0)
{
renderer.DrawVertexRenderStruct(stonesToRender[i]);
}
}*/
for (int i : triangleIndicesToDraw) {
// КРИТИЧЕСКОЕ ИЗМЕНЕНИЕ:
// Проверяем, что данные не просто существуют, а загружены в GPU
if (planetStones.statuses[i] == ChunkStatus::Live) {
// Дополнительная проверка на наличие данных (на всякий случай)
if (stonesToRender[i].data.PositionData.size() > 0) {
renderer.DrawVertexRenderStruct(stonesToRender[i]);
}
}
// Если статус Generating или Empty — мы просто пропускаем этот индекс.
// Камни появятся на экране плавно, как только отработает TaskManager и RefreshVBO.
}
CheckGlError();
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
glClear(GL_DEPTH_BUFFER_BIT);
}
void PlanetObject::drawAtmosphere(Renderer& renderer) {
static const std::string defaultShaderName = "defaultAtmosphere";
//static const std::string defaultShaderName = "defaultColor";
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);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipPosition);
auto zRange = planetData.calculateZRange(dist);
float currentZNear = zRange.first;
float currentZFar = zRange.second;
if (currentZNear < 200)
{
currentZNear = 200;
currentZFar = currentZNear * 100;
}
// 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.data());
renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data());
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
// В начале drawAtmosphere или как uniform
//float time = SDL_GetTicks() / 1000.0f;
//renderer.RenderUniform1f("uTime", time);
// В методе PlanetObject::drawAtmosphere
Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
// Получаем текущую матрицу вида (ModelView без трансляции объекта)
// В вашем движке это Environment::inverseShipMatrix
Matrix3f viewMatrix2 = Environment::inverseShipMatrix;
// Трансформируем вектор света в пространство вида
Vector3f viewLightDir = viewMatrix2 * worldLightDir;
// Передаем инвертированный вектор (направление НА источник)
Vector3f lightToSource = -viewLightDir.normalized();
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
// В методе Game::drawCubemap
//renderer.RenderUniform1f("uTime", SDL_GetTicks() / 1000.0f);
// Направление света в мировом пространстве для освещения облаков
//Vector3f worldLightDir = Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data());
// 1. Рассчитываем uPlayerLightFactor (как в Game.cpp)
Vector3f playerDirWorld = Environment::shipPosition.normalized();
Vector3f sunDirWorld = Vector3f(1.0f, -1.0f, -1.0f).normalized();
// Насколько игрок на свету
float playerLightFactor = playerDirWorld.dot(-sunDirWorld);
playerLightFactor = max(0.0f, (playerLightFactor + 0.2f) / 1.2f); // Мягкий порог
// 2. ОБЯЗАТЕЛЬНО передаем в шейдер
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
// 3. Убедитесь, что uSkyColor тоже передан (в коде выше его не было)
renderer.RenderUniform3fv("uSkyColor", color.data());
//Vector3f playerDirWorld = Environment::shipPosition.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения
renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
{
return planetData.distanceToPlanetSurfaceFast(viewerPosition);
}
} // namespace ZL