]> git.lizzy.rs Git - dragonfireclient.git/blob - src/particles.cpp
Position entity nametags relative to selection-box (#7031)
[dragonfireclient.git] / src / particles.cpp
1 /*
2 Minetest
3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "particles.h"
21 #include "client.h"
22 #include "collision.h"
23 #include "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::spawnParticle(ClientEnvironment *env, float radius,
285         bool is_attached, const v3f &attached_pos, float attached_yaw)
286 {
287         v3f ppos = m_player->getPosition() / BS;
288         v3f pos = random_v3f(m_minpos, m_maxpos);
289
290         // Need to apply this first or the following check
291         // will be wrong for attached spawners
292         if (is_attached) {
293                 pos.rotateXZBy(attached_yaw);
294                 pos += attached_pos;
295         }
296
297         if (pos.getDistanceFrom(ppos) > radius)
298                 return;
299
300         v3f vel = random_v3f(m_minvel, m_maxvel);
301         v3f acc = random_v3f(m_minacc, m_maxacc);
302
303         if (is_attached) {
304                 // Apply attachment yaw
305                 vel.rotateXZBy(attached_yaw);
306                 acc.rotateXZBy(attached_yaw);
307         }
308
309         float exptime = rand() / (float)RAND_MAX
310                         * (m_maxexptime - m_minexptime)
311                         + m_minexptime;
312         float size = rand() / (float)RAND_MAX
313                         * (m_maxsize - m_minsize)
314                         + m_minsize;
315
316         m_particlemanager->addParticle(new Particle(
317                 m_gamedef,
318                 m_player,
319                 env,
320                 pos,
321                 vel,
322                 acc,
323                 exptime,
324                 size,
325                 m_collisiondetection,
326                 m_collision_removal,
327                 m_vertical,
328                 m_texture,
329                 v2f(0.0, 0.0),
330                 v2f(1.0, 1.0),
331                 m_animation,
332                 m_glow
333         ));
334 }
335
336 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
337 {
338         m_time += dtime;
339
340         static thread_local const float radius =
341                         g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
342
343         bool unloaded = false;
344         bool is_attached = false;
345         v3f attached_pos = v3f(0,0,0);
346         float attached_yaw = 0;
347         if (m_attached_id != 0) {
348                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
349                         attached_pos = attached->getPosition() / BS;
350                         attached_yaw = attached->getYaw();
351                         is_attached = true;
352                 } else {
353                         unloaded = true;
354                 }
355         }
356
357         if (m_spawntime != 0) {
358                 // Spawner exists for a predefined timespan
359                 for (std::vector<float>::iterator i = m_spawntimes.begin();
360                                 i != m_spawntimes.end();) {
361                         if ((*i) <= m_time && m_amount > 0) {
362                                 m_amount--;
363
364                                 // Pretend to, but don't actually spawn a particle if it is
365                                 // attached to an unloaded object or distant from player.
366                                 if (!unloaded)
367                                         spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
368
369                                 i = m_spawntimes.erase(i);
370                         } else {
371                                 ++i;
372                         }
373                 }
374         } else {
375                 // Spawner exists for an infinity timespan, spawn on a per-second base
376
377                 // Skip this step if attached to an unloaded object
378                 if (unloaded)
379                         return;
380
381                 for (int i = 0; i <= m_amount; i++) {
382                         if (rand() / (float)RAND_MAX < dtime)
383                                 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
384                 }
385         }
386 }
387
388
389 ParticleManager::ParticleManager(ClientEnvironment* env) :
390         m_env(env)
391 {}
392
393 ParticleManager::~ParticleManager()
394 {
395         clearAll();
396 }
397
398 void ParticleManager::step(float dtime)
399 {
400         stepParticles (dtime);
401         stepSpawners (dtime);
402 }
403
404 void ParticleManager::stepSpawners (float dtime)
405 {
406         MutexAutoLock lock(m_spawner_list_lock);
407         for (std::map<u32, ParticleSpawner*>::iterator i =
408                         m_particle_spawners.begin();
409                         i != m_particle_spawners.end();)
410         {
411                 if (i->second->get_expired())
412                 {
413                         delete i->second;
414                         m_particle_spawners.erase(i++);
415                 }
416                 else
417                 {
418                         i->second->step(dtime, m_env);
419                         ++i;
420                 }
421         }
422 }
423
424 void ParticleManager::stepParticles (float dtime)
425 {
426         MutexAutoLock lock(m_particle_list_lock);
427         for(std::vector<Particle*>::iterator i = m_particles.begin();
428                         i != m_particles.end();)
429         {
430                 if ((*i)->get_expired())
431                 {
432                         (*i)->remove();
433                         delete *i;
434                         i = m_particles.erase(i);
435                 }
436                 else
437                 {
438                         (*i)->step(dtime);
439                         ++i;
440                 }
441         }
442 }
443
444 void ParticleManager::clearAll ()
445 {
446         MutexAutoLock lock(m_spawner_list_lock);
447         MutexAutoLock lock2(m_particle_list_lock);
448         for(std::map<u32, ParticleSpawner*>::iterator i =
449                         m_particle_spawners.begin();
450                         i != m_particle_spawners.end();)
451         {
452                 delete i->second;
453                 m_particle_spawners.erase(i++);
454         }
455
456         for(std::vector<Particle*>::iterator i =
457                         m_particles.begin();
458                         i != m_particles.end();)
459         {
460                 (*i)->remove();
461                 delete *i;
462                 i = m_particles.erase(i);
463         }
464 }
465
466 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
467         LocalPlayer *player)
468 {
469         switch (event->type) {
470                 case CE_DELETE_PARTICLESPAWNER: {
471                         MutexAutoLock lock(m_spawner_list_lock);
472                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
473                                         m_particle_spawners.end()) {
474                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
475                                 m_particle_spawners.erase(event->delete_particlespawner.id);
476                         }
477                         // no allocated memory in delete event
478                         break;
479                 }
480                 case CE_ADD_PARTICLESPAWNER: {
481                         {
482                                 MutexAutoLock lock(m_spawner_list_lock);
483                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
484                                                 m_particle_spawners.end()) {
485                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
486                                         m_particle_spawners.erase(event->add_particlespawner.id);
487                                 }
488                         }
489
490                         video::ITexture *texture =
491                                 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
492
493                         ParticleSpawner *toadd = new ParticleSpawner(client, player,
494                                         event->add_particlespawner.amount,
495                                         event->add_particlespawner.spawntime,
496                                         *event->add_particlespawner.minpos,
497                                         *event->add_particlespawner.maxpos,
498                                         *event->add_particlespawner.minvel,
499                                         *event->add_particlespawner.maxvel,
500                                         *event->add_particlespawner.minacc,
501                                         *event->add_particlespawner.maxacc,
502                                         event->add_particlespawner.minexptime,
503                                         event->add_particlespawner.maxexptime,
504                                         event->add_particlespawner.minsize,
505                                         event->add_particlespawner.maxsize,
506                                         event->add_particlespawner.collisiondetection,
507                                         event->add_particlespawner.collision_removal,
508                                         event->add_particlespawner.attached_id,
509                                         event->add_particlespawner.vertical,
510                                         texture,
511                                         event->add_particlespawner.id,
512                                         event->add_particlespawner.animation,
513                                         event->add_particlespawner.glow,
514                                         this);
515
516                         /* delete allocated content of event */
517                         delete event->add_particlespawner.minpos;
518                         delete event->add_particlespawner.maxpos;
519                         delete event->add_particlespawner.minvel;
520                         delete event->add_particlespawner.maxvel;
521                         delete event->add_particlespawner.minacc;
522                         delete event->add_particlespawner.texture;
523                         delete event->add_particlespawner.maxacc;
524
525                         {
526                                 MutexAutoLock lock(m_spawner_list_lock);
527                                 m_particle_spawners.insert(
528                                                 std::pair<u32, ParticleSpawner*>(
529                                                                 event->add_particlespawner.id,
530                                                                 toadd));
531                         }
532                         break;
533                 }
534                 case CE_SPAWN_PARTICLE: {
535                         video::ITexture *texture =
536                                 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
537
538                         Particle *toadd = new Particle(client, player, m_env,
539                                         *event->spawn_particle.pos,
540                                         *event->spawn_particle.vel,
541                                         *event->spawn_particle.acc,
542                                         event->spawn_particle.expirationtime,
543                                         event->spawn_particle.size,
544                                         event->spawn_particle.collisiondetection,
545                                         event->spawn_particle.collision_removal,
546                                         event->spawn_particle.vertical,
547                                         texture,
548                                         v2f(0.0, 0.0),
549                                         v2f(1.0, 1.0),
550                                         event->spawn_particle.animation,
551                                         event->spawn_particle.glow);
552
553                         addParticle(toadd);
554
555                         delete event->spawn_particle.pos;
556                         delete event->spawn_particle.vel;
557                         delete event->spawn_particle.acc;
558                         delete event->spawn_particle.texture;
559
560                         break;
561                 }
562                 default: break;
563         }
564 }
565
566 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
567         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
568 {
569         // No particles for "airlike" nodes
570         if (f.drawtype == NDT_AIRLIKE)
571                 return;
572
573         // set the amount of particles here
574         for (u16 j = 0; j < 32; j++) {
575                 addNodeParticle(gamedef, player, pos, n, f);
576         }
577 }
578
579 void ParticleManager::addNodeParticle(IGameDef* gamedef,
580         LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
581 {
582         // No particles for "airlike" nodes
583         if (f.drawtype == NDT_AIRLIKE)
584                 return;
585
586         // Texture
587         u8 texid = myrand_range(0, 5);
588         const TileLayer &tile = f.tiles[texid].layers[0];
589         video::ITexture *texture;
590         struct TileAnimationParams anim;
591         anim.type = TAT_NONE;
592
593         // Only use first frame of animated texture
594         if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
595                 texture = (*tile.frames)[0].texture;
596         else
597                 texture = tile.texture;
598
599         float size = rand() % 64 / 512.;
600         float visual_size = BS * size;
601         if (tile.scale)
602                 size /= tile.scale;
603         v2f texsize(size * 2, size * 2);
604         v2f texpos;
605         texpos.X = ((rand() % 64) / 64. - texsize.X);
606         texpos.Y = ((rand() % 64) / 64. - texsize.Y);
607
608         // Physics
609         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
610                         rand() % 100 / 35.,
611                         (rand() % 100 / 50. - 1) / 1.5);
612
613         v3f acceleration(0,-9,0);
614         v3f particlepos = v3f(
615                 (f32) pos.X + rand() %100 /200. - 0.25,
616                 (f32) pos.Y + rand() %100 /200. - 0.25,
617                 (f32) pos.Z + rand() %100 /200. - 0.25
618         );
619
620         video::SColor color;
621         if (tile.has_color)
622                 color = tile.color;
623         else
624                 n.getColor(f, &color);
625
626         Particle* toadd = new Particle(
627                 gamedef,
628                 player,
629                 m_env,
630                 particlepos,
631                 velocity,
632                 acceleration,
633                 rand() % 100 / 100., // expiration time
634                 visual_size,
635                 true,
636                 false,
637                 false,
638                 texture,
639                 texpos,
640                 texsize,
641                 anim,
642                 0,
643                 color);
644
645         addParticle(toadd);
646 }
647
648 void ParticleManager::addParticle(Particle* toadd)
649 {
650         MutexAutoLock lock(m_particle_list_lock);
651         m_particles.push_back(toadd);
652 }