3 Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
20 #include "particles.h"
22 #include "collision.h"
24 #include "util/numeric.h"
26 #include "environment.h"
27 #include "clientmap.h"
35 v3f random_v3f(v3f min, v3f max)
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);
44 scene::ISceneManager* smgr,
46 ClientEnvironment *env,
52 bool collisiondetection,
53 bool collision_removal,
55 video::ITexture *texture,
58 const struct TileAnimationParams &anim,
62 scene::ISceneNode(smgr->getRootSceneNode(), smgr)
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);
78 m_animation_frame = 0;
79 m_animation_time = 0.0;
87 m_velocity = velocity;
88 m_acceleration = acceleration;
89 m_expiration = expirationtime;
93 m_collisiondetection = collisiondetection;
94 m_collision_removal = collision_removal;
95 m_vertical = vertical;
99 m_collisionbox = aabb3f
100 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
101 this->setAutomaticCulling(scene::EAC_OFF);
110 Particle::~Particle()
114 void Particle::OnRegisterSceneNode()
117 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
119 ISceneNode::OnRegisterSceneNode();
122 void Particle::render()
124 video::IVideoDriver* driver = SceneManager->getVideoDriver();
125 driver->setMaterial(m_material);
126 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
128 u16 indices[] = {0,1,2, 2,3,0};
129 driver->drawVertexPrimitiveList(m_vertices, 4,
130 indices, 2, video::EVT_STANDARD,
131 scene::EPT_TRIANGLES, video::EIT_16BIT);
134 void Particle::step(float dtime)
137 if (m_collisiondetection) {
138 aabb3f box = m_collisionbox;
139 v3f p_pos = m_pos * BS;
140 v3f p_velocity = m_velocity * BS;
141 collisionMoveResult r = collisionMoveSimple(m_env,
142 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
143 &p_velocity, m_acceleration * BS);
144 if (m_collision_removal && r.collides) {
145 // force expiration of the particle
149 m_velocity = p_velocity / BS;
152 m_velocity += m_acceleration * dtime;
153 m_pos += m_velocity * dtime;
155 if (m_animation.type != TAT_NONE) {
156 m_animation_time += dtime;
157 int frame_length_i, frame_count;
158 m_animation.determineParams(
159 m_material.getTexture(0)->getSize(),
160 &frame_count, &frame_length_i, NULL);
161 float frame_length = frame_length_i / 1000.0;
162 while (m_animation_time > frame_length) {
164 m_animation_time -= frame_length;
175 void Particle::updateLight()
185 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
187 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
189 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
191 u8 m_light = decode_light(light + m_glow);
193 m_light * m_base_color.getRed() / 255,
194 m_light * m_base_color.getGreen() / 255,
195 m_light * m_base_color.getBlue() / 255);
198 void Particle::updateVertices()
200 f32 tx0, tx1, ty0, ty1;
202 if (m_animation.type != TAT_NONE) {
203 const v2u32 texsize = m_material.getTexture(0)->getSize();
204 v2f texcoord, framesize_f;
206 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
207 m_animation.determineParams(texsize, NULL, NULL, &framesize);
208 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
210 tx0 = m_texpos.X + texcoord.X;
211 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
212 ty0 = m_texpos.Y + texcoord.Y;
213 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
216 tx1 = m_texpos.X + m_texsize.X;
218 ty1 = m_texpos.Y + m_texsize.Y;
221 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
222 0, 0, 0, 0, m_color, tx0, ty1);
223 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
224 0, 0, 0, 0, m_color, tx1, ty1);
225 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
226 0, 0, 0, 0, m_color, tx1, ty0);
227 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
228 0, 0, 0, 0, m_color, tx0, ty0);
230 v3s16 camera_offset = m_env->getCameraOffset();
231 for(u16 i=0; i<4; i++)
234 v3f ppos = m_player->getPosition()/BS;
235 m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
237 m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
238 m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
240 m_box.addInternalPoint(m_vertices[i].Pos);
241 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
249 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
250 u16 amount, float time,
251 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
252 float minexptime, float maxexptime, float minsize, float maxsize,
253 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
254 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
256 ParticleManager *p_manager) :
257 m_particlemanager(p_manager)
270 m_minexptime = minexptime;
271 m_maxexptime = maxexptime;
274 m_collisiondetection = collisiondetection;
275 m_collision_removal = collision_removal;
276 m_attached_id = attached_id;
277 m_vertical = vertical;
283 for (u16 i = 0; i<=m_amount; i++)
285 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
286 m_spawntimes.push_back(spawntime);
290 ParticleSpawner::~ParticleSpawner() {}
292 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
296 bool unloaded = false;
297 bool is_attached = false;
298 v3f attached_pos = v3f(0,0,0);
299 float attached_yaw = 0;
300 if (m_attached_id != 0) {
301 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
302 attached_pos = attached->getPosition() / BS;
303 attached_yaw = attached->getYaw();
310 if (m_spawntime != 0) // Spawner exists for a predefined timespan
312 for(std::vector<float>::iterator i = m_spawntimes.begin();
313 i != m_spawntimes.end();)
315 if ((*i) <= m_time && m_amount > 0)
319 // Pretend to, but don't actually spawn a
320 // particle if it is attached to an unloaded
323 v3f pos = random_v3f(m_minpos, m_maxpos);
324 v3f vel = random_v3f(m_minvel, m_maxvel);
325 v3f acc = random_v3f(m_minacc, m_maxacc);
328 // Apply attachment yaw and position
329 pos.rotateXZBy(attached_yaw);
331 vel.rotateXZBy(attached_yaw);
332 acc.rotateXZBy(attached_yaw);
335 float exptime = rand()/(float)RAND_MAX
336 *(m_maxexptime-m_minexptime)
338 float size = rand()/(float)RAND_MAX
339 *(m_maxsize-m_minsize)
342 Particle* toadd = new Particle(
352 m_collisiondetection,
360 m_particlemanager->addParticle(toadd);
362 i = m_spawntimes.erase(i);
370 else // Spawner exists for an infinity timespan, spawn on a per-second base
372 // Skip this step if attached to an unloaded object
375 for (int i = 0; i <= m_amount; i++)
377 if (rand()/(float)RAND_MAX < dtime)
379 v3f pos = random_v3f(m_minpos, m_maxpos);
380 v3f vel = random_v3f(m_minvel, m_maxvel);
381 v3f acc = random_v3f(m_minacc, m_maxacc);
384 // Apply attachment yaw and position
385 pos.rotateXZBy(attached_yaw);
387 vel.rotateXZBy(attached_yaw);
388 acc.rotateXZBy(attached_yaw);
391 float exptime = rand()/(float)RAND_MAX
392 *(m_maxexptime-m_minexptime)
394 float size = rand()/(float)RAND_MAX
395 *(m_maxsize-m_minsize)
398 Particle* toadd = new Particle(
408 m_collisiondetection,
416 m_particlemanager->addParticle(toadd);
423 ParticleManager::ParticleManager(ClientEnvironment* env) :
427 ParticleManager::~ParticleManager()
432 void ParticleManager::step(float dtime)
434 stepParticles (dtime);
435 stepSpawners (dtime);
438 void ParticleManager::stepSpawners (float dtime)
440 MutexAutoLock lock(m_spawner_list_lock);
441 for (std::map<u32, ParticleSpawner*>::iterator i =
442 m_particle_spawners.begin();
443 i != m_particle_spawners.end();)
445 if (i->second->get_expired())
448 m_particle_spawners.erase(i++);
452 i->second->step(dtime, m_env);
458 void ParticleManager::stepParticles (float dtime)
460 MutexAutoLock lock(m_particle_list_lock);
461 for(std::vector<Particle*>::iterator i = m_particles.begin();
462 i != m_particles.end();)
464 if ((*i)->get_expired())
468 i = m_particles.erase(i);
478 void ParticleManager::clearAll ()
480 MutexAutoLock lock(m_spawner_list_lock);
481 MutexAutoLock lock2(m_particle_list_lock);
482 for(std::map<u32, ParticleSpawner*>::iterator i =
483 m_particle_spawners.begin();
484 i != m_particle_spawners.end();)
487 m_particle_spawners.erase(i++);
490 for(std::vector<Particle*>::iterator i =
492 i != m_particles.end();)
496 i = m_particles.erase(i);
500 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
501 scene::ISceneManager* smgr, LocalPlayer *player)
503 switch (event->type) {
504 case CE_DELETE_PARTICLESPAWNER: {
505 MutexAutoLock lock(m_spawner_list_lock);
506 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
507 m_particle_spawners.end()) {
508 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
509 m_particle_spawners.erase(event->delete_particlespawner.id);
511 // no allocated memory in delete event
514 case CE_ADD_PARTICLESPAWNER: {
516 MutexAutoLock lock(m_spawner_list_lock);
517 if (m_particle_spawners.find(event->add_particlespawner.id) !=
518 m_particle_spawners.end()) {
519 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
520 m_particle_spawners.erase(event->add_particlespawner.id);
524 video::ITexture *texture =
525 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
527 ParticleSpawner* toadd = new ParticleSpawner(client, smgr, player,
528 event->add_particlespawner.amount,
529 event->add_particlespawner.spawntime,
530 *event->add_particlespawner.minpos,
531 *event->add_particlespawner.maxpos,
532 *event->add_particlespawner.minvel,
533 *event->add_particlespawner.maxvel,
534 *event->add_particlespawner.minacc,
535 *event->add_particlespawner.maxacc,
536 event->add_particlespawner.minexptime,
537 event->add_particlespawner.maxexptime,
538 event->add_particlespawner.minsize,
539 event->add_particlespawner.maxsize,
540 event->add_particlespawner.collisiondetection,
541 event->add_particlespawner.collision_removal,
542 event->add_particlespawner.attached_id,
543 event->add_particlespawner.vertical,
545 event->add_particlespawner.id,
546 event->add_particlespawner.animation,
547 event->add_particlespawner.glow,
550 /* delete allocated content of event */
551 delete event->add_particlespawner.minpos;
552 delete event->add_particlespawner.maxpos;
553 delete event->add_particlespawner.minvel;
554 delete event->add_particlespawner.maxvel;
555 delete event->add_particlespawner.minacc;
556 delete event->add_particlespawner.texture;
557 delete event->add_particlespawner.maxacc;
560 MutexAutoLock lock(m_spawner_list_lock);
561 m_particle_spawners.insert(
562 std::pair<u32, ParticleSpawner*>(
563 event->add_particlespawner.id,
568 case CE_SPAWN_PARTICLE: {
569 video::ITexture *texture =
570 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
572 Particle* toadd = new Particle(client, smgr, player, m_env,
573 *event->spawn_particle.pos,
574 *event->spawn_particle.vel,
575 *event->spawn_particle.acc,
576 event->spawn_particle.expirationtime,
577 event->spawn_particle.size,
578 event->spawn_particle.collisiondetection,
579 event->spawn_particle.collision_removal,
580 event->spawn_particle.vertical,
584 event->spawn_particle.animation,
585 event->spawn_particle.glow);
589 delete event->spawn_particle.pos;
590 delete event->spawn_particle.vel;
591 delete event->spawn_particle.acc;
592 delete event->spawn_particle.texture;
600 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
601 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
602 const MapNode &n, const ContentFeatures &f)
604 for (u16 j = 0; j < 32; j++) // set the amount of particles here
606 addNodeParticle(gamedef, smgr, player, pos, n, f);
610 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
611 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
612 const MapNode &n, const ContentFeatures &f)
614 addNodeParticle(gamedef, smgr, player, pos, n, f);
617 void ParticleManager::addNodeParticle(IGameDef* gamedef,
618 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
619 const MapNode &n, const ContentFeatures &f)
622 u8 texid = myrand_range(0, 5);
623 const TileLayer &tile = f.tiles[texid].layers[0];
624 video::ITexture *texture;
625 struct TileAnimationParams anim;
626 anim.type = TAT_NONE;
628 // Only use first frame of animated texture
629 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
630 texture = tile.frames[0].texture;
632 texture = tile.texture;
634 float size = rand() % 64 / 512.;
635 float visual_size = BS * size;
636 v2f texsize(size * 2, size * 2);
638 texpos.X = ((rand() % 64) / 64. - texsize.X);
639 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
642 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
644 (rand() % 100 / 50. - 1) / 1.5);
646 v3f acceleration(0,-9,0);
647 v3f particlepos = v3f(
648 (f32) pos.X + rand() %100 /200. - 0.25,
649 (f32) pos.Y + rand() %100 /200. - 0.25,
650 (f32) pos.Z + rand() %100 /200. - 0.25
657 n.getColor(f, &color);
659 Particle* toadd = new Particle(
667 rand() % 100 / 100., // expiration time
682 void ParticleManager::addParticle(Particle* toadd)
684 MutexAutoLock lock(m_particle_list_lock);
685 m_particles.push_back(toadd);