#include "SparkEmitter.h" #include #include #include "OpenGlExtensions.h" namespace ZL { SparkEmitter::SparkEmitter() : emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200) { particles.resize(maxParticles); drawPositions.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6); lastEmissionTime = std::chrono::steady_clock::now(); sparkQuad.data = VertexDataStruct(); } SparkEmitter::SparkEmitter(const std::vector& positions, float rate) : emissionPoints(positions), emissionRate(rate), isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100) { particles.resize(maxParticles); drawPositions.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6); lastEmissionTime = std::chrono::steady_clock::now(); sparkQuad.data = VertexDataStruct(); } SparkEmitter::SparkEmitter(const std::vector& positions, std::shared_ptr tex, float rate) : emissionPoints(positions), texture(tex), emissionRate(rate), isActive(true), drawDataDirty(true), maxParticles(positions.size() * 100) { particles.resize(maxParticles); drawPositions.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6); lastEmissionTime = std::chrono::steady_clock::now(); sparkQuad.data = VertexDataStruct(); } void SparkEmitter::setTexture(std::shared_ptr tex) { texture = tex; } void SparkEmitter::prepareDrawData() { if (!drawDataDirty) return; drawPositions.clear(); drawTexCoords.clear(); if (getActiveParticleCount() == 0) { drawDataDirty = false; return; } std::vector> sortedParticles; sortedParticles.reserve(getActiveParticleCount()); for (const auto& particle : particles) { if (particle.active) { sortedParticles.push_back({ &particle, particle.position.v[2] }); } } std::sort(sortedParticles.begin(), sortedParticles.end(), [](const auto& a, const auto& b) { return a.second > b.second; }); for (const auto& [particlePtr, depth] : sortedParticles) { const auto& particle = *particlePtr; Vector3f pos = particle.position; float size = 0.04f * particle.scale; drawPositions.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] }); drawTexCoords.push_back({ 0.0f, 0.0f }); drawPositions.push_back({ pos.v[0] - size, pos.v[1] + size, pos.v[2] }); drawTexCoords.push_back({ 0.0f, 1.0f }); drawPositions.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] }); drawTexCoords.push_back({ 1.0f, 1.0f }); drawPositions.push_back({ pos.v[0] - size, pos.v[1] - size, pos.v[2] }); drawTexCoords.push_back({ 0.0f, 0.0f }); drawPositions.push_back({ pos.v[0] + size, pos.v[1] + size, pos.v[2] }); drawTexCoords.push_back({ 1.0f, 1.0f }); drawPositions.push_back({ pos.v[0] + size, pos.v[1] - size, pos.v[2] }); drawTexCoords.push_back({ 1.0f, 0.0f }); } drawDataDirty = false; } void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) { if (getActiveParticleCount() == 0) { return; } if (!texture) { return; } prepareDrawData(); if (drawPositions.empty()) { return; } sparkQuad.data.PositionData = drawPositions; sparkQuad.data.TexCoordData = drawTexCoords; sparkQuad.RefreshVBO(); static const std::string defaultShaderName = "default"; 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); float aspectRatio = static_cast(screenWidth) / static_cast(screenHeight); renderer.PushPerspectiveProjectionMatrix(1.0 / 1.5, aspectRatio, 1, 1000); glBindTexture(GL_TEXTURE_2D, texture->getTexID()); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE);// Аддитивное смешивание для эффекта свечения renderer.PushMatrix(); renderer.LoadIdentity(); renderer.TranslateMatrix({ 0, 0, -1.0f * zoom }); renderer.DrawVertexRenderStruct(sparkQuad); renderer.PopMatrix(); renderer.PopProjectionMatrix(); glDisable(GL_BLEND); renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vTexCoordName); renderer.shaderManager.PopShader(); } 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; drawDataDirty = true; } bool anyChanged = false; for (auto& particle : particles) { if (particle.active) { Vector3f oldPosition = particle.position; float oldScale = particle.scale; 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; anyChanged = true; } else { float lifeRatio = particle.lifeTime / particle.maxLifeTime; particle.scale = 1.0f - lifeRatio * 0.8f; if (oldPosition.v[0] != particle.position.v[0] || oldPosition.v[1] != particle.position.v[1] || oldPosition.v[2] != particle.position.v[2] || oldScale != particle.scale) { anyChanged = true; } } } } if (anyChanged) { drawDataDirty = true; } } void SparkEmitter::emit() { if (emissionPoints.empty()) return; bool emitted = false; 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; emitted = 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; emitted = true; } } if (emitted) { drawDataDirty = true; } } void SparkEmitter::setEmissionPoints(const std::vector& positions) { emissionPoints = positions; maxParticles = positions.size() * 100; particles.resize(maxParticles); drawDataDirty = 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& 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