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"
36 v3f random_v3f(v3f min, v3f max)
38 return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
39 rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
40 rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
45 scene::ISceneManager* smgr,
47 ClientEnvironment *env,
53 bool collisiondetection,
54 bool collision_removal,
56 video::ITexture *texture,
59 const struct TileAnimationParams &anim,
63 scene::ISceneNode(smgr->getRootSceneNode(), smgr)
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);
86 m_velocity = velocity;
87 m_acceleration = acceleration;
88 m_expiration = expirationtime;
91 m_collisiondetection = collisiondetection;
92 m_collision_removal = collision_removal;
93 m_vertical = vertical;
97 m_collisionbox = aabb3f
98 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
99 this->setAutomaticCulling(scene::EAC_OFF);
108 Particle::~Particle()
112 void Particle::OnRegisterSceneNode()
115 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
117 ISceneNode::OnRegisterSceneNode();
120 void Particle::render()
122 video::IVideoDriver* driver = SceneManager->getVideoDriver();
123 driver->setMaterial(m_material);
124 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
126 u16 indices[] = {0,1,2, 2,3,0};
127 driver->drawVertexPrimitiveList(m_vertices, 4,
128 indices, 2, video::EVT_STANDARD,
129 scene::EPT_TRIANGLES, video::EIT_16BIT);
132 void Particle::step(float dtime)
135 if (m_collisiondetection) {
136 aabb3f box = m_collisionbox;
137 v3f p_pos = m_pos * BS;
138 v3f p_velocity = m_velocity * BS;
139 collisionMoveResult r = collisionMoveSimple(m_env,
140 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
141 &p_velocity, m_acceleration * BS);
142 if (m_collision_removal && r.collides) {
143 // force expiration of the particle
147 m_velocity = p_velocity / BS;
150 m_velocity += m_acceleration * dtime;
151 m_pos += m_velocity * dtime;
153 if (m_animation.type != TAT_NONE) {
154 m_animation_time += dtime;
155 int frame_length_i, frame_count;
156 m_animation.determineParams(
157 m_material.getTexture(0)->getSize(),
158 &frame_count, &frame_length_i, NULL);
159 float frame_length = frame_length_i / 1000.0;
160 while (m_animation_time > frame_length) {
162 m_animation_time -= frame_length;
173 void Particle::updateLight()
183 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
185 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
187 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
189 u8 m_light = decode_light(light + m_glow);
191 m_light * m_base_color.getRed() / 255,
192 m_light * m_base_color.getGreen() / 255,
193 m_light * m_base_color.getBlue() / 255);
196 void Particle::updateVertices()
198 f32 tx0, tx1, ty0, ty1;
200 if (m_animation.type != TAT_NONE) {
201 const v2u32 texsize = m_material.getTexture(0)->getSize();
202 v2f texcoord, framesize_f;
204 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
205 m_animation.determineParams(texsize, NULL, NULL, &framesize);
206 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
208 tx0 = m_texpos.X + texcoord.X;
209 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
210 ty0 = m_texpos.Y + texcoord.Y;
211 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
214 tx1 = m_texpos.X + m_texsize.X;
216 ty1 = m_texpos.Y + m_texsize.Y;
219 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
220 0, 0, 0, 0, m_color, tx0, ty1);
221 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
222 0, 0, 0, 0, m_color, tx1, ty1);
223 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
224 0, 0, 0, 0, m_color, tx1, ty0);
225 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
226 0, 0, 0, 0, m_color, tx0, ty0);
228 v3s16 camera_offset = m_env->getCameraOffset();
229 for(u16 i=0; i<4; i++)
232 v3f ppos = m_player->getPosition()/BS;
233 m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
235 m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
236 m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
238 m_box.addInternalPoint(m_vertices[i].Pos);
239 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
247 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
248 u16 amount, float time,
249 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
250 float minexptime, float maxexptime, float minsize, float maxsize,
251 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
252 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
254 ParticleManager *p_manager) :
255 m_particlemanager(p_manager)
268 m_minexptime = minexptime;
269 m_maxexptime = maxexptime;
272 m_collisiondetection = collisiondetection;
273 m_collision_removal = collision_removal;
274 m_attached_id = attached_id;
275 m_vertical = vertical;
281 for (u16 i = 0; i<=m_amount; i++)
283 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
284 m_spawntimes.push_back(spawntime);
288 ParticleSpawner::~ParticleSpawner() {}
290 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
294 static thread_local const float radius =
295 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
297 bool unloaded = false;
298 bool is_attached = false;
299 v3f attached_pos = v3f(0,0,0);
300 float attached_yaw = 0;
301 if (m_attached_id != 0) {
302 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
303 attached_pos = attached->getPosition() / BS;
304 attached_yaw = attached->getYaw();
311 if (m_spawntime != 0) // Spawner exists for a predefined timespan
313 for(std::vector<float>::iterator i = m_spawntimes.begin();
314 i != m_spawntimes.end();)
316 if ((*i) <= m_time && m_amount > 0)
320 // Pretend to, but don't actually spawn a particle if it is
321 // attached to an unloaded object or distant from player.
323 v3f ppos = m_player->getPosition() / BS;
324 v3f pos = random_v3f(m_minpos, m_maxpos);
326 if (pos.getDistanceFrom(ppos) <= radius) {
327 v3f vel = random_v3f(m_minvel, m_maxvel);
328 v3f acc = random_v3f(m_minacc, m_maxacc);
331 // Apply attachment yaw and position
332 pos.rotateXZBy(attached_yaw);
334 vel.rotateXZBy(attached_yaw);
335 acc.rotateXZBy(attached_yaw);
338 float exptime = rand()/(float)RAND_MAX
339 *(m_maxexptime-m_minexptime)
341 float size = rand()/(float)RAND_MAX
342 *(m_maxsize-m_minsize)
345 Particle* toadd = new Particle(
355 m_collisiondetection,
363 m_particlemanager->addParticle(toadd);
366 i = m_spawntimes.erase(i);
374 else // Spawner exists for an infinity timespan, spawn on a per-second base
376 // Skip this step if attached to an unloaded object
379 for (int i = 0; i <= m_amount; i++)
381 if (rand()/(float)RAND_MAX < dtime)
383 // Do not spawn particle if distant from player
384 v3f ppos = m_player->getPosition() / BS;
385 v3f pos = random_v3f(m_minpos, m_maxpos);
387 if (pos.getDistanceFrom(ppos) <= radius) {
388 v3f vel = random_v3f(m_minvel, m_maxvel);
389 v3f acc = random_v3f(m_minacc, m_maxacc);
392 // Apply attachment yaw and position
393 pos.rotateXZBy(attached_yaw);
395 vel.rotateXZBy(attached_yaw);
396 acc.rotateXZBy(attached_yaw);
399 float exptime = rand()/(float)RAND_MAX
400 *(m_maxexptime-m_minexptime)
402 float size = rand()/(float)RAND_MAX
403 *(m_maxsize-m_minsize)
406 Particle* toadd = new Particle(
416 m_collisiondetection,
424 m_particlemanager->addParticle(toadd);
432 ParticleManager::ParticleManager(ClientEnvironment* env) :
436 ParticleManager::~ParticleManager()
441 void ParticleManager::step(float dtime)
443 stepParticles (dtime);
444 stepSpawners (dtime);
447 void ParticleManager::stepSpawners (float dtime)
449 MutexAutoLock lock(m_spawner_list_lock);
450 for (std::map<u32, ParticleSpawner*>::iterator i =
451 m_particle_spawners.begin();
452 i != m_particle_spawners.end();)
454 if (i->second->get_expired())
457 m_particle_spawners.erase(i++);
461 i->second->step(dtime, m_env);
467 void ParticleManager::stepParticles (float dtime)
469 MutexAutoLock lock(m_particle_list_lock);
470 for(std::vector<Particle*>::iterator i = m_particles.begin();
471 i != m_particles.end();)
473 if ((*i)->get_expired())
477 i = m_particles.erase(i);
487 void ParticleManager::clearAll ()
489 MutexAutoLock lock(m_spawner_list_lock);
490 MutexAutoLock lock2(m_particle_list_lock);
491 for(std::map<u32, ParticleSpawner*>::iterator i =
492 m_particle_spawners.begin();
493 i != m_particle_spawners.end();)
496 m_particle_spawners.erase(i++);
499 for(std::vector<Particle*>::iterator i =
501 i != m_particles.end();)
505 i = m_particles.erase(i);
509 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
510 scene::ISceneManager* smgr, LocalPlayer *player)
512 switch (event->type) {
513 case CE_DELETE_PARTICLESPAWNER: {
514 MutexAutoLock lock(m_spawner_list_lock);
515 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
516 m_particle_spawners.end()) {
517 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
518 m_particle_spawners.erase(event->delete_particlespawner.id);
520 // no allocated memory in delete event
523 case CE_ADD_PARTICLESPAWNER: {
525 MutexAutoLock lock(m_spawner_list_lock);
526 if (m_particle_spawners.find(event->add_particlespawner.id) !=
527 m_particle_spawners.end()) {
528 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
529 m_particle_spawners.erase(event->add_particlespawner.id);
533 video::ITexture *texture =
534 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
536 ParticleSpawner* toadd = new ParticleSpawner(client, smgr, player,
537 event->add_particlespawner.amount,
538 event->add_particlespawner.spawntime,
539 *event->add_particlespawner.minpos,
540 *event->add_particlespawner.maxpos,
541 *event->add_particlespawner.minvel,
542 *event->add_particlespawner.maxvel,
543 *event->add_particlespawner.minacc,
544 *event->add_particlespawner.maxacc,
545 event->add_particlespawner.minexptime,
546 event->add_particlespawner.maxexptime,
547 event->add_particlespawner.minsize,
548 event->add_particlespawner.maxsize,
549 event->add_particlespawner.collisiondetection,
550 event->add_particlespawner.collision_removal,
551 event->add_particlespawner.attached_id,
552 event->add_particlespawner.vertical,
554 event->add_particlespawner.id,
555 event->add_particlespawner.animation,
556 event->add_particlespawner.glow,
559 /* delete allocated content of event */
560 delete event->add_particlespawner.minpos;
561 delete event->add_particlespawner.maxpos;
562 delete event->add_particlespawner.minvel;
563 delete event->add_particlespawner.maxvel;
564 delete event->add_particlespawner.minacc;
565 delete event->add_particlespawner.texture;
566 delete event->add_particlespawner.maxacc;
569 MutexAutoLock lock(m_spawner_list_lock);
570 m_particle_spawners.insert(
571 std::pair<u32, ParticleSpawner*>(
572 event->add_particlespawner.id,
577 case CE_SPAWN_PARTICLE: {
578 video::ITexture *texture =
579 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
581 Particle* toadd = new Particle(client, smgr, player, m_env,
582 *event->spawn_particle.pos,
583 *event->spawn_particle.vel,
584 *event->spawn_particle.acc,
585 event->spawn_particle.expirationtime,
586 event->spawn_particle.size,
587 event->spawn_particle.collisiondetection,
588 event->spawn_particle.collision_removal,
589 event->spawn_particle.vertical,
593 event->spawn_particle.animation,
594 event->spawn_particle.glow);
598 delete event->spawn_particle.pos;
599 delete event->spawn_particle.vel;
600 delete event->spawn_particle.acc;
601 delete event->spawn_particle.texture;
609 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
610 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
611 const MapNode &n, const ContentFeatures &f)
613 for (u16 j = 0; j < 32; j++) // set the amount of particles here
615 addNodeParticle(gamedef, smgr, player, pos, n, f);
619 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
620 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
621 const MapNode &n, const ContentFeatures &f)
623 addNodeParticle(gamedef, smgr, player, pos, n, f);
626 void ParticleManager::addNodeParticle(IGameDef* gamedef,
627 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
628 const MapNode &n, const ContentFeatures &f)
631 u8 texid = myrand_range(0, 5);
632 const TileLayer &tile = f.tiles[texid].layers[0];
633 video::ITexture *texture;
634 struct TileAnimationParams anim;
635 anim.type = TAT_NONE;
637 // Only use first frame of animated texture
638 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
639 texture = tile.frames[0].texture;
641 texture = tile.texture;
643 float size = rand() % 64 / 512.;
644 float visual_size = BS * size;
645 v2f texsize(size * 2, size * 2);
647 texpos.X = ((rand() % 64) / 64. - texsize.X);
648 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
651 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
653 (rand() % 100 / 50. - 1) / 1.5);
655 v3f acceleration(0,-9,0);
656 v3f particlepos = v3f(
657 (f32) pos.X + rand() %100 /200. - 0.25,
658 (f32) pos.Y + rand() %100 /200. - 0.25,
659 (f32) pos.Z + rand() %100 /200. - 0.25
666 n.getColor(f, &color);
668 Particle* toadd = new Particle(
676 rand() % 100 / 100., // expiration time
691 void ParticleManager::addParticle(Particle* toadd)
693 MutexAutoLock lock(m_particle_list_lock);
694 m_particles.push_back(toadd);