mirror of
https://github.com/minetest/minetest.git
synced 2024-11-27 10:03:45 +01:00
Batched rendering of particles (#14489)
Co-authored-by: x2048 <codeforsmile@gmail.com> Co-authored-by: Desour <ds.desour@proton.me>
This commit is contained in:
parent
ff88ed7c75
commit
f8bff346f4
@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#include "particles.h"
|
||||
#include <cmath>
|
||||
#include <array>
|
||||
#include "client.h"
|
||||
#include "collision.h"
|
||||
#include "client/content_cao.h"
|
||||
@ -26,21 +27,27 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "client/renderingengine.h"
|
||||
#include "util/numeric.h"
|
||||
#include "light.h"
|
||||
#include "localplayer.h"
|
||||
#include "environment.h"
|
||||
#include "clientmap.h"
|
||||
#include "mapnode.h"
|
||||
#include "nodedef.h"
|
||||
#include "client.h"
|
||||
#include "settings.h"
|
||||
#include "profiler.h"
|
||||
|
||||
ClientParticleTexture::ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc)
|
||||
{
|
||||
tex = p;
|
||||
// note: getTextureForMesh not needed here because we don't use texture filtering
|
||||
ref = tsrc->getTexture(p.string);
|
||||
}
|
||||
|
||||
/*
|
||||
Particle
|
||||
*/
|
||||
|
||||
Particle::Particle(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ClientEnvironment *env,
|
||||
const ParticleParameters &p,
|
||||
const ClientParticleTexRef &texture,
|
||||
v2f texpos,
|
||||
@ -49,14 +56,10 @@ Particle::Particle(
|
||||
ParticleSpawner *parent,
|
||||
std::unique_ptr<ClientParticleTexture> owned_texture
|
||||
) :
|
||||
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
|
||||
((Client *)gamedef)->getSceneManager()),
|
||||
|
||||
m_expiration(p.expirationtime),
|
||||
|
||||
m_env(env),
|
||||
m_gamedef(gamedef),
|
||||
m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
|
||||
m_base_color(color),
|
||||
|
||||
m_texture(texture),
|
||||
m_texpos(texpos),
|
||||
m_texsize(texsize),
|
||||
@ -64,102 +67,30 @@ Particle::Particle(
|
||||
m_velocity(p.vel),
|
||||
m_acceleration(p.acc),
|
||||
m_p(p),
|
||||
m_player(player),
|
||||
|
||||
m_base_color(color),
|
||||
m_color(color),
|
||||
|
||||
m_parent(parent),
|
||||
m_owned_texture(std::move(owned_texture))
|
||||
{
|
||||
// Set material
|
||||
}
|
||||
|
||||
Particle::~Particle()
|
||||
{
|
||||
// translate blend modes to GL blend functions
|
||||
video::E_BLEND_FACTOR bfsrc, bfdst;
|
||||
video::E_BLEND_OPERATION blendop;
|
||||
const auto blendmode = texture.tex != nullptr
|
||||
? texture.tex->blendmode
|
||||
: ParticleParamTypes::BlendMode::alpha;
|
||||
|
||||
switch (blendmode) {
|
||||
case ParticleParamTypes::BlendMode::add:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::sub:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_REVSUBTRACT;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::screen:
|
||||
bfsrc = video::EBF_ONE;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
default: // includes ParticleParamTypes::BlendMode::alpha
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
if (m_buffer)
|
||||
m_buffer->release(m_index);
|
||||
}
|
||||
|
||||
// Texture
|
||||
m_material.Lighting = false;
|
||||
m_material.BackfaceCulling = false;
|
||||
m_material.FogEnable = true;
|
||||
m_material.forEachTexture([] (auto &tex) {
|
||||
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
||||
tex.MagFilter = video::ETMAGF_NEAREST;
|
||||
});
|
||||
|
||||
// correctly render layered transparent particles -- see #10398
|
||||
m_material.ZWriteEnable = video::EZW_AUTO;
|
||||
|
||||
// enable alpha blending and set blend mode
|
||||
m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
||||
m_material.MaterialTypeParam = video::pack_textureBlendFunc(
|
||||
bfsrc, bfdst,
|
||||
video::EMFN_MODULATE_1X,
|
||||
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
||||
m_material.BlendOperation = blendop;
|
||||
m_material.setTexture(0, m_texture.ref);
|
||||
}
|
||||
|
||||
// Irrlicht stuff
|
||||
this->setAutomaticCulling(scene::EAC_OFF);
|
||||
|
||||
// Init lighting
|
||||
updateLight();
|
||||
|
||||
// Init model
|
||||
updateVertices();
|
||||
}
|
||||
|
||||
void Particle::OnRegisterSceneNode()
|
||||
bool Particle::attachToBuffer(ParticleBuffer *buffer)
|
||||
{
|
||||
if (IsVisible)
|
||||
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
||||
|
||||
ISceneNode::OnRegisterSceneNode();
|
||||
auto index_opt = buffer->allocate();
|
||||
if (index_opt.has_value()) {
|
||||
m_index = index_opt.value();
|
||||
m_buffer = buffer;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Particle::render()
|
||||
{
|
||||
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
||||
driver->setMaterial(m_material);
|
||||
driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
|
||||
|
||||
u16 indices[] = {0,1,2, 2,3,0};
|
||||
driver->drawVertexPrimitiveList(m_vertices, 4,
|
||||
indices, 2, video::EVT_STANDARD,
|
||||
scene::EPT_TRIANGLES, video::EIT_16BIT);
|
||||
}
|
||||
|
||||
void Particle::step(float dtime)
|
||||
void Particle::step(float dtime, ClientEnvironment *env)
|
||||
{
|
||||
m_time += dtime;
|
||||
|
||||
@ -169,10 +100,10 @@ void Particle::step(float dtime)
|
||||
m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
|
||||
|
||||
if (m_p.collisiondetection) {
|
||||
aabb3f box = m_collisionbox;
|
||||
aabb3f box(v3f(-m_p.size / 2.0f), v3f(m_p.size / 2.0f));
|
||||
v3f p_pos = m_pos * BS;
|
||||
v3f p_velocity = m_velocity * BS;
|
||||
collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
|
||||
collisionMoveResult r = collisionMoveSimple(env, env->getGameDef(), BS * 0.5f,
|
||||
box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
|
||||
m_p.object_collision);
|
||||
|
||||
@ -215,7 +146,7 @@ void Particle::step(float dtime)
|
||||
m_animation_time += dtime;
|
||||
int frame_length_i = 0;
|
||||
m_p.animation.determineParams(
|
||||
m_material.getTexture(0)->getSize(),
|
||||
m_texture.ref->getSize(),
|
||||
NULL, &frame_length_i, NULL);
|
||||
float frame_length = frame_length_i / 1000.0;
|
||||
while (m_animation_time > frame_length) {
|
||||
@ -225,23 +156,19 @@ void Particle::step(float dtime)
|
||||
}
|
||||
|
||||
// animate particle alpha in accordance with settings
|
||||
float alpha = 1.f;
|
||||
if (m_texture.tex != nullptr)
|
||||
m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
||||
else
|
||||
m_alpha = 1.f;
|
||||
alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
|
||||
|
||||
// Update lighting
|
||||
updateLight();
|
||||
auto col = updateLight(env);
|
||||
col.setAlpha(255 * alpha);
|
||||
|
||||
// Update model
|
||||
updateVertices();
|
||||
|
||||
// Update position -- see #10398
|
||||
v3s16 camera_offset = m_env->getCameraOffset();
|
||||
setPosition(m_pos*BS - intToFloat(camera_offset, BS));
|
||||
updateVertices(env, col);
|
||||
}
|
||||
|
||||
void Particle::updateLight()
|
||||
video::SColor Particle::updateLight(ClientEnvironment *env)
|
||||
{
|
||||
u8 light = 0;
|
||||
bool pos_ok;
|
||||
@ -251,32 +178,37 @@ void Particle::updateLight()
|
||||
floor(m_pos.Y+0.5),
|
||||
floor(m_pos.Z+0.5)
|
||||
);
|
||||
MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
|
||||
MapNode n = env->getClientMap().getNode(p, &pos_ok);
|
||||
if (pos_ok)
|
||||
light = n.getLightBlend(m_env->getDayNightRatio(),
|
||||
m_gamedef->ndef()->getLightingFlags(n));
|
||||
light = n.getLightBlend(env->getDayNightRatio(),
|
||||
env->getGameDef()->ndef()->getLightingFlags(n));
|
||||
else
|
||||
light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
|
||||
light = blend_light(env->getDayNightRatio(), LIGHT_SUN, 0);
|
||||
|
||||
u8 m_light = decode_light(light + m_p.glow);
|
||||
m_color.set(m_alpha*255,
|
||||
return video::SColor(255,
|
||||
m_light * m_base_color.getRed() / 255,
|
||||
m_light * m_base_color.getGreen() / 255,
|
||||
m_light * m_base_color.getBlue() / 255);
|
||||
}
|
||||
|
||||
void Particle::updateVertices()
|
||||
void Particle::updateVertices(ClientEnvironment *env, video::SColor color)
|
||||
{
|
||||
f32 tx0, tx1, ty0, ty1;
|
||||
v2f scale;
|
||||
|
||||
if (!m_buffer)
|
||||
return;
|
||||
|
||||
video::S3DVertex *vertices = m_buffer->getVertices(m_index);
|
||||
|
||||
if (m_texture.tex != nullptr)
|
||||
scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
|
||||
else
|
||||
scale = v2f(1.f, 1.f);
|
||||
|
||||
if (m_p.animation.type != TAT_NONE) {
|
||||
const v2u32 texsize = m_material.getTexture(0)->getSize();
|
||||
const v2u32 texsize = m_texture.ref->getSize();
|
||||
v2f texcoord, framesize_f;
|
||||
v2u32 framesize;
|
||||
texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
|
||||
@ -297,31 +229,30 @@ void Particle::updateVertices()
|
||||
auto half = m_p.size * .5f,
|
||||
hx = half * scale.X,
|
||||
hy = half * scale.Y;
|
||||
m_vertices[0] = video::S3DVertex(-hx, -hy,
|
||||
0, 0, 0, 0, m_color, tx0, ty1);
|
||||
m_vertices[1] = video::S3DVertex(hx, -hy,
|
||||
0, 0, 0, 0, m_color, tx1, ty1);
|
||||
m_vertices[2] = video::S3DVertex(hx, hy,
|
||||
0, 0, 0, 0, m_color, tx1, ty0);
|
||||
m_vertices[3] = video::S3DVertex(-hx, hy,
|
||||
0, 0, 0, 0, m_color, tx0, ty0);
|
||||
vertices[0] = video::S3DVertex(-hx, -hy,
|
||||
0, 0, 0, 0, color, tx0, ty1);
|
||||
vertices[1] = video::S3DVertex(hx, -hy,
|
||||
0, 0, 0, 0, color, tx1, ty1);
|
||||
vertices[2] = video::S3DVertex(hx, hy,
|
||||
0, 0, 0, 0, color, tx1, ty0);
|
||||
vertices[3] = video::S3DVertex(-hx, hy,
|
||||
0, 0, 0, 0, color, tx0, ty0);
|
||||
|
||||
// Update position -- see #10398
|
||||
auto *player = env->getLocalPlayer();
|
||||
v3s16 camera_offset = env->getCameraOffset();
|
||||
|
||||
// see #10398
|
||||
// v3s16 camera_offset = m_env->getCameraOffset();
|
||||
// particle position is now handled by step()
|
||||
m_box.reset(v3f());
|
||||
|
||||
for (video::S3DVertex &vertex : m_vertices) {
|
||||
for (u16 i = 0; i < 4; i++) {
|
||||
video::S3DVertex &vertex = vertices[i];
|
||||
if (m_p.vertical) {
|
||||
v3f ppos = m_player->getPosition()/BS;
|
||||
v3f ppos = player->getPosition() / BS;
|
||||
vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
|
||||
core::DEGTORAD + 90);
|
||||
} else {
|
||||
vertex.Pos.rotateYZBy(m_player->getPitch());
|
||||
vertex.Pos.rotateXZBy(m_player->getYaw());
|
||||
vertex.Pos.rotateYZBy(player->getPitch());
|
||||
vertex.Pos.rotateXZBy(player->getYaw());
|
||||
}
|
||||
m_box.addInternalPoint(vertex.Pos);
|
||||
vertex.Pos += m_pos * BS - intToFloat(camera_offset, BS);
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,7 +261,6 @@ void Particle::updateVertices()
|
||||
*/
|
||||
|
||||
ParticleSpawner::ParticleSpawner(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
const ParticleSpawnerParameters ¶ms,
|
||||
u16 attached_id,
|
||||
@ -340,7 +270,6 @@ ParticleSpawner::ParticleSpawner(
|
||||
m_active(0),
|
||||
m_particlemanager(p_manager),
|
||||
m_time(0.0f),
|
||||
m_gamedef(gamedef),
|
||||
m_player(player),
|
||||
p(params),
|
||||
m_texpool(std::move(texpool)),
|
||||
@ -565,9 +494,6 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
|
||||
|
||||
++m_active;
|
||||
m_particlemanager->addParticle(std::make_unique<Particle>(
|
||||
m_gamedef,
|
||||
m_player,
|
||||
env,
|
||||
pp,
|
||||
texture,
|
||||
texpos,
|
||||
@ -624,6 +550,109 @@ void ParticleSpawner::step(float dtime, ClientEnvironment *env)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
ParticleBuffer
|
||||
*/
|
||||
|
||||
ParticleBuffer::ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material)
|
||||
: scene::ISceneNode(
|
||||
env->getGameDef()->getSceneManager()->getRootSceneNode(),
|
||||
env->getGameDef()->getSceneManager()),
|
||||
m_mesh_buffer(make_irr<scene::SMeshBuffer>())
|
||||
{
|
||||
m_mesh_buffer->getMaterial() = material;
|
||||
}
|
||||
|
||||
static constexpr u16 quad_indices[] = { 0, 1, 2, 2, 3, 0 };
|
||||
|
||||
std::optional<u16> ParticleBuffer::allocate()
|
||||
{
|
||||
u16 index;
|
||||
|
||||
m_usage_timer = 0;
|
||||
|
||||
if (!m_free_list.empty()) {
|
||||
index = m_free_list.back();
|
||||
m_free_list.pop_back();
|
||||
auto *vertices = static_cast<video::S3DVertex*>(m_mesh_buffer->getVertices());
|
||||
u16 *indices = m_mesh_buffer->getIndices();
|
||||
// reset vertices, because it is only written in Particle::step()
|
||||
for (u16 i = 0; i < 4; i++)
|
||||
vertices[4 * index + i] = video::S3DVertex();
|
||||
for (u16 i = 0; i < 6; i++)
|
||||
indices[6 * index + i] = 4 * index + quad_indices[i];
|
||||
return index;
|
||||
}
|
||||
|
||||
if (m_count >= MAX_PARTICLES_PER_BUFFER)
|
||||
return std::nullopt;
|
||||
|
||||
// append new vertices
|
||||
// note: Our buffer never gets smaller, but ParticleManager will delete
|
||||
// us after a while.
|
||||
std::array<video::S3DVertex, 4> vertices {};
|
||||
m_mesh_buffer->append(&vertices.front(), 4, quad_indices, 6);
|
||||
index = m_count++;
|
||||
return index;
|
||||
}
|
||||
|
||||
void ParticleBuffer::release(u16 index)
|
||||
{
|
||||
assert(index < m_count);
|
||||
u16 *indices = m_mesh_buffer->getIndices();
|
||||
for (u16 i = 0; i < 6; i++)
|
||||
indices[6 * index + i] = 0;
|
||||
m_free_list.push_back(index);
|
||||
}
|
||||
|
||||
video::S3DVertex *ParticleBuffer::getVertices(u16 index)
|
||||
{
|
||||
if (index >= m_count)
|
||||
return nullptr;
|
||||
m_bounding_box_dirty = true;
|
||||
return &(static_cast<video::S3DVertex *>(m_mesh_buffer->getVertices())[4 * index]);
|
||||
}
|
||||
|
||||
void ParticleBuffer::OnRegisterSceneNode()
|
||||
{
|
||||
if (IsVisible)
|
||||
SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
|
||||
scene::ISceneNode::OnRegisterSceneNode();
|
||||
}
|
||||
|
||||
const core::aabbox3df &ParticleBuffer::getBoundingBox() const
|
||||
{
|
||||
if (!m_bounding_box_dirty)
|
||||
return m_mesh_buffer->BoundingBox;
|
||||
|
||||
core::aabbox3df box;
|
||||
for (u16 i = 0; i < m_count; i++) {
|
||||
// check if this index is used
|
||||
static_assert(quad_indices[1] != 0);
|
||||
if (m_mesh_buffer->getIndices()[6 * i + 1] == 0)
|
||||
continue;
|
||||
|
||||
for (u16 j = 0; j < 4; j++)
|
||||
box.addInternalPoint(m_mesh_buffer->getPosition(i * 4 + j));
|
||||
}
|
||||
|
||||
m_mesh_buffer->BoundingBox = box;
|
||||
m_bounding_box_dirty = false;
|
||||
return m_mesh_buffer->BoundingBox;
|
||||
}
|
||||
|
||||
void ParticleBuffer::render()
|
||||
{
|
||||
video::IVideoDriver *driver = SceneManager->getVideoDriver();
|
||||
|
||||
if (isEmpty())
|
||||
return;
|
||||
|
||||
driver->setTransform(video::ETS_WORLD, core::matrix4());
|
||||
driver->setMaterial(m_mesh_buffer->getMaterial());
|
||||
driver->drawMeshBuffer(m_mesh_buffer.get());
|
||||
}
|
||||
|
||||
/*
|
||||
ParticleManager
|
||||
*/
|
||||
@ -641,6 +670,7 @@ void ParticleManager::step(float dtime)
|
||||
{
|
||||
stepParticles(dtime);
|
||||
stepSpawners(dtime);
|
||||
stepBuffers(dtime);
|
||||
}
|
||||
|
||||
void ParticleManager::stepSpawners(float dtime)
|
||||
@ -684,35 +714,59 @@ void ParticleManager::stepParticles(float dtime)
|
||||
assert(parent->hasActive());
|
||||
parent->decrActive();
|
||||
}
|
||||
// remove scene node
|
||||
p.remove();
|
||||
// delete
|
||||
m_particles[i] = std::move(m_particles.back());
|
||||
m_particles.pop_back();
|
||||
} else {
|
||||
p.step(dtime);
|
||||
p.step(dtime, m_env);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ParticleManager::stepBuffers(float dtime)
|
||||
{
|
||||
constexpr float INTERVAL = 0.5f;
|
||||
if (!m_buffer_gc.step(dtime, INTERVAL))
|
||||
return;
|
||||
|
||||
MutexAutoLock lock(m_particle_list_lock);
|
||||
|
||||
// remove buffers that have been unused for 5 seconds
|
||||
size_t alloc = 0;
|
||||
for (size_t i = 0; i < m_particle_buffers.size(); ) {
|
||||
auto &buf = m_particle_buffers[i];
|
||||
buf->m_usage_timer += INTERVAL;
|
||||
if (buf->isEmpty() && buf->m_usage_timer > 5.0f) {
|
||||
// delete and swap with last
|
||||
buf->remove();
|
||||
buf = std::move(m_particle_buffers.back());
|
||||
m_particle_buffers.pop_back();
|
||||
} else {
|
||||
i++;
|
||||
alloc += buf->m_count;
|
||||
}
|
||||
}
|
||||
|
||||
g_profiler->avg("ParticleManager: particle buffer count [#]", m_particle_buffers.size());
|
||||
if (!m_particle_buffers.empty())
|
||||
g_profiler->avg("ParticleManager: buffer allocated size [#]", alloc);
|
||||
}
|
||||
|
||||
void ParticleManager::clearAll()
|
||||
{
|
||||
MutexAutoLock lock(m_spawner_list_lock);
|
||||
MutexAutoLock lock2(m_particle_list_lock);
|
||||
|
||||
// clear particle spawners
|
||||
m_particle_spawners.clear();
|
||||
m_dying_particle_spawners.clear();
|
||||
|
||||
// clear particles
|
||||
for (std::unique_ptr<Particle> &p : m_particles) {
|
||||
// remove scene node
|
||||
p->remove();
|
||||
// delete
|
||||
p.reset();
|
||||
}
|
||||
m_particles.clear();
|
||||
|
||||
// have to remove from scene first because it keeps a reference
|
||||
for (auto &it : m_particle_buffers)
|
||||
it->remove();
|
||||
m_particle_buffers.clear();
|
||||
}
|
||||
|
||||
void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||
@ -744,7 +798,6 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||
|
||||
addParticleSpawner(event->add_particlespawner.id,
|
||||
std::make_unique<ParticleSpawner>(
|
||||
client,
|
||||
player,
|
||||
p,
|
||||
event->add_particlespawner.attached_id,
|
||||
@ -785,7 +838,7 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
|
||||
p.size = oldsize;
|
||||
|
||||
if (texture.ref) {
|
||||
addParticle(std::make_unique<Particle>(client, player, m_env,
|
||||
addParticle(std::make_unique<Particle>(
|
||||
p, texture, texpos, texsize, color, nullptr,
|
||||
std::move(texstore)));
|
||||
}
|
||||
@ -885,9 +938,6 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
|
||||
);
|
||||
|
||||
addParticle(std::make_unique<Particle>(
|
||||
gamedef,
|
||||
player,
|
||||
m_env,
|
||||
p,
|
||||
ClientParticleTexRef(ref),
|
||||
texpos,
|
||||
@ -902,13 +952,104 @@ void ParticleManager::reserveParticleSpace(size_t max_estimate)
|
||||
m_particles.reserve(m_particles.size() + max_estimate);
|
||||
}
|
||||
|
||||
void ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
||||
video::SMaterial ParticleManager::getMaterialForParticle(const ClientParticleTexRef &texture)
|
||||
{
|
||||
// translate blend modes to GL blend functions
|
||||
video::E_BLEND_FACTOR bfsrc, bfdst;
|
||||
video::E_BLEND_OPERATION blendop;
|
||||
const auto blendmode = texture.tex ? texture.tex->blendmode :
|
||||
ParticleParamTypes::BlendMode::alpha;
|
||||
|
||||
switch (blendmode) {
|
||||
case ParticleParamTypes::BlendMode::add:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::sub:
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_DST_ALPHA;
|
||||
blendop = video::EBO_REVSUBTRACT;
|
||||
break;
|
||||
|
||||
case ParticleParamTypes::BlendMode::screen:
|
||||
bfsrc = video::EBF_ONE;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
|
||||
default: // includes ParticleParamTypes::BlendMode::alpha
|
||||
bfsrc = video::EBF_SRC_ALPHA;
|
||||
bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
|
||||
blendop = video::EBO_ADD;
|
||||
break;
|
||||
}
|
||||
|
||||
video::SMaterial material;
|
||||
|
||||
// Texture
|
||||
material.Lighting = false;
|
||||
material.BackfaceCulling = false;
|
||||
material.FogEnable = true;
|
||||
material.forEachTexture([] (auto &tex) {
|
||||
tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
|
||||
tex.MagFilter = video::ETMAGF_NEAREST;
|
||||
});
|
||||
|
||||
// We don't have working transparency sorting. Disable Z-Write for
|
||||
// correct results for clipped-alpha at least.
|
||||
material.ZWriteEnable = video::EZW_OFF;
|
||||
|
||||
// enable alpha blending and set blend mode
|
||||
material.MaterialType = video::EMT_ONETEXTURE_BLEND;
|
||||
material.MaterialTypeParam = video::pack_textureBlendFunc(
|
||||
bfsrc, bfdst,
|
||||
video::EMFN_MODULATE_1X,
|
||||
video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
|
||||
material.BlendOperation = blendop;
|
||||
assert(texture.ref);
|
||||
material.setTexture(0, texture.ref);
|
||||
|
||||
return material;
|
||||
}
|
||||
|
||||
bool ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
|
||||
{
|
||||
MutexAutoLock lock(m_particle_list_lock);
|
||||
|
||||
m_particles.push_back(std::move(toadd));
|
||||
auto material = getMaterialForParticle(toadd->getTextureRef());
|
||||
|
||||
ParticleBuffer *found = nullptr;
|
||||
// simple shortcut when multiple particles of the same type get added
|
||||
if (!m_particles.empty()) {
|
||||
auto &last = m_particles.back();
|
||||
if (last->getBuffer() && last->getBuffer()->getMaterial(0) == material)
|
||||
found = last->getBuffer();
|
||||
}
|
||||
// search fitting buffer
|
||||
if (!found) {
|
||||
for (auto &buffer : m_particle_buffers) {
|
||||
if (buffer->getMaterial(0) == material) {
|
||||
found = buffer.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// or create a new one
|
||||
if (!found) {
|
||||
auto tmp = make_irr<ParticleBuffer>(m_env, material);
|
||||
found = tmp.get();
|
||||
m_particle_buffers.push_back(std::move(tmp));
|
||||
}
|
||||
|
||||
if (!toadd->attachToBuffer(found)) {
|
||||
infostream << "ParticleManager: buffer full, dropping particle" << std::endl;
|
||||
return false;
|
||||
}
|
||||
m_particles.push_back(std::move(toadd));
|
||||
return true;
|
||||
}
|
||||
|
||||
void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
|
||||
{
|
||||
|
@ -19,9 +19,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
#include "localplayer.h"
|
||||
#include "irr_ptr.h"
|
||||
#include "../particles.h"
|
||||
|
||||
struct ClientEvent;
|
||||
@ -29,6 +30,10 @@ class ParticleManager;
|
||||
class ClientEnvironment;
|
||||
struct MapNode;
|
||||
struct ContentFeatures;
|
||||
class LocalPlayer;
|
||||
class ITextureSource;
|
||||
class IGameDef;
|
||||
class Client;
|
||||
|
||||
struct ClientParticleTexture
|
||||
{
|
||||
@ -38,9 +43,7 @@ struct ClientParticleTexture
|
||||
video::ITexture *ref = nullptr;
|
||||
|
||||
ClientParticleTexture() = default;
|
||||
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *t):
|
||||
tex(p),
|
||||
ref(t->getTextureForMesh(p.string)) {};
|
||||
ClientParticleTexture(const ServerParticleTexture& p, ITextureSource *tsrc);
|
||||
};
|
||||
|
||||
struct ClientParticleTexRef
|
||||
@ -61,14 +64,12 @@ struct ClientParticleTexRef
|
||||
};
|
||||
|
||||
class ParticleSpawner;
|
||||
class ParticleBuffer;
|
||||
|
||||
class Particle : public scene::ISceneNode
|
||||
class Particle
|
||||
{
|
||||
public:
|
||||
Particle(
|
||||
IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ClientEnvironment *env,
|
||||
const ParticleParameters &p,
|
||||
const ClientParticleTexRef &texture,
|
||||
v2f texpos,
|
||||
@ -78,61 +79,46 @@ public:
|
||||
std::unique_ptr<ClientParticleTexture> owned_texture = nullptr
|
||||
);
|
||||
|
||||
virtual const aabb3f &getBoundingBox() const
|
||||
{
|
||||
return m_box;
|
||||
}
|
||||
~Particle();
|
||||
|
||||
virtual u32 getMaterialCount() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
DISABLE_CLASS_COPY(Particle)
|
||||
|
||||
virtual video::SMaterial& getMaterial(u32 i)
|
||||
{
|
||||
return m_material;
|
||||
}
|
||||
void step(float dtime, ClientEnvironment *env);
|
||||
|
||||
virtual void OnRegisterSceneNode();
|
||||
virtual void render();
|
||||
|
||||
void step(float dtime);
|
||||
|
||||
bool isExpired ()
|
||||
bool isExpired () const
|
||||
{ return m_expiration < m_time; }
|
||||
|
||||
ParticleSpawner *getParent() { return m_parent; }
|
||||
ParticleSpawner *getParent() const { return m_parent; }
|
||||
|
||||
const ClientParticleTexRef &getTextureRef() const { return m_texture; }
|
||||
|
||||
ParticleBuffer *getBuffer() const { return m_buffer; }
|
||||
bool attachToBuffer(ParticleBuffer *buffer);
|
||||
|
||||
private:
|
||||
void updateLight();
|
||||
void updateVertices();
|
||||
void setVertexAlpha(float a);
|
||||
video::SColor updateLight(ClientEnvironment *env);
|
||||
void updateVertices(ClientEnvironment *env, video::SColor color);
|
||||
|
||||
ParticleBuffer *m_buffer = nullptr;
|
||||
u16 m_index; // index in m_buffer
|
||||
|
||||
video::S3DVertex m_vertices[4];
|
||||
float m_time = 0.0f;
|
||||
float m_expiration;
|
||||
|
||||
ClientEnvironment *m_env;
|
||||
IGameDef *m_gamedef;
|
||||
aabb3f m_box;
|
||||
aabb3f m_collisionbox;
|
||||
// Color without lighting
|
||||
video::SColor m_base_color;
|
||||
|
||||
ClientParticleTexRef m_texture;
|
||||
video::SMaterial m_material;
|
||||
v2f m_texpos;
|
||||
v2f m_texsize;
|
||||
v3f m_pos;
|
||||
v3f m_velocity;
|
||||
v3f m_acceleration;
|
||||
const ParticleParameters m_p;
|
||||
LocalPlayer *m_player;
|
||||
|
||||
//! Color without lighting
|
||||
video::SColor m_base_color;
|
||||
//! Final rendered color
|
||||
video::SColor m_color;
|
||||
const ParticleParameters m_p;
|
||||
|
||||
float m_animation_time = 0.0f;
|
||||
int m_animation_frame = 0;
|
||||
float m_alpha = 0.0f;
|
||||
|
||||
ParticleSpawner *m_parent = nullptr;
|
||||
// Used if not spawned from a particlespawner
|
||||
@ -142,8 +128,7 @@ private:
|
||||
class ParticleSpawner
|
||||
{
|
||||
public:
|
||||
ParticleSpawner(IGameDef *gamedef,
|
||||
LocalPlayer *player,
|
||||
ParticleSpawner(LocalPlayer *player,
|
||||
const ParticleSpawnerParameters ¶ms,
|
||||
u16 attached_id,
|
||||
std::vector<ClientParticleTexture> &&texpool,
|
||||
@ -164,7 +149,6 @@ private:
|
||||
size_t m_active;
|
||||
ParticleManager *m_particlemanager;
|
||||
float m_time;
|
||||
IGameDef *m_gamedef;
|
||||
LocalPlayer *m_player;
|
||||
ParticleSpawnerParameters p;
|
||||
std::vector<ClientParticleTexture> m_texpool;
|
||||
@ -172,6 +156,55 @@ private:
|
||||
u16 m_attached_id;
|
||||
};
|
||||
|
||||
class ParticleBuffer : public scene::ISceneNode
|
||||
{
|
||||
friend class ParticleManager;
|
||||
public:
|
||||
ParticleBuffer(ClientEnvironment *env, const video::SMaterial &material);
|
||||
|
||||
// for pointer stability
|
||||
DISABLE_CLASS_COPY(ParticleBuffer)
|
||||
|
||||
/// Reserves one more slot for a particle (4 vertices, 6 indices)
|
||||
/// @return particle index within buffer
|
||||
std::optional<u16> allocate();
|
||||
/// Frees the particle at `index`
|
||||
void release(u16 index);
|
||||
|
||||
/// @return video::S3DVertex[4]
|
||||
video::S3DVertex *getVertices(u16 index);
|
||||
|
||||
inline bool isEmpty() const {
|
||||
return m_free_list.size() == m_count;
|
||||
}
|
||||
|
||||
virtual video::SMaterial &getMaterial(u32 num) override {
|
||||
return m_mesh_buffer->getMaterial();
|
||||
}
|
||||
virtual u32 getMaterialCount() const override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
virtual const core::aabbox3df &getBoundingBox() const override;
|
||||
|
||||
virtual void render() override;
|
||||
|
||||
virtual void OnRegisterSceneNode() override;
|
||||
|
||||
// we have 16-bit indices
|
||||
static constexpr u16 MAX_PARTICLES_PER_BUFFER = 16000;
|
||||
|
||||
private:
|
||||
irr_ptr<scene::SMeshBuffer> m_mesh_buffer;
|
||||
// unused (e.g. expired) particle indices for re-use
|
||||
std::vector<u16> m_free_list;
|
||||
// for automatic deletion when unused for a while. is reset on allocate().
|
||||
float m_usage_timer = 0;
|
||||
// total count of contained particles
|
||||
u16 m_count = 0;
|
||||
mutable bool m_bounding_box_dirty = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Class doing particle as well as their spawners handling
|
||||
*/
|
||||
@ -213,7 +246,9 @@ protected:
|
||||
ParticleParameters &p, video::ITexture **texture, v2f &texpos,
|
||||
v2f &texsize, video::SColor *color, u8 tilenum = 0);
|
||||
|
||||
void addParticle(std::unique_ptr<Particle> toadd);
|
||||
static video::SMaterial getMaterialForParticle(const ClientParticleTexRef &texture);
|
||||
|
||||
bool addParticle(std::unique_ptr<Particle> toadd);
|
||||
|
||||
private:
|
||||
void addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd);
|
||||
@ -221,17 +256,23 @@ private:
|
||||
|
||||
void stepParticles(float dtime);
|
||||
void stepSpawners(float dtime);
|
||||
void stepBuffers(float dtime);
|
||||
|
||||
void clearAll();
|
||||
|
||||
std::vector<std::unique_ptr<Particle>> m_particles;
|
||||
std::unordered_map<u64, std::unique_ptr<ParticleSpawner>> m_particle_spawners;
|
||||
std::vector<std::unique_ptr<ParticleSpawner>> m_dying_particle_spawners;
|
||||
// Start the particle spawner ids generated from here after u32_max. lower values are
|
||||
// for server sent spawners.
|
||||
u64 m_next_particle_spawner_id = U32_MAX + 1;
|
||||
std::vector<irr_ptr<ParticleBuffer>> m_particle_buffers;
|
||||
|
||||
// Start the particle spawner ids generated from here after u32_max.
|
||||
// lower values are for server sent spawners.
|
||||
u64 m_next_particle_spawner_id = static_cast<u64>(U32_MAX) + 1;
|
||||
|
||||
ClientEnvironment *m_env;
|
||||
|
||||
IntervalLimiter m_buffer_gc;
|
||||
|
||||
std::mutex m_particle_list_lock;
|
||||
std::mutex m_spawner_list_lock;
|
||||
};
|
||||
|
@ -31,7 +31,7 @@ enum TileAnimationType : u8
|
||||
|
||||
struct TileAnimationParams
|
||||
{
|
||||
enum TileAnimationType type;
|
||||
enum TileAnimationType type = TileAnimationType::TAT_NONE;
|
||||
union
|
||||
{
|
||||
// struct {
|
||||
|
Loading…
Reference in New Issue
Block a user