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