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