shadow-over-bishkek001/src/planet/PlanetObject.cpp

526 lines
18 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 "GameConfig.h"
#include "PlanetObject.h"
#include <random>
#include <cmath>
#include "render/OpenGlExtensions.h"
#include "Environment.h"
#include "StoneObject.h"
#include "utils/TaskManager.h"
#include <algorithm>
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, Camera& iCamera)
: renderer(iRenderer),
taskManager(iTaskManager),
mainThreadHandler(iMainThreadHandler),
camera(iCamera)
{
}
void PlanetObject::init() {
// 1. Инициализируем данные (генерация мешей)
planetData.init();
// 2. Забираем данные для VBO
// Берем максимальный LOD для начальной отрисовки
int lodIndex = planetData.getMaxLodIndex();
planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData;
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::shipState.position - lastUpdatePos).squaredNorm() < movementThreshold * movementThreshold
&& !triangleIndicesToDraw.empty()) {
//processMainThreadTasks(); // Все равно обрабатываем очередь OpenGL задач
return;
}
lastUpdatePos = Environment::shipState.position;
// 2. Получаем список видимых треугольников
auto newIndices = planetData.getTrianglesUnderCameraNew2(Environment::shipState.position);
std::sort(newIndices.begin(), newIndices.end());
// 3. Запуск генерации для новых индексов
for (int idx : newIndices) {
if (planetStones.statuses[idx] == ChunkStatus::Empty) {
planetStones.statuses[idx] = ChunkStatus::Generating;
// Запускаем задачу в фоне
taskManager.EnqueueBackgroundTask([this, idx]() {
// Имитация тяжелой работы (генерация меша)
// Тут мы просто копируем базовый камень + скейл/поворот
VertexDataStruct newData = planetStones.inflateOneDataOnly(idx, 0.75f); // 0.75f - scaleModifier
// Когда готово — перекидываем в главный поток для загрузки в GPU
mainThreadHandler.EnqueueMainThreadTask([this, idx, data = std::move(newData)]() mutable {
// Проверяем, всё ли еще актуален этот чанк (вдруг игрок улетел)
// Но пока упростим: всегда грузим, а чистильщик (пункт 4) потом удалит если что
if (planetStones.statuses[idx] == ChunkStatus::Generating) {
stonesToRender[idx].data = std::move(data);
stonesToRender[idx].RefreshVBO();
planetStones.statuses[idx] = ChunkStatus::Live;
}
});
});
}
}
// 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;
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);
}
stoneMapFB->Bind();
bakeStoneTexture(renderer);
stoneMapFB->Unbind();
stoneMapFB->GenerateMipmaps();
}
glViewport(0, 0, Environment::width, Environment::height);
//--------------------------
drawPlanet(renderer);
#if ENABLE_STONES
//drawStones(renderer);
#endif
//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";
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::shipState.position);
auto zRange = planetData.calculateZRange(dist);
const float currentZNear = zRange.first;
const float currentZFar = zRange.second;
renderer.PushPerspectiveProjectionMatrix(
1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar
);
// View from Camera
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.PushSpecialMatrix(camera.getViewMatrix());
// Planet at world origin (если у тебя есть PLANET_CENTER_OFFSET — добавь Translate туда)
// renderer.TranslateMatrix(PlanetData::PLANET_CENTER_OFFSET);
renderer.RenderUniform1i("Texture", 0);
renderer.RenderUniform1i("BakedTexture", 1);
// Камера (для освещения) — именно позиция камеры, а не shipState.position
Eigen::Vector3f camPos = camera.getPosition();
renderer.RenderUniform3fv("uViewPos", camPos.data());
renderer.RenderUniform1f("uHeightScale", 0.0f);
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 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());
renderer.DrawVertexRenderStruct(planetRenderStruct);
CheckGlError();
renderer.PopMatrix(); // special matrix
renderer.PopMatrix(); // original push
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";
renderer.shaderManager.PushShader(defaultShaderName2);
renderer.RenderUniform1i("Texture", 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vColorName);
renderer.EnableVertexAttribArray(vNormalName);
renderer.EnableVertexAttribArray(vTexCoordName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist);
const float currentZNear = zRange.first;
const float currentZFar = zRange.second;
renderer.PushPerspectiveProjectionMatrix(
1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar
);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.PushSpecialMatrix(camera.getViewMatrix());
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
renderer.RenderUniform1f("uCurrentZFar", currentZFar);
Eigen::Vector3f camPos = camera.getPosition();
renderer.RenderUniform3fv("uViewPos", camPos.data());
Eigen::Vector3f sunDirWorld = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uLightDirWorld", sunDirWorld.data());
Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
float playerLightFactor = (std::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) {
if (planetStones.statuses[i] == ChunkStatus::Live) {
if (!stonesToRender[i].data.PositionData.empty()) {
renderer.DrawVertexRenderStruct(stonesToRender[i]);
}
}
}
CheckGlError();
glDisable(GL_BLEND);
glDisable(GL_CULL_FACE);
renderer.PopMatrix(); // special
renderer.PopMatrix(); // original
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vColorName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void PlanetObject::drawAtmosphere(Renderer& renderer)
{
static const std::string defaultShaderName = "defaultAtmosphere";
static const std::string vPositionName = "vPosition";
static const std::string vNormalName = "vNormal";
glDepthMask(GL_FALSE);
renderer.shaderManager.PushShader(defaultShaderName);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vNormalName);
float dist = planetData.distanceToPlanetSurfaceFast(Environment::shipState.position);
auto zRange = planetData.calculateZRange(dist);
float currentZNear = zRange.first;
float currentZFar = zRange.second;
if (currentZNear < 200) {
currentZNear = 200;
currentZFar = currentZNear * 100;
}
renderer.PushPerspectiveProjectionMatrix(
1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
currentZNear, currentZFar
);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.PushSpecialMatrix(camera.getViewMatrix());
const Eigen::Matrix4f viewMatrix = renderer.GetCurrentModelViewMatrix();
renderer.RenderUniformMatrix4fv("ModelViewMatrix", false, viewMatrix.data());
Eigen::Vector3f color = { 0.0f, 0.5f, 1.0f };
renderer.RenderUniform3fv("uColor", color.data());
renderer.RenderUniform3fv("uSkyColor", color.data());
renderer.RenderUniform1f("uDistanceToPlanetSurface", dist);
Eigen::Vector3f worldLightDir = Eigen::Vector3f(1.0f, -1.0f, -1.0f).normalized();
renderer.RenderUniform3fv("uWorldLightDir", worldLightDir.data());
// Свет в view-space: берём матрицу вида (верхний левый 3x3)
Eigen::Matrix3f viewRot = camera.getViewMatrix().block<3, 3>(0, 0);
Eigen::Vector3f viewLightDir = (viewRot * worldLightDir).normalized();
Eigen::Vector3f lightToSource = -viewLightDir;
renderer.RenderUniform3fv("uLightDirView", lightToSource.data());
Eigen::Vector3f playerDirWorld = Environment::shipState.position.normalized();
renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
Eigen::Vector3f sunDirWorld = worldLightDir;
float playerLightFactor = (std::max)(0.0f, (playerDirWorld.dot(-sunDirWorld) + 0.2f) / 1.2f);
renderer.RenderUniform1f("uPlayerLightFactor", playerLightFactor);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct);
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
renderer.PopMatrix(); // special
renderer.PopMatrix(); // original
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vNormalName);
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition)
{
return planetData.distanceToPlanetSurfaceFast(viewerPosition);
}
} // namespace ZL