space-game001/StoneObject.cpp
2025-12-16 22:21:20 +03:00

308 lines
11 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "StoneObject.h"
#include "Utils.h"
#include <GL/gl.h>
#include <random>
#include <cmath>
#include "Renderer.h"
#include "PlanetData.h"
namespace ZL {
// Вспомогательная функция для получения случайного числа в диапазоне [min, max]
float getRandomFloat(std::mt19937& gen, float min, float max) {
std::uniform_real_distribution<> distrib(min, max);
return static_cast<float>(distrib(gen));
}
// Вспомогательная функция для генерации случайной точки на треугольнике
// Использует барицентрические координаты
Vector3f GetRandomPointOnTriangle(const Triangle& t, std::mt19937& engine) {
std::uniform_real_distribution<> distrib(0.0f, 1.0f);
float r1 = getRandomFloat(engine, 0.0f, 1.0f);
float r2 = getRandomFloat(engine, 0.0f, 1.0f);
// Преобразование r1, r2 для получения равномерного распределения
float a = 1.0f - std::sqrt(r1);
float b = std::sqrt(r1) * r2;
float c = 1.0f - a - b; // c = sqrt(r1) * (1 - r2)
// Барицентрические координаты
// P = a*p1 + b*p2 + c*p3
Vector3f p1_term = t.data[0] * a;
Vector3f p2_term = t.data[1] * b;
Vector3f p3_term = t.data[2] * c;
return p1_term + p2_term + p3_term;
}
// Икосаэдр (на основе золотого сечения phi)
// Координаты могут быть вычислены заранее для константного икосаэдра.
// Здесь только объявление, чтобы показать идею.
VertexDataStruct CreateBaseConvexPolyhedron(uint64_t seed) {
// --- КОНСТАНТЫ ПАРАМЕТРОВ (как вы просили) ---
//const float BASE_SCALE = 3.0f; // Общий размер камня
const float BASE_SCALE = 20.0f; // Общий размер камня
const float MIN_AXIS_SCALE = 0.5f; // Минимальное растяжение/сжатие по оси
const float MAX_AXIS_SCALE = 1.5f; // Максимальное растяжение/сжатие по оси
const float MIN_PERTURBATION = 0.05f; // Минимальное радиальное возмущение вершины
const float MAX_PERTURBATION = 0.25f; // Максимальное радиальное возмущение вершины
// const size_t SUBDIVISION_LEVEL = 1; // Уровень подразделения (для более круглого камня, пока опустим)
std::mt19937 engine(static_cast<unsigned int>(seed));
// Золотое сечение
const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
// 12 вершин икосаэдра
std::vector<Vector3f> initialVertices = {
{ -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 }
};
// 20 треугольных граней (индексы вершин)
std::vector<std::array<int, 3>> faces = {
// 5 треугольников вокруг вершины 0
{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
// 5 смежных полос
{1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
// 5 треугольников вокруг вершины 3
{3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
// 5 смежных полос
{4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}
};
// 1. Нормализация и Возмущение (Perturbation)
for (Vector3f& v : initialVertices) {
v = v.normalized() * BASE_SCALE; // Нормализация к сфере радиуса BASE_SCALE
// Радиальное возмущение:
float perturbation = getRandomFloat(engine, MIN_PERTURBATION, MAX_PERTURBATION);
v = v * (1.0f + perturbation);
}
// 2. Трансформация (Масштабирование и Поворот)
// Случайные масштабы по осям
Vector3f scaleFactors = {
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE),
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE),
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE)
};
// Применяем масштабирование
for (Vector3f& v : initialVertices) {
v.v[0] *= scaleFactors.v[0];
v.v[1] *= scaleFactors.v[1];
v.v[2] *= scaleFactors.v[2];
}
// Случайный поворот (например, вокруг трех осей)
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qFinal = slerp(qx, qy, 0.5f); // Простой пример комбинирования
qFinal = slerp(qFinal, qz, 0.5f).normalized();
Matrix3f rotationMatrix = QuatToMatrix(qFinal);
for (Vector3f& v : initialVertices) {
v = MultMatrixVector(rotationMatrix, v);
}
// 3. Сглаженные Нормали и Формирование Mesh
VertexDataStruct result;
// Карта для накопления нормалей по уникальным позициям вершин
// (Требует определенного оператора < для Vector3f в ZLMath.h, который у вас есть)
std::map<Vector3f, Vector3f> smoothNormalsMap;
// Предварительное заполнение карты нормалями
for (const auto& face : faces) {
Vector3f p1 = initialVertices[face[0]];
Vector3f p2 = initialVertices[face[1]];
Vector3f p3 = initialVertices[face[2]];
// Нормаль грани: (p2 - p1) x (p3 - p1)
Vector3f faceNormal = (p2 - p1).cross(p3 - p1).normalized();
smoothNormalsMap[p1] = smoothNormalsMap[p1] + faceNormal;
smoothNormalsMap[p2] = smoothNormalsMap[p2] + faceNormal;
smoothNormalsMap[p3] = smoothNormalsMap[p3] + faceNormal;
}
// Нормализация накопленных нормалей
for (auto& pair : smoothNormalsMap) {
pair.second = pair.second.normalized();
}
// 4. Финальное заполнение VertexDataStruct и Текстурные Координаты
for (const auto& face : faces) {
Vector3f p1 = initialVertices[face[0]];
Vector3f p2 = initialVertices[face[1]];
Vector3f p3 = initialVertices[face[2]];
// Позиции
result.PositionData.push_back(p1);
result.PositionData.push_back(p2);
result.PositionData.push_back(p3);
// Сглаженные Нормали (из карты)
result.NormalData.push_back(smoothNormalsMap[p1]);
result.NormalData.push_back(smoothNormalsMap[p2]);
result.NormalData.push_back(smoothNormalsMap[p3]);
// Текстурные Координаты (Планарная проекция на плоскость грани)
// p1 -> (0, 0), p2 -> (L_12, 0), p3 -> (L_13 * cos(angle), L_13 * sin(angle))
// Где L_xy - длина вектора, angle - угол между p2-p1 и p3-p1
Vector3f uAxis = (p2 - p1).normalized();
Vector3f vRaw = p3 - p1;
// Проекция vRaw на uAxis
float uProjLen = vRaw.dot(uAxis);
// Вектор V перпендикулярный U: vRaw - uProj
Vector3f vAxisRaw = vRaw - (uAxis * uProjLen);
// Длина оси V
float vLen = vAxisRaw.length();
// Нормализованная ось V
Vector3f vAxis = vAxisRaw.normalized();
// Координаты (u, v) для p1, p2, p3 относительно p1
Vector2f uv1 = { 0.0f, 0.0f };
Vector2f uv2 = { (p2 - p1).length(), 0.0f }; // p2-p1 вдоль оси U
Vector2f uv3 = { uProjLen, vLen }; // p3-p1: u-компонента = uProjLen, v-компонента = vLen
// Находим максимальный размер, чтобы масштабировать в [0, 1]
float maxUV = max(uv2.v[0], max(uv3.v[0], uv3.v[1]));
if (maxUV > 0.000001f) {
// Масштабируем:
result.TexCoordData.push_back(uv1);
result.TexCoordData.push_back(uv2 * (1.f / maxUV));
result.TexCoordData.push_back(uv3 * (1.f / maxUV));
}
else {
// Предотвращение деления на ноль для вырожденных граней
result.TexCoordData.push_back({ 0.0f, 0.0f });
result.TexCoordData.push_back({ 0.0f, 0.0f });
result.TexCoordData.push_back({ 0.0f, 0.0f });
}
}
return result;
}
VertexDataStruct CreateConvexPolyhedron(uint64_t seed, const LodLevel& planetLodLevel, const std::vector<int>& triangleIndices) {
VertexDataStruct finalMesh;
const int STONES_PER_TRIANGLE = 100;
// Константы трансформации (нужны здесь, чтобы каждый камень был уникальным)
const float MIN_AXIS_SCALE = 0.5f;
const float MAX_AXIS_SCALE = 1.5f;
// 1. Создаем БАЗОВЫЙ МЕШ ОДИН РАЗ, чтобы не повторять сложную генерацию
// Используем отдельный seed для базовой формы, чтобы она была уникальной,
// но оставалась одинаковой при разных seed для размещения.
uint64_t baseSeed = 1337; // Константный seed для формы камня
VertexDataStruct baseStone = CreateBaseConvexPolyhedron(baseSeed);
// 2. Итерируемся по заданным треугольникам
for (int triangleIndex : triangleIndices) {
std::mt19937 engine(static_cast<unsigned int>(seed));
if (triangleIndex >= planetLodLevel.triangles.size()) {
// Обработка ошибки
continue;
}
const Triangle& currentTriangle = planetLodLevel.triangles[triangleIndex];
// 3. Генерируем 100 камней на каждом треугольнике
for (int i = 0; i < STONES_PER_TRIANGLE; i++) {
// --- 3.1. Генерируем случайное местоположение на треугольнике ---
Vector3f stoneCenter = GetRandomPointOnTriangle(currentTriangle, engine);
// --- 3.2. Применяем случайную трансформацию (Масштаб + Поворот) ---
// Случайные масштабы
Vector3f scaleFactors = {
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE),
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE),
getRandomFloat(engine, MIN_AXIS_SCALE, MAX_AXIS_SCALE)
};
// Случайный поворот (например, вокруг оси нормали к поверхности)
// Для реалистичности, камень должен лежать на поверхности.
// Вектор 'вверх' для камня должен быть выровнен по нормали треугольника.
// 1. Создаем вращение вокруг нормали треугольника (currentTriangle.normal)
float angle = getRandomFloat(engine, 0.0f, 360.0f);
// (Для простоты здесь используется Matrix4f::Identity, но вам нужно реализовать
// QuaternionFromAxisAngle для корректного вращения вокруг заданной оси Normal)
// --- ПРОСТОЕ РЕШЕНИЕ: Случайный поворот вокруг трех осей ---
Vector4f qx = QuatFromRotateAroundX(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qy = QuatFromRotateAroundY(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qz = QuatFromRotateAroundZ(getRandomFloat(engine, 0.0f, 360.0f));
Vector4f qFinal = slerp(qx, qy, 0.5f);
qFinal = slerp(qFinal, qz, 0.5f).normalized();
Matrix3f rotationMatrix = QuatToMatrix(qFinal);
// --- 3.3. Трансформируем и Смещаем вершины базового меша ---
// Копируем данные для текущего камня
VertexDataStruct currentStone = baseStone;
// Применяем масштабирование, поворот, и смещение к каждой вершине
for (size_t j = 0; j < currentStone.PositionData.size(); j++) {
Vector3f& pos = currentStone.PositionData[j];
Vector3f& norm = currentStone.NormalData[j];
// 1. Масштабирование
pos.v[0] *= scaleFactors.v[0];
pos.v[1] *= scaleFactors.v[1];
pos.v[2] *= scaleFactors.v[2];
// 2. Поворот
pos = MultMatrixVector(rotationMatrix, pos);
norm = MultMatrixVector(rotationMatrix, norm);
// 3. Смещение (Translation)
pos = pos + stoneCenter;
}
// --- 3.4. Объединяем меши ---
finalMesh.PositionData.insert(finalMesh.PositionData.end(),
currentStone.PositionData.begin(),
currentStone.PositionData.end());
finalMesh.NormalData.insert(finalMesh.NormalData.end(),
currentStone.NormalData.begin(),
currentStone.NormalData.end());
finalMesh.TexCoordData.insert(finalMesh.TexCoordData.end(),
currentStone.TexCoordData.begin(),
currentStone.TexCoordData.end());
// ... аналогично для других данных (Tangent, Binormal, Color)
}
}
return finalMesh;
}
} // namespace ZL