diff --git a/CMakeLists.txt b/CMakeLists.txt index 819c8b6..171e6e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -440,6 +440,10 @@ add_executable(space-game001 SparkEmitter.h PlanetObject.cpp PlanetObject.h + PlanetData.cpp + PlanetData.h + Perlin.cpp + Perlin.h ) # Установка проекта по умолчанию для Visual Studio diff --git a/Environment.cpp b/Environment.cpp index 2ee0ed1..8d6479d 100644 --- a/Environment.cpp +++ b/Environment.cpp @@ -31,7 +31,7 @@ bool Environment::tapDownHold = false; Vector2f Environment::tapDownStartPos = { 0, 0 }; Vector2f Environment::tapDownCurrentPos = { 0, 0 }; -Vector3f Environment::shipPosition = {100,100,45000.f}; +Vector3f Environment::shipPosition = {0,0,45000.f}; float Environment::shipVelocity = 0.f; diff --git a/Game.cpp b/Game.cpp index cac9e95..a343918 100755 --- a/Game.cpp +++ b/Game.cpp @@ -366,6 +366,7 @@ namespace ZL renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom }); renderer.RotateMatrix(Environment::inverseShipMatrix); renderer.TranslateMatrix(-Environment::shipPosition); + renderer.TranslateMatrix({ 0.f, 0.f, 45000.f }); renderer.TranslateMatrix(boxCoordsArr[i].pos); renderer.RotateMatrix(boxCoordsArr[i].m); @@ -468,7 +469,7 @@ namespace ZL CheckGlError(); float skyPercent = 0.0; - float distance = planetObject.distanceToPlanetSurface(); + float distance = planetObject.distanceToPlanetSurface(Environment::shipPosition); if (distance > 1900.f) { skyPercent = 0.0f; @@ -484,12 +485,12 @@ namespace ZL drawCubemap(skyPercent); planetObject.draw(renderer); - if (planetObject.planetIsFar()) + if (planetObject.distanceToPlanetSurface(Environment::shipPosition) > 100.f) { glClear(GL_DEPTH_BUFFER_BIT); } drawShip(); - //drawBoxes(); + drawBoxes(); drawUI(); diff --git a/Perlin.cpp b/Perlin.cpp new file mode 100644 index 0000000..c9280b8 --- /dev/null +++ b/Perlin.cpp @@ -0,0 +1,73 @@ +#include "Perlin.h" +#include +#include +#include +#include + +namespace ZL { + + 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(static_cast(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 noiseCoeff) { + // ( , "") + float frequency = 7.0f; + + // ( -1 1) + float noiseValue = noise(pos.v[0] * frequency, pos.v[1] * frequency, pos.v[2] * frequency); + + // : 1.0 1.1 () + float height = 1.0f + (noiseValue * noiseCoeff); + + return height; + } + +} // namespace ZL \ No newline at end of file diff --git a/Perlin.h b/Perlin.h new file mode 100644 index 0000000..cc07273 --- /dev/null +++ b/Perlin.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include "ZLMath.h" + +namespace ZL { + + class PerlinNoise { + std::vector p; + public: + PerlinNoise(); + PerlinNoise(uint64_t seed); + + float fade(float t); + float lerp(float t, float a, float b); + float grad(int hash, float x, float y, float z); + + float noise(float x, float y, float z); + + float getSurfaceHeight(Vector3f pos, float noiseCoeff); + }; + +} // namespace ZL \ No newline at end of file diff --git a/PlanetData.cpp b/PlanetData.cpp new file mode 100644 index 0000000..fc6b66c --- /dev/null +++ b/PlanetData.cpp @@ -0,0 +1,483 @@ +#include "PlanetData.h" +#include +#include +#include +#include + +namespace ZL { + + const float PlanetData::PLANET_RADIUS = 20000.f; + const Vector3f PlanetData::PLANET_CENTER_OFFSET = Vector3f{ 0.f, 0.f, 0.0f }; + + // --- ( PlanetObject.cpp) --- + static constexpr float FAR_Z_NEAR = 2000.0f; + static constexpr float FAR_Z_FAR = 200000.0f; + static constexpr float TRANSITION_FAR_START = 3000.0f; + static constexpr float MIDDLE_Z_NEAR = 500.f; + static constexpr float MIDDLE_Z_FAR = 100000.f; + static constexpr float TRANSITION_MIDDLE_START = 500.f; + static constexpr float NEAR_Z_NEAR = 100.0f; + static constexpr float NEAR_Z_FAR = 20000.0f; + static constexpr float TRANSITION_NEAR_END = 100.f; + static constexpr float SUPER_NEAR_Z_NEAR = 30.0f; + static constexpr float SUPER_NEAR_Z_FAR = 6000.0f; + static constexpr float TRANSITION_SUPER_NEAR_END = 30.f; + + 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.02f); + planetMeshLods[i].vertexData.Scale(PLANET_RADIUS); + planetMeshLods[i].vertexData.Move(PLANET_CENTER_OFFSET); + } + + planetAtmosphereLod = generateSphere(5, 0); + planetAtmosphereLod.vertexData.Scale(PLANET_RADIUS * 1.03); + planetAtmosphereLod.vertexData.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(planetMeshLods.size() - 1); + } + + std::pair PlanetData::calculateZRange(float dToPlanetSurface) { + float currentZNear; + float currentZFar; + float alpha; + + if (dToPlanetSurface >= TRANSITION_FAR_START) { + currentZNear = FAR_Z_NEAR; + currentZFar = FAR_Z_FAR; + } + else if (dToPlanetSurface > TRANSITION_MIDDLE_START) { + const float transitionLength = TRANSITION_FAR_START - TRANSITION_MIDDLE_START; + float normalizedDist = (dToPlanetSurface - TRANSITION_MIDDLE_START) / transitionLength; + alpha = 1.0f - normalizedDist; + currentZNear = FAR_Z_NEAR * (1.0f - alpha) + MIDDLE_Z_NEAR * alpha; + currentZFar = FAR_Z_FAR * (1.0f - alpha) + MIDDLE_Z_FAR * alpha; + } + else if (dToPlanetSurface > TRANSITION_NEAR_END) { + const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END; + float normalizedDist = (dToPlanetSurface - TRANSITION_NEAR_END) / transitionLength; + alpha = 1.0f - normalizedDist; + 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) { + const float transitionLength = TRANSITION_NEAR_END - TRANSITION_SUPER_NEAR_END; + float normalizedDist = (dToPlanetSurface - TRANSITION_SUPER_NEAR_END) / transitionLength; + alpha = 1.0f - normalizedDist; + 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 { + currentZNear = SUPER_NEAR_Z_NEAR; + currentZFar = SUPER_NEAR_Z_FAR; + } + + return { currentZNear, currentZFar }; + } + + + float PlanetData::distanceToPlanetSurface(const Vector3f& viewerPosition) { + Vector3f shipLocalPosition = viewerPosition - PLANET_CENTER_OFFSET; + + // getTrianglesUnderCamera + // : . getTrianglesUnderCamera viewerPosition. + // , . + // , . + + // : getTrianglesUnderCamera - "" . distanceToPlanetSurface. + // LOD. MAX LOD . + + // . + // triangleUnderCamera, . + // triangleUnderCamera . + // getTrianglesUnderCamera. + + // : + // . + std::vector targetTriangles = getTrianglesUnderCamera(viewerPosition); + + if (targetTriangles.empty()) { + return (shipLocalPosition.length() - PLANET_RADIUS); + } + + int tri_index = targetTriangles[0]; + const auto& posData = planetMeshLods[currentLod].vertexData.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]; + + Vector3f P_proj = projectPointOnPlane(shipLocalPosition, A, B, C); + Vector3f P_closest = P_proj; + + return (shipLocalPosition - P_closest).length(); + } + + // --- getTrianglesUnderCamera ( triangleUnderCamera) --- + // , API + static std::vector recursiveTriangleSearch(int lod, const Vector3f& pos, const std::array& meshes) { + std::vector r; + // 0 ( ) + if (lod == 0) { + if (pos.v[1] >= 0) { + if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(0); + if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(1); + if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(2); + if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(3); + } + if (pos.v[1] <= 0) { + if (pos.v[0] >= 0 && pos.v[2] >= 0) r.push_back(4); + if (pos.v[0] <= 0 && pos.v[2] >= 0) r.push_back(5); + if (pos.v[0] <= 0 && pos.v[2] <= 0) r.push_back(6); + if (pos.v[0] >= 0 && pos.v[2] <= 0) r.push_back(7); + } + } + else { + // + std::vector r0 = recursiveTriangleSearch(lod - 1, pos, meshes); + for (int tri0 : r0) { + Vector3f a = meshes[lod - 1].vertexData.PositionData[tri0 * 3]; + Vector3f b = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 1]; + Vector3f c = meshes[lod - 1].vertexData.PositionData[tri0 * 3 + 2]; + + std::vector result = PlanetData::find_sub_triangle_spherical(a, b, c, pos); + for (int trix : result) { + r.push_back(tri0 * 4 + trix); + } + } + } + return r; + } + + std::vector PlanetData::getTrianglesUnderCamera(const Vector3f& viewerPosition) { + // LOD + return recursiveTriangleSearch(currentLod, viewerPosition, planetMeshLods); + } + + // --- (check_spherical_side, generateSphere ..) --- + // ( PlanetObject.cpp, + // Environment::shipPosition , , + // this->planetMeshLods) + + float PlanetData::check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon) { + Vector3f N_plane = V1.cross(V2); + float sign_P = P.dot(N_plane); + float sign_V3 = V3.dot(N_plane); + return sign_P * sign_V3; + } + + bool PlanetData::is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon) { + if (check_spherical_side(V1, V2, P, V3, epsilon) < -epsilon) return false; + if (check_spherical_side(V2, V3, P, V1, epsilon) < -epsilon) return false; + if (check_spherical_side(V3, V1, P, V2, epsilon) < -epsilon) return false; + return true; + } + + std::vector PlanetData::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 result; + const Vector3f a = a_orig.normalized(); + const Vector3f b = b_orig.normalized(); + const Vector3f c = c_orig.normalized(); + const Vector3f pxx_normalized = px_orig.normalized(); + 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(); + + if (is_inside_spherical_triangle(pxx_normalized, a, m_ab, m_ac, EPSILON)) result.push_back(0); + if (is_inside_spherical_triangle(pxx_normalized, m_ab, b, m_bc, EPSILON)) result.push_back(1); + if (is_inside_spherical_triangle(pxx_normalized, m_ac, m_bc, c, EPSILON)) result.push_back(2); + if (is_inside_spherical_triangle(pxx_normalized, m_ab, m_bc, m_ac, EPSILON)) result.push_back(3); + + return result; + } + + // ... subdivideTriangles, calculateSurfaceNormal, trianglesToVertices, generateSphere, findNeighbors + // PlanetObject.cpp " ", PlanetData:: . + // , perlin planetMeshLods this. + + std::vector PlanetData::subdivideTriangles(const std::vector& input) { + std::vector 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 PlanetData::calculateSurfaceNormal(Vector3f p_sphere, float noiseCoeff) { + // 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, noiseCoeff); + Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir, noiseCoeff); + Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir, noiseCoeff); + + // + Vector3f v1 = p1 - p0; + Vector3f v2 = p2 - p0; + + // - + // (v2, v1) (v1, v2) , + // , . + return (-v2.cross(v1)).normalized(); + } + + LodLevel PlanetData::trianglesToVertices(const std::vector& 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 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 PlanetData::generateSphere(int subdivisions, float noiseCoeff) { + // 1. ID + std::vector 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, noiseCoeff); + // : 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; + } + + + // findNeighbors: + std::vector PlanetData::findNeighbors(int index, int lod) { + const V2TMap& v2tMap = planetMeshLods[lod].v2tMap; + const auto& vertexIDs = planetMeshLods[lod].VertexIDs; + if ((index * 3 + 2) >= vertexIDs.size()) return {}; + std::set neighbors; + for (int i = 0; i < 3; ++i) { + VertexID v_id = vertexIDs[index * 3 + i]; + auto it = v2tMap.find(v_id); + if (it != v2tMap.end()) { + for (int tri_index : it->second) { + if (tri_index != index) { + neighbors.insert(tri_index); + } + } + } + } + return std::vector(neighbors.begin(), neighbors.end()); + } + + // ... ... + +} \ No newline at end of file diff --git a/PlanetData.h b/PlanetData.h new file mode 100644 index 0000000..89cb99a --- /dev/null +++ b/PlanetData.h @@ -0,0 +1,93 @@ +#pragma once + +#include "ZLMath.h" +#include "Perlin.h" +#include "Renderer.h" // VertexDataStruct +#include +#include +#include +#include +#include + +namespace ZL { + + using VertexID = std::string; + using V2TMap = std::map>; + + VertexID generateEdgeID(const VertexID& id1, const VertexID& id2); + + struct Triangle + { + std::array data; + std::array ids; + + Triangle(Vector3f p1, Vector3f p2, Vector3f p3) + : data{ p1, p2, p3 } + { + } + + Triangle(std::array idata, std::array iids) + : data{ idata } + , ids{ iids } + { + } + }; + + struct LodLevel + { + VertexDataStruct vertexData; + std::vector VertexIDs; + V2TMap v2tMap; + }; + + class PlanetData { + public: + static const float PLANET_RADIUS; + static const Vector3f PLANET_CENTER_OFFSET; + + private: + PerlinNoise perlin; + PerlinNoise colorPerlin; + + std::array planetMeshLods; + LodLevel planetAtmosphereLod; + + int currentLod; // + + std::map initialVertexMap; + + // + std::vector subdivideTriangles(const std::vector& inputTriangles); + Vector3f calculateSurfaceNormal(Vector3f p_sphere, float noiseCoeff); + LodLevel trianglesToVertices(const std::vector& triangles); + LodLevel generateSphere(int subdivisions, float noiseCoeff); + + // + static float check_spherical_side(const Vector3f& V1, const Vector3f& V2, const Vector3f& P, const Vector3f& V3, float epsilon = 1e-6f); + static bool is_inside_spherical_triangle(const Vector3f& P, const Vector3f& V1, const Vector3f& V2, const Vector3f& V3, float epsilon); + + public: + PlanetData(); + + void init(); + + // ( ) + const LodLevel& getLodLevel(int level) const; + const LodLevel& getAtmosphereLod() const; + int getCurrentLodIndex() const; + int getMaxLodIndex() const; + + // + std::pair calculateZRange(float distanceToSurface); + float distanceToPlanetSurface(const Vector3f& viewerPosition); + + // , + std::vector getTrianglesUnderCamera(const Vector3f& viewerPosition); + + std::vector findNeighbors(int index, int lod); + + static std::vector find_sub_triangle_spherical(const Vector3f& a_orig, const Vector3f& b_orig, const Vector3f& c_orig, const Vector3f& px_orig); + + }; + +} // namespace ZL \ No newline at end of file diff --git a/PlanetObject.cpp b/PlanetObject.cpp index e1fe455..a7ec4ee 100644 --- a/PlanetObject.cpp +++ b/PlanetObject.cpp @@ -6,302 +6,81 @@ 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 = 100000.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 = 20000.0f; - - - // Дистанция, где ЗАВЕРШАЕТСЯ переход MIDDLE -> NEAR - static constexpr float TRANSITION_NEAR_END = 100.f; - - // --- 3. Ближний диапазон (NEAR) --- -// Новые константы для максимальной точности - static constexpr float SUPER_NEAR_Z_NEAR = 30.0f; - static constexpr float SUPER_NEAR_Z_FAR = 6000.0f; - - - // Дистанция, где ЗАВЕРШАЕТСЯ переход MIDDLE -> NEAR - static constexpr float TRANSITION_SUPER_NEAR_END = 30.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 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; - - float normalizedDist = (dToPlanetSurface - TRANSITION_MIDDLE_START) / transitionLength; - alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) - - currentZNear = FAR_Z_NEAR * (1.0f - alpha) + MIDDLE_Z_NEAR * alpha; - currentZFar = FAR_Z_FAR * (1.0f - alpha) + MIDDLE_Z_FAR * alpha; - - } - else if (dToPlanetSurface > TRANSITION_NEAR_END) { - const float transitionLength = TRANSITION_MIDDLE_START - TRANSITION_NEAR_END; - - float normalizedDist = (dToPlanetSurface - TRANSITION_NEAR_END) / transitionLength; - alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) - - 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) { - const float transitionLength = TRANSITION_NEAR_END - TRANSITION_SUPER_NEAR_END; - - float normalizedDist = (dToPlanetSurface - TRANSITION_SUPER_NEAR_END) / transitionLength; - alpha = 1.0f - normalizedDist; // alpha = 0 (Далеко) ... 1 (Близко) - - - 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 { - 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 noiseCoeff) { - // Частота шума (чем больше, тем больше "холмов") - 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 * noiseCoeff); // * 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 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() { + // 1. Инициализируем данные (генерация мешей) + planetData.init(); - for (int i = 0; i < planetMeshLods.size(); i++) { - planetMeshLods[i] = generateSphere(i, 0.02f); - planetMeshLods[i].vertexData.Scale(PLANET_RADIUS); - planetMeshLods[i].vertexData.Move(PLANET_CENTER_OFFSET); - } - - planetRenderStruct.data = planetMeshLods[currentLod].vertexData; + // 2. Забираем данные для VBO + // Берем максимальный LOD для начальной отрисовки + int lodIndex = planetData.getMaxLodIndex(); + planetRenderStruct.data = planetData.getLodLevel(lodIndex).vertexData; planetRenderStruct.RefreshVBO(); sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/sand.png", "")); - //sandTexture = std::make_unique(CreateTextureDataFromPng("./resources/rock.png", "")); - - planetAtmosphereLod = generateSphere(5, 0); - - planetAtmosphere.data = planetAtmosphereLod.vertexData; - planetAtmosphere.data.Scale(PLANET_RADIUS*1.03); - planetAtmosphere.data.Move(PLANET_CENTER_OFFSET); - - planetAtmosphere.RefreshVBO(); + // Атмосфера + planetAtmosphereRenderStruct.data = planetData.getAtmosphereLod().vertexData; + planetAtmosphereRenderStruct.RefreshVBO(); } void PlanetObject::prepareDrawData() { if (!drawDataDirty) return; - drawDataDirty = false; } + void PlanetObject::update(float deltaTimeMs) { + // Получаем видимые треугольники, передавая позицию корабля + auto lr = planetData.getTrianglesUnderCamera(Environment::shipPosition); + int currentLod = planetData.getCurrentLodIndex(); + const auto& fullMesh = planetData.getLodLevel(currentLod).vertexData; + + planetRenderRedStruct.data.PositionData.clear(); + planetRenderRedStruct.data.TexCoordData.clear(); + // ... очистка остальных буферов ... + + planetRenderYellowStruct.data.PositionData.clear(); + planetRenderYellowStruct.data.TexCoordData.clear(); + + std::set usedYellow; + + // Заполняем красный (текущий треугольник под камерой) + for (int i : lr) { + planetRenderRedStruct.data.PositionData.push_back(fullMesh.PositionData[i * 3]); + planetRenderRedStruct.data.PositionData.push_back(fullMesh.PositionData[i * 3 + 1]); + planetRenderRedStruct.data.PositionData.push_back(fullMesh.PositionData[i * 3 + 2]); + planetRenderRedStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[i * 3]); + planetRenderRedStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[i * 3 + 1]); + planetRenderRedStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[i * 3 + 2]); + usedYellow.insert(i); + } + planetRenderRedStruct.RefreshVBO(); + + // Заполняем желтый (соседи) + for (int i : lr) { + auto neighbors = planetData.findNeighbors(i, currentLod); + for (int n : neighbors) { + if (usedYellow.count(n) == 0) { + usedYellow.insert(n); + planetRenderYellowStruct.data.PositionData.push_back(fullMesh.PositionData[n * 3]); + planetRenderYellowStruct.data.PositionData.push_back(fullMesh.PositionData[n * 3 + 1]); + planetRenderYellowStruct.data.PositionData.push_back(fullMesh.PositionData[n * 3 + 2]); + planetRenderYellowStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[n * 3]); + planetRenderYellowStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[n * 3 + 1]); + planetRenderYellowStruct.data.TexCoordData.push_back(fullMesh.TexCoordData[n * 3 + 2]); + } + } + } + planetRenderYellowStruct.RefreshVBO(); + } + + + void PlanetObject::draw(Renderer& renderer) { prepareDrawData(); @@ -323,7 +102,8 @@ namespace ZL { renderer.EnableVertexAttribArray(vTexCoordName); //renderer.EnableVertexAttribArray(vTexCoord3Name); - const auto zRange = calculateZRange(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); + auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -356,9 +136,6 @@ namespace ZL { 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); @@ -383,7 +160,7 @@ namespace ZL { glClear(GL_DEPTH_BUFFER_BIT); //-------------------------- -/* + if (planetRenderRedStruct.data.PositionData.size() > 0) { @@ -433,7 +210,7 @@ namespace ZL { renderer.shaderManager.PopShader(); CheckGlError(); - }*/ + } drawAtmosphere(renderer); } @@ -450,7 +227,8 @@ namespace ZL { renderer.EnableVertexAttribArray(vNormalName); - const auto zRange = calculateZRange(Environment::shipPosition); + float dist = planetData.distanceToPlanetSurface(Environment::shipPosition); + auto zRange = planetData.calculateZRange(dist); const float currentZNear = zRange.first; const float currentZFar = zRange.second; @@ -473,13 +251,12 @@ namespace ZL { 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); + renderer.DrawVertexRenderStruct(planetAtmosphereRenderStruct); glDisable(GL_BLEND); glDepthMask(GL_TRUE); renderer.PopMatrix(); @@ -492,451 +269,9 @@ namespace ZL { } - void PlanetObject::update(float deltaTimeMs) { - - auto lr = triangleUnderCamera(currentLod); - planetRenderRedStruct.data.PositionData.clear(); - planetRenderYellowStruct.data.PositionData.clear(); - - std::set 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]); - planetRenderRedStruct.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[i * 3]); - planetRenderRedStruct.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[i * 3 + 1]); - planetRenderRedStruct.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[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.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[n * 3]); - planetRenderYellowStruct.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[n * 3 + 1]); - planetRenderYellowStruct.data.TexCoordData.push_back(planetMeshLods[currentLod].vertexData.TexCoordData[n * 3 + 2]); - - } - } - } - planetRenderYellowStruct.RefreshVBO(); - - - } - - std::vector PlanetObject::subdivideTriangles(const std::vector& input) { - std::vector 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, float noiseCoeff) { - // 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, noiseCoeff); - Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir, noiseCoeff); - Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir, noiseCoeff); - - // Вектора от центральной точки к соседям - Vector3f v1 = p1 - p0; - Vector3f v2 = p2 - p0; - - // Нормаль - это перпендикуляр к этим двум векторам - // Порядок (v2, v1) или (v1, v2) зависит от системы координат, - // здесь подбираем так, чтобы нормаль смотрела наружу. - return (-v2.cross(v1)).normalized(); - } - - LodLevel PlanetObject::trianglesToVertices(const std::vector& 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 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, float noiseCoeff) { - // 1. Исходный октаэдр и присвоение ID - std::vector 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, noiseCoeff); - // Обратите внимание: 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 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 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(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 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 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 PlanetObject::triangleUnderCamera(int lod) + float PlanetObject::distanceToPlanetSurface(const Vector3f& viewerPosition) { - std::vector 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 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 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; + return planetData.distanceToPlanetSurface(viewerPosition); } diff --git a/PlanetObject.h b/PlanetObject.h index fad53b1..afa7595 100644 --- a/PlanetObject.h +++ b/PlanetObject.h @@ -13,107 +13,36 @@ #include #include #include +#include "Perlin.h" +#include "PlanetData.h" namespace ZL { - using VertexID = std::string; - using V2TMap = std::map>; + class PlanetObject { + private: + // Агрегация: логика и данные теперь здесь + PlanetData planetData; - VertexID generateEdgeID(const VertexID& id1, const VertexID& id2); - - struct Triangle - { - std::array data; - std::array ids; - - Triangle(Vector3f p1, Vector3f p2, Vector3f p3) - : data{ p1, p2, p3 } - { - } - - Triangle(std::array idata, std::array iids) - : data{ idata } - , ids{ iids } - { - } - }; - - struct LodLevel - { - VertexDataStruct vertexData; - std::vector VertexIDs; - V2TMap v2tMap; - }; - - class PerlinNoise { - std::vector p; - public: - PerlinNoise(); - PerlinNoise(uint64_t seed); - - float fade(float t); - float lerp(float t, float a, float b); - float grad(int hash, float x, float y, float z); - - float noise(float x, float y, float z); - - float getSurfaceHeight(Vector3f pos, float noiseCoeff); - }; - - - class PlanetObject { - private: - PerlinNoise perlin; - PerlinNoise colorPerlin; - void prepareDrawData(); - std::array planetMeshLods; - - - VertexRenderStruct planetRenderStruct; + // Данные только для рендеринга (OpenGL specific) + VertexRenderStruct planetRenderStruct; VertexRenderStruct planetRenderRedStruct; VertexRenderStruct planetRenderYellowStruct; - - LodLevel planetAtmosphereLod; - VertexRenderStruct planetAtmosphere; + VertexRenderStruct planetAtmosphereRenderStruct; std::shared_ptr sandTexture; - std::map 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"} - }; + bool drawDataDirty = true; + void prepareDrawData(); - public: - PlanetObject(); + public: + PlanetObject(); - void init(); - - void update(float deltaTimeMs); - - void draw(Renderer& renderer); + void init(); + void update(float deltaTimeMs); + void draw(Renderer& renderer); void drawAtmosphere(Renderer& renderer); - bool planetIsFar(); - float distanceToPlanetSurface(); - - - private: - bool drawDataDirty = true; - - int currentLod = planetMeshLods.size()-1; - - std::vector subdivideTriangles(const std::vector& inputTriangles); - Vector3f calculateSurfaceNormal(Vector3f p_sphere, float noiseCoeff); - LodLevel trianglesToVertices(const std::vector& triangles); - LodLevel generateSphere(int subdivisions, float noiseCoeff); - std::pair calculateZRange(const Vector3f& shipPosition); - - std::vector triangleUnderCamera(int lod); - std::vector findNeighbors(int index, int lod); - }; + float distanceToPlanetSurface(const Vector3f& viewerPosition); + }; } // namespace ZL \ No newline at end of file