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