526 lines
18 KiB
C++
526 lines
18 KiB
C++
#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
|