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