Working on planet

This commit is contained in:
Vladislav Khorev 2025-12-12 23:53:34 +03:00
parent 4c837eff0a
commit 3887208b8d
6 changed files with 961 additions and 550 deletions

View File

@ -438,6 +438,8 @@ add_executable(space-game001
Utils.h
SparkEmitter.cpp
SparkEmitter.h
PlanetObject.cpp
PlanetObject.h
)
# Установка проекта по умолчанию для Visual Studio

1059
Game.cpp

File diff suppressed because it is too large Load Diff

46
Game.h
View File

@ -5,6 +5,7 @@
#include "Environment.h"
#include "TextureManager.h"
#include "SparkEmitter.h"
#include "PlanetObject.h"
namespace ZL {
@ -44,30 +45,30 @@ namespace ZL {
size_t lastTickCount;
std::vector<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr;
std::shared_ptr<Texture> buttonTexture;
VertexRenderStruct button;
std::shared_ptr<Texture> musicVolumeBarTexture;
VertexRenderStruct musicVolumeBar;
std::shared_ptr<Texture> musicVolumeBarButtonTexture;
VertexRenderStruct musicVolumeBarButton;
std::vector<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr;
bool isDraggingVolume = false;
float musicVolume = 0.0f;
float volumeBarMinX = 1190.0f;
float volumeBarMaxX = 1200.0f;
float volumeBarMinY = 100.0f;
float volumeBarMaxY = 600.0f;
float musicVolumeBarButtonButtonCenterX = 1195.0f;
float musicVolumeBarButtonButtonRadius = 25.0f;
void UpdateVolumeFromMouse(int mouseX, int mouseY);
void UpdateVolumeKnob();
std::shared_ptr<Texture> buttonTexture;
VertexRenderStruct button;
std::shared_ptr<Texture> musicVolumeBarTexture;
VertexRenderStruct musicVolumeBar;
std::shared_ptr<Texture> musicVolumeBarButtonTexture;
VertexRenderStruct musicVolumeBarButton;
bool isDraggingVolume = false;
float musicVolume = 0.0f;
float volumeBarMinX = 1190.0f;
float volumeBarMaxX = 1200.0f;
float volumeBarMinY = 100.0f;
float volumeBarMaxY = 600.0f;
float musicVolumeBarButtonButtonCenterX = 1195.0f;
float musicVolumeBarButtonButtonRadius = 25.0f;
void UpdateVolumeFromMouse(int mouseX, int mouseY);
void UpdateVolumeKnob();
static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
@ -84,6 +85,7 @@ namespace ZL {
VertexDataStruct boxBase;
SparkEmitter sparkEmitter;
PlanetObject planetObject;
};

292
PlanetObject.cpp Normal file
View File

@ -0,0 +1,292 @@
#include "PlanetObject.h"
#include <random>
#include <cmath>
#include "OpenGlExtensions.h"
#include "Environment.h"
namespace ZL {
struct Triangle
{
std::array<Vector3f, 3> data;
Triangle(Vector3f p1, Vector3f p2, Vector3f p3)
: data{ p1, p2, p3 }
{
}
};
std::vector<Triangle> subdivideTriangles(const std::vector<Triangle>& inputTriangles, PerlinNoise& perlin) {
std::vector<Triangle> output;
output.reserve(inputTriangles.size() * 4);
for (const auto& t : inputTriangles) {
Vector3f a = t.data[0];
Vector3f b = t.data[1];
Vector3f c = t.data[2];
// 1. Вычисляем "сырые" середины
Vector3f m_ab = (a + b) * 0.5f;
Vector3f m_bc = (b + c) * 0.5f;
Vector3f m_ac = (a + c) * 0.5f;
// 2. Нормализуем их (получаем идеальную сферу радиуса 1)
m_ab = m_ab.normalized();
m_bc = m_bc.normalized();
m_ac = m_ac.normalized();
// 3. ПРИМЕНЯЕМ ШУМ: Смещаем точку по радиусу
m_ab = m_ab * perlin.getSurfaceHeight(m_ab);
m_bc = m_bc * perlin.getSurfaceHeight(m_bc);
m_ac = m_ac * perlin.getSurfaceHeight(m_ac);
// 4. Формируем новые треугольники
output.emplace_back(a, m_ab, m_ac);
output.emplace_back(m_ab, b, m_bc);
output.emplace_back(m_ac, m_bc, c);
output.emplace_back(m_ab, m_bc, m_ac);
}
return output;
}
Vector3f calculateSurfaceNormal(Vector3f p_sphere, PerlinNoise& perlin) {
// 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);
Vector3f p1 = p1_dir * perlin.getSurfaceHeight(p1_dir);
Vector3f p2 = p2_dir * perlin.getSurfaceHeight(p2_dir);
// Вектора от центральной точки к соседям
Vector3f v1 = p1 - p0;
Vector3f v2 = p2 - p0;
// Нормаль - это перпендикуляр к этим двум векторам
// Порядок (v2, v1) или (v1, v2) зависит от системы координат,
// здесь подбираем так, чтобы нормаль смотрела наружу.
return (-v2.cross(v1)).normalized();
}
VertexDataStruct trianglesToVertices(const std::vector<Triangle>& triangles, PerlinNoise& perlin) {
VertexDataStruct buffer;
buffer.PositionData.reserve(triangles.size() * 3);
buffer.NormalData.reserve(triangles.size() * 3);
for (const auto& t : triangles) {
// Проходим по всем 3 вершинам треугольника
for (int i = 0; i < 3; i++) {
// p_geometry - это уже точка на поверхности (с шумом)
Vector3f p_geometry = t.data[i];
// Нам нужно восстановить направление от центра к этой точке,
// чтобы передать его в функцию расчета нормали.
// Так как (0,0,0) - центр, то normalize(p) даст нам направление.
Vector3f p_dir = p_geometry.normalized();
// Считаем аналитическую нормаль для этой конкретной точки
Vector3f normal = calculateSurfaceNormal(p_dir, perlin);
buffer.PositionData.push_back({ p_geometry });
//buffer.NormalData.push_back({ normal });
//buffer.TexCoordData.push_back({ 0.0f, 0.0f });
}
}
return buffer;
}
// Генерация геометрии октаэдра с дублированием вершин для Flat Shading
VertexDataStruct generateOctahedron(PerlinNoise& perlin) {
VertexDataStruct buffer;
std::vector<Triangle> v = {
Triangle{
{ 0.0f, 1.0f, 0.0f}, // Top
{ 0.0f, 0.0f, 1.0f}, // Front
{ 1.0f, 0.0f, 0.0f}, // Right
},
Triangle{
{ 0.0f, 1.0f, 0.0f}, // Top
{ 1.0f, 0.0f, 0.0f}, // Right
{ 0.0f, 0.0f, -1.0f}, // Back
},
Triangle{
{ 0.0f, 1.0f, 0.0f}, // Top
{ 0.0f, 0.0f, -1.0f}, // Back
{-1.0f, 0.0f, 0.0f}, // Left
},
Triangle{
{ 0.0f, 1.0f, 0.0f}, // Top
{-1.0f, 0.0f, 0.0f}, // Left
{ 0.0f, 0.0f, 1.0f}, // Front
},
Triangle{
{ 0.0f, -1.0f, 0.0f}, // Bottom
{ 1.0f, 0.0f, 0.0f}, // Right
{ 0.0f, 0.0f, 1.0f}, // Front
},
Triangle{
{ 0.0f, -1.0f, 0.0f}, // Bottom
{ 0.0f, 0.0f, 1.0f}, // Front
{-1.0f, 0.0f, 0.0f}, // Left
},
Triangle{
{ 0.0f, -1.0f, 0.0f}, // Bottom
{-1.0f, 0.0f, 0.0f}, // Left
{ 0.0f, 0.0f, -1.0f}, // Back
},
Triangle{
{ 0.0f, -1.0f, 0.0f}, // Bottom
{ 0.0f, 0.0f, -1.0f}, // Back
{ 1.0f, 0.0f, 0.0f}, // Right
}
};
v = subdivideTriangles(v, perlin);
v = subdivideTriangles(v, perlin);
v = subdivideTriangles(v, perlin);
v = subdivideTriangles(v, perlin);
v = subdivideTriangles(v, perlin);
for (int i = 0; i < v.size(); i++) {
Vector3f p1 = v[i].data[0];
Vector3f p2 = v[i].data[1];
Vector3f p3 = v[i].data[2];
// Считаем нормаль грани через векторное произведение
Vector3f edge1 = p2 - p1;
Vector3f edge2 = p3 - p1;
Vector3f normal = (edge1.cross(edge2)).normalized();
// Дублируем нормаль для всех трех вершин треугольника (Flat shading)
buffer.PositionData.push_back({ p1 });
buffer.PositionData.push_back({ p2 });
buffer.PositionData.push_back({ p3 });
/*buffer.NormalData.push_back({normal});
buffer.NormalData.push_back({ normal });
buffer.NormalData.push_back({ normal });
buffer.TexCoordData.push_back({ 0.0f, 0.0f });
buffer.TexCoordData.push_back({ 0.0f, 0.0f });
buffer.TexCoordData.push_back({ 0.0f, 0.0f });*/
}
return buffer;
}
VertexDataStruct generateSphere(int subdivisions, PerlinNoise& perlin) {
// 1. Исходный октаэдр
std::vector<Triangle> geometry = {
Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, // Top-Front-Right
Triangle{{ 0.0f, 1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Top-Right-Back
Triangle{{ 0.0f, 1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, // Top-Back-Left
Triangle{{ 0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Top-Left-Front
Triangle{{ 0.0f, -1.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}}, // Bottom-Right-Front
Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, // Bottom-Front-Left
Triangle{{ 0.0f, -1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}}, // Bottom-Left-Back
Triangle{{ 0.0f, -1.0f, 0.0f}, { 0.0f, 0.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}} // Bottom-Back-Right
};
// 2. ПРИМЕНЯЕМ ШУМ К ИСХОДНЫМ ВЕРШИНАМ
// Проходимся по всем треугольникам и всем вершинам в них
for (auto& t : geometry) {
for (int i = 0; i < 3; i++) {
// Нормализуем (на всякий случай, хотя у октаэдра они и так норм)
Vector3f dir = t.data[i].normalized();
// Применяем высоту
t.data[i] = dir * perlin.getSurfaceHeight(dir);
}
}
// 3. Разбиваем N раз (новые вершины будут получать шум внутри функции)
for (int i = 0; i < subdivisions; i++) {
geometry = subdivideTriangles(geometry, perlin);
}
// 4. Генерируем нормали
// Благодаря тому, что мы реально сдвигали вершины, Flat Shading нормали
// рассчитаются правильно относительно нового рельефа.
return trianglesToVertices(geometry, perlin);
}
PlanetObject::PlanetObject()
{
}
void PlanetObject::init() {
planetMesh = generateSphere(7, perlin);
planetMesh.Scale(100.f);
planetMesh.Move({ 0,0,-200 });
planetRenderStruct.data = planetMesh;
planetRenderStruct.RefreshVBO();
}
void PlanetObject::prepareDrawData() {
if (!drawDataDirty) return;
drawDataDirty = false;
}
void PlanetObject::draw(Renderer& renderer) {
prepareDrawData();
static const std::string defaultShaderName = "defaultColor";
static const std::string vPositionName = "vPosition";
renderer.shaderManager.PushShader(defaultShaderName);
renderer.EnableVertexAttribArray(vPositionName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
1, 1000);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
renderer.DrawVertexRenderStruct(planetRenderStruct);
CheckGlError();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void PlanetObject::update(float deltaTimeMs) {
}
} // namespace ZL

100
PlanetObject.h Normal file
View File

@ -0,0 +1,100 @@
#pragma once
#include "ZLMath.h"
#include "Renderer.h"
#include "TextureManager.h"
#include <vector>
#include <chrono>
#include <iostream>
#include <string>
#include <array>
#include <numeric>
#include <random>
#include <algorithm>
namespace ZL {
class PerlinNoise {
std::vector<int> p;
public:
PerlinNoise() {
p.resize(256);
std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (можно задать seed)
std::default_random_engine engine(12345);
std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
}
float fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
float lerp(float t, float a, float b) { return a + t * (b - a); }
float 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 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 getSurfaceHeight(Vector3f pos) {
// Частота шума (чем больше, тем больше "холмов")
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 + (noiseValue * 0.1f); // * 0.2 даст вариацию высоты
return height;
}
};
class PlanetObject {
private:
PerlinNoise perlin;
void prepareDrawData();
VertexDataStruct planetMesh;
VertexRenderStruct planetRenderStruct;
public:
PlanetObject();
void init();
void update(float deltaTimeMs);
void draw(Renderer& renderer);
private:
bool drawDataDirty = true;
};
} // namespace ZL

View File

@ -56,6 +56,18 @@ namespace ZL {
return v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
}
float dot(const Vector3f& other) const {
return v[0] * other.v[0] + v[1] * other.v[1] + v[2] * other.v[2];
}
Vector3f cross(const Vector3f& other) const {
return Vector3f(
v[1] * other.v[2] - v[2] * other.v[1],
v[2] * other.v[0] - v[0] * other.v[2],
v[0] * other.v[1] - v[1] * other.v[0]
);
}
// Îïåðàòîð âû÷èòàíèÿ
/*Vector3f operator-(const Vector3f& other) const {
return Vector3f(v[0] - other.v[0], v[1] - other.v[1], v[2] - other.v[2]);