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