]> git.lizzy.rs Git - dragonfireclient.git/blob - src/particles.cpp
ParticleSpawner: fix offset being added twice
[dragonfireclient.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         v3f attached_offset = v3f(0,0,0);
257         if (m_attached_id != 0) {
258                 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id))
259                         attached_offset = attached->getPosition() / BS;
260                 else
261                         unloaded = true;
262         }
263
264         if (m_spawntime != 0) // Spawner exists for a predefined timespan
265         {
266                 for(std::vector<float>::iterator i = m_spawntimes.begin();
267                                 i != m_spawntimes.end();)
268                 {
269                         if ((*i) <= m_time && m_amount > 0)
270                         {
271                                 m_amount--;
272
273                                 // Pretend to, but don't actually spawn a
274                                 // particle if it is attached to an unloaded
275                                 // object.
276                                 if (!unloaded) {
277                                         v3f pos = random_v3f(m_minpos, m_maxpos);
278                                         v3f vel = random_v3f(m_minvel, m_maxvel);
279                                         v3f acc = random_v3f(m_minacc, m_maxacc);
280                                         // Make relative to offest
281                                         pos += attached_offset;
282                                         float exptime = rand()/(float)RAND_MAX
283                                                         *(m_maxexptime-m_minexptime)
284                                                         +m_minexptime;
285                                         float size = rand()/(float)RAND_MAX
286                                                         *(m_maxsize-m_minsize)
287                                                         +m_minsize;
288
289                                         Particle* toadd = new Particle(
290                                                 m_gamedef,
291                                                 m_smgr,
292                                                 m_player,
293                                                 env,
294                                                 pos,
295                                                 vel,
296                                                 acc,
297                                                 exptime,
298                                                 size,
299                                                 m_collisiondetection,
300                                                 m_collision_removal,
301                                                 m_vertical,
302                                                 m_texture,
303                                                 v2f(0.0, 0.0),
304                                                 v2f(1.0, 1.0));
305                                         m_particlemanager->addParticle(toadd);
306                                 }
307                                 i = m_spawntimes.erase(i);
308                         }
309                         else
310                         {
311                                 ++i;
312                         }
313                 }
314         }
315         else // Spawner exists for an infinity timespan, spawn on a per-second base
316         {
317                 // Skip this step if attached to an unloaded object
318                 if (unloaded)
319                         return;
320                 for (int i = 0; i <= m_amount; i++)
321                 {
322                         if (rand()/(float)RAND_MAX < dtime)
323                         {
324                                 v3f pos = random_v3f(m_minpos, m_maxpos)
325                                                 + attached_offset;
326                                 v3f vel = random_v3f(m_minvel, m_maxvel);
327                                 v3f acc = random_v3f(m_minacc, m_maxacc);
328                                 float exptime = rand()/(float)RAND_MAX
329                                                 *(m_maxexptime-m_minexptime)
330                                                 +m_minexptime;
331                                 float size = rand()/(float)RAND_MAX
332                                                 *(m_maxsize-m_minsize)
333                                                 +m_minsize;
334
335                                 Particle* toadd = new Particle(
336                                         m_gamedef,
337                                         m_smgr,
338                                         m_player,
339                                         env,
340                                         pos,
341                                         vel,
342                                         acc,
343                                         exptime,
344                                         size,
345                                         m_collisiondetection,
346                                         m_collision_removal,
347                                         m_vertical,
348                                         m_texture,
349                                         v2f(0.0, 0.0),
350                                         v2f(1.0, 1.0));
351                                 m_particlemanager->addParticle(toadd);
352                         }
353                 }
354         }
355 }
356
357
358 ParticleManager::ParticleManager(ClientEnvironment* env) :
359         m_env(env)
360 {}
361
362 ParticleManager::~ParticleManager()
363 {
364         clearAll();
365 }
366
367 void ParticleManager::step(float dtime)
368 {
369         stepParticles (dtime);
370         stepSpawners (dtime);
371 }
372
373 void ParticleManager::stepSpawners (float dtime)
374 {
375         MutexAutoLock lock(m_spawner_list_lock);
376         for (std::map<u32, ParticleSpawner*>::iterator i =
377                         m_particle_spawners.begin();
378                         i != m_particle_spawners.end();)
379         {
380                 if (i->second->get_expired())
381                 {
382                         delete i->second;
383                         m_particle_spawners.erase(i++);
384                 }
385                 else
386                 {
387                         i->second->step(dtime, m_env);
388                         ++i;
389                 }
390         }
391 }
392
393 void ParticleManager::stepParticles (float dtime)
394 {
395         MutexAutoLock lock(m_particle_list_lock);
396         for(std::vector<Particle*>::iterator i = m_particles.begin();
397                         i != m_particles.end();)
398         {
399                 if ((*i)->get_expired())
400                 {
401                         (*i)->remove();
402                         delete *i;
403                         i = m_particles.erase(i);
404                 }
405                 else
406                 {
407                         (*i)->step(dtime);
408                         ++i;
409                 }
410         }
411 }
412
413 void ParticleManager::clearAll ()
414 {
415         MutexAutoLock lock(m_spawner_list_lock);
416         MutexAutoLock lock2(m_particle_list_lock);
417         for(std::map<u32, ParticleSpawner*>::iterator i =
418                         m_particle_spawners.begin();
419                         i != m_particle_spawners.end();)
420         {
421                 delete i->second;
422                 m_particle_spawners.erase(i++);
423         }
424
425         for(std::vector<Particle*>::iterator i =
426                         m_particles.begin();
427                         i != m_particles.end();)
428         {
429                 (*i)->remove();
430                 delete *i;
431                 i = m_particles.erase(i);
432         }
433 }
434
435 void ParticleManager::handleParticleEvent(ClientEvent *event, IGameDef *gamedef,
436                 scene::ISceneManager* smgr, LocalPlayer *player)
437 {
438         switch (event->type) {
439                 case CE_DELETE_PARTICLESPAWNER: {
440                         MutexAutoLock lock(m_spawner_list_lock);
441                         if (m_particle_spawners.find(event->delete_particlespawner.id) !=
442                                         m_particle_spawners.end()) {
443                                 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
444                                 m_particle_spawners.erase(event->delete_particlespawner.id);
445                         }
446                         // no allocated memory in delete event
447                         break;
448                 }
449                 case CE_ADD_PARTICLESPAWNER: {
450                         {
451                                 MutexAutoLock lock(m_spawner_list_lock);
452                                 if (m_particle_spawners.find(event->add_particlespawner.id) !=
453                                                 m_particle_spawners.end()) {
454                                         delete m_particle_spawners.find(event->add_particlespawner.id)->second;
455                                         m_particle_spawners.erase(event->add_particlespawner.id);
456                                 }
457                         }
458
459                         video::ITexture *texture =
460                                 gamedef->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
461
462                         ParticleSpawner* toadd = new ParticleSpawner(gamedef, smgr, player,
463                                         event->add_particlespawner.amount,
464                                         event->add_particlespawner.spawntime,
465                                         *event->add_particlespawner.minpos,
466                                         *event->add_particlespawner.maxpos,
467                                         *event->add_particlespawner.minvel,
468                                         *event->add_particlespawner.maxvel,
469                                         *event->add_particlespawner.minacc,
470                                         *event->add_particlespawner.maxacc,
471                                         event->add_particlespawner.minexptime,
472                                         event->add_particlespawner.maxexptime,
473                                         event->add_particlespawner.minsize,
474                                         event->add_particlespawner.maxsize,
475                                         event->add_particlespawner.collisiondetection,
476                                         event->add_particlespawner.collision_removal,
477                                         event->add_particlespawner.attached_id,
478                                         event->add_particlespawner.vertical,
479                                         texture,
480                                         event->add_particlespawner.id,
481                                         this);
482
483                         /* delete allocated content of event */
484                         delete event->add_particlespawner.minpos;
485                         delete event->add_particlespawner.maxpos;
486                         delete event->add_particlespawner.minvel;
487                         delete event->add_particlespawner.maxvel;
488                         delete event->add_particlespawner.minacc;
489                         delete event->add_particlespawner.texture;
490                         delete event->add_particlespawner.maxacc;
491
492                         {
493                                 MutexAutoLock lock(m_spawner_list_lock);
494                                 m_particle_spawners.insert(
495                                                 std::pair<u32, ParticleSpawner*>(
496                                                                 event->add_particlespawner.id,
497                                                                 toadd));
498                         }
499                         break;
500                 }
501                 case CE_SPAWN_PARTICLE: {
502                         video::ITexture *texture =
503                                 gamedef->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
504
505                         Particle* toadd = new Particle(gamedef, smgr, player, m_env,
506                                         *event->spawn_particle.pos,
507                                         *event->spawn_particle.vel,
508                                         *event->spawn_particle.acc,
509                                         event->spawn_particle.expirationtime,
510                                         event->spawn_particle.size,
511                                         event->spawn_particle.collisiondetection,
512                                         event->spawn_particle.collision_removal,
513                                         event->spawn_particle.vertical,
514                                         texture,
515                                         v2f(0.0, 0.0),
516                                         v2f(1.0, 1.0));
517
518                         addParticle(toadd);
519
520                         delete event->spawn_particle.pos;
521                         delete event->spawn_particle.vel;
522                         delete event->spawn_particle.acc;
523
524                         break;
525                 }
526                 default: break;
527         }
528 }
529
530 void ParticleManager::addDiggingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
531                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
532 {
533         for (u16 j = 0; j < 32; j++) // set the amount of particles here
534         {
535                 addNodeParticle(gamedef, smgr, player, pos, tiles);
536         }
537 }
538
539 void ParticleManager::addPunchingParticles(IGameDef* gamedef, scene::ISceneManager* smgr,
540                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
541 {
542         addNodeParticle(gamedef, smgr, player, pos, tiles);
543 }
544
545 void ParticleManager::addNodeParticle(IGameDef* gamedef, scene::ISceneManager* smgr,
546                 LocalPlayer *player, v3s16 pos, const TileSpec tiles[])
547 {
548         // Texture
549         u8 texid = myrand_range(0, 5);
550         video::ITexture *texture = tiles[texid].texture;
551
552         // Only use first frame of animated texture
553         f32 ymax = 1;
554         if(tiles[texid].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES)
555                 ymax /= tiles[texid].animation_frame_count;
556
557         float size = rand() % 64 / 512.;
558         float visual_size = BS * size;
559         v2f texsize(size * 2, ymax * size * 2);
560         v2f texpos;
561         texpos.X = ((rand() % 64) / 64. - texsize.X);
562         texpos.Y = ymax * ((rand() % 64) / 64. - texsize.Y);
563
564         // Physics
565         v3f velocity((rand() % 100 / 50. - 1) / 1.5,
566                         rand() % 100 / 35.,
567                         (rand() % 100 / 50. - 1) / 1.5);
568
569         v3f acceleration(0,-9,0);
570         v3f particlepos = v3f(
571                 (f32) pos.X + rand() %100 /200. - 0.25,
572                 (f32) pos.Y + rand() %100 /200. - 0.25,
573                 (f32) pos.Z + rand() %100 /200. - 0.25
574         );
575
576         Particle* toadd = new Particle(
577                 gamedef,
578                 smgr,
579                 player,
580                 m_env,
581                 particlepos,
582                 velocity,
583                 acceleration,
584                 rand() % 100 / 100., // expiration time
585                 visual_size,
586                 true,
587                 false,
588                 false,
589                 texture,
590                 texpos,
591                 texsize);
592
593         addParticle(toadd);
594 }
595
596 void ParticleManager::addParticle(Particle* toadd)
597 {
598         MutexAutoLock lock(m_particle_list_lock);
599         m_particles.push_back(toadd);
600 }