434 lines
17 KiB
C++
434 lines
17 KiB
C++
#include "PlanetData.h"
|
||
#include <iostream>
|
||
#include <numeric>
|
||
#include <cmath>
|
||
#include <algorithm>
|
||
|
||
namespace ZL {
|
||
|
||
|
||
const float PlanetData::PLANET_RADIUS = 20000.f;
|
||
const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f };
|
||
|
||
// --- Константы диапазонов (перенесены из PlanetObject.cpp) ---
|
||
|
||
VertexID generateEdgeID(const VertexID& id1, const VertexID& id2) {
|
||
return id1 < id2 ? id1 + "_" + id2 : id2 + "_" + id1;
|
||
}
|
||
|
||
// Вспомогательная функция для проекции (локальная)
|
||
static 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);
|
||
float distance = N.dot(AP);
|
||
return P + N * (-distance);
|
||
}
|
||
|
||
PlanetData::PlanetData()
|
||
: perlin(77777)
|
||
, colorPerlin(123123)
|
||
, currentLod(0)
|
||
{
|
||
currentLod = planetMeshLods.size() - 1; // Start with max LOD
|
||
/*
|
||
initialVertexMap = {
|
||
{{ 0.0f, 1.0f, 0.0f}, "A"},
|
||
{{ 0.0f, -1.0f, 0.0f}, "B"},
|
||
{{ 1.0f, 0.0f, 0.0f}, "C"},
|
||
{{-1.0f, 0.0f, 0.0f}, "D"},
|
||
{{ 0.0f, 0.0f, 1.0f}, "E"},
|
||
{{ 0.0f, 0.0f, -1.0f}, "F"}
|
||
};*/
|
||
}
|
||
|
||
void PlanetData::init() {
|
||
for (int i = 0; i < planetMeshLods.size(); i++) {
|
||
//planetMeshLods[i] = generateSphere(i, 0.01f);
|
||
planetMeshLods[i] = generateSphere(i, 0.f);
|
||
planetMeshLods[i].Scale(PLANET_RADIUS);
|
||
planetMeshLods[i].Move(PLANET_CENTER_OFFSET);
|
||
}
|
||
|
||
planetAtmosphereLod = generateSphere(5, 0);
|
||
planetAtmosphereLod.Scale(PLANET_RADIUS * 1.03);
|
||
planetAtmosphereLod.Move(PLANET_CENTER_OFFSET);
|
||
}
|
||
|
||
const LodLevel& PlanetData::getLodLevel(int level) const {
|
||
return planetMeshLods.at(level);
|
||
}
|
||
|
||
|
||
const LodLevel& PlanetData::getAtmosphereLod() const {
|
||
return planetAtmosphereLod;
|
||
}
|
||
|
||
int PlanetData::getCurrentLodIndex() const {
|
||
return currentLod;
|
||
}
|
||
|
||
int PlanetData::getMaxLodIndex() const {
|
||
return static_cast<int>(planetMeshLods.size() - 1);
|
||
}
|
||
|
||
std::pair<float, float> PlanetData::calculateZRange(float dToPlanetSurface) {
|
||
|
||
|
||
float currentZNear;
|
||
float currentZFar;
|
||
float alpha;
|
||
|
||
if (dToPlanetSurface > 2000)
|
||
{
|
||
currentZNear = 1000;
|
||
currentZFar = currentZNear * 100;
|
||
}
|
||
else if (dToPlanetSurface > 1200)
|
||
{
|
||
currentZNear = 500;
|
||
currentZFar = currentZNear * 100;
|
||
}
|
||
else if (dToPlanetSurface > 650)
|
||
{
|
||
currentZNear = 250;
|
||
currentZFar = currentZNear * 100;
|
||
}
|
||
else if (dToPlanetSurface > 160)
|
||
{
|
||
currentZNear = 125;
|
||
currentZFar = currentZNear * 150;
|
||
}
|
||
else if (dToPlanetSurface > 100)
|
||
{
|
||
currentZNear = 65;
|
||
currentZFar = currentZNear * 170;
|
||
}
|
||
else if (dToPlanetSurface > 40)
|
||
{
|
||
currentZNear = 32;
|
||
currentZFar = 10000.f;
|
||
}
|
||
else if (dToPlanetSurface > 20)
|
||
{
|
||
currentZNear = 16;
|
||
currentZFar = 5000.f;
|
||
}
|
||
else if (dToPlanetSurface > 5)
|
||
{
|
||
currentZNear = 8;
|
||
currentZFar = 2500.f;
|
||
}
|
||
else
|
||
{
|
||
currentZNear = 4;
|
||
currentZFar = 1250.f;
|
||
}
|
||
|
||
return { currentZNear, currentZFar };
|
||
}
|
||
|
||
float PlanetData::distanceToPlanetSurfaceFast(const Vector3f& viewerPosition)
|
||
{
|
||
Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET;
|
||
|
||
return sqrt(shipLocalPosition.squaredNorm()) - PLANET_RADIUS;
|
||
}
|
||
|
||
|
||
std::vector<int> PlanetData::getBestTriangleUnderCamera(const Vector3f& viewerPosition) {
|
||
const LodLevel& finalLod = planetMeshLods[currentLod]; // Работаем с текущим активным LOD
|
||
Vector3f targetDir = (viewerPosition - PLANET_CENTER_OFFSET).normalized();
|
||
|
||
int bestTriangle = -1;
|
||
float maxDot = -1.0f;
|
||
|
||
// Шаг 1: Быстрый поиск ближайшего треугольника по "центроиду"
|
||
// Чтобы не проверять все, можно проверять каждый N-й или использовать
|
||
// предварительно вычисленные центры для LOD0, чтобы сузить круг.
|
||
// Но для надежности пройдемся по массиву (для 5-6 подразделений это быстро)
|
||
for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
|
||
// Вычисляем примерное направление на треугольник
|
||
Vector3f triDir = (finalLod.triangles[i].data[0] +
|
||
finalLod.triangles[i].data[1] +
|
||
finalLod.triangles[i].data[2]).normalized();
|
||
|
||
float dot = targetDir.dot(triDir);
|
||
if (dot > maxDot) {
|
||
maxDot = dot;
|
||
bestTriangle = i;
|
||
}
|
||
}
|
||
|
||
if (bestTriangle == -1) return {};
|
||
|
||
return { bestTriangle };
|
||
}
|
||
|
||
std::vector<int> PlanetData::getTrianglesUnderCameraNew2(const Vector3f& viewerPosition) {
|
||
const LodLevel& finalLod = planetMeshLods[currentLod];
|
||
Vector3f shipLocal = viewerPosition - PLANET_CENTER_OFFSET;
|
||
float currentDist = shipLocal.norm();
|
||
Vector3f targetDir = shipLocal.normalized();
|
||
|
||
// Желаемый радиус покрытия на поверхности планеты (в метрах/единицах движка)
|
||
// Подбери это значение так, чтобы камни вокруг корабля всегда были видны.
|
||
const float desiredCoverageRadius = 3000.0f;
|
||
|
||
// Вычисляем порог косинуса на основе желаемого радиуса и текущего расстояния.
|
||
// Чем мы дальше (currentDist больше), тем меньше должен быть угол отклонения
|
||
// от нормали, чтобы захватить ту же площадь.
|
||
float angle = atan2(desiredCoverageRadius, currentDist);
|
||
float searchThreshold = cos(angle);
|
||
|
||
// Ограничитель, чтобы не захватить всю планету или вообще ничего
|
||
searchThreshold = std::clamp(searchThreshold, 0.90f, 0.9999f);
|
||
|
||
std::vector<int> result;
|
||
for (int i = 0; i < (int)finalLod.triangles.size(); ++i) {
|
||
// Используем центроид (можно кэшировать в LodLevel для скорости)
|
||
Vector3f triDir = (finalLod.triangles[i].data[0] +
|
||
finalLod.triangles[i].data[1] +
|
||
finalLod.triangles[i].data[2]).normalized();
|
||
|
||
if (targetDir.dot(triDir) > searchThreshold) {
|
||
result.push_back(i);
|
||
}
|
||
}
|
||
|
||
if (result.empty()) return getBestTriangleUnderCamera(viewerPosition);
|
||
return result;
|
||
}
|
||
|
||
std::vector<Triangle> PlanetData::subdivideTriangles(const std::vector<Triangle>& input, float noiseCoeff) {
|
||
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();
|
||
|
||
//Vector3f pm_ab = m_ab * perlin.getSurfaceHeight(m_ab, noiseCoeff);
|
||
//Vector3f pm_bc = m_bc * perlin.getSurfaceHeight(m_bc, noiseCoeff);
|
||
//Vector3f pm_ac = m_ac * perlin.getSurfaceHeight(m_ac, noiseCoeff);
|
||
|
||
Vector3f pm_ab = m_ab;
|
||
Vector3f pm_bc = m_bc;
|
||
Vector3f pm_ac = m_ac;
|
||
|
||
// 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, pm_ab, pm_ac}, {id_a, id_mab, id_mac} }); // 0
|
||
output.emplace_back(Triangle{ {pm_ab, b, pm_bc}, {id_mab, id_b, id_mbc} }); // 1
|
||
output.emplace_back(Triangle{ {pm_ac, pm_bc, c}, {id_mac, id_mbc, id_c} }); // 2
|
||
output.emplace_back(Triangle{ {pm_ab, pm_bc, pm_ac}, {id_mab, id_mbc, id_mac} }); // 3
|
||
}
|
||
return output;
|
||
}
|
||
|
||
LodLevel PlanetData::createLodLevel(const std::vector<Triangle>& geometry) {
|
||
LodLevel result;
|
||
result.triangles = geometry;
|
||
|
||
size_t vertexCount = geometry.size() * 3;
|
||
|
||
result.VertexIDs.reserve(vertexCount);
|
||
|
||
for (const auto& t : geometry) {
|
||
for (int i = 0; i < 3; ++i) {
|
||
result.VertexIDs.push_back(t.ids[i]);
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
void PlanetData::recalculateMeshAttributes(LodLevel& lod)
|
||
{
|
||
size_t vertexCount = lod.triangles.size() * 3;
|
||
|
||
lod.vertexData.PositionData.clear();
|
||
lod.vertexData.NormalData.clear();
|
||
lod.vertexData.TexCoordData.clear();
|
||
lod.vertexData.TangentData.clear();
|
||
lod.vertexData.BinormalData.clear();
|
||
lod.vertexData.ColorData.clear();
|
||
|
||
lod.vertexData.PositionData.reserve(vertexCount);
|
||
lod.vertexData.NormalData.reserve(vertexCount);
|
||
lod.vertexData.TexCoordData.reserve(vertexCount);
|
||
lod.vertexData.TangentData.reserve(vertexCount);
|
||
lod.vertexData.BinormalData.reserve(vertexCount);
|
||
lod.vertexData.ColorData.reserve(vertexCount);
|
||
|
||
|
||
|
||
const std::array<Vector2f, 3> triangleUVs = {
|
||
Vector2f(0.5f, 1.0f),
|
||
Vector2f(0.0f, 0.0f),
|
||
Vector2f(1.0f, 0.0f)
|
||
};
|
||
|
||
const Vector3f colorPinkish = { 1.0f, 0.8f, 0.82f }; // Слегка розоватый
|
||
const Vector3f colorYellowish = { 1.0f, 1.0f, 0.75f }; // Слегка желтоватый
|
||
|
||
const float colorFrequency = 4.0f; // Масштаб пятен
|
||
|
||
|
||
for (const auto& t : lod.triangles) {
|
||
// --- Вычисляем локальный базис треугольника (как в GetRotationForTriangle) ---
|
||
Vector3f vA = t.data[0];
|
||
Vector3f vB = t.data[1];
|
||
Vector3f vC = t.data[2];
|
||
|
||
Vector3f x_axis = (vC - vB).normalized(); // Направление U
|
||
|
||
Vector3f edge1 = vB - vA;
|
||
Vector3f edge2 = vC - vA;
|
||
Vector3f z_axis = edge1.cross(edge2).normalized(); // Нормаль плоскости
|
||
|
||
// Проверка направления нормали наружу (от центра планеты)
|
||
Vector3f centerToTri = (vA + vB + vC).normalized();
|
||
if (z_axis.dot(centerToTri) < 0) {
|
||
z_axis = z_axis * -1.0f;
|
||
}
|
||
|
||
Vector3f y_axis = z_axis.cross(x_axis).normalized(); // Направление V
|
||
|
||
for (int i = 0; i < 3; ++i) {
|
||
lod.vertexData.PositionData.push_back(t.data[i]);
|
||
lod.vertexData.NormalData.push_back(z_axis);
|
||
lod.vertexData.TexCoordData.push_back(triangleUVs[i]);
|
||
lod.vertexData.TangentData.push_back(x_axis);
|
||
lod.vertexData.BinormalData.push_back(y_axis);
|
||
|
||
// Используем один шум для выбора между розовым и желтым
|
||
Vector3f dir = t.data[i].normalized();
|
||
float blendFactor = colorPerlin.noise(
|
||
dir(0) * colorFrequency,
|
||
dir(1) * colorFrequency,
|
||
dir(2) * colorFrequency
|
||
);
|
||
|
||
// Приводим шум из диапазона [-1, 1] в [0, 1]
|
||
blendFactor = blendFactor * 0.5f + 0.5f;
|
||
|
||
// Линейная интерполяция между двумя цветами
|
||
Vector3f finalColor;
|
||
|
||
finalColor = colorPinkish + blendFactor * (colorYellowish - colorPinkish);
|
||
|
||
lod.vertexData.ColorData.push_back(finalColor);
|
||
}
|
||
}
|
||
}
|
||
|
||
LodLevel PlanetData::generateSphere(int subdivisions, float noiseCoeff) {
|
||
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
|
||
|
||
// 12 базовых вершин икосаэдра
|
||
std::vector<Vector3f> icosaVertices = {
|
||
{-1, t, 0}, { 1, t, 0}, {-1, -t, 0}, { 1, -t, 0},
|
||
{ 0, -1, t}, { 0, 1, t}, { 0, -1, -t}, { 0, 1, -t},
|
||
{ t, 0, -1}, { t, 0, 1}, {-t, 0, -1}, {-t, 0, 1}
|
||
};
|
||
|
||
// Нормализуем вершины
|
||
for (auto& v : icosaVertices) v = v.normalized();
|
||
|
||
// 20 граней икосаэдра
|
||
struct IndexedTri { int v1, v2, v3; };
|
||
std::vector<IndexedTri> faces = {
|
||
{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
|
||
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
|
||
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
|
||
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
|
||
};
|
||
|
||
std::vector<Triangle> geometry;
|
||
for (auto& f : faces) {
|
||
Triangle tri;
|
||
tri.data[0] = icosaVertices[f.v1];
|
||
tri.data[1] = icosaVertices[f.v2];
|
||
tri.data[2] = icosaVertices[f.v3];
|
||
|
||
// Генерируем ID для базовых вершин (можно использовать их координаты)
|
||
for (int i = 0; i < 3; ++i) {
|
||
tri.ids[i] = std::to_string(tri.data[i](0)) + "_" +
|
||
std::to_string(tri.data[i](1)) + "_" +
|
||
std::to_string(tri.data[i](2));
|
||
}
|
||
geometry.push_back(tri);
|
||
}
|
||
|
||
// 3. Разбиваем N раз
|
||
for (int i = 0; i < subdivisions; i++) {
|
||
geometry = subdivideTriangles(geometry, 0.0f); // Шум пока игнорируем
|
||
}
|
||
|
||
// 4. Создаем LodLevel и заполняем топологию (v2tMap)
|
||
LodLevel lodLevel = createLodLevel(geometry);
|
||
|
||
// Пересобираем v2tMap (она критична для релаксации)
|
||
lodLevel.v2tMap.clear();
|
||
for (size_t i = 0; i < geometry.size(); ++i) {
|
||
for (int j = 0; j < 3; ++j) {
|
||
lodLevel.v2tMap[geometry[i].ids[j]].push_back((int)i);
|
||
}
|
||
}
|
||
|
||
// 5. Применяем итеративную релаксацию (Lloyd-like)
|
||
// 5-10 итераций достаточно для отличной сетки
|
||
applySphericalRelaxation(lodLevel, 8);
|
||
|
||
// 6. Накладываем шум и обновляем атрибуты
|
||
// ... (твой код наложения шума через Perlin)
|
||
|
||
recalculateMeshAttributes(lodLevel);
|
||
return lodLevel;
|
||
}
|
||
|
||
|
||
|
||
void PlanetData::applySphericalRelaxation(LodLevel& lod, int iterations) {
|
||
for (int iter = 0; iter < iterations; ++iter) {
|
||
std::map<VertexID, Vector3f> newPositions;
|
||
|
||
for (auto const& [vID, connectedTris] : lod.v2tMap) {
|
||
Vector3f centroid(0, 0, 0);
|
||
|
||
// Находим среднюю точку среди центров всех соседних треугольников
|
||
for (int triIdx : connectedTris) {
|
||
const auto& tri = lod.triangles[triIdx];
|
||
Vector3f faceCenter = (tri.data[0] + tri.data[1] + tri.data[2]) * (1.0f / 3.0f);
|
||
centroid = centroid + faceCenter;
|
||
}
|
||
|
||
centroid = centroid * (1.0f / (float)connectedTris.size());
|
||
|
||
// Проецируем обратно на единичную сферу
|
||
newPositions[vID] = centroid.normalized();
|
||
}
|
||
|
||
// Синхронизируем данные в треугольниках
|
||
for (auto& tri : lod.triangles) {
|
||
for (int i = 0; i < 3; ++i) {
|
||
tri.data[i] = newPositions[tri.ids[i]];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} |