added sparks on the back of the spaceship

This commit is contained in:
Vlad 2025-12-08 19:28:47 +06:00
parent b768b27d49
commit b855cff0e6
7 changed files with 316 additions and 37 deletions

View File

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

139
Game.cpp
View File

@ -1,4 +1,4 @@
#include "Game.h"
#include "Game.h"
#include "AnimatedModel.h"
#include "BoneAnimatedModel.h"
#include "Utils.h"
@ -19,11 +19,11 @@ namespace ZL
Vector4f generateRandomQuaternion(std::mt19937& gen)
{
// Распределение для генерации случайных координат кватерниона
// Ðàñïðåäåëåíèå äëÿ ãåíåðàöèè ñëó÷àéíûõ êîîðäèíàò êâàòåðíèîíà
std::normal_distribution<> distrib(0.0, 1.0);
// Генерируем четыре случайных числа из нормального распределения N(0, 1).
// Нормализация этого вектора дает равномерное распределение по 4D-сфере (т.е. кватернион единичной длины).
// Ãåíåðèðóåì ÷åòûðå ñëó÷àéíûõ ÷èñëà èç íîðìàëüíîãî ðàñïðåäåëåíèÿ N(0, 1).
// Íîðìàëèçàöèÿ ýòîãî âåêòîðà äàåò ðàâíîìåðíîå ðàñïðåäåëåíèå ïî 4D-ñôåðå (ò.å. êâàòåðíèîí åäèíè÷íîé äëèíû).
Vector4f randomQuat = {
(float)distrib(gen),
(float)distrib(gen),
@ -35,25 +35,25 @@ namespace ZL
}
// --- Основная функция генерации ---
// --- Îñíîâíàÿ ôóíêöèÿ ãåíåðàöèè ---
std::vector<BoxCoords> generateRandomBoxCoords(int N)
{
// Константы
// Êîíñòàíòû
const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; // Работаем с квадратом расстояния
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE; // Ðàáîòàåì ñ êâàäðàòîì ðàññòîÿíèÿ
const float MIN_COORD = -100.0f;
const float MAX_COORD = 100.0f;
const int MAX_ATTEMPTS = 1000; // Ограничение на количество попыток, чтобы избежать бесконечного цикла
const int MAX_ATTEMPTS = 1000; // Îãðàíè÷åíèå íà êîëè÷åñòâî ïîïûòîê, ÷òîáû èçáåæàòü áåñêîíå÷íîãî öèêëà
std::vector<BoxCoords> boxCoordsArr;
boxCoordsArr.reserve(N); // Резервируем память
boxCoordsArr.reserve(N); // Ðåçåðâèðóåì ïàìÿòü
// 1. Инициализация генератора псевдослучайных чисел
// Используем Mersenne Twister (mt19937) как высококачественный генератор
// 1. Èíèöèàëèçàöèÿ ãåíåðàòîðà ïñåâäîñëó÷àéíûõ ÷èñåë
// Èñïîëüçóåì Mersenne Twister (mt19937) êàê âûñîêîêà÷åñòâåííûé ãåíåðàòîð
std::random_device rd;
std::mt19937 gen(rd());
// 2. Определение равномерного распределения для координат [MIN_COORD, MAX_COORD]
// 2. Îïðåäåëåíèå ðàâíîìåðíîãî ðàñïðåäåëåíèÿ äëÿ êîîðäèíàò [MIN_COORD, MAX_COORD]
std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);
int generatedCount = 0;
@ -63,53 +63,53 @@ namespace ZL
bool accepted = false;
int attempts = 0;
// Попытка найти подходящие координаты
// Ïîïûòêà íàéòè ïîäõîäÿùèå êîîðäèíàòû
while (!accepted && attempts < MAX_ATTEMPTS)
{
// Генерируем новые случайные координаты
// Ãåíåðèðóåì íîâûå ñëó÷àéíûå êîîðäèíàòû
Vector3f newPos(
(float)distrib(gen),
(float)distrib(gen),
(float)distrib(gen)
);
// Проверка расстояния до всех уже существующих объектов
accepted = true; // Предполагаем, что подходит, пока не доказано обратное
// Ïðîâåðêà ðàññòîÿíèÿ äî âñåõ óæå ñóùåñòâóþùèõ îáúåêòîâ
accepted = true; // Ïðåäïîëàãàåì, ÷òî ïîäõîäèò, ïîêà íå äîêàçàíî îáðàòíîå
for (const auto& existingBox : boxCoordsArr)
{
// Расчет вектора разности
// Ðàñ÷åò âåêòîðà ðàçíîñòè
Vector3f diff = newPos - existingBox.pos;
// Расчет квадрата расстояния
// Ðàñ÷åò êâàäðàòà ðàññòîÿíèÿ
float distanceSquared = diff.squaredNorm();
// Если квадрат расстояния меньше квадрата минимального расстояния
// Åñëè êâàäðàò ðàññòîÿíèÿ ìåíüøå êâàäðàòà ìèíèìàëüíîãî ðàññòîÿíèÿ
if (distanceSquared < MIN_DISTANCE_SQUARED)
{
accepted = false; // Отклоняем, слишком близко
break; // Нет смысла проверять дальше, если одно нарушение найдено
accepted = false; // Îòêëîíÿåì, ñëèøêîì áëèçêî
break; // Íåò ñìûñëà ïðîâåðÿòü äàëüøå, åñëè îäíî íàðóøåíèå íàéäåíî
}
}
if (accepted)
{
// 2. Генерируем случайный кватернион
// 2. Ãåíåðèðóåì ñëó÷àéíûé êâàòåðíèîí
Vector4f randomQuat = generateRandomQuaternion(gen);
// 3. Преобразуем его в матрицу вращения
// 3. Ïðåîáðàçóåì åãî â ìàòðèöó âðàùåíèÿ
Matrix3f randomMatrix = QuatToMatrix(randomQuat);
// 4. Добавляем объект с новой случайной матрицей
// 4. Äîáàâëÿåì îáúåêò ñ íîâîé ñëó÷àéíîé ìàòðèöåé
boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
generatedCount++;
}
attempts++;
}
// Если превышено максимальное количество попыток, выходим из цикла,
// чтобы избежать зависания, если N слишком велико или диапазон слишком мал.
// Åñëè ïðåâûøåíî ìàêñèìàëüíîå êîëè÷åñòâî ïîïûòîê, âûõîäèì èç öèêëà,
// ÷òîáû èçáåæàòü çàâèñàíèÿ, åñëè N ñëèøêîì âåëèêî èëè äèàïàçîí ñëèøêîì ìàë.
if (!accepted) {
std::cerr << "Предупреждение: Не удалось сгенерировать " << N << " объектов. Сгенерировано: " << generatedCount << std::endl;
std::cerr << "Ïðåäóïðåæäåíèå: Íå óäàëîñü ñãåíåðèðîâàòü " << N << " îáúåêòîâ. Ñãåíåðèðîâàíî: " << generatedCount << std::endl;
break;
}
}
@ -123,6 +123,11 @@ Game::Game()
, newTickCount(0)
, lastTickCount(0)
{
std::vector<Vector3f> emissionPoints = {
Vector3f{-2.1f, 0.9f, 5.0f},
Vector3f{2.1f, 0.9f, 5.0f}
};
sparkEmitter = SparkEmitter(emissionPoints, 100.0f);
}
Game::~Game() {
@ -193,6 +198,26 @@ void Game::setup() {
boxRenderArr[i].RefreshVBO();
}
sparkTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/spark.png", CONST_ZIP_FILE));
/*singleSpark.data.PositionData.push_back({-1, -1, 0});
singleSpark.data.PositionData.push_back({ -1, 1, 0 });
singleSpark.data.PositionData.push_back({ 1, 1, 0 });
singleSpark.data.PositionData.push_back({ -1, -1, 0 });
singleSpark.data.PositionData.push_back({ 1, 1, 0 });
singleSpark.data.PositionData.push_back({ 1, -1, 0 });
singleSpark.data.TexCoordData.push_back({0,0});
singleSpark.data.TexCoordData.push_back({ 0,1 });
singleSpark.data.TexCoordData.push_back({1,1});
singleSpark.data.TexCoordData.push_back({0,0});
singleSpark.data.TexCoordData.push_back({ 1,1 });
singleSpark.data.TexCoordData.push_back({ 1,0 });
singleSpark.RefreshVBO();*/
sparkQuad.data = CreateRect2D({ 0, 0 }, { 0.08f, 0.08f }, 0);
sparkQuad.RefreshVBO();
renderer.InitOpenGL();
}
@ -230,6 +255,49 @@ void Game::drawCubemap()
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawEffects()
{
static const std::string defaultShaderName = "default";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
if (sparkEmitter.getActiveParticleCount() == 0) {
return;
}
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
renderer.EnableVertexAttribArray(vTexCoordName);
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
1, 1000);
glBindTexture(GL_TEXTURE_2D, sparkTexture->getTexID());
const auto& particles = sparkEmitter.getParticles();
for (const auto& particle : particles) {
if (!particle.active) continue;
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
renderer.TranslateMatrix(particle.position);
renderer.ScaleMatrix(particle.scale);
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.DrawVertexRenderStruct(sparkQuad);
renderer.PopMatrix();
}
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawShip()
{
static const std::string defaultShaderName = "default";
@ -254,7 +322,9 @@ void Game::drawShip()
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship);
drawEffects();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
@ -339,6 +409,7 @@ void Game::processTickCount() {
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
//gameObjects.updateScene(delta);
sparkEmitter.update(static_cast<float>(delta));
if (Environment::tapDownHold) {
@ -405,23 +476,23 @@ void Game::update() {
}
else if (event.type == SDL_MOUSEBUTTONDOWN) {
// 1. Обработка нажатия кнопки мыши
// 1. Îáðàáîòêà íàæàòèÿ êíîïêè ìûøè
Environment::tapDownHold = true;
// Координаты начального нажатия
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ
Environment::tapDownStartPos.v[0] = event.button.x;
Environment::tapDownStartPos.v[1] = event.button.y;
// Начальная позиция также становится текущей
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé
Environment::tapDownCurrentPos.v[0] = event.button.x;
Environment::tapDownCurrentPos.v[1] = event.button.y;
}
else if (event.type == SDL_MOUSEBUTTONUP) {
// 2. Обработка отпускания кнопки мыши
// 2. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè
Environment::tapDownHold = false;
}
else if (event.type == SDL_MOUSEMOTION) {
// 3. Обработка перемещения мыши
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè
if (Environment::tapDownHold) {
// Обновление текущей позиции, если кнопка удерживается
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
Environment::tapDownCurrentPos.v[0] = event.motion.x;
Environment::tapDownCurrentPos.v[1] = event.motion.y;
}

8
Game.h
View File

@ -1,9 +1,10 @@
#pragma once
#pragma once
#include "OpenGlExtensions.h"
#include "Renderer.h"
#include "Environment.h"
#include "TextureManager.h"
#include "SparkEmitter.h"
namespace ZL {
@ -30,6 +31,7 @@ private:
void processTickCount();
void drawScene();
void drawCubemap();
void drawEffects();
void drawShip();
void drawBoxes();
@ -43,6 +45,7 @@ private:
static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
std::shared_ptr<Texture> sparkTexture;
std::shared_ptr<Texture> spaceshipTexture;
std::shared_ptr<Texture> cubemapTexture;
VertexDataStruct spaceshipBase;
@ -56,6 +59,9 @@ private:
std::vector<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr;
//VertexRenderStruct singleSpark;
SparkEmitter sparkEmitter;
VertexRenderStruct sparkQuad;
};
} // namespace ZL

147
SparkEmitter.cpp Normal file
View File

@ -0,0 +1,147 @@
#include "SparkEmitter.h"
#include <random>
#include <cmath>
#include "OpenGlExtensions.h"
namespace ZL {
SparkEmitter::SparkEmitter()
: emissionRate(100.0f), isActive(true) {
particles.resize(200);
lastEmissionTime = std::chrono::steady_clock::now();
}
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
: emissionPoints(positions), emissionRate(rate), isActive(true) {
particles.resize(positions.size() * 100);
lastEmissionTime = std::chrono::steady_clock::now();
}
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
emissionPoints = positions;
particles.resize(positions.size() * 100);
}
void SparkEmitter::setEmissionRate(float rateMs) {
emissionRate = rateMs;
}
void SparkEmitter::setActive(bool active) {
isActive = active;
}
void SparkEmitter::update(float deltaTimeMs) {
auto currentTime = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count();
if (isActive && elapsed >= emissionRate) {
emit();
lastEmissionTime = currentTime;
}
for (auto& particle : particles) {
if (particle.active) {
particle.position.v[0] += particle.velocity.v[0] * deltaTimeMs / 1000.0f;
particle.position.v[1] += particle.velocity.v[1] * deltaTimeMs / 1000.0f;
particle.position.v[2] += particle.velocity.v[2] * deltaTimeMs / 1000.0f;
particle.lifeTime += deltaTimeMs;
if (particle.lifeTime >= particle.maxLifeTime) {
particle.active = false;
}
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
}
}
}
void SparkEmitter::emit() {
if (emissionPoints.empty()) return;
for (int i = 0; i < emissionPoints.size(); ++i) {
bool particleFound = false;
for (auto& particle : particles) {
if (!particle.active) {
initParticle(particle, i);
particle.active = true;
particle.lifeTime = 0;
particle.position = emissionPoints[i];
particle.emitterIndex = i;
particleFound = true;
break;
}
}
if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0;
float maxLifeTime = 0;
for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeTime) {
maxLifeTime = particles[j].lifeTime;
oldestIndex = j;
}
}
initParticle(particles[oldestIndex], i);
particles[oldestIndex].active = true;
particles[oldestIndex].lifeTime = 0;
particles[oldestIndex].position = emissionPoints[i];
particles[oldestIndex].emitterIndex = i;
}
}
}
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
particle.velocity = getRandomVelocity(emitterIndex);
particle.scale = 1.0f;
particle.maxLifeTime = 800.0f + (rand() % 400); // От 800 до 1200 мс
particle.emitterIndex = emitterIndex;
}
Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) {
static std::random_device rd;
static std::mt19937 gen(rd());
static std::uniform_real_distribution<> angleDist(0, 2 * M_PI);
static std::uniform_real_distribution<> speedDist(0.5f, 2.0f);
static std::uniform_real_distribution<> zSpeedDist(1.0f, 3.0f);
float angle = angleDist(gen);
float speed = speedDist(gen);
float zSpeed = zSpeedDist(gen);
if (emitterIndex == 0) {
return Vector3f{
cosf(angle) * speed - 0.3f,
sinf(angle) * speed,
zSpeed
};
}
else {
return Vector3f{
cosf(angle) * speed + 0.3f,
sinf(angle) * speed,
zSpeed
};
}
}
const std::vector<SparkParticle>& SparkEmitter::getParticles() const {
return particles;
}
size_t SparkEmitter::getActiveParticleCount() const {
size_t count = 0;
for (const auto& particle : particles) {
if (particle.active) {
count++;
}
}
return count;
}
} // namespace ZL

50
SparkEmitter.h Normal file
View File

@ -0,0 +1,50 @@
#pragma once
#include "ZLMath.h"
#include <vector>
#include <chrono>
namespace ZL {
struct SparkParticle {
Vector3f position;
Vector3f velocity;
float scale;
float lifeTime;
float maxLifeTime;
bool active;
int emitterIndex;
SparkParticle() : position({ 0,0,0 }), velocity({ 0,0,0 }), scale(1.0f),
lifeTime(0), maxLifeTime(1000.0f), active(false), emitterIndex(0) {
}
};
class SparkEmitter {
private:
std::vector<SparkParticle> particles;
std::vector<Vector3f> emissionPoints;
std::chrono::steady_clock::time_point lastEmissionTime;
float emissionRate;
bool isActive;
public:
SparkEmitter();
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
void setEmissionPoints(const std::vector<Vector3f>& positions);
void setEmissionRate(float rateMs);
void setActive(bool active);
void update(float deltaTimeMs);
void emit();
const std::vector<SparkParticle>& getParticles() const;
size_t getActiveParticleCount() const;
private:
void initParticle(SparkParticle& particle, int emitterIndex);
Vector3f getRandomVelocity(int emitterIndex);
};
} // namespace ZL

BIN
resources/spark.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -5,8 +5,8 @@ varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
gl_FragColor = vec4(color.rgb*0.9 + vec3(0.1, 0.1, 0.1), 1.0);
//gl_FragColor = vec4(color.rgb*0.9 + vec3(0.1, 0.1, 0.1), 1.0);
//gl_FragColor = color;
gl_FragColor = color;
}