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