Add core.remove_detached_inventory (#7684)
[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 <cmath>
22 #include "client.h"
23 #include "collision.h"
24 #include "client/clientevent.h"
25 #include "client/renderingengine.h"
26 #include "util/numeric.h"
27 #include "light.h"
28 #include "environment.h"
29 #include "clientmap.h"
30 #include "mapnode.h"
31 #include "nodedef.h"
32 #include "client.h"
33 #include "settings.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         LocalPlayer *player,
49         ClientEnvironment *env,
50         v3f pos,
51         v3f velocity,
52         v3f acceleration,
53         float expirationtime,
54         float size,
55         bool collisiondetection,
56         bool collision_removal,
57         bool object_collision,
58         bool vertical,
59         video::ITexture *texture,
60         v2f texpos,
61         v2f texsize,
62         const struct TileAnimationParams &anim,
63         u8 glow,
64         video::SColor color
65 ):
66         scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
67                 RenderingEngine::get_scene_manager())
68 {
69         // Misc
70         m_gamedef = gamedef;
71         m_env = env;
72
73         // Texture
74         m_material.setFlag(video::EMF_LIGHTING, false);
75         m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
76         m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
77         m_material.setFlag(video::EMF_FOG_ENABLE, true);
78         m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
79         m_material.setTexture(0, texture);
80         m_texpos = texpos;
81         m_texsize = texsize;
82         m_animation = anim;
83
84         // Color
85         m_base_color = color;
86         m_color = color;
87
88         // Particle related
89         m_pos = pos;
90         m_velocity = velocity;
91         m_acceleration = acceleration;
92         m_expiration = expirationtime;
93         m_player = player;
94         m_size = size;
95         m_collisiondetection = collisiondetection;
96         m_collision_removal = collision_removal;
97         m_object_collision = object_collision;
98         m_vertical = vertical;
99         m_glow = glow;
100
101         // Irrlicht stuff
102         m_collisionbox = aabb3f
103                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
104         this->setAutomaticCulling(scene::EAC_OFF);
105
106         // Init lighting
107         updateLight();
108
109         // Init model
110         updateVertices();
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, m_gamedef, BS * 0.5f,
141                         box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
142                         m_object_collision);
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 (video::S3DVertex &vertex : m_vertices) {
231                 if (m_vertical) {
232                         v3f ppos = m_player->getPosition()/BS;
233                         vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
234                                 core::DEGTORAD + 90);
235                 } else {
236                         vertex.Pos.rotateYZBy(m_player->getPitch());
237                         vertex.Pos.rotateXZBy(m_player->getYaw());
238                 }
239                 m_box.addInternalPoint(vertex.Pos);
240                 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
241         }
242 }
243
244 /*
245         ParticleSpawner
246 */
247
248 ParticleSpawner::ParticleSpawner(
249         IGameDef *gamedef,
250         LocalPlayer *player,
251         u16 amount,
252         float time,
253         v3f minpos, v3f maxpos,
254         v3f minvel, v3f maxvel,
255         v3f minacc, v3f maxacc,
256         float minexptime, float maxexptime,
257         float minsize, float maxsize,
258         bool collisiondetection,
259         bool collision_removal,
260         bool object_collision,
261         u16 attached_id,
262         bool vertical,
263         video::ITexture *texture,
264         u32 id,
265         const struct TileAnimationParams &anim,
266         u8 glow,
267         ParticleManager *p_manager
268 ):
269         m_particlemanager(p_manager)
270 {
271         m_gamedef = gamedef;
272         m_player = player;
273         m_amount = amount;
274         m_spawntime = time;
275         m_minpos = minpos;
276         m_maxpos = maxpos;
277         m_minvel = minvel;
278         m_maxvel = maxvel;
279         m_minacc = minacc;
280         m_maxacc = maxacc;
281         m_minexptime = minexptime;
282         m_maxexptime = maxexptime;
283         m_minsize = minsize;
284         m_maxsize = maxsize;
285         m_collisiondetection = collisiondetection;
286         m_collision_removal = collision_removal;
287         m_object_collision = object_collision;
288         m_attached_id = attached_id;
289         m_vertical = vertical;
290         m_texture = texture;
291         m_time = 0;
292         m_animation = anim;
293         m_glow = glow;
294
295         for (u16 i = 0; i<=m_amount; i++)
296         {
297                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
298                 m_spawntimes.push_back(spawntime);
299         }
300 }
301
302 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
303         bool is_attached, const v3f &attached_pos, float attached_yaw)
304 {
305         v3f ppos = m_player->getPosition() / BS;
306         v3f pos = random_v3f(m_minpos, m_maxpos);
307
308         // Need to apply this first or the following check
309         // will be wrong for attached spawners
310         if (is_attached) {
311                 pos.rotateXZBy(attached_yaw);
312                 pos += attached_pos;
313         }
314
315         if (pos.getDistanceFrom(ppos) > radius)
316                 return;
317
318         v3f vel = random_v3f(m_minvel, m_maxvel);
319         v3f acc = random_v3f(m_minacc, m_maxacc);
320
321         if (is_attached) {
322                 // Apply attachment yaw
323                 vel.rotateXZBy(attached_yaw);
324                 acc.rotateXZBy(attached_yaw);
325         }
326
327         float exptime = rand() / (float)RAND_MAX
328                         * (m_maxexptime - m_minexptime)
329                         + m_minexptime;
330         float size = rand() / (float)RAND_MAX
331                         * (m_maxsize - m_minsize)
332                         + m_minsize;
333
334         m_particlemanager->addParticle(new Particle(
335                 m_gamedef,
336                 m_player,
337                 env,
338                 pos,
339                 vel,
340                 acc,
341                 exptime,
342                 size,
343                 m_collisiondetection,
344                 m_collision_removal,
345                 m_object_collision,
346                 m_vertical,
347                 m_texture,
348                 v2f(0.0, 0.0),
349                 v2f(1.0, 1.0),
350                 m_animation,
351                 m_glow
352         ));
353 }
354
355 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
356 {
357         m_time += dtime;
358
359         static thread_local const float radius =
360                         g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
361
362         bool unloaded = false;
363         bool is_attached = false;
364         v3f attached_pos = v3f(0,0,0);
365         float attached_yaw = 0;
366         if (m_attached_id != 0) {
367                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
368                         attached_pos = attached->getPosition() / BS;
369                         attached_yaw = attached->getYaw();
370                         is_attached = true;
371                 } else {
372                         unloaded = true;
373                 }
374         }
375
376         if (m_spawntime != 0) {
377                 // Spawner exists for a predefined timespan
378                 for (std::vector<float>::iterator i = m_spawntimes.begin();
379                                 i != m_spawntimes.end();) {
380                         if ((*i) <= m_time && m_amount > 0) {
381                                 m_amount--;
382
383                                 // Pretend to, but don't actually spawn a particle if it is
384                                 // attached to an unloaded object or distant from player.
385                                 if (!unloaded)
386                                         spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
387
388                                 i = m_spawntimes.erase(i);
389                         } else {
390                                 ++i;
391                         }
392                 }
393         } else {
394                 // Spawner exists for an infinity timespan, spawn on a per-second base
395
396                 // Skip this step if attached to an unloaded object
397                 if (unloaded)
398                         return;
399
400                 for (int i = 0; i <= m_amount; i++) {
401                         if (rand() / (float)RAND_MAX < dtime)
402                                 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
403                 }
404         }
405 }
406
407
408 ParticleManager::ParticleManager(ClientEnvironment* env) :
409         m_env(env)
410 {}
411
412 ParticleManager::~ParticleManager()
413 {
414         clearAll();
415 }
416
417 void ParticleManager::step(float dtime)
418 {
419         stepParticles (dtime);
420         stepSpawners (dtime);
421 }
422
423 void ParticleManager::stepSpawners (float dtime)
424 {
425         MutexAutoLock lock(m_spawner_list_lock);
426         for (std::map<u32, ParticleSpawner*>::iterator i =
427                         m_particle_spawners.begin();
428                         i != m_particle_spawners.end();)
429         {
430                 if (i->second->get_expired())
431                 {
432                         delete i->second;
433                         m_particle_spawners.erase(i++);
434                 }
435                 else
436                 {
437                         i->second->step(dtime, m_env);
438                         ++i;
439                 }
440         }
441 }
442
443 void ParticleManager::stepParticles (float dtime)
444 {
445         MutexAutoLock lock(m_particle_list_lock);
446         for(std::vector<Particle*>::iterator i = m_particles.begin();
447                         i != m_particles.end();)
448         {
449                 if ((*i)->get_expired())
450                 {
451                         (*i)->remove();
452                         delete *i;
453                         i = m_particles.erase(i);
454                 }
455                 else
456                 {
457                         (*i)->step(dtime);
458                         ++i;
459                 }
460         }
461 }
462
463 void ParticleManager::clearAll ()
464 {
465         MutexAutoLock lock(m_spawner_list_lock);
466         MutexAutoLock lock2(m_particle_list_lock);
467         for(std::map<u32, ParticleSpawner*>::iterator i =
468                         m_particle_spawners.begin();
469                         i != m_particle_spawners.end();)
470         {
471                 delete i->second;
472                 m_particle_spawners.erase(i++);
473         }
474
475         for(std::vector<Particle*>::iterator i =
476                         m_particles.begin();
477                         i != m_particles.end();)
478         {
479                 (*i)->remove();
480                 delete *i;
481                 i = m_particles.erase(i);
482         }
483 }
484
485 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
486         LocalPlayer *player)
487 {
488         switch (event->type) {
489                 case CE_DELETE_PARTICLESPAWNER: {
490                         MutexAutoLock lock(m_spawner_list_lock);
491                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
492                                         m_particle_spawners.end()) {
493                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
494                                 m_particle_spawners.erase(event->delete_particlespawner.id);
495                         }
496                         // no allocated memory in delete event
497                         break;
498                 }
499                 case CE_ADD_PARTICLESPAWNER: {
500                         {
501                                 MutexAutoLock lock(m_spawner_list_lock);
502                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
503                                                 m_particle_spawners.end()) {
504                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
505                                         m_particle_spawners.erase(event->add_particlespawner.id);
506                                 }
507                         }
508
509                         video::ITexture *texture =
510                                 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
511
512                         ParticleSpawner *toadd = new ParticleSpawner(client, player,
513                                         event->add_particlespawner.amount,
514                                         event->add_particlespawner.spawntime,
515                                         *event->add_particlespawner.minpos,
516                                         *event->add_particlespawner.maxpos,
517                                         *event->add_particlespawner.minvel,
518                                         *event->add_particlespawner.maxvel,
519                                         *event->add_particlespawner.minacc,
520                                         *event->add_particlespawner.maxacc,
521                                         event->add_particlespawner.minexptime,
522                                         event->add_particlespawner.maxexptime,
523                                         event->add_particlespawner.minsize,
524                                         event->add_particlespawner.maxsize,
525                                         event->add_particlespawner.collisiondetection,
526                                         event->add_particlespawner.collision_removal,
527                                         event->add_particlespawner.object_collision,
528                                         event->add_particlespawner.attached_id,
529                                         event->add_particlespawner.vertical,
530                                         texture,
531                                         event->add_particlespawner.id,
532                                         event->add_particlespawner.animation,
533                                         event->add_particlespawner.glow,
534                                         this);
535
536                         /* delete allocated content of event */
537                         delete event->add_particlespawner.minpos;
538                         delete event->add_particlespawner.maxpos;
539                         delete event->add_particlespawner.minvel;
540                         delete event->add_particlespawner.maxvel;
541                         delete event->add_particlespawner.minacc;
542                         delete event->add_particlespawner.texture;
543                         delete event->add_particlespawner.maxacc;
544
545                         {
546                                 MutexAutoLock lock(m_spawner_list_lock);
547                                 m_particle_spawners.insert(
548                                                 std::pair<u32, ParticleSpawner*>(
549                                                                 event->add_particlespawner.id,
550                                                                 toadd));
551                         }
552                         break;
553                 }
554                 case CE_SPAWN_PARTICLE: {
555                         video::ITexture *texture =
556                                 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
557
558                         Particle *toadd = new Particle(client, player, m_env,
559                                         *event->spawn_particle.pos,
560                                         *event->spawn_particle.vel,
561                                         *event->spawn_particle.acc,
562                                         event->spawn_particle.expirationtime,
563                                         event->spawn_particle.size,
564                                         event->spawn_particle.collisiondetection,
565                                         event->spawn_particle.collision_removal,
566                                         event->spawn_particle.object_collision,
567                                         event->spawn_particle.vertical,
568                                         texture,
569                                         v2f(0.0, 0.0),
570                                         v2f(1.0, 1.0),
571                                         event->spawn_particle.animation,
572                                         event->spawn_particle.glow);
573
574                         addParticle(toadd);
575
576                         delete event->spawn_particle.pos;
577                         delete event->spawn_particle.vel;
578                         delete event->spawn_particle.acc;
579                         delete event->spawn_particle.texture;
580
581                         break;
582                 }
583                 default: break;
584         }
585 }
586
587 // The final burst of particles when a node is finally dug, *not* particles
588 // spawned during the digging of a node.
589
590 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
591         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
592 {
593         // No particles for "airlike" nodes
594         if (f.drawtype == NDT_AIRLIKE)
595                 return;
596
597         for (u16 j = 0; j < 16; j++) {
598                 addNodeParticle(gamedef, player, pos, n, f);
599         }
600 }
601
602 // During the digging of a node particles are spawned individually by this
603 // function, called from Game::handleDigging() in game.cpp.
604
605 void ParticleManager::addNodeParticle(IGameDef* gamedef,
606         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
607 {
608         // No particles for "airlike" nodes
609         if (f.drawtype == NDT_AIRLIKE)
610                 return;
611
612         // Texture
613         u8 texid = myrand_range(0, 5);
614         const TileLayer &tile = f.tiles[texid].layers[0];
615         video::ITexture *texture;
616         struct TileAnimationParams anim;
617         anim.type = TAT_NONE;
618
619         // Only use first frame of animated texture
620         if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
621                 texture = (*tile.frames)[0].texture;
622         else
623                 texture = tile.texture;
624
625         float size = (rand() % 8) / 64.0f;
626         float visual_size = BS * size;
627         if (tile.scale)
628                 size /= tile.scale;
629         v2f texsize(size * 2.0f, size * 2.0f);
630         v2f texpos;
631         texpos.X = (rand() % 64) / 64.0f - texsize.X;
632         texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
633
634         // Physics
635         v3f velocity(
636                 (rand() % 150) / 50.0f - 1.5f,
637                 (rand() % 150) / 50.0f,
638                 (rand() % 150) / 50.0f - 1.5f
639         );
640         v3f acceleration(
641                 0.0f,
642                 -player->movement_gravity * player->physics_override_gravity / BS,
643                 0.0f
644         );
645         v3f particlepos = v3f(
646                 (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
647                 (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
648                 (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
649         );
650
651         video::SColor color;
652         if (tile.has_color)
653                 color = tile.color;
654         else
655                 n.getColor(f, &color);
656
657         Particle* toadd = new Particle(
658                 gamedef,
659                 player,
660                 m_env,
661                 particlepos,
662                 velocity,
663                 acceleration,
664                 (rand() % 100) / 100.0f, // expiration time
665                 visual_size,
666                 true,
667                 false,
668                 false,
669                 false,
670                 texture,
671                 texpos,
672                 texsize,
673                 anim,
674                 0,
675                 color);
676
677         addParticle(toadd);
678 }
679
680 void ParticleManager::addParticle(Particle* toadd)
681 {
682         MutexAutoLock lock(m_particle_list_lock);
683         m_particles.push_back(toadd);
684 }