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