v2d & aabbox3d<f32> & sky cleanups
[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         if (event->type == CE_DELETE_PARTICLESPAWNER) {
412                 MutexAutoLock lock(m_spawner_list_lock);
413                 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
414                                 m_particle_spawners.end())
415                 {
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                 return;
421         }
422
423         if (event->type == CE_ADD_PARTICLESPAWNER) {
424
425                 {
426                         MutexAutoLock lock(m_spawner_list_lock);
427                         if (m_particle_spawners.find(event->add_particlespawner.id) !=
428                                                         m_particle_spawners.end())
429                         {
430                                 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
431                                 m_particle_spawners.erase(event->add_particlespawner.id);
432                         }
433                 }
434                 video::ITexture *texture =
435                         gamedef->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
436
437                 ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player,
438                                 event->add_particlespawner.amount,
439                                 event->add_particlespawner.spawntime,
440                                 *event->add_particlespawner.minpos,
441                                 *event->add_particlespawner.maxpos,
442                                 *event->add_particlespawner.minvel,
443                                 *event->add_particlespawner.maxvel,
444                                 *event->add_particlespawner.minacc,
445                                 *event->add_particlespawner.maxacc,
446                                 event->add_particlespawner.minexptime,
447                                 event->add_particlespawner.maxexptime,
448                                 event->add_particlespawner.minsize,
449                                 event->add_particlespawner.maxsize,
450                                 event->add_particlespawner.collisiondetection,
451                                 event->add_particlespawner.vertical,
452                                 texture,
453                                 event->add_particlespawner.id,
454                                 this);
455
456                 /* delete allocated content of event */
457                 delete event->add_particlespawner.minpos;
458                 delete event->add_particlespawner.maxpos;
459                 delete event->add_particlespawner.minvel;
460                 delete event->add_particlespawner.maxvel;
461                 delete event->add_particlespawner.minacc;
462                 delete event->add_particlespawner.texture;
463                 delete event->add_particlespawner.maxacc;
464
465                 {
466                         MutexAutoLock lock(m_spawner_list_lock);
467                         m_particle_spawners.insert(
468                                         std::pair<u32, ParticleSpawner*>(
469                                                         event->add_particlespawner.id,
470                                                         toadd));
471                 }
472
473                 return;
474         }
475
476         if (event->type == CE_SPAWN_PARTICLE) {
477                 video::ITexture *texture =
478                         gamedef->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
479
480                 Particle* toadd = new Particle(gamedef, smgr, player, m_env,
481                                 *event->spawn_particle.pos,
482                                 *event->spawn_particle.vel,
483                                 *event->spawn_particle.acc,
484                                 event->spawn_particle.expirationtime,
485                                 event->spawn_particle.size,
486                                 event->spawn_particle.collisiondetection,
487                                 event->spawn_particle.vertical,
488                                 texture,
489                                 v2f(0.0, 0.0),
490                                 v2f(1.0, 1.0));
491
492                 addParticle(toadd);
493
494                 delete event->spawn_particle.pos;
495                 delete event->spawn_particle.vel;
496                 delete event->spawn_particle.acc;
497
498                 return;
499         }
500 }
501
502 void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
503                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
504 {
505         for (u16 j = 0; j < 32; j++) // set the amount of particles here
506         {
507                 addNodeParticle(gamedef, smgr, player, pos, tiles);
508         }
509 }
510
511 void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
512                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
513 {
514         addNodeParticle(gamedef, smgr, player, pos, tiles);
515 }
516
517 void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
518                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
519 {
520         // Texture
521         u8 texid = myrand_range(0, 5);
522         video::ITexture *texture = tiles[texid].texture;
523
524         // Only use first frame of animated texture
525         f32 ymax = 1;
526         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
527                 ymax /= tiles[texid].animation_frame_count;
528
529         float size = rand() % 64 / 512.;
530         float visual_size = BS * size;
531         v2f texsize(size * 2, ymax * size * 2);
532         v2f texpos;
533         texpos.X = ((rand() % 64) / 64. - texsize.X);
534         texpos.Y = ymax * ((rand() % 64) / 64. - texsize.Y);
535
536         // Physics
537         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
538                         rand() % 100 / 35.,
539                         (rand() % 100 / 50. - 1) / 1.5);
540
541         v3f acceleration(0,-9,0);
542         v3f particlepos = v3f(
543                 (f32) pos.X + rand() %100 /200. - 0.25,
544                 (f32) pos.Y + rand() %100 /200. - 0.25,
545                 (f32) pos.Z + rand() %100 /200. - 0.25
546         );
547
548         Particle* toadd = new Particle(
549                 gamedef,
550                 smgr,
551                 player,
552                 m_env,
553                 particlepos,
554                 velocity,
555                 acceleration,
556                 rand() % 100 / 100., // expiration time
557                 visual_size,
558                 true,
559                 false,
560                 texture,
561                 texpos,
562                 texsize);
563
564         addParticle(toadd);
565 }
566
567 void ParticleManager::addParticle(Particle* toadd)
568 {
569         MutexAutoLock lock(m_particle_list_lock);
570         m_particles.push_back(toadd);
571 }