Fix memory leaks due to messed up memory handling for particles as well as their...
[oweals/minetest.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 "constants.h"
22 #include "debug.h"
23 #include "main.h" // For g_profiler and g_settings
24 #include "settings.h"
25 #include "tile.h"
26 #include "gamedef.h"
27 #include "collision.h"
28 #include <stdlib.h>
29 #include "util/numeric.h"
30 #include "light.h"
31 #include "environment.h"
32 #include "clientmap.h"
33 #include "mapnode.h"
34 #include "client.h"
35
36 /*
37         Utility
38 */
39
40 v3f random_v3f(v3f min, v3f max)
41 {
42         return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
43                         rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
44                         rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
45 }
46
47 Particle::Particle(
48         IGameDef *gamedef,
49         scene::ISceneManager* smgr,
50         LocalPlayer *player,
51         ClientEnvironment *env,
52         v3f pos,
53         v3f velocity,
54         v3f acceleration,
55         float expirationtime,
56         float size,
57         bool collisiondetection,
58         bool vertical,
59         video::ITexture *texture,
60         v2f texpos,
61         v2f texsize
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
79
80         // Particle related
81         m_pos = pos;
82         m_velocity = velocity;
83         m_acceleration = acceleration;
84         m_expiration = expirationtime;
85         m_time = 0;
86         m_player = player;
87         m_size = size;
88         m_collisiondetection = collisiondetection;
89         m_vertical = vertical;
90
91         // Irrlicht stuff
92         m_collisionbox = core::aabbox3d<f32>
93                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
94         this->setAutomaticCulling(scene::EAC_OFF);
95
96         // Init lighting
97         updateLight();
98
99         // Init model
100         updateVertices();
101 }
102
103 Particle::~Particle()
104 {
105 }
106
107 void Particle::OnRegisterSceneNode()
108 {
109         if (IsVisible)
110         {
111                 SceneManager->registerNodeForRendering
112                                 (this, scene::ESNRP_TRANSPARENT);
113                 SceneManager->registerNodeForRendering
114                                 (this, scene::ESNRP_SOLID);
115         }
116
117         ISceneNode::OnRegisterSceneNode();
118 }
119
120 void Particle::render()
121 {
122         // TODO: Render particles in front of water and the selectionbox
123
124         video::IVideoDriver* driver = SceneManager->getVideoDriver();
125         driver->setMaterial(m_material);
126         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
127
128         u16 indices[] = {0,1,2, 2,3,0};
129         driver->drawVertexPrimitiveList(m_vertices, 4,
130                         indices, 2, video::EVT_STANDARD,
131                         scene::EPT_TRIANGLES, video::EIT_16BIT);
132 }
133
134 void Particle::step(float dtime)
135 {
136         m_time += dtime;
137         if (m_collisiondetection)
138         {
139                 core::aabbox3d<f32> box = m_collisionbox;
140                 v3f p_pos = m_pos*BS;
141                 v3f p_velocity = m_velocity*BS;
142                 v3f p_acceleration = m_acceleration*BS;
143                 collisionMoveSimple(m_env, m_gamedef,
144                         BS*0.5, box,
145                         0, dtime,
146                         p_pos, p_velocity, p_acceleration);
147                 m_pos = p_pos/BS;
148                 m_velocity = p_velocity/BS;
149                 m_acceleration = p_acceleration/BS;
150         }
151         else
152         {
153                 m_velocity += m_acceleration * dtime;
154                 m_pos += m_velocity * dtime;
155         }
156
157         // Update lighting
158         updateLight();
159
160         // Update model
161         updateVertices();
162 }
163
164 void Particle::updateLight()
165 {
166         u8 light = 0;
167         bool pos_ok;
168
169         v3s16 p = v3s16(
170                 floor(m_pos.X+0.5),
171                 floor(m_pos.Y+0.5),
172                 floor(m_pos.Z+0.5)
173         );
174         MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
175         if (pos_ok)
176                 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
177         else
178                 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
179
180         m_light = decode_light(light);
181 }
182
183 void Particle::updateVertices()
184 {
185         video::SColor c(255, m_light, m_light, m_light);
186         f32 tx0 = m_texpos.X;
187         f32 tx1 = m_texpos.X + m_texsize.X;
188         f32 ty0 = m_texpos.Y;
189         f32 ty1 = m_texpos.Y + m_texsize.Y;
190
191         m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
192                         c, tx0, ty1);
193         m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
194                         c, tx1, ty1);
195         m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
196                         c, tx1, ty0);
197         m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
198                         c, tx0, ty0);
199
200         v3s16 camera_offset = m_env->getCameraOffset();
201         for(u16 i=0; i<4; i++)
202         {
203                 if (m_vertical) {
204                         v3f ppos = m_player->getPosition()/BS;
205                         m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
206                 } else {
207                         m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
208                         m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
209                 }
210                 m_box.addInternalPoint(m_vertices[i].Pos);
211                 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
212         }
213 }
214
215 /*
216         ParticleSpawner
217 */
218
219 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
220         u16 amount, float time,
221         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
222         float minexptime, float maxexptime, float minsize, float maxsize,
223         bool collisiondetection, bool vertical, video::ITexture *texture, u32 id,
224         ParticleManager *p_manager) :
225         m_particlemanager(p_manager)
226 {
227         m_gamedef = gamedef;
228         m_smgr = smgr;
229         m_player = player;
230         m_amount = amount;
231         m_spawntime = time;
232         m_minpos = minpos;
233         m_maxpos = maxpos;
234         m_minvel = minvel;
235         m_maxvel = maxvel;
236         m_minacc = minacc;
237         m_maxacc = maxacc;
238         m_minexptime = minexptime;
239         m_maxexptime = maxexptime;
240         m_minsize = minsize;
241         m_maxsize = maxsize;
242         m_collisiondetection = collisiondetection;
243         m_vertical = vertical;
244         m_texture = texture;
245         m_time = 0;
246
247         for (u16 i = 0; i<=m_amount; i++)
248         {
249                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
250                 m_spawntimes.push_back(spawntime);
251         }
252 }
253
254 ParticleSpawner::~ParticleSpawner() {}
255
256 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
257 {
258         m_time += dtime;
259
260         if (m_spawntime != 0) // Spawner exists for a predefined timespan
261         {
262                 for(std::vector<float>::iterator i = m_spawntimes.begin();
263                                 i != m_spawntimes.end();)
264                 {
265                         if ((*i) <= m_time && m_amount > 0)
266                         {
267                                 m_amount--;
268
269                                 v3f pos = random_v3f(m_minpos, m_maxpos);
270                                 v3f vel = random_v3f(m_minvel, m_maxvel);
271                                 v3f acc = random_v3f(m_minacc, m_maxacc);
272                                 float exptime = rand()/(float)RAND_MAX
273                                                 *(m_maxexptime-m_minexptime)
274                                                 +m_minexptime;
275                                 float size = rand()/(float)RAND_MAX
276                                                 *(m_maxsize-m_minsize)
277                                                 +m_minsize;
278
279                                 Particle* toadd = new Particle(
280                                         m_gamedef,
281                                         m_smgr,
282                                         m_player,
283                                         env,
284                                         pos,
285                                         vel,
286                                         acc,
287                                         exptime,
288                                         size,
289                                         m_collisiondetection,
290                                         m_vertical,
291                                         m_texture,
292                                         v2f(0.0, 0.0),
293                                         v2f(1.0, 1.0));
294                                 m_particlemanager->addParticle(toadd);
295                                 i = m_spawntimes.erase(i);
296                         }
297                         else
298                         {
299                                 i++;
300                         }
301                 }
302         }
303         else // Spawner exists for an infinity timespan, spawn on a per-second base
304         {
305                 for (int i = 0; i <= m_amount; i++)
306                 {
307                         if (rand()/(float)RAND_MAX < dtime)
308                         {
309                                 v3f pos = random_v3f(m_minpos, m_maxpos);
310                                 v3f vel = random_v3f(m_minvel, m_maxvel);
311                                 v3f acc = random_v3f(m_minacc, m_maxacc);
312                                 float exptime = rand()/(float)RAND_MAX
313                                                 *(m_maxexptime-m_minexptime)
314                                                 +m_minexptime;
315                                 float size = rand()/(float)RAND_MAX
316                                                 *(m_maxsize-m_minsize)
317                                                 +m_minsize;
318
319                                 new Particle(
320                                         m_gamedef,
321                                         m_smgr,
322                                         m_player,
323                                         env,
324                                         pos,
325                                         vel,
326                                         acc,
327                                         exptime,
328                                         size,
329                                         m_collisiondetection,
330                                         m_vertical,
331                                         m_texture,
332                                         v2f(0.0, 0.0),
333                                         v2f(1.0, 1.0));
334                         }
335                 }
336         }
337 }
338
339
340 ParticleManager::ParticleManager(ClientEnvironment* env) :
341         m_env(env)
342 {}
343
344 ParticleManager::~ParticleManager()
345 {
346         clearAll();
347 }
348
349 void ParticleManager::step(float dtime)
350 {
351         stepParticles (dtime);
352         stepSpawners (dtime);
353 }
354
355 void ParticleManager::stepSpawners (float dtime)
356 {
357         JMutexAutoLock lock(m_spawner_list_lock);
358         for(std::map<u32, ParticleSpawner*>::iterator i = 
359                         m_particle_spawners.begin();
360                         i != m_particle_spawners.end();)
361         {
362                 if (i->second->get_expired())
363                 {
364                         delete i->second;
365                         m_particle_spawners.erase(i++);
366                 }
367                 else
368                 {
369                         i->second->step(dtime, m_env);
370                         i++;
371                 }
372         }
373 }
374
375 void ParticleManager::stepParticles (float dtime)
376 {
377         JMutexAutoLock lock(m_particle_list_lock);
378         for(std::vector<Particle*>::iterator i = m_particles.begin();
379                         i != m_particles.end();)
380         {
381                 if ((*i)->get_expired())
382                 {
383                         (*i)->remove();
384                         delete *i;
385                         i = m_particles.erase(i);
386                 }
387                 else
388                 {
389                         (*i)->step(dtime);
390                         i++;
391                 }
392         }
393 }
394
395 void ParticleManager::clearAll ()
396 {
397         JMutexAutoLock lock(m_spawner_list_lock);
398         JMutexAutoLock lock2(m_particle_list_lock);
399         for(std::map<u32, ParticleSpawner*>::iterator i =
400                         m_particle_spawners.begin();
401                         i != m_particle_spawners.end();)
402         {
403                 delete i->second;
404                 m_particle_spawners.erase(i++);
405         }
406
407         for(std::vector<Particle*>::iterator i =
408                         m_particles.begin();
409                         i != m_particles.end();)
410         {
411                 (*i)->remove();
412                 delete *i;
413                 i = m_particles.erase(i);
414         }
415 }
416
417 void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef,
418                 scene::ISceneManager* smgr, LocalPlayer *player)
419 {
420         if (event->type == CE_DELETE_PARTICLESPAWNER) {
421                 JMutexAutoLock lock(m_spawner_list_lock);
422                 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
423                                 m_particle_spawners.end())
424                 {
425                         delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
426                         m_particle_spawners.erase(event->delete_particlespawner.id);
427                 }
428                 // no allocated memory in delete event
429                 return;
430         }
431
432         if (event->type == CE_ADD_PARTICLESPAWNER) {
433
434                 {
435                         JMutexAutoLock lock(m_spawner_list_lock);
436                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
437                                                         m_particle_spawners.end())
438                         {
439                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
440                                 m_particle_spawners.erase(event->delete_particlespawner.id);
441                         }
442                 }
443                 video::ITexture *texture =
444                         gamedef->tsrc()->getTexture(*(event->add_particlespawner.texture));
445
446                 ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player,
447                                 event->add_particlespawner.amount,
448                                 event->add_particlespawner.spawntime,
449                                 *event->add_particlespawner.minpos,
450                                 *event->add_particlespawner.maxpos,
451                                 *event->add_particlespawner.minvel,
452                                 *event->add_particlespawner.maxvel,
453                                 *event->add_particlespawner.minacc,
454                                 *event->add_particlespawner.maxacc,
455                                 event->add_particlespawner.minexptime,
456                                 event->add_particlespawner.maxexptime,
457                                 event->add_particlespawner.minsize,
458                                 event->add_particlespawner.maxsize,
459                                 event->add_particlespawner.collisiondetection,
460                                 event->add_particlespawner.vertical,
461                                 texture,
462                                 event->add_particlespawner.id,
463                                 this);
464
465                 /* delete allocated content of event */
466                 delete event->add_particlespawner.minpos;
467                 delete event->add_particlespawner.maxpos;
468                 delete event->add_particlespawner.minvel;
469                 delete event->add_particlespawner.maxvel;
470                 delete event->add_particlespawner.minacc;
471                 delete event->add_particlespawner.texture;
472                 delete event->add_particlespawner.maxacc;
473
474                 {
475                         JMutexAutoLock lock(m_spawner_list_lock);
476                         m_particle_spawners.insert(
477                                         std::pair<u32, ParticleSpawner*>(
478                                                         event->delete_particlespawner.id,
479                                                         toadd));
480                 }
481
482                 return;
483         }
484
485         if (event->type == CE_SPAWN_PARTICLE) {
486                 video::ITexture *texture =
487                         gamedef->tsrc()->getTexture(*(event->spawn_particle.texture));
488
489                 Particle* toadd = new Particle(gamedef, smgr, player, m_env,
490                                 *event->spawn_particle.pos,
491                                 *event->spawn_particle.vel,
492                                 *event->spawn_particle.acc,
493                                 event->spawn_particle.expirationtime,
494                                 event->spawn_particle.size,
495                                 event->spawn_particle.collisiondetection,
496                                 event->spawn_particle.vertical,
497                                 texture,
498                                 v2f(0.0, 0.0),
499                                 v2f(1.0, 1.0));
500
501                 addParticle(toadd);
502
503                 delete event->spawn_particle.pos;
504                 delete event->spawn_particle.vel;
505                 delete event->spawn_particle.acc;
506
507                 return;
508         }
509 }
510
511 void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
512                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
513 {
514         for (u16 j = 0; j < 32; j++) // set the amount of particles here
515         {
516                 addNodeParticle(gamedef, smgr, player, pos, tiles);
517         }
518 }
519
520 void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
521                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
522 {
523         addNodeParticle(gamedef, smgr, player, pos, tiles);
524 }
525
526 void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
527                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
528 {
529         // Texture
530         u8 texid = myrand_range(0, 5);
531         video::ITexture *texture = tiles[texid].texture;
532
533         // Only use first frame of animated texture
534         f32 ymax = 1;
535         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
536                 ymax /= tiles[texid].animation_frame_count;
537
538         float size = rand() % 64 / 512.;
539         float visual_size = BS * size;
540         v2f texsize(size * 2, ymax * size * 2);
541         v2f texpos;
542         texpos.X = ((rand() % 64) / 64. - texsize.X);
543         texpos.Y = ymax * ((rand() % 64) / 64. - texsize.Y);
544
545         // Physics
546         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
547                         rand() % 100 / 35.,
548                         (rand() % 100 / 50. - 1) / 1.5);
549
550         v3f acceleration(0,-9,0);
551         v3f particlepos = v3f(
552                 (f32) pos.X + rand() %100 /200. - 0.25,
553                 (f32) pos.Y + rand() %100 /200. - 0.25,
554                 (f32) pos.Z + rand() %100 /200. - 0.25
555         );
556
557         Particle* toadd = new Particle(
558                 gamedef,
559                 smgr,
560                 player,
561                 m_env,
562                 particlepos,
563                 velocity,
564                 acceleration,
565                 rand() % 100 / 100., // expiration time
566                 visual_size,
567                 true,
568                 false,
569                 texture,
570                 texpos,
571                 texsize);
572
573         addParticle(toadd);
574 }
575
576 void ParticleManager::addParticle(Particle* toadd)
577 {
578         JMutexAutoLock lock(m_particle_list_lock);
579         m_particles.push_back(toadd);
580 }