Expand Up
@@ -38,105 +38,98 @@ with this program; if not, write to the Free Software Foundation, Inc.,
*/
Particle::Particle (
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientTexRef& texture,
v2f texpos,
v2f texsize,
video::SColor color
):
scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
((Client *)gamedef)->getSceneManager()),
m_texture(texture)
IGameDef *gamedef,
LocalPlayer *player,
ClientEnvironment *env,
const ParticleParameters &p,
const ClientParticleTexRef &texture,
v2f texpos,
v2f texsize,
video::SColor color,
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_texture(texture),
m_texpos(texpos),
m_texsize(texsize),
m_pos(p.pos),
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))
{
// Misc
m_gamedef = gamedef;
m_env = env;
// 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 ;
}
// Set material
{
// 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 ;
// 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 );
m_texpos = texpos;
m_texsize = texsize;
m_animation = p.animation ;
// Color
m_base_color = color;
m_color = color;
// Particle related
m_pos = p.pos ;
m_velocity = p.vel ;
m_acceleration = p.acc ;
m_drag = p.drag ;
m_jitter = p.jitter ;
m_bounce = p.bounce ;
m_expiration = p.expirationtime ;
m_player = player;
m_size = p.size ;
m_collisiondetection = p.collisiondetection ;
m_collision_removal = p.collision_removal ;
m_object_collision = p.object_collision ;
m_vertical = p.vertical ;
m_glow = p.glow ;
m_alpha = 0 ;
m_parent = nullptr ;
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 ;
}
// 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
const float c = p.size / 2 ;
m_collisionbox = aabb3f (-c, -c, -c, c, c, c);
this ->setAutomaticCulling (scene::EAC_OFF);
// Init lighting
Expand All
@@ -146,14 +139,6 @@ Particle::Particle(
updateVertices ();
}
Particle::~Particle ()
{
/* if our textures aren't owned by a particlespawner, we need to clean
* them up ourselves when the particle dies */
if (m_parent == nullptr )
delete m_texture.tex ;
}
void Particle::OnRegisterSceneNode ()
{
if (IsVisible)
Expand All
@@ -180,20 +165,20 @@ void Particle::step(float dtime)
// apply drag (not handled by collisionMoveSimple) and brownian motion
v3f av = vecAbsolute (m_velocity);
av -= av * (m_drag * dtime);
m_velocity = av*vecSign (m_velocity) + v3f (m_jitter .pickWithin ())*dtime;
av -= av * (m_p. drag * dtime);
m_velocity = av*vecSign (m_velocity) + v3f (m_p. jitter .pickWithin ())*dtime;
if (m_collisiondetection ) {
if (m_p. collisiondetection ) {
aabb3f box = m_collisionbox;
v3f p_pos = m_pos * BS;
v3f p_velocity = m_velocity * BS;
collisionMoveResult r = collisionMoveSimple (m_env, m_gamedef, BS * 0 .5f ,
box, 0 .0f , dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr ,
m_object_collision );
m_p. object_collision );
f32 bounciness = m_bounce .pickWithin ();
if (r.collides && (m_collision_removal || bounciness > 0 )) {
if (m_collision_removal ) {
f32 bounciness = m_p. bounce .pickWithin ();
if (r.collides && (m_p. collision_removal || bounciness > 0 )) {
if (m_p. collision_removal ) {
// force expiration of the particle
m_expiration = -1 .0f ;
} else if (bounciness > 0 ) {
Expand Down
Expand Up
@@ -226,10 +211,10 @@ void Particle::step(float dtime)
m_velocity += m_acceleration * dtime;
}
if (m_animation .type != TAT_NONE) {
if (m_p. animation .type != TAT_NONE) {
m_animation_time += dtime;
int frame_length_i, frame_count;
m_animation .determineParams (
m_p. animation .determineParams (
m_material.getTexture (0 )->getSize (),
&frame_count, &frame_length_i, NULL );
float frame_length = frame_length_i / 1000.0 ;
Expand Down
Expand Up
@@ -273,7 +258,7 @@ void Particle::updateLight()
else
light = blend_light (m_env->getDayNightRatio (), LIGHT_SUN, 0 );
u8 m_light = decode_light (light + m_glow );
u8 m_light = decode_light (light + m_p. glow );
m_color.set (m_alpha*255 ,
m_light * m_base_color.getRed () / 255 ,
m_light * m_base_color.getGreen () / 255 ,
Expand All
@@ -290,12 +275,12 @@ void Particle::updateVertices()
else
scale = v2f (1 .f , 1 .f );
if (m_animation .type != TAT_NONE) {
if (m_p. animation .type != TAT_NONE) {
const v2u32 texsize = m_material.getTexture (0 )->getSize ();
v2f texcoord, framesize_f;
v2u32 framesize;
texcoord = m_animation .getTextureCoords (texsize, m_animation_frame);
m_animation .determineParams (texsize, NULL , NULL , &framesize);
texcoord = m_p. animation .getTextureCoords (texsize, m_animation_frame);
m_p. animation .determineParams (texsize, NULL , NULL , &framesize);
framesize_f = v2f (framesize.X / (float ) texsize.X , framesize.Y / (float ) texsize.Y );
tx0 = m_texpos.X + texcoord.X ;
Expand All
@@ -309,7 +294,7 @@ void Particle::updateVertices()
ty1 = m_texpos.Y + m_texsize.Y ;
}
auto half = m_size * .5f ,
auto half = m_p. size * .5f ,
hx = half * scale.X ,
hy = half * scale.Y ;
m_vertices[0 ] = video::S3DVertex (-hx, -hy,
Expand All
@@ -328,7 +313,7 @@ void Particle::updateVertices()
m_box.reset (v3f ());
for (video::S3DVertex &vertex : m_vertices) {
if (m_vertical ) {
if (m_p. vertical ) {
v3f ppos = m_player->getPosition ()/BS;
vertex.Pos .rotateXZBy (std::atan2 (ppos.Z - m_pos.Z , ppos.X - m_pos.X ) /
core::DEGTORAD + 90 );
Expand All
@@ -345,25 +330,22 @@ void Particle::updateVertices()
*/
ParticleSpawner::ParticleSpawner (
IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters &p,
u16 attached_id,
std::unique_ptr<ClientTexture[]>& texpool,
size_t texcount,
ParticleManager *p_manager
):
m_particlemanager(p_manager), p(p)
IGameDef *gamedef,
LocalPlayer *player,
const ParticleSpawnerParameters ¶ms,
u16 attached_id,
std::vector<ClientParticleTexture> &&texpool,
ParticleManager *p_manager
) :
m_active(0 ),
m_particlemanager(p_manager),
m_time(0 .0f ),
m_gamedef(gamedef),
m_player(player),
p(params),
m_texpool(std::move(texpool)),
m_attached_id(attached_id)
{
m_gamedef = gamedef;
m_player = player;
m_attached_id = attached_id;
m_texpool = std::move (texpool);
m_texcount = texcount;
m_time = 0 ;
m_active = 0 ;
m_dying = false ;
m_spawntimes.reserve (p.amount + 1 );
for (u16 i = 0 ; i <= p.amount ; i++) {
float spawntime = myrand_float () * p.time ;
Expand Down
Expand Up
@@ -398,22 +380,22 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
fac = m_time / (p.time +0 .1f );
}
auto r_pos = p.pos .blend (fac);
auto r_vel = p.vel .blend (fac);
auto r_acc = p.acc .blend (fac);
auto r_drag = p.drag .blend (fac);
auto r_pos = p.pos .blend (fac);
auto r_vel = p.vel .blend (fac);
auto r_acc = p.acc .blend (fac);
auto r_drag = p.drag .blend (fac);
auto r_radius = p.radius .blend (fac);
auto r_jitter = p.jitter .blend (fac);
auto r_bounce = p.bounce .blend (fac);
v3f attractor_origin = p.attractor_origin .blend (fac);
v3f attractor_origin = p.attractor_origin .blend (fac);
v3f attractor_direction = p.attractor_direction .blend (fac);
auto attractor_obj = findObjectByID (env, p.attractor_attachment );
auto attractor_obj = findObjectByID (env, p.attractor_attachment );
auto attractor_direction_obj = findObjectByID (env, p.attractor_direction_attachment );
auto r_exp = p.exptime .blend (fac);
auto r_size = p.size .blend (fac);
auto r_exp = p.exptime .blend (fac);
auto r_size = p.size .blend (fac);
auto r_attract = p.attract .blend (fac);
auto attract = r_attract.pickWithin ();
auto attract = r_attract.pickWithin ();
v3f ppos = m_player->getPosition () / BS;
v3f pos = r_pos.pickWithin ();
Expand Down
Expand Up
@@ -538,7 +520,7 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
p.copyCommon (pp);
ClientTexRef texture;
ClientParticleTexRef texture;
v2f texpos, texsize;
video::SColor color (0xFFFFFFFF );
Expand All
@@ -549,9 +531,10 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
texpos, texsize, &color, p.node_tile ))
return ;
} else {
if (m_texcount == 0 )
if (m_texpool. size () == 0 )
return ;
texture = decltype (texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range (0 ,m_texcount-1 )]);
texture = ClientParticleTexRef (m_texpool[m_texpool.size () == 1 ? 0
: myrand_range (0 , m_texpool.size ()-1 )]);
texpos = v2f (0 .0f , 0 .0f );
texsize = v2f (1 .0f , 1 .0f );
if (texture.tex ->animated )
Expand Down
Expand Up
@@ -581,18 +564,17 @@ void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
pp.size = r_size.pickWithin ();
++m_active;
auto pa = new Particle (
m_gamedef,
m_player,
env,
pp,
texture,
texpos,
texsize,
color
);
pa->m_parent = this ;
m_particlemanager->addParticle (pa);
m_particlemanager->addParticle (std::make_unique<Particle>(
m_gamedef,
m_player,
env,
pp,
texture,
texpos,
texsize,
color,
this
));
}
void ParticleSpawner::step (float dtime, ClientEnvironment *env)
Expand Down
Expand Up
@@ -664,37 +646,51 @@ void ParticleManager::step(float dtime)
void ParticleManager::stepSpawners (float dtime)
{
MutexAutoLock lock (m_spawner_list_lock);
for (auto i = m_particle_spawners.begin (); i != m_particle_spawners.end ();) {
if (i->second ->getExpired ()) {
// the particlespawner owns the textures, so we need to make
// sure there are no active particles before we free it
if (i->second ->m_active == 0 ) {
delete i->second ;
m_particle_spawners.erase (i++);
} else {
++i;
}
for (size_t i = 0 ; i < m_dying_particle_spawners.size ();) {
// the particlespawner owns the textures, so we need to make
// sure there are no active particles before we free it
if (!m_dying_particle_spawners[i]->hasActive ()) {
m_dying_particle_spawners[i] = std::move (m_dying_particle_spawners.back ());
m_dying_particle_spawners.pop_back ();
} else {
i->second ->step (dtime, m_env);
++i;
}
}
for (auto it = m_particle_spawners.begin (); it != m_particle_spawners.end ();) {
auto &ps = it->second ;
if (ps->getExpired ()) {
// same as above
if (ps->hasActive ())
m_dying_particle_spawners.push_back (std::move (ps));
it = m_particle_spawners.erase (it);
} else {
ps->step (dtime, m_env);
++it;
}
}
}
void ParticleManager::stepParticles (float dtime)
{
MutexAutoLock lock (m_particle_list_lock);
for (auto i = m_particles.begin (); i != m_particles.end ();) {
if ((*i)->get_expired ()) {
if ((*i)->m_parent ) {
assert ((*i)->m_parent ->m_active != 0 );
--(*i)->m_parent ->m_active ;
for (size_t i = 0 ; i < m_particles.size ();) {
Particle &p = *m_particles[i];
if (p.isExpired ()) {
ParticleSpawner *parent = p.getParent ();
if (parent) {
assert (parent->hasActive ());
parent->decrActive ();
}
(*i)->remove ();
delete *i;
i = m_particles.erase (i);
// remove scene node
p.remove ();
// delete
m_particles[i] = std::move (m_particles.back ());
m_particles.pop_back ();
} else {
(*i)-> step (dtime);
p. step (dtime);
++i;
}
}
Expand All
@@ -704,17 +700,19 @@ void ParticleManager::clearAll()
{
MutexAutoLock lock (m_spawner_list_lock);
MutexAutoLock lock2 (m_particle_list_lock);
for (auto i = m_particle_spawners.begin (); i != m_particle_spawners.end ();) {
delete i->second ;
m_particle_spawners.erase (i++);
}
for (auto i = m_particles.begin (); i != m_particles.end ();)
{
(*i)->remove ();
delete *i;
i = m_particles.erase (i);
// 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 ();
}
void ParticleManager::handleParticleEvent (ClientEvent *event, Client *client,
Expand All
@@ -732,39 +730,36 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
const ParticleSpawnerParameters &p = *event->add_particlespawner .p ;
// texture pool
std::unique_ptr<ClientTexture[]> texpool = nullptr ;
size_t txpsz = 0 ;
std::vector<ClientParticleTexture> texpool;
if (!p.texpool .empty ()) {
txpsz = p.texpool .size ();
texpool = decltype (texpool)(new ClientTexture [txpsz]);
size_t txpsz = p.texpool .size ();
texpool.reserve (txpsz);
for (size_t i = 0 ; i < txpsz; ++i) {
texpool[i] = ClientTexture (p.texpool [i], client->tsrc ());
texpool. emplace_back (p.texpool [i], client->tsrc ());
}
} else {
// no texpool in use, use fallback texture
txpsz = 1 ;
texpool = decltype (texpool)(new ClientTexture[1 ] {
ClientTexture (p.texture , client->tsrc ())
});
texpool.emplace_back (p.texture , client->tsrc ());
}
auto toadd = new ParticleSpawner (client, player,
p,
event->add_particlespawner .attached_id ,
texpool,
txpsz,
this );
addParticleSpawner (event->add_particlespawner .id , toadd);
addParticleSpawner (event->add_particlespawner .id ,
std::make_unique<ParticleSpawner>(
client,
player,
p,
event->add_particlespawner .attached_id ,
std::move (texpool),
this )
);
delete event->add_particlespawner .p ;
break ;
}
case CE_SPAWN_PARTICLE: {
ParticleParameters &p = *event->spawn_particle ;
ClientTexRef texture;
ClientParticleTexRef texture;
std::unique_ptr<ClientParticleTexture> texstore;
v2f texpos, texsize;
video::SColor color (0xFFFFFFFF );
Expand All
@@ -778,9 +773,9 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
/* with no particlespawner to own the texture, we need
* to save it on the heap. it will be freed when the
* particle is destroyed */
auto texstore = new ClientTexture (p.texture , client->tsrc ());
texstore = std::make_unique<ClientParticleTexture> (p.texture , client->tsrc ());
texture = ClientTexRef (*texstore);
texture = ClientParticleTexRef (*texstore);
texpos = v2f (0 .0f , 0 .0f );
texsize = v2f (1 .0f , 1 .0f );
}
Expand All
@@ -790,10 +785,9 @@ void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
p.size = oldsize;
if (texture.ref ) {
Particle *toadd = new Particle (client, player, m_env,
p, texture, texpos, texsize, color);
addParticle (toadd);
addParticle (std::make_unique<Particle>(client, player, m_env,
p, texture, texpos, texsize, color, nullptr ,
std::move (texstore)));
}
delete event->spawn_particle ;
Expand Down
Expand Up
@@ -890,43 +884,53 @@ void ParticleManager::addNodeParticle(IGameDef *gamedef,
(f32)pos.Z + myrand_range (0 .f , .5f ) - .25f
);
Particle *toadd = new Particle (
addParticle (std::make_unique< Particle> (
gamedef,
player,
m_env,
p,
ClientTexRef (ref),
ClientParticleTexRef (ref),
texpos,
texsize,
color);
addParticle (toadd);
color));
}
void ParticleManager::reserveParticleSpace (size_t max_estimate)
{
MutexAutoLock lock (m_particle_list_lock);
m_particles.reserve (m_particles.size () + max_estimate);
}
void ParticleManager::addParticle (Particle * toadd)
void ParticleManager::addParticle (std::unique_ptr< Particle> toadd)
{
MutexAutoLock lock (m_particle_list_lock);
m_particles.push_back (toadd);
m_particles.push_back (std::move (toadd));
}
void ParticleManager::addParticleSpawner (u64 id, ParticleSpawner * toadd)
void ParticleManager::addParticleSpawner (u64 id, std::unique_ptr< ParticleSpawner> toadd)
{
MutexAutoLock lock (m_spawner_list_lock);
m_particle_spawners[id] = toadd;
auto &slot = m_particle_spawners[id];
if (slot) {
// do not kill spawners here. children are still alive
errorstream << " ParticleManager: Failed to add spawner with id " << id
<< " . Id already in use." << std::endl;
return ;
}
slot = std::move (toadd);
}
void ParticleManager::deleteParticleSpawner (u64 id)
{
MutexAutoLock lock (m_spawner_list_lock);
auto it = m_particle_spawners.find (id);
if (it != m_particle_spawners.end ()) {
it->second ->setDying ();
m_dying_particle_spawners.push_back (std::move (it->second ));
m_particle_spawners.erase (it);
}
}