space-game001/src/SparkEmitter.cpp
2026-03-08 19:07:02 +03:00

633 lines
20 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "SparkEmitter.h"
#include <random>
#include <cmath>
//#include "renderer/OpenGlExtensions.h"
#include <fstream>
#include "external/nlohmann/json.hpp"
#include <iostream>
#include "Environment.h"
#include <stdexcept>
#include "utils/Utils.h"
#include "GameConstants.h"
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), configured(false),
useWorldSpace(false) {
particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6);
lastEmissionTime = std::chrono::steady_clock::now();
sparkQuad.data = VertexDataStruct();
}
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
zSpeedRange(copyFrom.zSpeedRange),
scaleRange(copyFrom.scaleRange),
lifeTimeRange(copyFrom.lifeTimeRange),
shaderProgramName(copyFrom.shaderProgramName),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
{
}
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), configured(false),
useWorldSpace(false) {
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), configured(false),
useWorldSpace(false) {
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(bool withRotation) {
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) {
Vector3f posCam;
if (withRotation) {
Vector3f rel = particle.position - Environment::shipState.position;
posCam = Environment::inverseShipMatrix * rel;
}
else {
posCam = particle.position;
}
sortedParticles.push_back({ &particle, posCam(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 posCam;
if (useWorldSpace) {
Vector3f rel = particle.position - Environment::shipState.position;
posCam = Environment::inverseShipMatrix * rel;
}
else {
posCam = particle.position;
}
float size = particleSize * particle.scale;
if (withRotation)
{
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam);
drawTexCoords.push_back({ 0.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, -size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 0.0f });
}
else
{
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) - size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 1.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 0.0f });
}
}
drawDataDirty = false;
}
void SparkEmitter::prepareForDraw(bool withRotation) {
if (!configured) return;
prepareDrawData(withRotation);
if (!drawPositions.empty()) {
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
}
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight)
{
draw(renderer, zoom, screenWidth, screenHeight, true);
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 1!");
}
if (getActiveParticleCount() == 0) {
return;
}
if (!texture) {
throw std::runtime_error("Failed to load spark emitter config file 2!");
}
//prepareDrawData(withRotation);
if (drawPositions.empty()) {
return;
}
/*
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
*/
renderer.shaderManager.PushShader(shaderProgramName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.SetMatrix();
glBindTexture(GL_TEXTURE_2D, texture->getTexID());
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
//renderer.PushMatrix();
renderer.DrawVertexRenderStruct(sparkQuad);
//renderer.PopMatrix();
glDisable(GL_BLEND);
renderer.shaderManager.PopShader();
}
void SparkEmitter::update(float deltaTimeMs) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 3!");
}
auto currentTime = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
currentTime - lastEmissionTime).count();
if (isActive && elapsed >= static_cast<long long>(emissionRate)) {
int emitCount = static_cast<int>(elapsed / emissionRate);
float elapsedF = static_cast<float>(elapsed);
for (int e = 0; e < emitCount; ++e) {
// e=0 — самая старая эмиссия, e=emitCount-1 — самая свежая
float ageMs = static_cast<float>(emitCount - 1 - e) * emissionRate;
// lerpT=0 → текущая позиция (новейшая), lerpT=1 → предыдущая (самая старая)
float lerpT = (elapsedF > 0.0f) ? min(ageMs / elapsedF, 1.0f) : 0.0f;
emit(ageMs, lerpT);
}
lastEmissionTime += std::chrono::milliseconds(
static_cast<long long>(emitCount * emissionRate));
drawDataDirty = true;
}
bool anyChanged = false;
for (auto& particle : particles) {
if (particle.active) {
Vector3f oldPosition = particle.position;
float oldScale = particle.scale;
particle.position(0) += particle.velocity(0) * deltaTimeMs / 1000.0f;
particle.position(1) += particle.velocity(1) * deltaTimeMs / 1000.0f;
particle.position(2) += particle.velocity(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(0) != particle.position(0) ||
oldPosition(1) != particle.position(1) ||
oldPosition(2) != particle.position(2) ||
oldScale != particle.scale) {
anyChanged = true;
}
}
}
}
if (anyChanged) {
drawDataDirty = true;
}
}
void SparkEmitter::emit(float ageMs, float lerpT) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 4!");
}
if (emissionPoints.empty()) return;
bool emitted = false;
auto applyAge = [](SparkParticle& particle, float age) {
if (age <= 0.0f) return;
particle.position(0) += particle.velocity(0) * age / 1000.0f;
particle.position(1) += particle.velocity(1) * age / 1000.0f;
particle.position(2) += particle.velocity(2) * age / 1000.0f;
particle.lifeTime = age;
if (particle.lifeTime >= particle.maxLifeTime) {
particle.active = false;
} else {
float lifeRatio = particle.lifeTime / particle.maxLifeTime;
particle.scale = 1.0f - lifeRatio * 0.8f;
}
};
// Вычисляем стартовую позицию с интерполяцией между prev и current
// lerpT=0 → текущая позиция (новейшая эмиссия), lerpT=1 → предыдущая (самая старая)
bool canInterp = (prevEmissionPoints.size() == emissionPoints.size());
for (int i = 0; i < (int)emissionPoints.size(); ++i) {
Vector3f birthPos = emissionPoints[i];
if (canInterp && lerpT > 0.0f) {
birthPos = emissionPoints[i] * (1.0f - lerpT) + prevEmissionPoints[i] * lerpT;
}
bool particleFound = false;
for (auto& particle : particles) {
if (!particle.active) {
initParticle(particle, i);
particle.active = true;
particle.lifeTime = 0;
particle.position = birthPos;
particle.emitterIndex = i;
applyAge(particle, ageMs);
particleFound = true;
emitted = true;
break;
}
}
if (!particleFound && !particles.empty()) {
size_t oldestIndex = 0;
float maxLifeRatio = 0;
for (size_t j = 0; j < particles.size(); ++j) {
if (particles[j].lifeTime > maxLifeRatio) {
maxLifeRatio = particles[j].lifeTime;
oldestIndex = j;
}
}
initParticle(particles[oldestIndex], i);
particles[oldestIndex].active = true;
particles[oldestIndex].lifeTime = 0;
particles[oldestIndex].position = birthPos;
particles[oldestIndex].emitterIndex = i;
applyAge(particles[oldestIndex], ageMs);
emitted = true;
}
}
if (emitted) {
drawDataDirty = true;
}
}
void SparkEmitter::setEmissionPoints(const std::vector<Vector3f>& positions) {
prevEmissionPoints = emissionPoints;
emissionPoints = positions;
drawDataDirty = true;
}
void SparkEmitter::resetEmissionPoints(const std::vector<Vector3f>& positions)
{
prevEmissionPoints.clear();
emissionPoints = positions;
drawDataDirty = true;
}
void SparkEmitter::initParticle(SparkParticle& particle, int emitterIndex) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 5!");
}
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) {
if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 6!");
}
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::string content;
try {
if (zipFile.empty()) {
content = readTextFile(path);
}
else {
auto buf = readFileFromZIP(path, zipFile);
if (buf.empty()) {
std::cerr << "Failed to read JSON from zip: " << path << " in " << zipFile << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 7!");
}
content.assign(buf.begin(), buf.end());
}
}
catch (const std::exception& e) {
std::cerr << "Failed to open JSON file: " << path << " : " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 8!");
}
json j;
try {
j = json::parse(content);
std::cout << "JSON parsed successfully" << std::endl;
}
catch (const std::exception& e) {
std::cerr << "JSON parse error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 9!");
}
std::cout << "JSON content: " << j.dump(2) << std::endl;
auto requireKey = [&](const std::string& key) {
if (!j.contains(key)) {
std::cerr << "Missing required key in spark JSON: " << key << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 9!");
}
};
requireKey("emissionPoints");
requireKey("texture");
requireKey("speedRange");
requireKey("zSpeedRange");
requireKey("scaleRange");
requireKey("lifeTimeRange");
requireKey("emissionRate");
requireKey("maxParticles");
requireKey("particleSize");
requireKey("biasX");
requireKey("shaderProgramName");
emissionRate = j["emissionRate"].get<float>();
maxParticles = j["maxParticles"].get<int>();
particles.resize(maxParticles);
drawPositions.reserve(maxParticles * 6);
drawTexCoords.reserve(maxParticles * 6);
std::cout << "Max particles: " << maxParticles << std::endl;
particleSize = j["particleSize"].get<float>();
std::cout << "Particle size: " << particleSize << std::endl;
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(0)
<< ", " << randomPoint(1) << ", " << randomPoint(2) << "]" << std::endl;
}
}
}
if (!points.empty()) {
setEmissionPoints(points);
std::cout << "Total emission points: " << emissionPoints.size() << std::endl;
}
else {
setEmissionPoints({});
std::cout << "Emission points parsed but empty" << std::endl;
//throw std::runtime_error("Failed to load spark emitter config file 10!");
}
}
else {
std::cerr << "emissionPoints is not array" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 11!");
}
// 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;
}
else {
std::cerr << "speedRange missing or invalid" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 12!");
}
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;
}
else {
std::cerr << "zSpeedRange missing or invalid" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 13!");
}
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;
}
else {
std::cerr << "scaleRange missing or invalid" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 14!");
}
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;
}
else {
std::cerr << "lifeTimeRange missing or invalid" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 15!");
}
// texture
if (j.contains("texture") && j["texture"].is_string()) {
std::string texPath = j["texture"].get<std::string>();
std::cout << "Loading texture: " << texPath << " From zip file: " << zipFile << std::endl;
try {
std::cout << "Loading texture step 1" << std::endl;
auto texData = CreateTextureDataFromPng(texPath.c_str(), zipFile.c_str());
std::cout << "Loading texture step 2" << std::endl;
texture = std::make_shared<Texture>(texData);
std::cout << "Texture loaded successfully, ID: " << texture->getTexID() << std::endl;
}
catch (const std::exception& e) {
std::cout << "Texture load error: " << e.what() << std::endl;
std::cerr << "Texture load error: " << e.what() << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 16!");
}
}
else {
std::cerr << "No texture specified or invalid type in JSON" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 17!");
}
std::cout << "Working with shaders 1" << std::endl;
// shaders
if (j.contains("shaderProgramName") && j["shaderProgramName"].is_string()) {
shaderProgramName = j["shaderProgramName"].get<std::string>();
std::cout << "Shader program name: " << shaderProgramName << std::endl;
}
else {
std::cerr << "shaderProgramName missing or invalid" << std::endl;
throw std::runtime_error("Failed to load spark emitter config file 18!");
}
drawDataDirty = true;
configured = true;
std::cout << "SparkEmitter configuration loaded successfully!" << std::endl;
return true;
}
} // namespace ZL