Tolerate packet reordering in the early init process
[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 "constants.h"
22 #include "debug.h"
23 #include "settings.h"
24 #include "client/tile.h"
25 #include "gamedef.h"
26 #include "collision.h"
27 #include <stdlib.h>
28 #include "util/numeric.h"
29 #include "light.h"
30 #include "environment.h"
31 #include "clientmap.h"
32 #include "mapnode.h"
33 #include "client.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         scene::ISceneManager* smgr,
49         LocalPlayer *player,
50         ClientEnvironment *env,
51         v3f pos,
52         v3f velocity,
53         v3f acceleration,
54         float expirationtime,
55         float size,
56         bool collisiondetection,
57         bool vertical,
58         video::ITexture *texture,
59         v2f texpos,
60         v2f texsize
61 ):
62         scene::ISceneNode(smgr->getRootSceneNode(), smgr)
63 {
64         // Misc
65         m_gamedef = gamedef;
66         m_env = env;
67
68         // Texture
69         m_material.setFlag(video::EMF_LIGHTING, false);
70         m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
71         m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
72         m_material.setFlag(video::EMF_FOG_ENABLE, true);
73         m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
74         m_material.setTexture(0, texture);
75         m_texpos = texpos;
76         m_texsize = texsize;
77
78
79         // Particle related
80         m_pos = pos;
81         m_velocity = velocity;
82         m_acceleration = acceleration;
83         m_expiration = expirationtime;
84         m_time = 0;
85         m_player = player;
86         m_size = size;
87         m_collisiondetection = collisiondetection;
88         m_vertical = vertical;
89
90         // Irrlicht stuff
91         m_collisionbox = aabb3f
92                         (-size/2,-size/2,-size/2,size/2,size/2,size/2);
93         this->setAutomaticCulling(scene::EAC_OFF);
94
95         // Init lighting
96         updateLight();
97
98         // Init model
99         updateVertices();
100 }
101
102 Particle::~Particle()
103 {
104 }
105
106 void Particle::OnRegisterSceneNode()
107 {
108         if (IsVisible)
109                 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
110
111         ISceneNode::OnRegisterSceneNode();
112 }
113
114 void Particle::render()
115 {
116         video::IVideoDriver* driver = SceneManager->getVideoDriver();
117         driver->setMaterial(m_material);
118         driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
119
120         u16 indices[] = {0,1,2, 2,3,0};
121         driver->drawVertexPrimitiveList(m_vertices, 4,
122                         indices, 2, video::EVT_STANDARD,
123                         scene::EPT_TRIANGLES, video::EIT_16BIT);
124 }
125
126 void Particle::step(float dtime)
127 {
128         m_time += dtime;
129         if (m_collisiondetection)
130         {
131                 aabb3f box = m_collisionbox;
132                 v3f p_pos = m_pos*BS;
133                 v3f p_velocity = m_velocity*BS;
134                 collisionMoveSimple(m_env, m_gamedef,
135                         BS*0.5, box,
136                         0, dtime,
137                         &p_pos, &p_velocity, m_acceleration * BS);
138                 m_pos = p_pos/BS;
139                 m_velocity = p_velocity/BS;
140         }
141         else
142         {
143                 m_velocity += m_acceleration * dtime;
144                 m_pos += m_velocity * dtime;
145         }
146
147         // Update lighting
148         updateLight();
149
150         // Update model
151         updateVertices();
152 }
153
154 void Particle::updateLight()
155 {
156         u8 light = 0;
157         bool pos_ok;
158
159         v3s16 p = v3s16(
160                 floor(m_pos.X+0.5),
161                 floor(m_pos.Y+0.5),
162                 floor(m_pos.Z+0.5)
163         );
164         MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
165         if (pos_ok)
166                 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
167         else
168                 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
169
170         m_light = decode_light(light);
171 }
172
173 void Particle::updateVertices()
174 {
175         video::SColor c(255, m_light, m_light, m_light);
176         f32 tx0 = m_texpos.X;
177         f32 tx1 = m_texpos.X + m_texsize.X;
178         f32 ty0 = m_texpos.Y;
179         f32 ty1 = m_texpos.Y + m_texsize.Y;
180
181         m_vertices[0] = video::S3DVertex(-m_size/2,-m_size/2,0, 0,0,0,
182                         c, tx0, ty1);
183         m_vertices[1] = video::S3DVertex(m_size/2,-m_size/2,0, 0,0,0,
184                         c, tx1, ty1);
185         m_vertices[2] = video::S3DVertex(m_size/2,m_size/2,0, 0,0,0,
186                         c, tx1, ty0);
187         m_vertices[3] = video::S3DVertex(-m_size/2,m_size/2,0, 0,0,0,
188                         c, tx0, ty0);
189
190         v3s16 camera_offset = m_env->getCameraOffset();
191         for(u16 i=0; i<4; i++)
192         {
193                 if (m_vertical) {
194                         v3f ppos = m_player->getPosition()/BS;
195                         m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
196                 } else {
197                         m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
198                         m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
199                 }
200                 m_box.addInternalPoint(m_vertices[i].Pos);
201                 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
202         }
203 }
204
205 /*
206         ParticleSpawner
207 */
208
209 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
210         u16 amount, float time,
211         v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
212         float minexptime, float maxexptime, float minsize, float maxsize,
213         bool collisiondetection, bool vertical, video::ITexture *texture, u32 id,
214         ParticleManager *p_manager) :
215         m_particlemanager(p_manager)
216 {
217         m_gamedef = gamedef;
218         m_smgr = smgr;
219         m_player = player;
220         m_amount = amount;
221         m_spawntime = time;
222         m_minpos = minpos;
223         m_maxpos = maxpos;
224         m_minvel = minvel;
225         m_maxvel = maxvel;
226         m_minacc = minacc;
227         m_maxacc = maxacc;
228         m_minexptime = minexptime;
229         m_maxexptime = maxexptime;
230         m_minsize = minsize;
231         m_maxsize = maxsize;
232         m_collisiondetection = collisiondetection;
233         m_vertical = vertical;
234         m_texture = texture;
235         m_time = 0;
236
237         for (u16 i = 0; i<=m_amount; i++)
238         {
239                 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
240                 m_spawntimes.push_back(spawntime);
241         }
242 }
243
244 ParticleSpawner::~ParticleSpawner() {}
245
246 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
247 {
248         m_time += dtime;
249
250         if (m_spawntime != 0) // Spawner exists for a predefined timespan
251         {
252                 for(std::vector<float>::iterator i = m_spawntimes.begin();
253                                 i != m_spawntimes.end();)
254                 {
255                         if ((*i) <= m_time && m_amount > 0)
256                         {
257                                 m_amount--;
258
259                                 v3f pos = random_v3f(m_minpos, m_maxpos);
260                                 v3f vel = random_v3f(m_minvel, m_maxvel);
261                                 v3f acc = random_v3f(m_minacc, m_maxacc);
262                                 float exptime = rand()/(float)RAND_MAX
263                                                 *(m_maxexptime-m_minexptime)
264                                                 +m_minexptime;
265                                 float size = rand()/(float)RAND_MAX
266                                                 *(m_maxsize-m_minsize)
267                                                 +m_minsize;
268
269                                 Particle* toadd = new Particle(
270                                         m_gamedef,
271                                         m_smgr,
272                                         m_player,
273                                         env,
274                                         pos,
275                                         vel,
276                                         acc,
277                                         exptime,
278                                         size,
279                                         m_collisiondetection,
280                                         m_vertical,
281                                         m_texture,
282                                         v2f(0.0, 0.0),
283                                         v2f(1.0, 1.0));
284                                 m_particlemanager->addParticle(toadd);
285                                 i = m_spawntimes.erase(i);
286                         }
287                         else
288                         {
289                                 ++i;
290                         }
291                 }
292         }
293         else // Spawner exists for an infinity timespan, spawn on a per-second base
294         {
295                 for (int i = 0; i <= m_amount; i++)
296                 {
297                         if (rand()/(float)RAND_MAX < dtime)
298                         {
299                                 v3f pos = random_v3f(m_minpos, m_maxpos);
300                                 v3f vel = random_v3f(m_minvel, m_maxvel);
301                                 v3f acc = random_v3f(m_minacc, m_maxacc);
302                                 float exptime = rand()/(float)RAND_MAX
303                                                 *(m_maxexptime-m_minexptime)
304                                                 +m_minexptime;
305                                 float size = rand()/(float)RAND_MAX
306                                                 *(m_maxsize-m_minsize)
307                                                 +m_minsize;
308
309                                 Particle* toadd = new Particle(
310                                         m_gamedef,
311                                         m_smgr,
312                                         m_player,
313                                         env,
314                                         pos,
315                                         vel,
316                                         acc,
317                                         exptime,
318                                         size,
319                                         m_collisiondetection,
320                                         m_vertical,
321                                         m_texture,
322                                         v2f(0.0, 0.0),
323                                         v2f(1.0, 1.0));
324                                 m_particlemanager->addParticle(toadd);
325                         }
326                 }
327         }
328 }
329
330
331 ParticleManager::ParticleManager(ClientEnvironment* env) :
332         m_env(env)
333 {}
334
335 ParticleManager::~ParticleManager()
336 {
337         clearAll();
338 }
339
340 void ParticleManager::step(float dtime)
341 {
342         stepParticles (dtime);
343         stepSpawners (dtime);
344 }
345
346 void ParticleManager::stepSpawners (float dtime)
347 {
348         MutexAutoLock lock(m_spawner_list_lock);
349         for (std::map<u32, ParticleSpawner*>::iterator i =
350                         m_particle_spawners.begin();
351                         i != m_particle_spawners.end();)
352         {
353                 if (i->second->get_expired())
354                 {
355                         delete i->second;
356                         m_particle_spawners.erase(i++);
357                 }
358                 else
359                 {
360                         i->second->step(dtime, m_env);
361                         ++i;
362                 }
363         }
364 }
365
366 void ParticleManager::stepParticles (float dtime)
367 {
368         MutexAutoLock lock(m_particle_list_lock);
369         for(std::vector<Particle*>::iterator i = m_particles.begin();
370                         i != m_particles.end();)
371         {
372                 if ((*i)->get_expired())
373                 {
374                         (*i)->remove();
375                         delete *i;
376                         i = m_particles.erase(i);
377                 }
378                 else
379                 {
380                         (*i)->step(dtime);
381                         ++i;
382                 }
383         }
384 }
385
386 void ParticleManager::clearAll ()
387 {
388         MutexAutoLock lock(m_spawner_list_lock);
389         MutexAutoLock lock2(m_particle_list_lock);
390         for(std::map<u32, ParticleSpawner*>::iterator i =
391                         m_particle_spawners.begin();
392                         i != m_particle_spawners.end();)
393         {
394                 delete i->second;
395                 m_particle_spawners.erase(i++);
396         }
397
398         for(std::vector<Particle*>::iterator i =
399                         m_particles.begin();
400                         i != m_particles.end();)
401         {
402                 (*i)->remove();
403                 delete *i;
404                 i = m_particles.erase(i);
405         }
406 }
407
408 void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef,
409                 scene::ISceneManager* smgr, LocalPlayer *player)
410 {
411         switch (event->type) {
412                 case CE_DELETE_PARTICLESPAWNER: {
413                         MutexAutoLock lock(m_spawner_list_lock);
414                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
415                                         m_particle_spawners.end()) {
416                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
417                                 m_particle_spawners.erase(event->delete_particlespawner.id);
418                         }
419                         // no allocated memory in delete event
420                         break;
421                 }
422                 case CE_ADD_PARTICLESPAWNER: {
423                         {
424                                 MutexAutoLock lock(m_spawner_list_lock);
425                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
426                                                 m_particle_spawners.end()) {
427                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
428                                         m_particle_spawners.erase(event->add_particlespawner.id);
429                                 }
430                         }
431
432                         video::ITexture *texture =
433                                 gamedef->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
434
435                         ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player,
436                                         event->add_particlespawner.amount,
437                                         event->add_particlespawner.spawntime,
438                                         *event->add_particlespawner.minpos,
439                                         *event->add_particlespawner.maxpos,
440                                         *event->add_particlespawner.minvel,
441                                         *event->add_particlespawner.maxvel,
442                                         *event->add_particlespawner.minacc,
443                                         *event->add_particlespawner.maxacc,
444                                         event->add_particlespawner.minexptime,
445                                         event->add_particlespawner.maxexptime,
446                                         event->add_particlespawner.minsize,
447                                         event->add_particlespawner.maxsize,
448                                         event->add_particlespawner.collisiondetection,
449                                         event->add_particlespawner.vertical,
450                                         texture,
451                                         event->add_particlespawner.id,
452                                         this);
453
454                         /* delete allocated content of event */
455                         delete event->add_particlespawner.minpos;
456                         delete event->add_particlespawner.maxpos;
457                         delete event->add_particlespawner.minvel;
458                         delete event->add_particlespawner.maxvel;
459                         delete event->add_particlespawner.minacc;
460                         delete event->add_particlespawner.texture;
461                         delete event->add_particlespawner.maxacc;
462
463                         {
464                                 MutexAutoLock lock(m_spawner_list_lock);
465                                 m_particle_spawners.insert(
466                                                 std::pair<u32, ParticleSpawner*>(
467                                                                 event->add_particlespawner.id,
468                                                                 toadd));
469                         }
470                         break;
471                 }
472                 case CE_SPAWN_PARTICLE: {
473                         video::ITexture *texture =
474                                 gamedef->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
475
476                         Particle* toadd = new Particle(gamedef, smgr, player, m_env,
477                                         *event->spawn_particle.pos,
478                                         *event->spawn_particle.vel,
479                                         *event->spawn_particle.acc,
480                                         event->spawn_particle.expirationtime,
481                                         event->spawn_particle.size,
482                                         event->spawn_particle.collisiondetection,
483                                         event->spawn_particle.vertical,
484                                         texture,
485                                         v2f(0.0, 0.0),
486                                         v2f(1.0, 1.0));
487
488                         addParticle(toadd);
489
490                         delete event->spawn_particle.pos;
491                         delete event->spawn_particle.vel;
492                         delete event->spawn_particle.acc;
493
494                         break;
495                 }
496                 default: break;
497         }
498 }
499
500 void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
501                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
502 {
503         for (u16 j = 0; j < 32; j++) // set the amount of particles here
504         {
505                 addNodeParticle(gamedef, smgr, player, pos, tiles);
506         }
507 }
508
509 void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
510                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
511 {
512         addNodeParticle(gamedef, smgr, player, pos, tiles);
513 }
514
515 void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
516                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
517 {
518         // Texture
519         u8 texid = myrand_range(0, 5);
520         video::ITexture *texture = tiles[texid].texture;
521
522         // Only use first frame of animated texture
523         f32 ymax = 1;
524         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
525                 ymax /= tiles[texid].animation_frame_count;
526
527         float size = rand() % 64 / 512.;
528         float visual_size = BS * size;
529         v2f texsize(size * 2, ymax * size * 2);
530         v2f texpos;
531         texpos.X = ((rand() % 64) / 64. - texsize.X);
532         texpos.Y = ymax * ((rand() % 64) / 64. - texsize.Y);
533
534         // Physics
535         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
536                         rand() % 100 / 35.,
537                         (rand() % 100 / 50. - 1) / 1.5);
538
539         v3f acceleration(0,-9,0);
540         v3f particlepos = v3f(
541                 (f32) pos.X + rand() %100 /200. - 0.25,
542                 (f32) pos.Y + rand() %100 /200. - 0.25,
543                 (f32) pos.Z + rand() %100 /200. - 0.25
544         );
545
546         Particle* toadd = new Particle(
547                 gamedef,
548                 smgr,
549                 player,
550                 m_env,
551                 particlepos,
552                 velocity,
553                 acceleration,
554                 rand() % 100 / 100., // expiration time
555                 visual_size,
556                 true,
557                 false,
558                 texture,
559                 texpos,
560                 texsize);
561
562         addParticle(toadd);
563 }
564
565 void ParticleManager::addParticle(Particle* toadd)
566 {
567         MutexAutoLock lock(m_particle_list_lock);
568         m_particles.push_back(toadd);
569 }