space-game001/SparkEmitter.cpp
2025-12-17 17:05:40 +06:00

463 lines
14 KiB
C++

#include "SparkEmitter.h"
#include <random>
#include <cmath>
#include "OpenGlExtensions.h"
#include <fstream>
#include "external/nlohmann/json.hpp"
#include <iostream>
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<Vector3f>& 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<Vector3f>& positions,
std::shared_ptr<Texture> 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<Texture> tex) {
texture = tex;
}
void SparkEmitter::prepareDrawData() {
if (!drawDataDirty) return;
drawPositions.clear();
drawTexCoords.clear();
if (getActiveParticleCount() == 0) {
drawDataDirty = false;
return;
}
std::vector<std::pair<const SparkParticle*, float>> 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<float>(screenWidth) / static_cast<float>(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<std::chrono::milliseconds>(
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<Vector3f>& 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<float> scaleDist(scaleRange.min, scaleRange.max);
particle.scale = scaleDist(gen);
std::uniform_real_distribution<float> 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<float> angleDist(0.0f, 2.0f * static_cast<float>(M_PI));
std::uniform_real_distribution<float> speedDist(speedRange.min, speedRange.max);
std::uniform_real_distribution<float> 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<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;
}
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<float>();
}
if (j.contains("maxParticles")) {
maxParticles = j["maxParticles"].get<int>();
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<float>();
std::cout << "Particle size: " << particleSize << std::endl;
}
if (j.contains("biasX")) {
biasX = j["biasX"].get<float>();
std::cout << "Bias X: " << biasX << std::endl;
}
// emissionPoints
std::vector<Vector3f> 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<float>(), arr[1].get<float>(), arr[2].get<float>() });
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<int>();
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<float> dx(minArr[0].get<float>(), maxArr[0].get<float>());
std::uniform_real_distribution<float> dy(minArr[1].get<float>(), maxArr[1].get<float>());
std::uniform_real_distribution<float> dz(minArr[2].get<float>(), maxArr[2].get<float>());
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<float>();
speedRange.max = a[1].get<float>();
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<float>();
zSpeedRange.max = a[1].get<float>();
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<float>();
scaleRange.max = a[1].get<float>();
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<float>();
lifeTimeRange.max = a[1].get<float>();
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::string>();
std::cout << "Loading texture: " << texPath << std::endl;
try {
auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
texture = std::make_shared<Texture>(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::string>();
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>();
std::string f = j["fragmentShader"].get<std::string>();
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