VoxelEngine/src/graphics/render/ParticlesRenderer.cpp
2024-11-19 10:15:42 +03:00

176 lines
5.2 KiB
C++

#include "ParticlesRenderer.hpp"
#include <set>
#include "assets/Assets.hpp"
#include "assets/assets_util.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/core/Texture.hpp"
#include "graphics/render/MainBatch.hpp"
#include "window/Camera.hpp"
#include "world/Level.hpp"
#include "voxels/Chunks.hpp"
#include "settings.hpp"
size_t ParticlesRenderer::visibleParticles = 0;
size_t ParticlesRenderer::aliveEmitters = 0;
ParticlesRenderer::ParticlesRenderer(
const Assets& assets, const Level& level, const GraphicsSettings* settings
)
: batch(std::make_unique<MainBatch>(4096)),
level(level),
assets(assets),
settings(settings) {}
ParticlesRenderer::~ParticlesRenderer() = default;
static inline void update_particle(
Particle& particle, float delta, const Chunks& chunks
) {
const auto& preset = particle.emitter->preset;
auto& pos = particle.position;
auto& vel = particle.velocity;
vel += delta * preset.acceleration;
if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) {
vel *= 0.0f;
}
pos += vel * delta;
particle.lifetime -= delta;
}
void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
const auto& right = camera.right;
const auto& up = camera.up;
const auto& chunks = *level.chunks;
bool backlight = settings->backlight.get();
std::vector<const Texture*> unusedTextures;
for (auto& [texture, vec] : particles) {
if (vec.empty()) {
unusedTextures.push_back(texture);
continue;
}
batch->setTexture(texture);
visibleParticles += vec.size();
auto iter = vec.begin();
while (iter != vec.end()) {
auto& particle = *iter;
auto& preset = particle.emitter->preset;
if (!preset.frames.empty()) {
float time = preset.lifetime - particle.lifetime;
int framesCount = preset.frames.size();
int frameid = time / preset.lifetime * framesCount;
int frameid2 = glm::min(
(time + delta) / preset.lifetime * framesCount,
framesCount - 1.0f
);
if (frameid2 != frameid) {
auto tregion = util::get_texture_region(
assets, preset.frames.at(frameid2), ""
);
if (tregion.texture == texture) {
particle.region = tregion.region;
}
}
}
update_particle(particle, delta, chunks);
glm::vec4 light(1, 1, 1, 0);
if (preset.lighting) {
light = MainBatch::sampleLight(
particle.position, chunks, backlight
);
light *= 0.9f + (particle.random % 100) * 0.001f;
}
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;
batch->quad(
particle.position,
right,
preset.globalUpVector ? glm::vec3(0, 1, 0) : up,
preset.size * scale,
light,
glm::vec3(1.0f),
particle.region
);
if (particle.lifetime <= 0.0f) {
iter = vec.erase(iter);
} else {
iter++;
}
}
}
batch->flush();
for (const auto& texture : unusedTextures) {
particles.erase(texture);
}
}
void ParticlesRenderer::render(const Camera& camera, float delta) {
batch->begin();
aliveEmitters = emitters.size();
visibleParticles = 0;
renderParticles(camera, delta);
auto iter = emitters.begin();
while (iter != emitters.end()) {
auto& emitter = *iter->second;
auto texture = emitter.getTexture();
const auto& found = particles.find(texture);
std::vector<Particle>* vec;
if (found == particles.end()) {
if (emitter.isDead()) {
// destruct Emitter only when there is no particles spawned by it
iter = emitters.erase(iter);
continue;
}
vec = &particles[texture];
} else {
vec = &found->second;
}
emitter.update(delta, camera.position, *vec);
iter++;
}
}
void ParticlesRenderer::gc() {
std::set<Emitter*> usedEmitters;
for (const auto& [_, vec] : particles) {
for (const auto& particle : vec) {
usedEmitters.insert(particle.emitter);
}
}
auto iter = emitters.begin();
while (iter != emitters.end()) {
auto emitter = iter->second.get();
if (usedEmitters.find(emitter) == usedEmitters.end()) {
iter = emitters.erase(iter);
} else {
iter++;
}
}
}
Emitter* ParticlesRenderer::getEmitter(u64id_t id) const {
const auto& found = emitters.find(id);
if (found == emitters.end()) {
return nullptr;
}
return found->second.get();
}
u64id_t ParticlesRenderer::add(std::unique_ptr<Emitter> emitter) {
u64id_t uid = nextEmitter++;
emitters[uid] = std::move(emitter);
return uid;
}