3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "particles.h"
23 #include "collision.h"
24 #include "client/content_cao.h"
25 #include "client/clientevent.h"
26 #include "client/renderingengine.h"
27 #include "util/numeric.h"
29 #include "environment.h"
30 #include "clientmap.h"
43 ClientEnvironment *env,
44 const ParticleParameters &p,
45 const ClientTexRef& texture,
50 scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
51 ((Client *)gamedef)->getSceneManager()),
58 // translate blend modes to GL blend functions
59 video::E_BLEND_FACTOR bfsrc, bfdst;
60 video::E_BLEND_OPERATION blendop;
61 const auto blendmode = texture.tex != nullptr
62 ? texture.tex -> blendmode
63 : ParticleParamTypes::BlendMode::alpha;
66 case ParticleParamTypes::BlendMode::add:
67 bfsrc = video::EBF_SRC_ALPHA;
68 bfdst = video::EBF_DST_ALPHA;
69 blendop = video::EBO_ADD;
72 case ParticleParamTypes::BlendMode::sub:
73 bfsrc = video::EBF_SRC_ALPHA;
74 bfdst = video::EBF_DST_ALPHA;
75 blendop = video::EBO_REVSUBTRACT;
78 case ParticleParamTypes::BlendMode::screen:
79 bfsrc = video::EBF_ONE;
80 bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
81 blendop = video::EBO_ADD;
84 default: // includes ParticleParamTypes::BlendMode::alpha
85 bfsrc = video::EBF_SRC_ALPHA;
86 bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
87 blendop = video::EBO_ADD;
92 m_material.setFlag(video::EMF_LIGHTING, false);
93 m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
94 m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
95 m_material.setFlag(video::EMF_FOG_ENABLE, true);
97 // correctly render layered transparent particles -- see #10398
98 m_material.setFlag(video::EMF_ZWRITE_ENABLE, true);
100 // enable alpha blending and set blend mode
101 m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
102 m_material.MaterialTypeParam = video::pack_textureBlendFunc(
104 video::EMFN_MODULATE_1X,
105 video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
106 m_material.BlendOperation = blendop;
107 m_material.setTexture(0, m_texture.ref);
110 m_animation = p.animation;
113 m_base_color = color;
119 m_acceleration = p.acc;
123 m_expiration = p.expirationtime;
126 m_collisiondetection = p.collisiondetection;
127 m_collision_removal = p.collision_removal;
128 m_object_collision = p.object_collision;
129 m_vertical = p.vertical;
135 const float c = p.size / 2;
136 m_collisionbox = aabb3f(-c, -c, -c, c, c, c);
137 this->setAutomaticCulling(scene::EAC_OFF);
146 Particle::~Particle()
148 /* if our textures aren't owned by a particlespawner, we need to clean
149 * them up ourselves when the particle dies */
150 if (m_parent == nullptr)
151 delete m_texture.tex;
154 void Particle::OnRegisterSceneNode()
157 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
159 ISceneNode::OnRegisterSceneNode();
162 void Particle::render()
164 video::IVideoDriver *driver = SceneManager->getVideoDriver();
165 driver->setMaterial(m_material);
166 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
168 u16 indices[] = {0,1,2, 2,3,0};
169 driver->drawVertexPrimitiveList(m_vertices, 4,
170 indices, 2, video::EVT_STANDARD,
171 scene::EPT_TRIANGLES, video::EIT_16BIT);
174 void Particle::step(float dtime)
178 // apply drag (not handled by collisionMoveSimple) and brownian motion
179 v3f av = vecAbsolute(m_velocity);
180 av -= av * (m_drag * dtime);
181 m_velocity = av*vecSign(m_velocity) + v3f(m_jitter.pickWithin())*dtime;
183 if (m_collisiondetection) {
184 aabb3f box = m_collisionbox;
185 v3f p_pos = m_pos * BS;
186 v3f p_velocity = m_velocity * BS;
187 collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
188 box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
191 f32 bounciness = m_bounce.pickWithin();
192 if (r.collides && (m_collision_removal || bounciness > 0)) {
193 if (m_collision_removal) {
194 // force expiration of the particle
195 m_expiration = -1.0f;
196 } else if (bounciness > 0) {
197 /* cheap way to get a decent bounce effect is to only invert the
198 * largest component of the velocity vector, so e.g. you don't
199 * have a rock immediately bounce back in your face when you try
200 * to skip it across the water (as would happen if we simply
201 * downscaled and negated the velocity vector). this means
202 * bounciness will work properly for cubic objects, but meshes
203 * with diagonal angles and entities will not yield the correct
204 * visual. this is probably unavoidable */
205 if (av.Y > av.X && av.Y > av.Z) {
206 m_velocity.Y = -(m_velocity.Y * bounciness);
207 } else if (av.X > av.Y && av.X > av.Z) {
208 m_velocity.X = -(m_velocity.X * bounciness);
209 } else if (av.Z > av.Y && av.Z > av.X) {
210 m_velocity.Z = -(m_velocity.Z * bounciness);
211 } else { // well now we're in a bit of a pickle
212 m_velocity = -(m_velocity * bounciness);
216 m_velocity = p_velocity / BS;
220 // apply acceleration
221 m_velocity += m_acceleration * dtime;
222 m_pos += m_velocity * dtime;
225 if (m_animation.type != TAT_NONE) {
226 m_animation_time += dtime;
227 int frame_length_i, frame_count;
228 m_animation.determineParams(
229 m_material.getTexture(0)->getSize(),
230 &frame_count, &frame_length_i, NULL);
231 float frame_length = frame_length_i / 1000.0;
232 while (m_animation_time > frame_length) {
234 m_animation_time -= frame_length;
238 // animate particle alpha in accordance with settings
239 if (m_texture.tex != nullptr)
240 m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
250 // Update position -- see #10398
251 v3s16 camera_offset = m_env->getCameraOffset();
252 setPosition(m_pos*BS - intToFloat(camera_offset, BS));
255 void Particle::updateLight()
265 MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
267 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
269 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
271 u8 m_light = decode_light(light + m_glow);
272 m_color.set(m_alpha*255,
273 m_light * m_base_color.getRed() / 255,
274 m_light * m_base_color.getGreen() / 255,
275 m_light * m_base_color.getBlue() / 255);
278 void Particle::updateVertices()
280 f32 tx0, tx1, ty0, ty1;
283 if (m_texture.tex != nullptr)
284 scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
286 scale = v2f(1.f, 1.f);
288 if (m_animation.type != TAT_NONE) {
289 const v2u32 texsize = m_material.getTexture(0)->getSize();
290 v2f texcoord, framesize_f;
292 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
293 m_animation.determineParams(texsize, NULL, NULL, &framesize);
294 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
296 tx0 = m_texpos.X + texcoord.X;
297 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
298 ty0 = m_texpos.Y + texcoord.Y;
299 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
302 tx1 = m_texpos.X + m_texsize.X;
304 ty1 = m_texpos.Y + m_texsize.Y;
307 auto half = m_size * .5f,
310 m_vertices[0] = video::S3DVertex(-hx, -hy,
311 0, 0, 0, 0, m_color, tx0, ty1);
312 m_vertices[1] = video::S3DVertex(hx, -hy,
313 0, 0, 0, 0, m_color, tx1, ty1);
314 m_vertices[2] = video::S3DVertex(hx, hy,
315 0, 0, 0, 0, m_color, tx1, ty0);
316 m_vertices[3] = video::S3DVertex(-hx, hy,
317 0, 0, 0, 0, m_color, tx0, ty0);
321 // v3s16 camera_offset = m_env->getCameraOffset();
322 // particle position is now handled by step()
325 for (video::S3DVertex &vertex : m_vertices) {
327 v3f ppos = m_player->getPosition()/BS;
328 vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
329 core::DEGTORAD + 90);
331 vertex.Pos.rotateYZBy(m_player->getPitch());
332 vertex.Pos.rotateXZBy(m_player->getYaw());
334 m_box.addInternalPoint(vertex.Pos);
342 ParticleSpawner::ParticleSpawner(
345 const ParticleSpawnerParameters &p,
347 std::unique_ptr<ClientTexture[]>& texpool,
349 ParticleManager *p_manager
351 m_particlemanager(p_manager), p(p)
355 m_attached_id = attached_id;
356 m_texpool = std::move(texpool);
357 m_texcount = texcount;
362 m_spawntimes.reserve(p.amount + 1);
363 for (u16 i = 0; i <= p.amount; i++) {
364 float spawntime = myrand_float() * p.time;
365 m_spawntimes.push_back(spawntime);
368 size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
370 auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
371 max_particles = p.amount / maxGenerations;
373 auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
374 max_particles = p.amount * longestLife;
377 p_manager->reserveParticleSpace(max_particles * 1.2);
381 GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
384 return env->getGenericCAO(id);
388 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
389 const core::matrix4 *attached_absolute_pos_rot_matrix)
392 if (p.time != 0) { // ensure safety from divide-by-zeroes
393 fac = m_time / (p.time+0.1f);
396 auto r_pos = p.pos.blend(fac);
397 auto r_vel = p.vel.blend(fac);
398 auto r_acc = p.acc.blend(fac);
399 auto r_drag = p.drag.blend(fac);
400 auto r_radius = p.radius.blend(fac);
401 auto r_jitter = p.jitter.blend(fac);
402 auto r_bounce = p.bounce.blend(fac);
403 v3f attractor_origin = p.attractor_origin.blend(fac);
404 v3f attractor_direction = p.attractor_direction.blend(fac);
405 auto attractor_obj = findObjectByID(env, p.attractor_attachment);
406 auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
408 auto r_exp = p.exptime.blend(fac);
409 auto r_size = p.size.blend(fac);
410 auto r_attract = p.attract.blend(fac);
411 auto attract = r_attract.pickWithin();
413 v3f ppos = m_player->getPosition() / BS;
414 v3f pos = r_pos.pickWithin();
415 v3f sphere_radius = r_radius.pickWithin();
417 // Need to apply this first or the following check
418 // will be wrong for attached spawners
419 if (attached_absolute_pos_rot_matrix) {
421 attached_absolute_pos_rot_matrix->transformVect(pos);
423 v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
424 pos.X += camera_offset.X;
425 pos.Y += camera_offset.Y;
426 pos.Z += camera_offset.Z;
429 if (pos.getDistanceFromSQ(ppos) > radius*radius)
432 // Parameters for the single particle we're about to spawn
433 ParticleParameters pp;
436 pp.vel = r_vel.pickWithin();
437 pp.acc = r_acc.pickWithin();
438 pp.drag = r_drag.pickWithin();
439 pp.jitter = r_jitter;
440 pp.bounce = r_bounce;
442 if (attached_absolute_pos_rot_matrix) {
443 // Apply attachment rotation
444 attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
445 attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
449 attractor_origin += attractor_obj->getPosition() / BS;
450 if (attractor_direction_obj) {
451 auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
452 if (attractor_absolute_pos_rot_matrix)
453 attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
456 pp.expirationtime = r_exp.pickWithin();
458 if (sphere_radius != v3f()) {
459 f32 l = sphere_radius.getLength();
460 v3f mag = sphere_radius;
463 v3f ofs = v3f(l,0,0);
464 ofs.rotateXZBy(myrand_range(0.f,360.f));
465 ofs.rotateYZBy(myrand_range(0.f,360.f));
466 ofs.rotateXYBy(myrand_range(0.f,360.f));
471 if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
473 f32 dist = 0; /* =0 necessary to silence warning */
474 switch (p.attractor_kind) {
475 case ParticleParamTypes::AttractorKind::none:
478 case ParticleParamTypes::AttractorKind::point: {
479 dist = pp.pos.getDistanceFrom(attractor_origin);
480 dir = pp.pos - attractor_origin;
485 case ParticleParamTypes::AttractorKind::line: {
486 // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
487 const auto& lorigin = attractor_origin;
488 v3f ldir = attractor_direction;
490 auto origin_to_point = pp.pos - lorigin;
491 auto scalar_projection = origin_to_point.dotProduct(ldir);
492 auto point_on_line = lorigin + (ldir * scalar_projection);
494 dist = pp.pos.getDistanceFrom(point_on_line);
495 dir = (point_on_line - pp.pos);
497 dir *= -1; // flip it around so strength=1 attracts, not repulses
501 case ParticleParamTypes::AttractorKind::plane: {
502 // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
503 const v3f& porigin = attractor_origin;
504 v3f normal = attractor_direction;
506 v3f point_to_origin = porigin - pp.pos;
507 f32 factor = normal.dotProduct(point_to_origin);
508 if (numericAbsolute(factor) == 0.0f) {
511 factor = numericSign(factor);
512 dir = normal * factor;
514 dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
515 dir *= -1; // flip it around so strength=1 attracts, not repulses
520 f32 speedTowards = numericAbsolute(attract) * dist;
521 v3f avel = dir * speedTowards;
522 if (attract > 0 && speedTowards > 0) {
524 if (p.attractor_kill) {
525 // make sure the particle dies after crossing the attractor threshold
526 f32 timeToCenter = dist / speedTowards;
527 if (timeToCenter < pp.expirationtime)
528 pp.expirationtime = timeToCenter;
536 ClientTexRef texture;
538 video::SColor color(0xFFFFFFFF);
540 if (p.node.getContent() != CONTENT_IGNORE) {
541 const ContentFeatures &f =
542 m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
543 if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
544 texpos, texsize, &color, p.node_tile))
549 texture = decltype(texture)(m_texpool[m_texcount == 1 ? 0 : myrand_range(0,m_texcount-1)]);
550 texpos = v2f(0.0f, 0.0f);
551 texsize = v2f(1.0f, 1.0f);
552 if (texture.tex->animated)
553 pp.animation = texture.tex->animation;
556 // synchronize animation length with particle life if desired
557 if (pp.animation.type != TAT_NONE) {
558 if (pp.animation.type == TAT_VERTICAL_FRAMES &&
559 pp.animation.vertical_frames.length < 0) {
560 auto& a = pp.animation.vertical_frames;
561 // we add a tiny extra value to prevent the first frame
562 // from flickering back on just before the particle dies
563 a.length = (pp.expirationtime / -a.length) + 0.1;
564 } else if (pp.animation.type == TAT_SHEET_2D &&
565 pp.animation.sheet_2d.frame_length < 0) {
566 auto& a = pp.animation.sheet_2d;
567 auto frames = a.frames_w * a.frames_h;
568 auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
569 pp.animation.sheet_2d.frame_length = frames / runtime;
573 // Allow keeping default random size
574 if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
575 pp.size = r_size.pickWithin();
578 auto pa = new Particle(
589 m_particlemanager->addParticle(pa);
592 void ParticleSpawner::step(float dtime, ClientEnvironment *env)
596 static thread_local const float radius =
597 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
599 bool unloaded = false;
600 const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
602 if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
603 attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
610 // Spawner exists for a predefined timespan
611 for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) {
612 if ((*i) <= m_time && p.amount > 0) {
615 // Pretend to, but don't actually spawn a particle if it is
616 // attached to an unloaded object or distant from player.
618 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
620 i = m_spawntimes.erase(i);
626 // Spawner exists for an infinity timespan, spawn on a per-second base
628 // Skip this step if attached to an unloaded object
632 for (int i = 0; i <= p.amount; i++) {
633 if (myrand_float() < dtime)
634 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
643 ParticleManager::ParticleManager(ClientEnvironment *env) :
647 ParticleManager::~ParticleManager()
652 void ParticleManager::step(float dtime)
654 stepParticles (dtime);
655 stepSpawners (dtime);
658 void ParticleManager::stepSpawners(float dtime)
660 MutexAutoLock lock(m_spawner_list_lock);
661 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
662 if (i->second->getExpired()) {
663 // the particlespawner owns the textures, so we need to make
664 // sure there are no active particles before we free it
665 if (i->second->m_active == 0) {
667 m_particle_spawners.erase(i++);
672 i->second->step(dtime, m_env);
678 void ParticleManager::stepParticles(float dtime)
680 MutexAutoLock lock(m_particle_list_lock);
681 for (auto i = m_particles.begin(); i != m_particles.end();) {
682 if ((*i)->get_expired()) {
683 if ((*i)->m_parent) {
684 assert((*i)->m_parent->m_active != 0);
685 --(*i)->m_parent->m_active;
689 i = m_particles.erase(i);
697 void ParticleManager::clearAll()
699 MutexAutoLock lock(m_spawner_list_lock);
700 MutexAutoLock lock2(m_particle_list_lock);
701 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
703 m_particle_spawners.erase(i++);
706 for(auto i = m_particles.begin(); i != m_particles.end();)
710 i = m_particles.erase(i);
714 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
717 switch (event->type) {
718 case CE_DELETE_PARTICLESPAWNER: {
719 deleteParticleSpawner(event->delete_particlespawner.id);
720 // no allocated memory in delete event
723 case CE_ADD_PARTICLESPAWNER: {
724 deleteParticleSpawner(event->add_particlespawner.id);
726 const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
729 std::unique_ptr<ClientTexture[]> texpool = nullptr;
731 if (!p.texpool.empty()) {
732 txpsz = p.texpool.size();
733 texpool = decltype(texpool)(new ClientTexture [txpsz]);
735 for (size_t i = 0; i < txpsz; ++i) {
736 texpool[i] = ClientTexture(p.texpool[i], client->tsrc());
739 // no texpool in use, use fallback texture
741 texpool = decltype(texpool)(new ClientTexture[1] {
742 ClientTexture(p.texture, client->tsrc())
746 auto toadd = new ParticleSpawner(client, player,
748 event->add_particlespawner.attached_id,
753 addParticleSpawner(event->add_particlespawner.id, toadd);
755 delete event->add_particlespawner.p;
758 case CE_SPAWN_PARTICLE: {
759 ParticleParameters &p = *event->spawn_particle;
761 ClientTexRef texture;
763 video::SColor color(0xFFFFFFFF);
765 f32 oldsize = p.size;
767 if (p.node.getContent() != CONTENT_IGNORE) {
768 const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
769 getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
770 texsize, &color, p.node_tile);
772 /* with no particlespawner to own the texture, we need
773 * to save it on the heap. it will be freed when the
774 * particle is destroyed */
775 auto texstore = new ClientTexture(p.texture, client->tsrc());
777 texture = ClientTexRef(*texstore);
778 texpos = v2f(0.0f, 0.0f);
779 texsize = v2f(1.0f, 1.0f);
782 // Allow keeping default random size
787 Particle *toadd = new Particle(client, player, m_env,
788 p, texture, texpos, texsize, color);
793 delete event->spawn_particle;
800 bool ParticleManager::getNodeParticleParams(const MapNode &n,
801 const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture,
802 v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum)
804 // No particles for "airlike" nodes
805 if (f.drawtype == NDT_AIRLIKE)
810 if (tilenum > 0 && tilenum <= 6)
813 texid = myrand_range(0,5);
814 const TileLayer &tile = f.tiles[texid].layers[0];
815 p.animation.type = TAT_NONE;
817 // Only use first frame of animated texture
818 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
819 *texture = (*tile.frames)[0].texture;
821 *texture = tile.texture;
823 float size = (myrand_range(0,8)) / 64.0f;
827 texsize = v2f(size * 2.0f, size * 2.0f);
828 texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
829 texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
834 n.getColor(f, color);
839 // The final burst of particles when a node is finally dug, *not* particles
840 // spawned during the digging of a node.
842 void ParticleManager::addDiggingParticles(IGameDef *gamedef,
843 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
845 // No particles for "airlike" nodes
846 if (f.drawtype == NDT_AIRLIKE)
849 for (u16 j = 0; j < 16; j++) {
850 addNodeParticle(gamedef, player, pos, n, f);
854 // During the digging of a node particles are spawned individually by this
855 // function, called from Game::handleDigging() in game.cpp.
857 void ParticleManager::addNodeParticle(IGameDef *gamedef,
858 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
860 ParticleParameters p;
861 video::ITexture *ref = nullptr;
865 if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
868 p.expirationtime = myrand_range(0, 100) / 100.0f;
872 myrand_range(-1.5f,1.5f),
873 myrand_range(0.f,3.f),
874 myrand_range(-1.5f,1.5f)
878 -player->movement_gravity * player->physics_override_gravity / BS,
882 (f32)pos.X + myrand_range(0.f, .5f) - .25f,
883 (f32)pos.Y + myrand_range(0.f, .5f) - .25f,
884 (f32)pos.Z + myrand_range(0.f, .5f) - .25f
887 Particle *toadd = new Particle(
900 void ParticleManager::reserveParticleSpace(size_t max_estimate)
902 MutexAutoLock lock(m_particle_list_lock);
903 m_particles.reserve(m_particles.size() + max_estimate);
906 void ParticleManager::addParticle(Particle *toadd)
908 MutexAutoLock lock(m_particle_list_lock);
909 m_particles.push_back(toadd);
913 void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd)
915 MutexAutoLock lock(m_spawner_list_lock);
916 m_particle_spawners[id] = toadd;
919 void ParticleManager::deleteParticleSpawner(u64 id)
921 MutexAutoLock lock(m_spawner_list_lock);
922 auto it = m_particle_spawners.find(id);
923 if (it != m_particle_spawners.end()) {
924 it->second->setDying();