#include "SparkEmitter.h" #include #include #include "OpenGlExtensions.h" #include #include "external/nlohmann/json.hpp" #include namespace ZL { using json = nlohmann::json; SparkEmitter::SparkEmitter() : emissionRate(100.0f), isActive(true), drawDataDirty(true), maxParticles(200), shaderProgramName("default"), particleSize(0.04f), biasX(0.3f) { 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), shaderProgramName("default"), particleSize(0.04f), biasX(0.3f) { 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), shaderProgramName("default"), particleSize(0.04f), biasX(0.3f) { 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 = particleSize * 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 vPositionName = "vPosition"; static const std::string vTexCoordName = "vTexCoord"; static const std::string textureUniformName = "Texture"; renderer.shaderManager.PushShader(shaderProgramName); 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; drawDataDirty = true; } void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) { particle.velocity = getRandomVelocity(emitterIndex); static std::random_device rd; static std::mt19937 gen(rd()); std::uniform_real_distribution scaleDist(scaleRange.min, scaleRange.max); particle.scale = scaleDist(gen); std::uniform_real_distribution lifeDist(lifeTimeRange.min, lifeTimeRange.max); particle.maxLifeTime = lifeDist(gen); particle.emitterIndex = emitterIndex; } Vector3f SparkEmitter::getRandomVelocity(int emitterIndex) { static std::random_device rd; static std::mt19937 gen(rd()); std::uniform_real_distribution angleDist(0.0f, 2.0f * static_cast(M_PI)); std::uniform_real_distribution speedDist(speedRange.min, speedRange.max); std::uniform_real_distribution zSpeedDist(zSpeedRange.min, zSpeedRange.max); float angle = angleDist(gen); float speed = speedDist(gen); float zSpeed = zSpeedDist(gen); // Теперь biasX берется из JSON if (emitterIndex == 0) { return Vector3f{ cosf(angle) * speed - biasX, sinf(angle) * speed, zSpeed }; } else { return Vector3f{ cosf(angle) * speed + biasX, 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; } bool SparkEmitter::loadFromJsonFile(const std::string& path, Renderer& renderer, const std::string& zipFile) { std::cout << "Loading spark config from: " << path << std::endl; std::ifstream in(path); if (!in.is_open()) { std::cerr << "Failed to open JSON file: " << path << std::endl; return false; } json j; try { in >> j; std::cout << "JSON parsed successfully" << std::endl; } catch (const std::exception& e) { std::cerr << "JSON parse error: " << e.what() << std::endl; return false; } std::cout << "JSON content: " << j.dump(2) << std::endl; // Основные параметры if (j.contains("emissionRate")) { emissionRate = j["emissionRate"].get(); } if (j.contains("maxParticles")) { maxParticles = j["maxParticles"].get(); particles.resize(maxParticles); drawPositions.reserve(maxParticles * 6); drawTexCoords.reserve(maxParticles * 6); std::cout << "Max particles: " << maxParticles << std::endl; } if (j.contains("particleSize")) { particleSize = j["particleSize"].get(); std::cout << "Particle size: " << particleSize << std::endl; } if (j.contains("biasX")) { biasX = j["biasX"].get(); std::cout << "Bias X: " << biasX << std::endl; } // emissionPoints std::vector points; if (j.contains("emissionPoints") && j["emissionPoints"].is_array()) { for (const auto& el : j["emissionPoints"]) { if (el.contains("position") && el["position"].is_array()) { auto arr = el["position"]; points.push_back(Vector3f{ arr[0].get(), arr[1].get(), arr[2].get() }); std::cout << "Fixed point: [" << arr[0] << ", " << arr[1] << ", " << arr[2] << "]" << std::endl; } else if (el.contains("positionRange") && el["positionRange"].is_object()) { auto pr = el["positionRange"]; auto minArr = pr["min"]; auto maxArr = pr["max"]; int count = 1; if (el.contains("count")) count = el["count"].get(); std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dx(minArr[0].get(), maxArr[0].get()); std::uniform_real_distribution dy(minArr[1].get(), maxArr[1].get()); std::uniform_real_distribution dz(minArr[2].get(), maxArr[2].get()); for (int k = 0; k < count; ++k) { Vector3f randomPoint{ dx(gen), dy(gen), dz(gen) }; points.push_back(randomPoint); std::cout << "Random point " << k + 1 << ": [" << randomPoint.v[0] << ", " << randomPoint.v[1] << ", " << randomPoint.v[2] << "]" << std::endl; } } } if (!points.empty()) { setEmissionPoints(points); std::cout << "Total emission points: " << emissionPoints.size() << std::endl; } } // Ranges if (j.contains("speedRange") && j["speedRange"].is_array()) { auto a = j["speedRange"]; speedRange.min = a[0].get(); speedRange.max = a[1].get(); std::cout << "Speed range: [" << speedRange.min << ", " << speedRange.max << "]" << std::endl; } if (j.contains("zSpeedRange") && j["zSpeedRange"].is_array()) { auto a = j["zSpeedRange"]; zSpeedRange.min = a[0].get(); zSpeedRange.max = a[1].get(); std::cout << "Z speed range: [" << zSpeedRange.min << ", " << zSpeedRange.max << "]" << std::endl; } if (j.contains("scaleRange") && j["scaleRange"].is_array()) { auto a = j["scaleRange"]; scaleRange.min = a[0].get(); scaleRange.max = a[1].get(); std::cout << "Scale range: [" << scaleRange.min << ", " << scaleRange.max << "]" << std::endl; } if (j.contains("lifeTimeRange") && j["lifeTimeRange"].is_array()) { auto a = j["lifeTimeRange"]; lifeTimeRange.min = a[0].get(); lifeTimeRange.max = a[1].get(); std::cout << "Life time range: [" << lifeTimeRange.min << ", " << lifeTimeRange.max << "]" << std::endl; } // texture if (j.contains("texture") && j["texture"].is_string()) { std::string texPath = j["texture"].get(); std::cout << "Loading texture: " << texPath << std::endl; try { auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str()); texture = std::make_shared(texData); std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl; } catch (const std::exception& e) { std::cerr << "Texture load error: " << e.what() << std::endl; return false; } } else { std::cout << "No texture specified in JSON" << std::endl; return false; } // shaders if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) { shaderProgramName = j["shaderProgramName"].get(); std::cout << "Shader program name: " << shaderProgramName << std::endl; } if (j.contains("vertexShader") && j.contains("fragmentShader") && j["vertexShader"].is_string() && j["fragmentShader"].is_string()) { std::string v = j["vertexShader"].get(); std::string f = j["fragmentShader"].get(); std::cout << "Loading shaders - vertex: " << v << ", fragment: " << f << std::endl; try { renderer.shaderManager.AddShaderFromFiles(shaderProgramName, v, f, zipFile.c_str()); std::cout << "Shaders loaded successfully" << std::endl; } catch (const std::exception& e) { std::cerr << "Shader load error: " << e.what() << std::endl; std::cerr << "Using default shader" << std::endl; shaderProgramName = "default"; } } drawDataDirty = true; std::cout << "SparkEmitter configuration loaded successfully!" << std::endl; return true; } } // namespace ZL