]> git.lizzy.rs Git - dragonfireclient.git/blob - src/particles.cpp
Implement GItlab CI daily builds for windows platform (32 & 64) (#5923)
[dragonfireclient.git] / src / particles.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
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.
9
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.
14
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.
18 */
19
20 #include "particles.h"
21 #include "client.h"
22 #include "collision.h"
23 #include <stdlib.h>
24 #include "util/numeric.h"
25 #include "light.h"
26 #include "environment.h"
27 #include "clientmap.h"
28 #include "mapnode.h"
29 #include "client.h"
30 #include "settings.h"
31
32 /*
33         Utility
34 */
35
36 v3f random_v3f(v3f min, v3f max)
37 {
38         return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
39                         rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
40                         rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
41 }
42
43 Particle::Particle(
44         IGameDef *gamedef,
45         scene::ISceneManager* smgr,
46         LocalPlayer *player,
47         ClientEnvironment *env,
48         v3f pos,
49         v3f velocity,
50         v3f acceleration,
51         float expirationtime,
52         float size,
53         bool collisiondetection,
54         bool collision_removal,
55         bool vertical,
56         video::ITexture *texture,
57         v2f texpos,
58         v2f texsize,
59         const struct TileAnimationParams &anim,
60         u8 glow,
61         video::SColor color
62 ):
63         scene::ISceneNode(smgr->getRootSceneNode(), smgr)
64 {
65         // Misc
66         m_gamedef = gamedef;
67         m_env = env;
68
69         // Texture
70         m_material.setFlag(video::EMF_LIGHTING, false);
71         m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
72         m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
73         m_material.setFlag(video::EMF_FOG_ENABLE, true);
74         m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
75         m_material.setTexture(0, texture);
76         m_texpos = texpos;
77         m_texsize = texsize;
78         m_animation = anim;
79         m_animation_frame = 0;
80         m_animation_time = 0.0;
81
82         // Color
83         m_base_color = color;
84         m_color = color;
85
86         // Particle related
87         m_pos = pos;
88         m_velocity = velocity;
89         m_acceleration = acceleration;
90         m_expiration = expirationtime;
91         m_time = 0;
92         m_player = player;
93         m_size = size;
94         m_collisiondetection = collisiondetection;
95         m_collision_removal = collision_removal;
96         m_vertical = vertical;
97         m_glow = glow;
98
99         // Irrlicht stuff
100         m_collisionbox = aabb3f
101                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
102         this->setAutomaticCulling(scene::EAC_OFF);
103
104         // Init lighting
105         updateLight();
106
107         // Init model
108         updateVertices();
109 }
110
111 Particle::~Particle()
112 {
113 }
114
115 void Particle::OnRegisterSceneNode()
116 {
117         if (IsVisible)
118                 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
119
120         ISceneNode::OnRegisterSceneNode();
121 }
122
123 void Particle::render()
124 {
125         video::IVideoDriver* driver = SceneManager->getVideoDriver();
126         driver->setMaterial(m_material);
127         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
128
129         u16 indices[] = {0,1,2, 2,3,0};
130         driver->drawVertexPrimitiveList(m_vertices, 4,
131                         indices, 2, video::EVT_STANDARD,
132                         scene::EPT_TRIANGLES, video::EIT_16BIT);
133 }
134
135 void Particle::step(float dtime)
136 {
137         m_time += dtime;
138         if (m_collisiondetection) {
139                 aabb3f box = m_collisionbox;
140                 v3f p_pos = m_pos * BS;
141                 v3f p_velocity = m_velocity * BS;
142                 collisionMoveResult r = collisionMoveSimple(m_env,
143                         m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
144                         &p_velocity, m_acceleration * BS);
145                 if (m_collision_removal && r.collides) {
146                         // force expiration of the particle
147                         m_expiration = -1.0;
148                 } else {
149                         m_pos = p_pos / BS;
150                         m_velocity = p_velocity / BS;
151                 }
152         } else {
153                 m_velocity += m_acceleration * dtime;
154                 m_pos += m_velocity * dtime;
155         }
156         if (m_animation.type != TAT_NONE) {
157                 m_animation_time += dtime;
158                 int frame_length_i, frame_count;
159                 m_animation.determineParams(
160                                 m_material.getTexture(0)->getSize(),
161                                 &frame_count, &frame_length_i, NULL);
162                 float frame_length = frame_length_i / 1000.0;
163                 while (m_animation_time > frame_length) {
164                         m_animation_frame++;
165                         m_animation_time -= frame_length;
166                 }
167         }
168
169         // Update lighting
170         updateLight();
171
172         // Update model
173         updateVertices();
174 }
175
176 void Particle::updateLight()
177 {
178         u8 light = 0;
179         bool pos_ok;
180
181         v3s16 p = v3s16(
182                 floor(m_pos.X+0.5),
183                 floor(m_pos.Y+0.5),
184                 floor(m_pos.Z+0.5)
185         );
186         MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
187         if (pos_ok)
188                 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
189         else
190                 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
191
192         u8 m_light = decode_light(light + m_glow);
193         m_color.set(255,
194                 m_light * m_base_color.getRed() / 255,
195                 m_light * m_base_color.getGreen() / 255,
196                 m_light * m_base_color.getBlue() / 255);
197 }
198
199 void Particle::updateVertices()
200 {
201         f32 tx0, tx1, ty0, ty1;
202
203         if (m_animation.type != TAT_NONE) {
204                 const v2u32 texsize = m_material.getTexture(0)->getSize();
205                 v2f texcoord, framesize_f;
206                 v2u32 framesize;
207                 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
208                 m_animation.determineParams(texsize, NULL, NULL, &framesize);
209                 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
210
211                 tx0 = m_texpos.X + texcoord.X;
212                 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
213                 ty0 = m_texpos.Y + texcoord.Y;
214                 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
215         } else {
216                 tx0 = m_texpos.X;
217                 tx1 = m_texpos.X + m_texsize.X;
218                 ty0 = m_texpos.Y;
219                 ty1 = m_texpos.Y + m_texsize.Y;
220         }
221
222         m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
223                 0, 0, 0, 0, m_color, tx0, ty1);
224         m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
225                 0, 0, 0, 0, m_color, tx1, ty1);
226         m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
227                 0, 0, 0, 0, m_color, tx1, ty0);
228         m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
229                 0, 0, 0, 0, m_color, tx0, ty0);
230
231         v3s16 camera_offset = m_env->getCameraOffset();
232         for(u16 i=0; i<4; i++)
233         {
234                 if (m_vertical) {
235                         v3f ppos = m_player->getPosition()/BS;
236                         m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
237                 } else {
238                         m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
239                         m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
240                 }
241                 m_box.addInternalPoint(m_vertices[i].Pos);
242                 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
243         }
244 }
245
246 /*
247         ParticleSpawner
248 */
249
250 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
251         u16 amount, float time,
252         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
253         float minexptime, float maxexptime, float minsize, float maxsize,
254         bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
255         video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
256         u8 glow,
257         ParticleManager *p_manager) :
258         m_particlemanager(p_manager)
259 {
260         m_gamedef = gamedef;
261         m_smgr = smgr;
262         m_player = player;
263         m_amount = amount;
264         m_spawntime = time;
265         m_minpos = minpos;
266         m_maxpos = maxpos;
267         m_minvel = minvel;
268         m_maxvel = maxvel;
269         m_minacc = minacc;
270         m_maxacc = maxacc;
271         m_minexptime = minexptime;
272         m_maxexptime = maxexptime;
273         m_minsize = minsize;
274         m_maxsize = maxsize;
275         m_collisiondetection = collisiondetection;
276         m_collision_removal = collision_removal;
277         m_attached_id = attached_id;
278         m_vertical = vertical;
279         m_texture = texture;
280         m_time = 0;
281         m_animation = anim;
282         m_glow = glow;
283
284         for (u16 i = 0; i<=m_amount; i++)
285         {
286                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
287                 m_spawntimes.push_back(spawntime);
288         }
289 }
290
291 ParticleSpawner::~ParticleSpawner() {}
292
293 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
294 {
295         m_time += dtime;
296
297         static const float radius =
298                         g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
299
300         bool unloaded = false;
301         bool is_attached = false;
302         v3f attached_pos = v3f(0,0,0);
303         float attached_yaw = 0;
304         if (m_attached_id != 0) {
305                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
306                         attached_pos = attached->getPosition() / BS;
307                         attached_yaw = attached->getYaw();
308                         is_attached = true;
309                 } else {
310                         unloaded = true;
311                 }
312         }
313
314         if (m_spawntime != 0) // Spawner exists for a predefined timespan
315         {
316                 for(std::vector<float>::iterator i = m_spawntimes.begin();
317                                 i != m_spawntimes.end();)
318                 {
319                         if ((*i) <= m_time && m_amount > 0)
320                         {
321                                 m_amount--;
322
323                                 // Pretend to, but don't actually spawn a particle if it is
324                                 // attached to an unloaded object or distant from player.
325                                 if (!unloaded) {
326                                         v3f ppos = m_player->getPosition() / BS;
327                                         v3f pos = random_v3f(m_minpos, m_maxpos);
328
329                                         if (pos.getDistanceFrom(ppos) <= radius) {
330                                                 v3f vel = random_v3f(m_minvel, m_maxvel);
331                                                 v3f acc = random_v3f(m_minacc, m_maxacc);
332
333                                                 if (is_attached) {
334                                                         // Apply attachment yaw and position
335                                                         pos.rotateXZBy(attached_yaw);
336                                                         pos += attached_pos;
337                                                         vel.rotateXZBy(attached_yaw);
338                                                         acc.rotateXZBy(attached_yaw);
339                                                 }
340
341                                                 float exptime = rand()/(float)RAND_MAX
342                                                                 *(m_maxexptime-m_minexptime)
343                                                                 +m_minexptime;
344                                                 float size = rand()/(float)RAND_MAX
345                                                                 *(m_maxsize-m_minsize)
346                                                                 +m_minsize;
347
348                                                 Particle* toadd = new Particle(
349                                                         m_gamedef,
350                                                         m_smgr,
351                                                         m_player,
352                                                         env,
353                                                         pos,
354                                                         vel,
355                                                         acc,
356                                                         exptime,
357                                                         size,
358                                                         m_collisiondetection,
359                                                         m_collision_removal,
360                                                         m_vertical,
361                                                         m_texture,
362                                                         v2f(0.0, 0.0),
363                                                         v2f(1.0, 1.0),
364                                                         m_animation,
365                                                         m_glow);
366                                                 m_particlemanager->addParticle(toadd);
367                                         }
368                                 }
369                                 i = m_spawntimes.erase(i);
370                         }
371                         else
372                         {
373                                 ++i;
374                         }
375                 }
376         }
377         else // Spawner exists for an infinity timespan, spawn on a per-second base
378         {
379                 // Skip this step if attached to an unloaded object
380                 if (unloaded)
381                         return;
382                 for (int i = 0; i <= m_amount; i++)
383                 {
384                         if (rand()/(float)RAND_MAX < dtime)
385                         {
386                                 // Do not spawn particle if distant from player
387                                 v3f ppos = m_player->getPosition() / BS;
388                                 v3f pos = random_v3f(m_minpos, m_maxpos);
389
390                                 if (pos.getDistanceFrom(ppos) <= radius) {
391                                         v3f vel = random_v3f(m_minvel, m_maxvel);
392                                         v3f acc = random_v3f(m_minacc, m_maxacc);
393
394                                         if (is_attached) {
395                                                 // Apply attachment yaw and position
396                                                 pos.rotateXZBy(attached_yaw);
397                                                 pos += attached_pos;
398                                                 vel.rotateXZBy(attached_yaw);
399                                                 acc.rotateXZBy(attached_yaw);
400                                         }
401
402                                         float exptime = rand()/(float)RAND_MAX
403                                                         *(m_maxexptime-m_minexptime)
404                                                         +m_minexptime;
405                                         float size = rand()/(float)RAND_MAX
406                                                         *(m_maxsize-m_minsize)
407                                                         +m_minsize;
408
409                                         Particle* toadd = new Particle(
410                                                 m_gamedef,
411                                                 m_smgr,
412                                                 m_player,
413                                                 env,
414                                                 pos,
415                                                 vel,
416                                                 acc,
417                                                 exptime,
418                                                 size,
419                                                 m_collisiondetection,
420                                                 m_collision_removal,
421                                                 m_vertical,
422                                                 m_texture,
423                                                 v2f(0.0, 0.0),
424                                                 v2f(1.0, 1.0),
425                                                 m_animation,
426                                                 m_glow);
427                                         m_particlemanager->addParticle(toadd);
428                                 }
429                         }
430                 }
431         }
432 }
433
434
435 ParticleManager::ParticleManager(ClientEnvironment* env) :
436         m_env(env)
437 {}
438
439 ParticleManager::~ParticleManager()
440 {
441         clearAll();
442 }
443
444 void ParticleManager::step(float dtime)
445 {
446         stepParticles (dtime);
447         stepSpawners (dtime);
448 }
449
450 void ParticleManager::stepSpawners (float dtime)
451 {
452         MutexAutoLock lock(m_spawner_list_lock);
453         for (std::map<u32, ParticleSpawner*>::iterator i =
454                         m_particle_spawners.begin();
455                         i != m_particle_spawners.end();)
456         {
457                 if (i->second->get_expired())
458                 {
459                         delete i->second;
460                         m_particle_spawners.erase(i++);
461                 }
462                 else
463                 {
464                         i->second->step(dtime, m_env);
465                         ++i;
466                 }
467         }
468 }
469
470 void ParticleManager::stepParticles (float dtime)
471 {
472         MutexAutoLock lock(m_particle_list_lock);
473         for(std::vector<Particle*>::iterator i = m_particles.begin();
474                         i != m_particles.end();)
475         {
476                 if ((*i)->get_expired())
477                 {
478                         (*i)->remove();
479                         delete *i;
480                         i = m_particles.erase(i);
481                 }
482                 else
483                 {
484                         (*i)->step(dtime);
485                         ++i;
486                 }
487         }
488 }
489
490 void ParticleManager::clearAll ()
491 {
492         MutexAutoLock lock(m_spawner_list_lock);
493         MutexAutoLock lock2(m_particle_list_lock);
494         for(std::map<u32, ParticleSpawner*>::iterator i =
495                         m_particle_spawners.begin();
496                         i != m_particle_spawners.end();)
497         {
498                 delete i->second;
499                 m_particle_spawners.erase(i++);
500         }
501
502         for(std::vector<Particle*>::iterator i =
503                         m_particles.begin();
504                         i != m_particles.end();)
505         {
506                 (*i)->remove();
507                 delete *i;
508                 i = m_particles.erase(i);
509         }
510 }
511
512 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
513                 scene::ISceneManager* smgr, LocalPlayer *player)
514 {
515         switch (event->type) {
516                 case CE_DELETE_PARTICLESPAWNER: {
517                         MutexAutoLock lock(m_spawner_list_lock);
518                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
519                                         m_particle_spawners.end()) {
520                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
521                                 m_particle_spawners.erase(event->delete_particlespawner.id);
522                         }
523                         // no allocated memory in delete event
524                         break;
525                 }
526                 case CE_ADD_PARTICLESPAWNER: {
527                         {
528                                 MutexAutoLock lock(m_spawner_list_lock);
529                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
530                                                 m_particle_spawners.end()) {
531                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
532                                         m_particle_spawners.erase(event->add_particlespawner.id);
533                                 }
534                         }
535
536                         video::ITexture *texture =
537                                 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
538
539                         ParticleSpawner* toadd = new ParticleSpawner(client, smgr, player,
540                                         event->add_particlespawner.amount,
541                                         event->add_particlespawner.spawntime,
542                                         *event->add_particlespawner.minpos,
543                                         *event->add_particlespawner.maxpos,
544                                         *event->add_particlespawner.minvel,
545                                         *event->add_particlespawner.maxvel,
546                                         *event->add_particlespawner.minacc,
547                                         *event->add_particlespawner.maxacc,
548                                         event->add_particlespawner.minexptime,
549                                         event->add_particlespawner.maxexptime,
550                                         event->add_particlespawner.minsize,
551                                         event->add_particlespawner.maxsize,
552                                         event->add_particlespawner.collisiondetection,
553                                         event->add_particlespawner.collision_removal,
554                                         event->add_particlespawner.attached_id,
555                                         event->add_particlespawner.vertical,
556                                         texture,
557                                         event->add_particlespawner.id,
558                                         event->add_particlespawner.animation,
559                                         event->add_particlespawner.glow,
560                                         this);
561
562                         /* delete allocated content of event */
563                         delete event->add_particlespawner.minpos;
564                         delete event->add_particlespawner.maxpos;
565                         delete event->add_particlespawner.minvel;
566                         delete event->add_particlespawner.maxvel;
567                         delete event->add_particlespawner.minacc;
568                         delete event->add_particlespawner.texture;
569                         delete event->add_particlespawner.maxacc;
570
571                         {
572                                 MutexAutoLock lock(m_spawner_list_lock);
573                                 m_particle_spawners.insert(
574                                                 std::pair<u32, ParticleSpawner*>(
575                                                                 event->add_particlespawner.id,
576                                                                 toadd));
577                         }
578                         break;
579                 }
580                 case CE_SPAWN_PARTICLE: {
581                         video::ITexture *texture =
582                                 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
583
584                         Particle* toadd = new Particle(client, smgr, player, m_env,
585                                         *event->spawn_particle.pos,
586                                         *event->spawn_particle.vel,
587                                         *event->spawn_particle.acc,
588                                         event->spawn_particle.expirationtime,
589                                         event->spawn_particle.size,
590                                         event->spawn_particle.collisiondetection,
591                                         event->spawn_particle.collision_removal,
592                                         event->spawn_particle.vertical,
593                                         texture,
594                                         v2f(0.0, 0.0),
595                                         v2f(1.0, 1.0),
596                                         event->spawn_particle.animation,
597                                         event->spawn_particle.glow);
598
599                         addParticle(toadd);
600
601                         delete event->spawn_particle.pos;
602                         delete event->spawn_particle.vel;
603                         delete event->spawn_particle.acc;
604                         delete event->spawn_particle.texture;
605
606                         break;
607                 }
608                 default: break;
609         }
610 }
611
612 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
613         scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
614         const MapNode &n, const ContentFeatures &f)
615 {
616         for (u16 j = 0; j < 32; j++) // set the amount of particles here
617         {
618                 addNodeParticle(gamedef, smgr, player, pos, n, f);
619         }
620 }
621
622 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
623         scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
624         const MapNode &n, const ContentFeatures &f)
625 {
626         addNodeParticle(gamedef, smgr, player, pos, n, f);
627 }
628
629 void ParticleManager::addNodeParticle(IGameDef* gamedef,
630         scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
631         const MapNode &n, const ContentFeatures &f)
632 {
633         // Texture
634         u8 texid = myrand_range(0, 5);
635         const TileLayer &tile = f.tiles[texid].layers[0];
636         video::ITexture *texture;
637         struct TileAnimationParams anim;
638         anim.type = TAT_NONE;
639
640         // Only use first frame of animated texture
641         if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
642                 texture = tile.frames[0].texture;
643         else
644                 texture = tile.texture;
645
646         float size = rand() % 64 / 512.;
647         float visual_size = BS * size;
648         v2f texsize(size * 2, size * 2);
649         v2f texpos;
650         texpos.X = ((rand() % 64) / 64. - texsize.X);
651         texpos.Y = ((rand() % 64) / 64. - texsize.Y);
652
653         // Physics
654         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
655                         rand() % 100 / 35.,
656                         (rand() % 100 / 50. - 1) / 1.5);
657
658         v3f acceleration(0,-9,0);
659         v3f particlepos = v3f(
660                 (f32) pos.X + rand() %100 /200. - 0.25,
661                 (f32) pos.Y + rand() %100 /200. - 0.25,
662                 (f32) pos.Z + rand() %100 /200. - 0.25
663         );
664
665         video::SColor color;
666         if (tile.has_color)
667                 color = tile.color;
668         else
669                 n.getColor(f, &color);
670
671         Particle* toadd = new Particle(
672                 gamedef,
673                 smgr,
674                 player,
675                 m_env,
676                 particlepos,
677                 velocity,
678                 acceleration,
679                 rand() % 100 / 100., // expiration time
680                 visual_size,
681                 true,
682                 false,
683                 false,
684                 texture,
685                 texpos,
686                 texsize,
687                 anim,
688                 0,
689                 color);
690
691         addParticle(toadd);
692 }
693
694 void ParticleManager::addParticle(Particle* toadd)
695 {
696         MutexAutoLock lock(m_particle_list_lock);
697         m_particles.push_back(toadd);
698 }