diff --git a/CMakeLists.txt b/CMakeLists.txt index 0388759..5462c7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -436,6 +436,8 @@ add_executable(space-game001 OpenGlExtensions.h Utils.cpp Utils.h + SparkEmitter.cpp + SparkEmitter.h ) # Установка проекта по умолчанию для Visual Studio diff --git a/Game.cpp b/Game.cpp index d19757a..7d9b83e 100755 --- a/Game.cpp +++ b/Game.cpp @@ -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 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 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 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(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(Environment::width) / static_cast(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(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; } diff --git a/Game.h b/Game.h index b97d62e..a6bd0fb 100755 --- a/Game.h +++ b/Game.h @@ -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 sparkTexture; std::shared_ptr spaceshipTexture; std::shared_ptr cubemapTexture; VertexDataStruct spaceshipBase; @@ -56,6 +59,9 @@ private: std::vector boxCoordsArr; std::vector boxRenderArr; + //VertexRenderStruct singleSpark; + SparkEmitter sparkEmitter; + VertexRenderStruct sparkQuad; }; } // namespace ZL \ No newline at end of file diff --git a/SparkEmitter.cpp b/SparkEmitter.cpp new file mode 100644 index 0000000..66ec93d --- /dev/null +++ b/SparkEmitter.cpp @@ -0,0 +1,147 @@ +#include "SparkEmitter.h" +#include +#include +#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& 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& 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( + 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& 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 \ No newline at end of file diff --git a/SparkEmitter.h b/SparkEmitter.h new file mode 100644 index 0000000..b058c65 --- /dev/null +++ b/SparkEmitter.h @@ -0,0 +1,50 @@ +#pragma once + +#include "ZLMath.h" +#include +#include + +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 particles; + std::vector emissionPoints; + std::chrono::steady_clock::time_point lastEmissionTime; + float emissionRate; + bool isActive; + + public: + SparkEmitter(); + SparkEmitter(const std::vector& positions, float rate = 100.0f); + + void setEmissionPoints(const std::vector& positions); + void setEmissionRate(float rateMs); + void setActive(bool active); + + void update(float deltaTimeMs); + void emit(); + + const std::vector& getParticles() const; + size_t getActiveParticleCount() const; + + private: + void initParticle(SparkParticle& particle, int emitterIndex); + Vector3f getRandomVelocity(int emitterIndex); + }; + +} // namespace ZL \ No newline at end of file diff --git a/resources/spark.png b/resources/spark.png new file mode 100644 index 0000000..4ed63fd --- /dev/null +++ b/resources/spark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8885a2b593c5dc0e8ac5cf5204bf3d82eb45ad6dae9b56e2bc88a41e61144c89 +size 2222 diff --git a/shaders/default_desktop.fragment b/shaders/default_desktop.fragment index 2470304..ae06b6a 100644 --- a/shaders/default_desktop.fragment +++ b/shaders/default_desktop.fragment @@ -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; } \ No newline at end of file