2efee6adaeb2e9be78b0780460f63635c876b7bf
[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 "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, u16 attached_id, 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_attached_id = attached_id;
238         m_vertical = vertical;
239         m_texture = texture;
240         m_time = 0;
241
242         for (u16 i = 0; i<=m_amount; i++)
243         {
244                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
245                 m_spawntimes.push_back(spawntime);
246         }
247 }
248
249 ParticleSpawner::~ParticleSpawner() {}
250
251 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
252 {
253         m_time += dtime;
254
255         bool unloaded = false;
256         v3f attached_offset = v3f(0,0,0);
257         if (m_attached_id != 0) {
258                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id))
259                         attached_offset = attached->getPosition() / BS;
260                 else
261                         unloaded = true;
262         }
263
264         if (m_spawntime != 0) // Spawner exists for a predefined timespan
265         {
266                 for(std::vector<float>::iterator i = m_spawntimes.begin();
267                                 i != m_spawntimes.end();)
268                 {
269                         if ((*i) <= m_time && m_amount > 0)
270                         {
271                                 m_amount--;
272
273                                 // Pretend to, but don't actually spawn a
274                                 // particle if it is attached to an unloaded
275                                 // object.
276                                 if (!unloaded) {
277                                         v3f pos = random_v3f(m_minpos, m_maxpos)
278                                                         + attached_offset;
279                                         v3f vel = random_v3f(m_minvel, m_maxvel);
280                                         v3f acc = random_v3f(m_minacc, m_maxacc);
281                                         // Make relative to offest
282                                         pos += attached_offset;
283                                         float exptime = rand()/(float)RAND_MAX
284                                                         *(m_maxexptime-m_minexptime)
285                                                         +m_minexptime;
286                                         float size = rand()/(float)RAND_MAX
287                                                         *(m_maxsize-m_minsize)
288                                                         +m_minsize;
289
290                                         Particle* toadd = new Particle(
291                                                 m_gamedef,
292                                                 m_smgr,
293                                                 m_player,
294                                                 env,
295                                                 pos,
296                                                 vel,
297                                                 acc,
298                                                 exptime,
299                                                 size,
300                                                 m_collisiondetection,
301                                                 m_collision_removal,
302                                                 m_vertical,
303                                                 m_texture,
304                                                 v2f(0.0, 0.0),
305                                                 v2f(1.0, 1.0));
306                                         m_particlemanager->addParticle(toadd);
307                                 }
308                                 i = m_spawntimes.erase(i);
309                         }
310                         else
311                         {
312                                 ++i;
313                         }
314                 }
315         }
316         else // Spawner exists for an infinity timespan, spawn on a per-second base
317         {
318                 // Skip this step if attached to an unloaded object
319                 if (unloaded)
320                         return;
321                 for (int i = 0; i <= m_amount; i++)
322                 {
323                         if (rand()/(float)RAND_MAX < dtime)
324                         {
325                                 v3f pos = random_v3f(m_minpos, m_maxpos)
326                                                 + attached_offset;
327                                 v3f vel = random_v3f(m_minvel, m_maxvel);
328                                 v3f acc = random_v3f(m_minacc, m_maxacc);
329                                 float exptime = rand()/(float)RAND_MAX
330                                                 *(m_maxexptime-m_minexptime)
331                                                 +m_minexptime;
332                                 float size = rand()/(float)RAND_MAX
333                                                 *(m_maxsize-m_minsize)
334                                                 +m_minsize;
335
336                                 Particle* toadd = new Particle(
337                                         m_gamedef,
338                                         m_smgr,
339                                         m_player,
340                                         env,
341                                         pos,
342                                         vel,
343                                         acc,
344                                         exptime,
345                                         size,
346                                         m_collisiondetection,
347                                         m_collision_removal,
348                                         m_vertical,
349                                         m_texture,
350                                         v2f(0.0, 0.0),
351                                         v2f(1.0, 1.0));
352                                 m_particlemanager->addParticle(toadd);
353                         }
354                 }
355         }
356 }
357
358
359 ParticleManager::ParticleManager(ClientEnvironment* env) :
360         m_env(env)
361 {}
362
363 ParticleManager::~ParticleManager()
364 {
365         clearAll();
366 }
367
368 void ParticleManager::step(float dtime)
369 {
370         stepParticles (dtime);
371         stepSpawners (dtime);
372 }
373
374 void ParticleManager::stepSpawners (float dtime)
375 {
376         MutexAutoLock lock(m_spawner_list_lock);
377         for (std::map<u32, ParticleSpawner*>::iterator i =
378                         m_particle_spawners.begin();
379                         i != m_particle_spawners.end();)
380         {
381                 if (i->second->get_expired())
382                 {
383                         delete i->second;
384                         m_particle_spawners.erase(i++);
385                 }
386                 else
387                 {
388                         i->second->step(dtime, m_env);
389                         ++i;
390                 }
391         }
392 }
393
394 void ParticleManager::stepParticles (float dtime)
395 {
396         MutexAutoLock lock(m_particle_list_lock);
397         for(std::vector<Particle*>::iterator i = m_particles.begin();
398                         i != m_particles.end();)
399         {
400                 if ((*i)->get_expired())
401                 {
402                         (*i)->remove();
403                         delete *i;
404                         i = m_particles.erase(i);
405                 }
406                 else
407                 {
408                         (*i)->step(dtime);
409                         ++i;
410                 }
411         }
412 }
413
414 void ParticleManager::clearAll ()
415 {
416         MutexAutoLock lock(m_spawner_list_lock);
417         MutexAutoLock lock2(m_particle_list_lock);
418         for(std::map<u32, ParticleSpawner*>::iterator i =
419                         m_particle_spawners.begin();
420                         i != m_particle_spawners.end();)
421         {
422                 delete i->second;
423                 m_particle_spawners.erase(i++);
424         }
425
426         for(std::vector<Particle*>::iterator i =
427                         m_particles.begin();
428                         i != m_particles.end();)
429         {
430                 (*i)->remove();
431                 delete *i;
432                 i = m_particles.erase(i);
433         }
434 }
435
436 void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef,
437                 scene::ISceneManager* smgr, LocalPlayer *player)
438 {
439         switch (event->type) {
440                 case CE_DELETE_PARTICLESPAWNER: {
441                         MutexAutoLock lock(m_spawner_list_lock);
442                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
443                                         m_particle_spawners.end()) {
444                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
445                                 m_particle_spawners.erase(event->delete_particlespawner.id);
446                         }
447                         // no allocated memory in delete event
448                         break;
449                 }
450                 case CE_ADD_PARTICLESPAWNER: {
451                         {
452                                 MutexAutoLock lock(m_spawner_list_lock);
453                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
454                                                 m_particle_spawners.end()) {
455                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
456                                         m_particle_spawners.erase(event->add_particlespawner.id);
457                                 }
458                         }
459
460                         video::ITexture *texture =
461                                 gamedef->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
462
463                         ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player,
464                                         event->add_particlespawner.amount,
465                                         event->add_particlespawner.spawntime,
466                                         *event->add_particlespawner.minpos,
467                                         *event->add_particlespawner.maxpos,
468                                         *event->add_particlespawner.minvel,
469                                         *event->add_particlespawner.maxvel,
470                                         *event->add_particlespawner.minacc,
471                                         *event->add_particlespawner.maxacc,
472                                         event->add_particlespawner.minexptime,
473                                         event->add_particlespawner.maxexptime,
474                                         event->add_particlespawner.minsize,
475                                         event->add_particlespawner.maxsize,
476                                         event->add_particlespawner.collisiondetection,
477                                         event->add_particlespawner.collision_removal,
478                                         event->add_particlespawner.attached_id,
479                                         event->add_particlespawner.vertical,
480                                         texture,
481                                         event->add_particlespawner.id,
482                                         this);
483
484                         /* delete allocated content of event */
485                         delete event->add_particlespawner.minpos;
486                         delete event->add_particlespawner.maxpos;
487                         delete event->add_particlespawner.minvel;
488                         delete event->add_particlespawner.maxvel;
489                         delete event->add_particlespawner.minacc;
490                         delete event->add_particlespawner.texture;
491                         delete event->add_particlespawner.maxacc;
492
493                         {
494                                 MutexAutoLock lock(m_spawner_list_lock);
495                                 m_particle_spawners.insert(
496                                                 std::pair<u32, ParticleSpawner*>(
497                                                                 event->add_particlespawner.id,
498                                                                 toadd));
499                         }
500                         break;
501                 }
502                 case CE_SPAWN_PARTICLE: {
503                         video::ITexture *texture =
504                                 gamedef->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
505
506                         Particle* toadd = new Particle(gamedef, smgr, player, m_env,
507                                         *event->spawn_particle.pos,
508                                         *event->spawn_particle.vel,
509                                         *event->spawn_particle.acc,
510                                         event->spawn_particle.expirationtime,
511                                         event->spawn_particle.size,
512                                         event->spawn_particle.collisiondetection,
513                                         event->spawn_particle.collision_removal,
514                                         event->spawn_particle.vertical,
515                                         texture,
516                                         v2f(0.0, 0.0),
517                                         v2f(1.0, 1.0));
518
519                         addParticle(toadd);
520
521                         delete event->spawn_particle.pos;
522                         delete event->spawn_particle.vel;
523                         delete event->spawn_particle.acc;
524
525                         break;
526                 }
527                 default: break;
528         }
529 }
530
531 void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
532                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
533 {
534         for (u16 j = 0; j < 32; j++) // set the amount of particles here
535         {
536                 addNodeParticle(gamedef, smgr, player, pos, tiles);
537         }
538 }
539
540 void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
541                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
542 {
543         addNodeParticle(gamedef, smgr, player, pos, tiles);
544 }
545
546 void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
547                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
548 {
549         // Texture
550         u8 texid = myrand_range(0, 5);
551         video::ITexture *texture = tiles[texid].texture;
552
553         // Only use first frame of animated texture
554         f32 ymax = 1;
555         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
556                 ymax /= tiles[texid].animation_frame_count;
557
558         float size = rand() % 64 / 512.;
559         float visual_size = BS * size;
560         v2f texsize(size * 2, ymax * size * 2);
561         v2f texpos;
562         texpos.X = ((rand() % 64) / 64. - texsize.X);
563         texpos.Y = ymax * ((rand() % 64) / 64. - texsize.Y);
564
565         // Physics
566         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
567                         rand() % 100 / 35.,
568                         (rand() % 100 / 50. - 1) / 1.5);
569
570         v3f acceleration(0,-9,0);
571         v3f particlepos = v3f(
572                 (f32) pos.X + rand() %100 /200. - 0.25,
573                 (f32) pos.Y + rand() %100 /200. - 0.25,
574                 (f32) pos.Z + rand() %100 /200. - 0.25
575         );
576
577         Particle* toadd = new Particle(
578                 gamedef,
579                 smgr,
580                 player,
581                 m_env,
582                 particlepos,
583                 velocity,
584                 acceleration,
585                 rand() % 100 / 100., // expiration time
586                 visual_size,
587                 true,
588                 false,
589                 false,
590                 texture,
591                 texpos,
592                 texsize);
593
594         addParticle(toadd);
595 }
596
597 void ParticleManager::addParticle(Particle* toadd)
598 {
599         MutexAutoLock lock(m_particle_list_lock);
600         m_particles.push_back(toadd);
601 }