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