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