]> git.lizzy.rs Git - minetest.git/blob - src/particles.cpp
Fix a formspec crash triggered by ae9b5e00989756bb676429530dfe81039009001c
[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 "client/renderingengine.h"
24 #include "util/numeric.h"
25 #include "light.h"
26 #include "environment.h"
27 #include "clientmap.h"
28 #include "mapnode.h"
29 #include "nodedef.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 void Particle::OnRegisterSceneNode()
110 {
111         if (IsVisible)
112                 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
113
114         ISceneNode::OnRegisterSceneNode();
115 }
116
117 void Particle::render()
118 {
119         video::IVideoDriver* driver = SceneManager->getVideoDriver();
120         driver->setMaterial(m_material);
121         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
122
123         u16 indices[] = {0,1,2, 2,3,0};
124         driver->drawVertexPrimitiveList(m_vertices, 4,
125                         indices, 2, video::EVT_STANDARD,
126                         scene::EPT_TRIANGLES, video::EIT_16BIT);
127 }
128
129 void Particle::step(float dtime)
130 {
131         m_time += dtime;
132         if (m_collisiondetection) {
133                 aabb3f box = m_collisionbox;
134                 v3f p_pos = m_pos * BS;
135                 v3f p_velocity = m_velocity * BS;
136                 collisionMoveResult r = collisionMoveSimple(m_env,
137                         m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
138                         &p_velocity, m_acceleration * BS);
139                 if (m_collision_removal && r.collides) {
140                         // force expiration of the particle
141                         m_expiration = -1.0;
142                 } else {
143                         m_pos = p_pos / BS;
144                         m_velocity = p_velocity / BS;
145                 }
146         } else {
147                 m_velocity += m_acceleration * dtime;
148                 m_pos += m_velocity * dtime;
149         }
150         if (m_animation.type != TAT_NONE) {
151                 m_animation_time += dtime;
152                 int frame_length_i, frame_count;
153                 m_animation.determineParams(
154                                 m_material.getTexture(0)->getSize(),
155                                 &frame_count, &frame_length_i, NULL);
156                 float frame_length = frame_length_i / 1000.0;
157                 while (m_animation_time > frame_length) {
158                         m_animation_frame++;
159                         m_animation_time -= frame_length;
160                 }
161         }
162
163         // Update lighting
164         updateLight();
165
166         // Update model
167         updateVertices();
168 }
169
170 void Particle::updateLight()
171 {
172         u8 light = 0;
173         bool pos_ok;
174
175         v3s16 p = v3s16(
176                 floor(m_pos.X+0.5),
177                 floor(m_pos.Y+0.5),
178                 floor(m_pos.Z+0.5)
179         );
180         MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
181         if (pos_ok)
182                 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
183         else
184                 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
185
186         u8 m_light = decode_light(light + m_glow);
187         m_color.set(255,
188                 m_light * m_base_color.getRed() / 255,
189                 m_light * m_base_color.getGreen() / 255,
190                 m_light * m_base_color.getBlue() / 255);
191 }
192
193 void Particle::updateVertices()
194 {
195         f32 tx0, tx1, ty0, ty1;
196
197         if (m_animation.type != TAT_NONE) {
198                 const v2u32 texsize = m_material.getTexture(0)->getSize();
199                 v2f texcoord, framesize_f;
200                 v2u32 framesize;
201                 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
202                 m_animation.determineParams(texsize, NULL, NULL, &framesize);
203                 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
204
205                 tx0 = m_texpos.X + texcoord.X;
206                 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
207                 ty0 = m_texpos.Y + texcoord.Y;
208                 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
209         } else {
210                 tx0 = m_texpos.X;
211                 tx1 = m_texpos.X + m_texsize.X;
212                 ty0 = m_texpos.Y;
213                 ty1 = m_texpos.Y + m_texsize.Y;
214         }
215
216         m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
217                 0, 0, 0, 0, m_color, tx0, ty1);
218         m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
219                 0, 0, 0, 0, m_color, tx1, ty1);
220         m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
221                 0, 0, 0, 0, m_color, tx1, ty0);
222         m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
223                 0, 0, 0, 0, m_color, tx0, ty0);
224
225         v3s16 camera_offset = m_env->getCameraOffset();
226         for (video::S3DVertex &vertex : m_vertices) {
227                 if (m_vertical) {
228                         v3f ppos = m_player->getPosition()/BS;
229                         vertex.Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
230                 } else {
231                         vertex.Pos.rotateYZBy(m_player->getPitch());
232                         vertex.Pos.rotateXZBy(m_player->getYaw());
233                 }
234                 m_box.addInternalPoint(vertex.Pos);
235                 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
236         }
237 }
238
239 /*
240         ParticleSpawner
241 */
242
243 ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player,
244         u16 amount, float time,
245         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
246         float minexptime, float maxexptime, float minsize, float maxsize,
247         bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
248         video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
249         u8 glow,
250         ParticleManager *p_manager) :
251         m_particlemanager(p_manager)
252 {
253         m_gamedef = gamedef;
254         m_player = player;
255         m_amount = amount;
256         m_spawntime = time;
257         m_minpos = minpos;
258         m_maxpos = maxpos;
259         m_minvel = minvel;
260         m_maxvel = maxvel;
261         m_minacc = minacc;
262         m_maxacc = maxacc;
263         m_minexptime = minexptime;
264         m_maxexptime = maxexptime;
265         m_minsize = minsize;
266         m_maxsize = maxsize;
267         m_collisiondetection = collisiondetection;
268         m_collision_removal = collision_removal;
269         m_attached_id = attached_id;
270         m_vertical = vertical;
271         m_texture = texture;
272         m_time = 0;
273         m_animation = anim;
274         m_glow = glow;
275
276         for (u16 i = 0; i<=m_amount; i++)
277         {
278                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
279                 m_spawntimes.push_back(spawntime);
280         }
281 }
282
283 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
284 {
285         m_time += dtime;
286
287         static thread_local const float radius =
288                         g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
289
290         bool unloaded = false;
291         bool is_attached = false;
292         v3f attached_pos = v3f(0,0,0);
293         float attached_yaw = 0;
294         if (m_attached_id != 0) {
295                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
296                         attached_pos = attached->getPosition() / BS;
297                         attached_yaw = attached->getYaw();
298                         is_attached = true;
299                 } else {
300                         unloaded = true;
301                 }
302         }
303
304         if (m_spawntime != 0) // Spawner exists for a predefined timespan
305         {
306                 for(std::vector<float>::iterator i = m_spawntimes.begin();
307                                 i != m_spawntimes.end();)
308                 {
309                         if ((*i) <= m_time && m_amount > 0)
310                         {
311                                 m_amount--;
312
313                                 // Pretend to, but don't actually spawn a particle if it is
314                                 // attached to an unloaded object or distant from player.
315                                 if (!unloaded) {
316                                         v3f ppos = m_player->getPosition() / BS;
317                                         v3f pos = random_v3f(m_minpos, m_maxpos);
318
319                                         if (pos.getDistanceFrom(ppos) <= radius) {
320                                                 v3f vel = random_v3f(m_minvel, m_maxvel);
321                                                 v3f acc = random_v3f(m_minacc, m_maxacc);
322
323                                                 if (is_attached) {
324                                                         // Apply attachment yaw and position
325                                                         pos.rotateXZBy(attached_yaw);
326                                                         pos += attached_pos;
327                                                         vel.rotateXZBy(attached_yaw);
328                                                         acc.rotateXZBy(attached_yaw);
329                                                 }
330
331                                                 float exptime = rand()/(float)RAND_MAX
332                                                                 *(m_maxexptime-m_minexptime)
333                                                                 +m_minexptime;
334                                                 float size = rand()/(float)RAND_MAX
335                                                                 *(m_maxsize-m_minsize)
336                                                                 +m_minsize;
337
338                                                 Particle* toadd = new Particle(
339                                                         m_gamedef,
340                                                         m_player,
341                                                         env,
342                                                         pos,
343                                                         vel,
344                                                         acc,
345                                                         exptime,
346                                                         size,
347                                                         m_collisiondetection,
348                                                         m_collision_removal,
349                                                         m_vertical,
350                                                         m_texture,
351                                                         v2f(0.0, 0.0),
352                                                         v2f(1.0, 1.0),
353                                                         m_animation,
354                                                         m_glow);
355                                                 m_particlemanager->addParticle(toadd);
356                                         }
357                                 }
358                                 i = m_spawntimes.erase(i);
359                         }
360                         else
361                         {
362                                 ++i;
363                         }
364                 }
365         }
366         else // Spawner exists for an infinity timespan, spawn on a per-second base
367         {
368                 // Skip this step if attached to an unloaded object
369                 if (unloaded)
370                         return;
371                 for (int i = 0; i <= m_amount; i++)
372                 {
373                         if (rand()/(float)RAND_MAX < dtime)
374                         {
375                                 // Do not spawn particle if distant from player
376                                 v3f ppos = m_player->getPosition() / BS;
377                                 v3f pos = random_v3f(m_minpos, m_maxpos);
378
379                                 if (pos.getDistanceFrom(ppos) <= radius) {
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_player,
401                                                 env,
402                                                 pos,
403                                                 vel,
404                                                 acc,
405                                                 exptime,
406                                                 size,
407                                                 m_collisiondetection,
408                                                 m_collision_removal,
409                                                 m_vertical,
410                                                 m_texture,
411                                                 v2f(0.0, 0.0),
412                                                 v2f(1.0, 1.0),
413                                                 m_animation,
414                                                 m_glow);
415                                         m_particlemanager->addParticle(toadd);
416                                 }
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         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, 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, 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         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
602 {
603         // set the amount of particles here
604         for (u16 j = 0; j < 32; j++) {
605                 addNodeParticle(gamedef, player, pos, n, f);
606         }
607 }
608
609 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
610         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
611 {
612         addNodeParticle(gamedef, player, pos, n, f);
613 }
614
615 void ParticleManager::addNodeParticle(IGameDef* gamedef,
616         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
617 {
618         // Texture
619         u8 texid = myrand_range(0, 5);
620         const TileLayer &tile = f.tiles[texid].layers[0];
621         video::ITexture *texture;
622         struct TileAnimationParams anim;
623         anim.type = TAT_NONE;
624
625         // Only use first frame of animated texture
626         if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
627                 texture = (*tile.frames)[0].texture;
628         else
629                 texture = tile.texture;
630
631         float size = rand() % 64 / 512.;
632         float visual_size = BS * size;
633         v2f texsize(size * 2, size * 2);
634         v2f texpos;
635         texpos.X = ((rand() % 64) / 64. - texsize.X);
636         texpos.Y = ((rand() % 64) / 64. - texsize.Y);
637
638         // Physics
639         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
640                         rand() % 100 / 35.,
641                         (rand() % 100 / 50. - 1) / 1.5);
642
643         v3f acceleration(0,-9,0);
644         v3f particlepos = v3f(
645                 (f32) pos.X + rand() %100 /200. - 0.25,
646                 (f32) pos.Y + rand() %100 /200. - 0.25,
647                 (f32) pos.Z + rand() %100 /200. - 0.25
648         );
649
650         video::SColor color;
651         if (tile.has_color)
652                 color = tile.color;
653         else
654                 n.getColor(f, &color);
655
656         Particle* toadd = new Particle(
657                 gamedef,
658                 player,
659                 m_env,
660                 particlepos,
661                 velocity,
662                 acceleration,
663                 rand() % 100 / 100., // expiration time
664                 visual_size,
665                 true,
666                 false,
667                 false,
668                 texture,
669                 texpos,
670                 texsize,
671                 anim,
672                 0,
673                 color);
674
675         addParticle(toadd);
676 }
677
678 void ParticleManager::addParticle(Particle* toadd)
679 {
680         MutexAutoLock lock(m_particle_list_lock);
681         m_particles.push_back(toadd);
682 }