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