fix spark

This commit is contained in:
Vlad 2025-12-11 20:09:41 +06:00
parent b855cff0e6
commit 115cbbb7fa
4 changed files with 760 additions and 673 deletions

948
Game.cpp
View File

@ -12,516 +12,528 @@
namespace ZL namespace ZL
{ {
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
const char* CONST_ZIP_FILE = "space-game001.zip"; const char* CONST_ZIP_FILE = "space-game001.zip";
#else #else
const char* CONST_ZIP_FILE = ""; const char* CONST_ZIP_FILE = "";
#endif #endif
Vector4f generateRandomQuaternion(std::mt19937& gen) Vector4f generateRandomQuaternion(std::mt19937& gen)
{ {
// Ðàñïðåäåëåíèå äëÿ ãåíåðàöèè ñëó÷àéíûõ êîîðäèíàò êâàòåðíèîíà // Ðàñïðåäåëåíèå äëÿ ãåíåðàöèè ñëó÷àéíûõ êîîðäèíàò êâàòåðíèîíà
std::normal_distribution<> distrib(0.0, 1.0); std::normal_distribution<> distrib(0.0, 1.0);
// Ãåíåðèðóåì ÷åòûðå ñëó÷àéíûõ ÷èñëà èç íîðìàëüíîãî ðàñïðåäåëåíèÿ N(0, 1). // Ãåíåðèðóåì ÷åòûðå ñëó÷àéíûõ ÷èñëà èç íîðìàëüíîãî ðàñïðåäåëåíèÿ N(0, 1).
// Íîðìàëèçàöèÿ ýòîãî âåêòîðà äàåò ðàâíîìåðíîå ðàñïðåäåëåíèå ïî 4D-ñôåðå (ò.å. êâàòåðíèîí åäèíè÷íîé äëèíû). // Íîðìàëèçàöèÿ ýòîãî âåêòîðà äàåò ðàâíîìåðíîå ðàñïðåäåëåíèå ïî 4D-ñôåðå (ò.å. êâàòåðíèîí åäèíè÷íîé äëèíû).
Vector4f randomQuat = { Vector4f randomQuat = {
(float)distrib(gen), (float)distrib(gen),
(float)distrib(gen), (float)distrib(gen),
(float)distrib(gen), (float)distrib(gen),
(float)distrib(gen) (float)distrib(gen)
}; };
return randomQuat.normalized(); return randomQuat.normalized();
} }
// --- Îñíîâíàÿ ôóíêöèÿ ãåíåðàöèè --- // --- Îñíîâíàÿ ôóíêöèÿ ãåíåðàöèè ---
std::vector<BoxCoords> generateRandomBoxCoords(int N) std::vector<BoxCoords> generateRandomBoxCoords(int N)
{ {
// Êîíñòàíòû // Êîíñòàíòû
const float MIN_DISTANCE = 3.0f; 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 MIN_COORD = -100.0f;
const float MAX_COORD = 100.0f; const float MAX_COORD = 100.0f;
const int MAX_ATTEMPTS = 1000; // Îãðàíè÷åíèå íà êîëè÷åñòâî ïîïûòîê, ÷òîáû èçáåæàòü áåñêîíå÷íîãî öèêëà const int MAX_ATTEMPTS = 1000; // Îãðàíè÷åíèå íà êîëè÷åñòâî ïîïûòîê, ÷òîáû èçáåæàòü áåñêîíå÷íîãî öèêëà
std::vector<BoxCoords> boxCoordsArr; std::vector<BoxCoords> boxCoordsArr;
boxCoordsArr.reserve(N); // Ðåçåðâèðóåì ïàìÿòü boxCoordsArr.reserve(N); // Ðåçåðâèðóåì ïàìÿòü
// 1. Èíèöèàëèçàöèÿ ãåíåðàòîðà ïñåâäîñëó÷àéíûõ ÷èñåë // 1. Èíèöèàëèçàöèÿ ãåíåðàòîðà ïñåâäîñëó÷àéíûõ ÷èñåë
// Èñïîëüçóåì Mersenne Twister (mt19937) êàê âûñîêîêà÷åñòâåííûé ãåíåðàòîð // Èñïîëüçóåì Mersenne Twister (mt19937) êàê âûñîêîêà÷åñòâåííûé ãåíåðàòîð
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
// 2. Îïðåäåëåíèå ðàâíîìåðíîãî ðàñïðåäåëåíèÿ äëÿ êîîðäèíàò [MIN_COORD, MAX_COORD] // 2. Îïðåäåëåíèå ðàâíîìåðíîãî ðàñïðåäåëåíèÿ äëÿ êîîðäèíàò [MIN_COORD, MAX_COORD]
std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD); std::uniform_real_distribution<> distrib(MIN_COORD, MAX_COORD);
int generatedCount = 0; int generatedCount = 0;
while (generatedCount < N) while (generatedCount < N)
{ {
bool accepted = false; bool accepted = false;
int attempts = 0; int attempts = 0;
// Ïîïûòêà íàéòè ïîäõîäÿùèå êîîðäèíàòû // Ïîïûòêà íàéòè ïîäõîäÿùèå êîîðäèíàòû
while (!accepted && attempts < MAX_ATTEMPTS) while (!accepted && attempts < MAX_ATTEMPTS)
{ {
// Ãåíåðèðóåì íîâûå ñëó÷àéíûå êîîðäèíàòû // Ãåíåðèðóåì íîâûå ñëó÷àéíûå êîîðäèíàòû
Vector3f newPos( Vector3f newPos(
(float)distrib(gen), (float)distrib(gen),
(float)distrib(gen), (float)distrib(gen),
(float)distrib(gen) (float)distrib(gen)
); );
// Ïðîâåðêà ðàññòîÿíèÿ äî âñåõ óæå ñóùåñòâóþùèõ îáúåêòîâ // Ïðîâåðêà ðàññòîÿíèÿ äî âñåõ óæå ñóùåñòâóþùèõ îáúåêòîâ
accepted = true; // Ïðåäïîëàãàåì, ÷òî ïîäõîäèò, ïîêà íå äîêàçàíî îáðàòíîå accepted = true; // Ïðåäïîëàãàåì, ÷òî ïîäõîäèò, ïîêà íå äîêàçàíî îáðàòíîå
for (const auto& existingBox : boxCoordsArr) for (const auto& existingBox : boxCoordsArr)
{ {
// Ðàñ÷åò âåêòîðà ðàçíîñòè // Ðàñ÷åò âåêòîðà ðàçíîñòè
Vector3f diff = newPos - existingBox.pos; Vector3f diff = newPos - existingBox.pos;
// Ðàñ÷åò êâàäðàòà ðàññòîÿíèÿ // Ðàñ÷åò êâàäðàòà ðàññòîÿíèÿ
float distanceSquared = diff.squaredNorm(); float distanceSquared = diff.squaredNorm();
// Åñëè êâàäðàò ðàññòîÿíèÿ ìåíüøå êâàäðàòà ìèíèìàëüíîãî ðàññòîÿíèÿ // Åñëè êâàäðàò ðàññòîÿíèÿ ìåíüøå êâàäðàòà ìèíèìàëüíîãî ðàññòîÿíèÿ
if (distanceSquared < MIN_DISTANCE_SQUARED) if (distanceSquared < MIN_DISTANCE_SQUARED)
{ {
accepted = false; // Îòêëîíÿåì, ñëèøêîì áëèçêî accepted = false; // Îòêëîíÿåì, ñëèøêîì áëèçêî
break; // Íåò ñìûñëà ïðîâåðÿòü äàëüøå, åñëè îäíî íàðóøåíèå íàéäåíî break; // Íåò ñìûñëà ïðîâåðÿòü äàëüøå, åñëè îäíî íàðóøåíèå íàéäåíî
} }
} }
if (accepted) if (accepted)
{ {
// 2. Ãåíåðèðóåì ñëó÷àéíûé êâàòåðíèîí // 2. Ãåíåðèðóåì ñëó÷àéíûé êâàòåðíèîí
Vector4f randomQuat = generateRandomQuaternion(gen); Vector4f randomQuat = generateRandomQuaternion(gen);
// 3. Ïðåîáðàçóåì åãî â ìàòðèöó âðàùåíèÿ // 3. Ïðåîáðàçóåì åãî â ìàòðèöó âðàùåíèÿ
Matrix3f randomMatrix = QuatToMatrix(randomQuat); Matrix3f randomMatrix = QuatToMatrix(randomQuat);
// 4. Äîáàâëÿåì îáúåêò ñ íîâîé ñëó÷àéíîé ìàòðèöåé // 4. Äîáàâëÿåì îáúåêò ñ íîâîé ñëó÷àéíîé ìàòðèöåé
boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix }); boxCoordsArr.emplace_back(BoxCoords{ newPos, randomMatrix });
generatedCount++; generatedCount++;
} }
attempts++; attempts++;
} }
// Åñëè ïðåâûøåíî ìàêñèìàëüíîå êîëè÷åñòâî ïîïûòîê, âûõîäèì èç öèêëà, // Åñëè ïðåâûøåíî ìàêñèìàëüíîå êîëè÷åñòâî ïîïûòîê, âûõîäèì èç öèêëà,
// ÷òîáû èçáåæàòü çàâèñàíèÿ, åñëè N ñëèøêîì âåëèêî èëè äèàïàçîí ñëèøêîì ìàë. // ÷òîáû èçáåæàòü çàâèñàíèÿ, åñëè N ñëèøêîì âåëèêî èëè äèàïàçîí ñëèøêîì ìàë.
if (!accepted) { if (!accepted) {
std::cerr << "Ïðåäóïðåæäåíèå: Íå óäàëîñü ñãåíåðèðîâàòü " << N << " îáúåêòîâ. Ñãåíåðèðîâàíî: " << generatedCount << std::endl; std::cerr << "Ïðåäóïðåæäåíèå: Íå óäàëîñü ñãåíåðèðîâàòü " << N << " îáúåêòîâ. Ñãåíåðèðîâàíî: " << generatedCount << std::endl;
break; break;
} }
} }
return boxCoordsArr; return boxCoordsArr;
} }
Game::Game() Game::Game()
: window(nullptr) : window(nullptr)
, glContext(nullptr) , glContext(nullptr)
, newTickCount(0) , newTickCount(0)
, lastTickCount(0) , lastTickCount(0)
{ {
std::vector<Vector3f> emissionPoints = { std::vector<Vector3f> emissionPoints = {
Vector3f{-2.1f, 0.9f, 5.0f}, Vector3f{-2.1f, 0.9f, 5.0f},
Vector3f{2.1f, 0.9f, 5.0f} Vector3f{2.1f, 0.9f, 5.0f}
}; };
sparkEmitter = SparkEmitter(emissionPoints, 100.0f); sparkEmitter = SparkEmitter(emissionPoints, 100.0f);
} }
Game::~Game() { Game::~Game() {
if (glContext) { if (glContext) {
SDL_GL_DeleteContext(glContext); SDL_GL_DeleteContext(glContext);
} }
if (window) { if (window) {
SDL_DestroyWindow(window); SDL_DestroyWindow(window);
} }
SDL_Quit(); SDL_Quit();
} }
void Game::setup() { void Game::setup() {
glContext = SDL_GL_CreateContext(ZL::Environment::window); glContext = SDL_GL_CreateContext(ZL::Environment::window);
ZL::BindOpenGlFunctions(); ZL::BindOpenGlFunctions();
ZL::CheckGlError(); ZL::CheckGlError();
// Initialize renderer // Initialize renderer
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor.vertex", "./shaders/defaultColor_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor.vertex", "./shaders/defaultColor_web.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env.vertex", "./shaders/env_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env.vertex", "./shaders/env_web.fragment", CONST_ZIP_FILE);
#else #else
renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("default", "./shaders/default.vertex", "./shaders/default_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor.vertex", "./shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("defaultColor", "./shaders/defaultColor.vertex", "./shaders/defaultColor_desktop.fragment", CONST_ZIP_FILE);
renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env.vertex", "./shaders/env_desktop.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env", "./shaders/env.vertex", "./shaders/env_desktop.fragment", CONST_ZIP_FILE);
#endif #endif
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
CreateTextureDataFromBmp24("./resources/sky/space_rt.bmp", CONST_ZIP_FILE), CreateTextureDataFromBmp24("./resources/sky/space_rt.bmp", CONST_ZIP_FILE),
CreateTextureDataFromBmp24("./resources/sky/space_lf.bmp", CONST_ZIP_FILE), CreateTextureDataFromBmp24("./resources/sky/space_lf.bmp", CONST_ZIP_FILE),
CreateTextureDataFromBmp24("./resources/sky/space_up.bmp", CONST_ZIP_FILE), CreateTextureDataFromBmp24("./resources/sky/space_up.bmp", CONST_ZIP_FILE),
CreateTextureDataFromBmp24("./resources/sky/space_dn.bmp", CONST_ZIP_FILE), CreateTextureDataFromBmp24("./resources/sky/space_dn.bmp", CONST_ZIP_FILE),
CreateTextureDataFromBmp24("./resources/sky/space_bk.bmp", CONST_ZIP_FILE), CreateTextureDataFromBmp24("./resources/sky/space_bk.bmp", CONST_ZIP_FILE),
CreateTextureDataFromBmp24("./resources/sky/space_ft.bmp", CONST_ZIP_FILE) CreateTextureDataFromBmp24("./resources/sky/space_ft.bmp", CONST_ZIP_FILE)
}); });
cubemap.data = ZL::CreateCubemap(500);
cubemap.RefreshVBO();
//Load texture
spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/DefaultMaterial_BaseColor.png", CONST_ZIP_FILE));
spaceshipBase = LoadFromTextFile02("./resources/spaceship005.txt", CONST_ZIP_FILE);
spaceshipBase.RotateByMatrix(QuatToMatrix(QuatFromRotateAroundY(M_PI / 2.0)));
//spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 });
spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 });
spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO();
//Boxes
boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/box/box.png", CONST_ZIP_FILE));
boxBase = LoadFromTextFile02("./resources/box/box.txt", CONST_ZIP_FILE);
boxCoordsArr = generateRandomBoxCoords(50);
boxRenderArr.resize(boxCoordsArr.size());
for (int i = 0; i < boxCoordsArr.size(); i++)
{
boxRenderArr[i].AssignFrom(boxBase);
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();
}
void Game::drawCubemap()
{
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(envShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
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.RotateMatrix(Environment::inverseShipMatrix);
CheckGlError();
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
renderer.DrawVertexRenderStruct(cubemap);
CheckGlError();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
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";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
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);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
renderer.DrawVertexRenderStruct(spaceship);
drawEffects();
renderer.PopMatrix();
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawBoxes() cubemap.data = ZL::CreateCubemap(500);
{ cubemap.RefreshVBO();
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
renderer.shaderManager.PushShader(defaultShaderName); //Load texture
renderer.RenderUniform1i(textureUniformName, 0); spaceshipTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/DefaultMaterial_BaseColor.png", CONST_ZIP_FILE));
renderer.EnableVertexAttribArray(vPositionName); spaceshipBase = LoadFromTextFile02("./resources/spaceship005.txt", CONST_ZIP_FILE);
renderer.EnableVertexAttribArray(vTexCoordName); spaceshipBase.RotateByMatrix(QuatToMatrix(QuatFromRotateAroundY(M_PI / 2.0)));
//spaceshipBase.Move(Vector3f{ -0.52998, -13, 0 });
spaceshipBase.Move(Vector3f{ -0.52998, -10, 10 });
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, spaceship.AssignFrom(spaceshipBase);
static_cast<float>(Environment::width) / static_cast<float>(Environment::height), spaceship.RefreshVBO();
1, 1000);
for (int i = 0; i < boxCoordsArr.size(); i++) //Boxes
{ boxTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/box/box.png", CONST_ZIP_FILE));
renderer.PushMatrix(); boxBase = LoadFromTextFile02("./resources/box/box.txt", CONST_ZIP_FILE);
renderer.LoadIdentity(); boxCoordsArr = generateRandomBoxCoords(50);
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
renderer.TranslateMatrix(boxCoordsArr[i].pos);
renderer.RotateMatrix(boxCoordsArr[i].m);
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID()); boxRenderArr.resize(boxCoordsArr.size());
renderer.DrawVertexRenderStruct(boxRenderArr[i]);
renderer.PopMatrix(); for (int i = 0; i < boxCoordsArr.size(); i++)
} {
renderer.PopProjectionMatrix(); boxRenderArr[i].AssignFrom(boxBase);
renderer.DisableVertexAttribArray(vPositionName); boxRenderArr[i].RefreshVBO();
renderer.DisableVertexAttribArray(vTexCoordName); }
renderer.shaderManager.PopShader(); sparkTexture = std::make_unique<Texture>(CreateTextureDataFromPng("./resources/spark.png", CONST_ZIP_FILE));
CheckGlError();
}
void Game::drawScene() { sparkQuad.data = CreateRect2D({ 0, 0 }, { 0.08f, 0.08f }, 0);
static const std::string defaultShaderName = "default"; sparkQuad.RefreshVBO();
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition"; renderer.InitOpenGL();
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture"; }
glClearColor(0.0f, 0.5f, 1.0f, 1.0f); void Game::drawCubemap()
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); {
static const std::string defaultShaderName = "default";
glViewport(0, 0, Environment::width, Environment::height); static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
CheckGlError(); static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
drawCubemap();
drawShip(); renderer.shaderManager.PushShader(envShaderName);
drawBoxes(); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
CheckGlError(); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
} static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
1, 1000);
void Game::processTickCount() { renderer.PushMatrix();
renderer.LoadIdentity();
if (lastTickCount == 0) { renderer.RotateMatrix(Environment::inverseShipMatrix);
lastTickCount = SDL_GetTicks64();
return; CheckGlError();
}
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture->getTexID());
newTickCount = SDL_GetTicks64(); renderer.DrawVertexRenderStruct(cubemap);
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ? CheckGlError();
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
//gameObjects.updateScene(delta); renderer.PopMatrix();
sparkEmitter.update(static_cast<float>(delta)); renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
if (Environment::tapDownHold) {
renderer.shaderManager.PopShader();
float diffx = Environment::tapDownCurrentPos.v[0] - Environment::tapDownStartPos.v[0]; CheckGlError();
float diffy = Environment::tapDownCurrentPos.v[1] - Environment::tapDownStartPos.v[1]; }
void Game::drawEffects()
if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold {
{ if (sparkEmitter.getActiveParticleCount() == 0) {
return;
float rotationPower = sqrtf(diffx * diffx + diffy * diffy); }
//std::cout << rotationPower << std::endl; sparkQuad.data.PositionData.clear();
sparkQuad.data.TexCoordData.clear();
float deltaAlpha = rotationPower * delta * M_PI / 500000.f;
const auto& particles = sparkEmitter.getParticles();
Vector3f rotationDirection = { diffy, diffx, 0 }; for (const auto& particle : particles) {
if (!particle.active) continue;
rotationDirection = rotationDirection.normalized();
Vector3f pos = particle.position;
Vector4f rotateQuat = { float size = 0.04f * particle.scale;
rotationDirection.v[0] * sin(deltaAlpha * 0.5f),
rotationDirection.v[1] * sin(deltaAlpha * 0.5f), sparkQuad.data.PositionData.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] });
rotationDirection.v[2] * sin(deltaAlpha * 0.5f), sparkQuad.data.TexCoordData.push_back({ 0.0f, 0.0f });
cos(deltaAlpha * 0.5f) };
sparkQuad.data.PositionData.push_back({ pos.v[0] - size, pos.v[1] + size, pos.v[2] });
Matrix3f rotateMat = QuatToMatrix(rotateQuat); sparkQuad.data.TexCoordData.push_back({ 0.0f, 1.0f });
Environment::shipMatrix = MultMatrixMatrix(Environment::shipMatrix, rotateMat); sparkQuad.data.PositionData.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] });
Environment::inverseShipMatrix = InverseMatrix(Environment::shipMatrix); sparkQuad.data.TexCoordData.push_back({ 1.0f, 1.0f });
} sparkQuad.data.PositionData.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] });
} sparkQuad.data.TexCoordData.push_back({ 0.0f, 0.0f });
if (fabs(Environment::shipVelocity) > 0.01f) sparkQuad.data.PositionData.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] });
{ sparkQuad.data.TexCoordData.push_back({ 1.0f, 1.0f });
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity*delta / 1000.f };
Vector3f velocityDirectionAdjusted = MultMatrixVector(Environment::shipMatrix, velocityDirection); sparkQuad.data.PositionData.push_back({ pos.v[0] + size, pos.v[1] - size, pos.v[2] });
sparkQuad.data.TexCoordData.push_back({ 1.0f, 0.0f });
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted; }
}
if (sparkQuad.data.PositionData.empty()) return;
lastTickCount = newTickCount;
} sparkQuad.RefreshVBO();
}
static const std::string defaultShaderName = "default";
void Game::render() { static const std::string vPositionName = "vPosition";
SDL_GL_MakeCurrent(ZL::Environment::window, glContext); static const std::string vTexCoordName = "vTexCoord";
ZL::CheckGlError(); static const std::string textureUniformName = "Texture";
glClearColor(0.0f, 1.0f, 0.0f, 1.0f); renderer.shaderManager.PushShader(defaultShaderName);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); renderer.RenderUniform1i(textureUniformName, 0);
renderer.EnableVertexAttribArray(vPositionName);
drawScene(); renderer.EnableVertexAttribArray(vTexCoordName);
processTickCount();
renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
SDL_GL_SwapWindow(ZL::Environment::window); static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
} 1, 1000);
void Game::update() {
SDL_Event event; glBindTexture(GL_TEXTURE_2D, sparkTexture->getTexID());
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) { renderer.PushMatrix();
Environment::exitGameLoop = true; renderer.LoadIdentity();
renderer.TranslateMatrix({ 0, 0, -1.0f * Environment::zoom });
}
else if (event.type == SDL_MOUSEBUTTONDOWN) { renderer.DrawVertexRenderStruct(sparkQuad);
// 1. Îáðàáîòêà íàæàòèÿ êíîïêè ìûøè
Environment::tapDownHold = true; renderer.PopMatrix();
// Êîîðäèíàòû íà÷àëüíîãî íàæàòèÿ renderer.PopProjectionMatrix();
Environment::tapDownStartPos.v[0] = event.button.x; renderer.DisableVertexAttribArray(vPositionName);
Environment::tapDownStartPos.v[1] = event.button.y; renderer.DisableVertexAttribArray(vTexCoordName);
// Íà÷àëüíàÿ ïîçèöèÿ òàêæå ñòàíîâèòñÿ òåêóùåé renderer.shaderManager.PopShader();
Environment::tapDownCurrentPos.v[0] = event.button.x; CheckGlError();
Environment::tapDownCurrentPos.v[1] = event.button.y; }
}
else if (event.type == SDL_MOUSEBUTTONUP) { void Game::drawShip()
// 2. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè {
Environment::tapDownHold = false; static const std::string defaultShaderName = "default";
} static const std::string envShaderName = "env";
else if (event.type == SDL_MOUSEMOTION) { static const std::string vPositionName = "vPosition";
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè static const std::string vTexCoordName = "vTexCoord";
if (Environment::tapDownHold) { static const std::string textureUniformName = "Texture";
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
Environment::tapDownCurrentPos.v[0] = event.motion.x; renderer.shaderManager.PushShader(defaultShaderName);
Environment::tapDownCurrentPos.v[1] = event.motion.y; renderer.RenderUniform1i(textureUniformName, 0);
} renderer.EnableVertexAttribArray(vPositionName);
} renderer.EnableVertexAttribArray(vTexCoordName);
else if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f; renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5,
if (event.wheel.y > 0) { static_cast<float>(Environment::width) / static_cast<float>(Environment::height),
Environment::zoom -= zoomstep; 1, 1000);
} renderer.PushMatrix();
else if (event.wheel.y < 0) {
Environment::zoom += zoomstep; renderer.LoadIdentity();
} renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
if (Environment::zoom < zoomstep) {
Environment::zoom = zoomstep;
} glBindTexture(GL_TEXTURE_2D, spaceshipTexture->getTexID());
} renderer.DrawVertexRenderStruct(spaceship);
else if (event.type == SDL_KEYUP)
{ drawEffects();
if (event.key.keysym.sym == SDLK_i)
{ renderer.PopMatrix();
Environment::shipVelocity += 1.f; renderer.PopProjectionMatrix();
} renderer.DisableVertexAttribArray(vPositionName);
if (event.key.keysym.sym == SDLK_k) renderer.DisableVertexAttribArray(vTexCoordName);
{
Environment::shipVelocity -= 1.f; renderer.shaderManager.PopShader();
} CheckGlError();
} }
}
render(); void Game::drawBoxes()
} {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
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);
for (int i = 0; i < boxCoordsArr.size(); i++)
{
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipPosition);
renderer.TranslateMatrix(boxCoordsArr[i].pos);
renderer.RotateMatrix(boxCoordsArr[i].m);
glBindTexture(GL_TEXTURE_2D, boxTexture->getTexID());
renderer.DrawVertexRenderStruct(boxRenderArr[i]);
renderer.PopMatrix();
}
renderer.PopProjectionMatrix();
renderer.DisableVertexAttribArray(vPositionName);
renderer.DisableVertexAttribArray(vTexCoordName);
renderer.shaderManager.PopShader();
CheckGlError();
}
void Game::drawScene() {
static const std::string defaultShaderName = "default";
static const std::string envShaderName = "env";
static const std::string vPositionName = "vPosition";
static const std::string vTexCoordName = "vTexCoord";
static const std::string textureUniformName = "Texture";
glClearColor(0.0f, 0.5f, 1.0f, 1.0f);
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(0, 0, Environment::width, Environment::height);
CheckGlError();
drawCubemap();
drawShip();
drawBoxes();
CheckGlError();
}
void Game::processTickCount() {
if (lastTickCount == 0) {
lastTickCount = SDL_GetTicks64();
return;
}
newTickCount = SDL_GetTicks64();
if (newTickCount - lastTickCount > CONST_TIMER_INTERVAL) {
size_t delta = (newTickCount - lastTickCount > CONST_MAX_TIME_INTERVAL) ?
CONST_MAX_TIME_INTERVAL : newTickCount - lastTickCount;
//gameObjects.updateScene(delta);
sparkEmitter.update(static_cast<float>(delta));
if (Environment::tapDownHold) {
float diffx = Environment::tapDownCurrentPos.v[0] - Environment::tapDownStartPos.v[0];
float diffy = Environment::tapDownCurrentPos.v[1] - Environment::tapDownStartPos.v[1];
if (abs(diffy) > 5.0 || abs(diffx) > 5.0) //threshold
{
float rotationPower = sqrtf(diffx * diffx + diffy * diffy);
//std::cout << rotationPower << std::endl;
float deltaAlpha = rotationPower * delta * M_PI / 500000.f;
Vector3f rotationDirection = { diffy, diffx, 0 };
rotationDirection = rotationDirection.normalized();
Vector4f rotateQuat = {
rotationDirection.v[0] * sin(deltaAlpha * 0.5f),
rotationDirection.v[1] * sin(deltaAlpha * 0.5f),
rotationDirection.v[2] * sin(deltaAlpha * 0.5f),
cos(deltaAlpha * 0.5f) };
Matrix3f rotateMat = QuatToMatrix(rotateQuat);
Environment::shipMatrix = MultMatrixMatrix(Environment::shipMatrix, rotateMat);
Environment::inverseShipMatrix = InverseMatrix(Environment::shipMatrix);
}
}
if (fabs(Environment::shipVelocity) > 0.01f)
{
Vector3f velocityDirection = { 0,0, -Environment::shipVelocity * delta / 1000.f };
Vector3f velocityDirectionAdjusted = MultMatrixVector(Environment::shipMatrix, velocityDirection);
Environment::shipPosition = Environment::shipPosition + velocityDirectionAdjusted;
}
lastTickCount = newTickCount;
}
}
void Game::render() {
SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError();
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene();
processTickCount();
SDL_GL_SwapWindow(ZL::Environment::window);
}
void Game::update() {
SDL_Event event;
while (SDL_PollEvent(&event)) {
if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true;
}
else if (event.type == SDL_MOUSEBUTTONDOWN) {
// 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. Îáðàáîòêà îòïóñêàíèÿ êíîïêè ìûøè
Environment::tapDownHold = false;
}
else if (event.type == SDL_MOUSEMOTION) {
// 3. Îáðàáîòêà ïåðåìåùåíèÿ ìûøè
if (Environment::tapDownHold) {
// Îáíîâëåíèå òåêóùåé ïîçèöèè, åñëè êíîïêà óäåðæèâàåòñÿ
Environment::tapDownCurrentPos.v[0] = event.motion.x;
Environment::tapDownCurrentPos.v[1] = event.motion.y;
}
}
else if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f;
if (event.wheel.y > 0) {
Environment::zoom -= zoomstep;
}
else if (event.wheel.y < 0) {
Environment::zoom += zoomstep;
}
if (Environment::zoom < zoomstep) {
Environment::zoom = zoomstep;
}
}
else if (event.type == SDL_KEYUP)
{
if (event.key.keysym.sym == SDLK_i)
{
Environment::shipVelocity += 1.f;
}
if (event.key.keysym.sym == SDLK_k)
{
Environment::shipVelocity -= 1.f;
}
}
}
render();
}
} // namespace ZL } // namespace ZL

82
Game.h
View File

@ -9,59 +9,59 @@
namespace ZL { namespace ZL {
struct BoxCoords struct BoxCoords
{ {
Vector3f pos; Vector3f pos;
Matrix3f m; Matrix3f m;
}; };
class Game { class Game {
public: public:
Game(); Game();
~Game(); ~Game();
void setup(); void setup();
void update(); void update();
void render(); void render();
bool shouldExit() const { return Environment::exitGameLoop; } bool shouldExit() const { return Environment::exitGameLoop; }
private: private:
void processTickCount(); void processTickCount();
void drawScene(); void drawScene();
void drawCubemap(); void drawCubemap();
void drawEffects(); void drawEffects();
void drawShip(); void drawShip();
void drawBoxes(); void drawBoxes();
SDL_Window* window; SDL_Window* window;
SDL_GLContext glContext; SDL_GLContext glContext;
Renderer renderer; Renderer renderer;
size_t newTickCount; size_t newTickCount;
size_t lastTickCount; size_t lastTickCount;
static const size_t CONST_TIMER_INTERVAL = 10; static const size_t CONST_TIMER_INTERVAL = 10;
static const size_t CONST_MAX_TIME_INTERVAL = 1000; static const size_t CONST_MAX_TIME_INTERVAL = 1000;
std::shared_ptr<Texture> sparkTexture; std::shared_ptr<Texture> sparkTexture;
std::shared_ptr<Texture> spaceshipTexture; std::shared_ptr<Texture> spaceshipTexture;
std::shared_ptr<Texture> cubemapTexture; std::shared_ptr<Texture> cubemapTexture;
VertexDataStruct spaceshipBase; VertexDataStruct spaceshipBase;
VertexRenderStruct spaceship; VertexRenderStruct spaceship;
VertexRenderStruct cubemap; VertexRenderStruct cubemap;
std::shared_ptr<Texture> boxTexture; std::shared_ptr<Texture> boxTexture;
VertexDataStruct boxBase; VertexDataStruct boxBase;
std::vector<BoxCoords> boxCoordsArr; std::vector<BoxCoords> boxCoordsArr;
std::vector<VertexRenderStruct> boxRenderArr; std::vector<VertexRenderStruct> boxRenderArr;
//VertexRenderStruct singleSpark; //VertexRenderStruct singleSpark;
SparkEmitter sparkEmitter; SparkEmitter sparkEmitter;
VertexRenderStruct sparkQuad; VertexRenderStruct sparkQuad;
}; };
} // namespace ZL } // namespace ZL

View File

@ -5,143 +5,207 @@
namespace ZL { namespace ZL {
SparkEmitter::SparkEmitter() SparkEmitter::SparkEmitter()
: emissionRate(100.0f), isActive(true) { : emissionRate(100.0f), isActive(true), buffersDirty(true), maxParticles(200) {
particles.resize(200); particles.resize(maxParticles);
lastEmissionTime = std::chrono::steady_clock::now(); positionBuffer.resize(maxParticles * 4, 0.0f);
} texCoordBuffer.resize(maxParticles * 2, 0.0f);
lastEmissionTime = std::chrono::steady_clock::now();
}
SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate) SparkEmitter::SparkEmitter(const std::vector<Vector3f>& positions, float rate)
: emissionPoints(positions), emissionRate(rate), isActive(true) { : emissionPoints(positions), emissionRate(rate), isActive(true),
particles.resize(positions.size() * 100); buffersDirty(true), maxParticles(positions.size() * 100) {
lastEmissionTime = std::chrono::steady_clock::now(); particles.resize(maxParticles);
} positionBuffer.resize(maxParticles * 4, 0.0f);
texCoordBuffer.resize(maxParticles * 2, 0.0f);
lastEmissionTime = std::chrono::steady_clock::now();
}
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) { void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
emissionPoints = positions; emissionPoints = positions;
particles.resize(positions.size() * 100); maxParticles = positions.size() * 100;
} particles.resize(maxParticles);
positionBuffer.resize(maxParticles * 4, 0.0f);
texCoordBuffer.resize(maxParticles * 2, 0.0f);
buffersDirty = true;
}
void SparkEmitter::setEmissionRate(float rateMs) { void SparkEmitter::updateBuffers() {
emissionRate = rateMs; if (!buffersDirty) return;
}
void SparkEmitter::setActive(bool active) { size_t bufferIndex = 0;
isActive = active; for (const auto& particle : particles) {
} if (particle.active) {
positionBuffer[bufferIndex * 4] = particle.position.v[0];
positionBuffer[bufferIndex * 4 + 1] = particle.position.v[1];
positionBuffer[bufferIndex * 4 + 2] = particle.position.v[2];
positionBuffer[bufferIndex * 4 + 3] = particle.scale;
void SparkEmitter::update(float deltaTimeMs) { texCoordBuffer[bufferIndex * 2] = 0.0f;
auto currentTime = std::chrono::steady_clock::now(); texCoordBuffer[bufferIndex * 2 + 1] = 0.0f;
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count();
if (isActive && elapsed >= emissionRate) { bufferIndex++;
emit(); }
lastEmissionTime = currentTime; }
}
for (auto& particle : particles) { for (size_t i = bufferIndex; i < maxParticles; i++) {
if (particle.active) { positionBuffer[i * 4] = 0.0f;
particle.position.v[0] += particle.velocity.v[0] * deltaTimeMs / 1000.0f; positionBuffer[i * 4 + 1] = 0.0f;
particle.position.v[1] += particle.velocity.v[1] * deltaTimeMs / 1000.0f; positionBuffer[i * 4 + 2] = 0.0f;
particle.position.v[2] += particle.velocity.v[2] * deltaTimeMs / 1000.0f; positionBuffer[i * 4 + 3] = 0.0f;
texCoordBuffer[i * 2] = 0.0f;
texCoordBuffer[i * 2 + 1] = 0.0f;
}
particle.lifeTime += deltaTimeMs; buffersDirty = false;
}
if (particle.lifeTime >= particle.maxLifeTime) { void SparkEmitter::update(float deltaTimeMs) {
particle.active = false; auto currentTime = std::chrono::steady_clock::now();
} auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count();
float lifeRatio = particle.lifeTime / particle.maxLifeTime; if (isActive && elapsed >= emissionRate) {
particle.scale = 1.0f - lifeRatio * 0.8f; emit();
} lastEmissionTime = currentTime;
} }
}
void SparkEmitter::emit() { bool anyChanged = false;
if (emissionPoints.empty()) return; for (auto& particle : particles) {
if (particle.active) {
Vector3f oldPosition = particle.position;
for (int i = 0; i < emissionPoints.size(); ++i) { particle.position.v[0] += particle.velocity.v[0] * deltaTimeMs / 1000.0f;
bool particleFound = false; particle.position.v[1] += particle.velocity.v[1] * deltaTimeMs / 1000.0f;
particle.position.v[2] += particle.velocity.v[2] * deltaTimeMs / 1000.0f;
for (auto& particle : particles) { particle.lifeTime += deltaTimeMs;
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()) { if (particle.lifeTime >= particle.maxLifeTime) {
size_t oldestIndex = 0; particle.active = false;
float maxLifeTime = 0; anyChanged = true;
}
else {
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
for (size_t j = 0; j < particles.size(); ++j) { if (oldPosition.v[0] != particle.position.v[0] ||
if (particles[j].lifeTime > maxLifeTime) { oldPosition.v[1] != particle.position.v[1] ||
maxLifeTime = particles[j].lifeTime; oldPosition.v[2] != particle.position.v[2]) {
oldestIndex = j; anyChanged = true;
} }
} }
}
}
initParticle(particles[oldestIndex], i); if (anyChanged) {
particles[oldestIndex].active = true; buffersDirty = true;
particles[oldestIndex].lifeTime = 0; }
particles[oldestIndex].position = emissionPoints[i]; }
particles[oldestIndex].emitterIndex = i;
}
}
}
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) { void SparkEmitter::emit() {
particle.velocity = getRandomVelocity(emitterIndex); if (emissionPoints.empty()) return;
particle.scale = 1.0f; bool emitted = false;
particle.maxLifeTime = 800.0f + (rand() % 400); // От 800 до 1200 мс
particle.emitterIndex = emitterIndex;
}
Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) { for (int i = 0; i < emissionPoints.size(); ++i) {
static std::random_device rd; bool particleFound = false;
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); for (auto& particle : particles) {
float speed = speedDist(gen); if (!particle.active) {
float zSpeed = zSpeedDist(gen); initParticle(particle, i);
particle.active = true;
particle.lifeTime = 0;
particle.position = emissionPoints[i];
particle.emitterIndex = i;
particleFound = true;
emitted = true;
break;
}
}
if (emitterIndex == 0) { if (!particleFound && !particles.empty()) {
return Vector3f{ size_t oldestIndex = 0;
cosf(angle) * speed - 0.3f, float maxLifeTime = 0;
sinf(angle) * speed,
zSpeed
};
}
else {
return Vector3f{
cosf(angle) * speed + 0.3f,
sinf(angle) * speed,
zSpeed
};
}
}
const std::vector<SparkParticle>& SparkEmitter::getParticles() const { for (size_t j = 0; j < particles.size(); ++j) {
return particles; if (particles[j].lifeTime > maxLifeTime) {
} maxLifeTime = particles[j].lifeTime;
oldestIndex = j;
}
}
size_t SparkEmitter::getActiveParticleCount() const { initParticle(particles[oldestIndex], i);
size_t count = 0; particles[oldestIndex].active = true;
for (const auto& particle : particles) { particles[oldestIndex].lifeTime = 0;
if (particle.active) { particles[oldestIndex].position = emissionPoints[i];
count++; particles[oldestIndex].emitterIndex = i;
} emitted = true;
} }
return count; }
}
if (emitted) {
buffersDirty = true;
}
}
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
particle.velocity = getRandomVelocity(emitterIndex);
particle.scale = 1.0f;
particle.maxLifeTime = 800.0f + (rand() % 400);
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;
}
size_t SparkEmitter::getActiveParticleBufferIndex(size_t particleIndex) const {
size_t activeIndex = 0;
for (size_t i = 0; i <= particleIndex; i++) {
if (particles[i].active) {
if (i == particleIndex) return activeIndex;
activeIndex++;
}
}
return 0;
}
} // namespace ZL } // namespace ZL

View File

@ -6,45 +6,56 @@
namespace ZL { namespace ZL {
struct SparkParticle { struct SparkParticle {
Vector3f position; Vector3f position;
Vector3f velocity; Vector3f velocity;
float scale; float scale;
float lifeTime; float lifeTime;
float maxLifeTime; float maxLifeTime;
bool active; bool active;
int emitterIndex; int emitterIndex;
Vector2f texCoord;
SparkParticle() : position({ 0,0,0 }), velocity({ 0,0,0 }), scale(1.0f), SparkParticle() : position({ 0,0,0 }), velocity({ 0,0,0 }), scale(1.0f),
lifeTime(0), maxLifeTime(1000.0f), active(false), emitterIndex(0) { lifeTime(0), maxLifeTime(1000.0f), active(false), emitterIndex(0) {
} }
}; };
class SparkEmitter { class SparkEmitter {
private: private:
std::vector<SparkParticle> particles; std::vector<SparkParticle> particles;
std::vector<Vector3f> emissionPoints; std::vector<Vector3f> emissionPoints;
std::chrono::steady_clock::time_point lastEmissionTime; std::chrono::steady_clock::time_point lastEmissionTime;
float emissionRate; float emissionRate;
bool isActive; bool isActive;
public: std::vector<float> positionBuffer;
SparkEmitter(); std::vector<float> texCoordBuffer;
SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f); bool buffersDirty;
int maxParticles;
void setEmissionPoints(const std::vector<Vector3f>& positions); public:
void setEmissionRate(float rateMs); SparkEmitter();
void setActive(bool active); SparkEmitter(const std::vector<Vector3f>& positions, float rate = 100.0f);
void update(float deltaTimeMs); void setEmissionPoints(const std::vector<Vector3f>& positions);
void emit();
const std::vector<SparkParticle>& getParticles() const; void update(float deltaTimeMs);
size_t getActiveParticleCount() const; void emit();
private: const std::vector<SparkParticle>& getParticles() const;
void initParticle(SparkParticle& particle, int emitterIndex); size_t getActiveParticleCount() const;
Vector3f getRandomVelocity(int emitterIndex);
}; const float* getPositionBuffer() const { return positionBuffer.data(); }
const float* getTexCoordBuffer() const { return texCoordBuffer.data(); }
size_t getBufferSize() const { return positionBuffer.size() / 4; }
void updateBuffers();
size_t getActiveParticleBufferIndex(size_t particleIndex) const;
private:
void initParticle(SparkParticle& particle, int emitterIndex);
Vector3f getRandomVelocity(int emitterIndex);
};
} // namespace ZL } // namespace ZL