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