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);
79 m_animation_frame = 0;
80 m_animation_time = 0.0;
88 m_velocity = velocity;
89 m_acceleration = acceleration;
90 m_expiration = expirationtime;
94 m_collisiondetection = collisiondetection;
95 m_collision_removal = collision_removal;
96 m_vertical = vertical;
100 m_collisionbox = aabb3f
101 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
102 this->setAutomaticCulling(scene::EAC_OFF);
111 Particle::~Particle()
115 void Particle::OnRegisterSceneNode()
118 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
120 ISceneNode::OnRegisterSceneNode();
123 void Particle::render()
125 video::IVideoDriver* driver = SceneManager->getVideoDriver();
126 driver->setMaterial(m_material);
127 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
129 u16 indices[] = {0,1,2, 2,3,0};
130 driver->drawVertexPrimitiveList(m_vertices, 4,
131 indices, 2, video::EVT_STANDARD,
132 scene::EPT_TRIANGLES, video::EIT_16BIT);
135 void Particle::step(float dtime)
138 if (m_collisiondetection) {
139 aabb3f box = m_collisionbox;
140 v3f p_pos = m_pos * BS;
141 v3f p_velocity = m_velocity * BS;
142 collisionMoveResult r = collisionMoveSimple(m_env,
143 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
144 &p_velocity, m_acceleration * BS);
145 if (m_collision_removal && r.collides) {
146 // force expiration of the particle
150 m_velocity = p_velocity / BS;
153 m_velocity += m_acceleration * dtime;
154 m_pos += m_velocity * dtime;
156 if (m_animation.type != TAT_NONE) {
157 m_animation_time += dtime;
158 int frame_length_i, frame_count;
159 m_animation.determineParams(
160 m_material.getTexture(0)->getSize(),
161 &frame_count, &frame_length_i, NULL);
162 float frame_length = frame_length_i / 1000.0;
163 while (m_animation_time > frame_length) {
165 m_animation_time -= frame_length;
176 void Particle::updateLight()
186 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
188 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
190 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
192 u8 m_light = decode_light(light + m_glow);
194 m_light * m_base_color.getRed() / 255,
195 m_light * m_base_color.getGreen() / 255,
196 m_light * m_base_color.getBlue() / 255);
199 void Particle::updateVertices()
201 f32 tx0, tx1, ty0, ty1;
203 if (m_animation.type != TAT_NONE) {
204 const v2u32 texsize = m_material.getTexture(0)->getSize();
205 v2f texcoord, framesize_f;
207 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
208 m_animation.determineParams(texsize, NULL, NULL, &framesize);
209 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
211 tx0 = m_texpos.X + texcoord.X;
212 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
213 ty0 = m_texpos.Y + texcoord.Y;
214 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
217 tx1 = m_texpos.X + m_texsize.X;
219 ty1 = m_texpos.Y + m_texsize.Y;
222 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
223 0, 0, 0, 0, m_color, tx0, ty1);
224 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
225 0, 0, 0, 0, m_color, tx1, ty1);
226 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
227 0, 0, 0, 0, m_color, tx1, ty0);
228 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
229 0, 0, 0, 0, m_color, tx0, ty0);
231 v3s16 camera_offset = m_env->getCameraOffset();
232 for(u16 i=0; i<4; i++)
235 v3f ppos = m_player->getPosition()/BS;
236 m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
238 m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
239 m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
241 m_box.addInternalPoint(m_vertices[i].Pos);
242 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
250 ParticleSpawner::ParticleSpawner(IGameDef* gamedef, scene::ISceneManager *smgr, LocalPlayer *player,
251 u16 amount, float time,
252 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
253 float minexptime, float maxexptime, float minsize, float maxsize,
254 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
255 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
257 ParticleManager *p_manager) :
258 m_particlemanager(p_manager)
271 m_minexptime = minexptime;
272 m_maxexptime = maxexptime;
275 m_collisiondetection = collisiondetection;
276 m_collision_removal = collision_removal;
277 m_attached_id = attached_id;
278 m_vertical = vertical;
284 for (u16 i = 0; i<=m_amount; i++)
286 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
287 m_spawntimes.push_back(spawntime);
291 ParticleSpawner::~ParticleSpawner() {}
293 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
297 static const float radius =
298 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
300 bool unloaded = false;
301 bool is_attached = false;
302 v3f attached_pos = v3f(0,0,0);
303 float attached_yaw = 0;
304 if (m_attached_id != 0) {
305 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
306 attached_pos = attached->getPosition() / BS;
307 attached_yaw = attached->getYaw();
314 if (m_spawntime != 0) // Spawner exists for a predefined timespan
316 for(std::vector<float>::iterator i = m_spawntimes.begin();
317 i != m_spawntimes.end();)
319 if ((*i) <= m_time && m_amount > 0)
323 // Pretend to, but don't actually spawn a particle if it is
324 // attached to an unloaded object or distant from player.
326 v3f ppos = m_player->getPosition() / BS;
327 v3f pos = random_v3f(m_minpos, m_maxpos);
329 if (pos.getDistanceFrom(ppos) <= radius) {
330 v3f vel = random_v3f(m_minvel, m_maxvel);
331 v3f acc = random_v3f(m_minacc, m_maxacc);
334 // Apply attachment yaw and position
335 pos.rotateXZBy(attached_yaw);
337 vel.rotateXZBy(attached_yaw);
338 acc.rotateXZBy(attached_yaw);
341 float exptime = rand()/(float)RAND_MAX
342 *(m_maxexptime-m_minexptime)
344 float size = rand()/(float)RAND_MAX
345 *(m_maxsize-m_minsize)
348 Particle* toadd = new Particle(
358 m_collisiondetection,
366 m_particlemanager->addParticle(toadd);
369 i = m_spawntimes.erase(i);
377 else // Spawner exists for an infinity timespan, spawn on a per-second base
379 // Skip this step if attached to an unloaded object
382 for (int i = 0; i <= m_amount; i++)
384 if (rand()/(float)RAND_MAX < dtime)
386 // Do not spawn particle if distant from player
387 v3f ppos = m_player->getPosition() / BS;
388 v3f pos = random_v3f(m_minpos, m_maxpos);
390 if (pos.getDistanceFrom(ppos) <= radius) {
391 v3f vel = random_v3f(m_minvel, m_maxvel);
392 v3f acc = random_v3f(m_minacc, m_maxacc);
395 // Apply attachment yaw and position
396 pos.rotateXZBy(attached_yaw);
398 vel.rotateXZBy(attached_yaw);
399 acc.rotateXZBy(attached_yaw);
402 float exptime = rand()/(float)RAND_MAX
403 *(m_maxexptime-m_minexptime)
405 float size = rand()/(float)RAND_MAX
406 *(m_maxsize-m_minsize)
409 Particle* toadd = new Particle(
419 m_collisiondetection,
427 m_particlemanager->addParticle(toadd);
435 ParticleManager::ParticleManager(ClientEnvironment* env) :
439 ParticleManager::~ParticleManager()
444 void ParticleManager::step(float dtime)
446 stepParticles (dtime);
447 stepSpawners (dtime);
450 void ParticleManager::stepSpawners (float dtime)
452 MutexAutoLock lock(m_spawner_list_lock);
453 for (std::map<u32, ParticleSpawner*>::iterator i =
454 m_particle_spawners.begin();
455 i != m_particle_spawners.end();)
457 if (i->second->get_expired())
460 m_particle_spawners.erase(i++);
464 i->second->step(dtime, m_env);
470 void ParticleManager::stepParticles (float dtime)
472 MutexAutoLock lock(m_particle_list_lock);
473 for(std::vector<Particle*>::iterator i = m_particles.begin();
474 i != m_particles.end();)
476 if ((*i)->get_expired())
480 i = m_particles.erase(i);
490 void ParticleManager::clearAll ()
492 MutexAutoLock lock(m_spawner_list_lock);
493 MutexAutoLock lock2(m_particle_list_lock);
494 for(std::map<u32, ParticleSpawner*>::iterator i =
495 m_particle_spawners.begin();
496 i != m_particle_spawners.end();)
499 m_particle_spawners.erase(i++);
502 for(std::vector<Particle*>::iterator i =
504 i != m_particles.end();)
508 i = m_particles.erase(i);
512 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
513 scene::ISceneManager* smgr, LocalPlayer *player)
515 switch (event->type) {
516 case CE_DELETE_PARTICLESPAWNER: {
517 MutexAutoLock lock(m_spawner_list_lock);
518 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
519 m_particle_spawners.end()) {
520 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
521 m_particle_spawners.erase(event->delete_particlespawner.id);
523 // no allocated memory in delete event
526 case CE_ADD_PARTICLESPAWNER: {
528 MutexAutoLock lock(m_spawner_list_lock);
529 if (m_particle_spawners.find(event->add_particlespawner.id) !=
530 m_particle_spawners.end()) {
531 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
532 m_particle_spawners.erase(event->add_particlespawner.id);
536 video::ITexture *texture =
537 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
539 ParticleSpawner* toadd = new ParticleSpawner(client, smgr, player,
540 event->add_particlespawner.amount,
541 event->add_particlespawner.spawntime,
542 *event->add_particlespawner.minpos,
543 *event->add_particlespawner.maxpos,
544 *event->add_particlespawner.minvel,
545 *event->add_particlespawner.maxvel,
546 *event->add_particlespawner.minacc,
547 *event->add_particlespawner.maxacc,
548 event->add_particlespawner.minexptime,
549 event->add_particlespawner.maxexptime,
550 event->add_particlespawner.minsize,
551 event->add_particlespawner.maxsize,
552 event->add_particlespawner.collisiondetection,
553 event->add_particlespawner.collision_removal,
554 event->add_particlespawner.attached_id,
555 event->add_particlespawner.vertical,
557 event->add_particlespawner.id,
558 event->add_particlespawner.animation,
559 event->add_particlespawner.glow,
562 /* delete allocated content of event */
563 delete event->add_particlespawner.minpos;
564 delete event->add_particlespawner.maxpos;
565 delete event->add_particlespawner.minvel;
566 delete event->add_particlespawner.maxvel;
567 delete event->add_particlespawner.minacc;
568 delete event->add_particlespawner.texture;
569 delete event->add_particlespawner.maxacc;
572 MutexAutoLock lock(m_spawner_list_lock);
573 m_particle_spawners.insert(
574 std::pair<u32, ParticleSpawner*>(
575 event->add_particlespawner.id,
580 case CE_SPAWN_PARTICLE: {
581 video::ITexture *texture =
582 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
584 Particle* toadd = new Particle(client, smgr, player, m_env,
585 *event->spawn_particle.pos,
586 *event->spawn_particle.vel,
587 *event->spawn_particle.acc,
588 event->spawn_particle.expirationtime,
589 event->spawn_particle.size,
590 event->spawn_particle.collisiondetection,
591 event->spawn_particle.collision_removal,
592 event->spawn_particle.vertical,
596 event->spawn_particle.animation,
597 event->spawn_particle.glow);
601 delete event->spawn_particle.pos;
602 delete event->spawn_particle.vel;
603 delete event->spawn_particle.acc;
604 delete event->spawn_particle.texture;
612 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
613 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
614 const MapNode &n, const ContentFeatures &f)
616 for (u16 j = 0; j < 32; j++) // set the amount of particles here
618 addNodeParticle(gamedef, smgr, player, pos, n, f);
622 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
623 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
624 const MapNode &n, const ContentFeatures &f)
626 addNodeParticle(gamedef, smgr, player, pos, n, f);
629 void ParticleManager::addNodeParticle(IGameDef* gamedef,
630 scene::ISceneManager* smgr, LocalPlayer *player, v3s16 pos,
631 const MapNode &n, const ContentFeatures &f)
634 u8 texid = myrand_range(0, 5);
635 const TileLayer &tile = f.tiles[texid].layers[0];
636 video::ITexture *texture;
637 struct TileAnimationParams anim;
638 anim.type = TAT_NONE;
640 // Only use first frame of animated texture
641 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
642 texture = tile.frames[0].texture;
644 texture = tile.texture;
646 float size = rand() % 64 / 512.;
647 float visual_size = BS * size;
648 v2f texsize(size * 2, size * 2);
650 texpos.X = ((rand() % 64) / 64. - texsize.X);
651 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
654 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
656 (rand() % 100 / 50. - 1) / 1.5);
658 v3f acceleration(0,-9,0);
659 v3f particlepos = v3f(
660 (f32) pos.X + rand() %100 /200. - 0.25,
661 (f32) pos.Y + rand() %100 /200. - 0.25,
662 (f32) pos.Z + rand() %100 /200. - 0.25
669 n.getColor(f, &color);
671 Particle* toadd = new Particle(
679 rand() % 100 / 100., // expiration time
694 void ParticleManager::addParticle(Particle* toadd)
696 MutexAutoLock lock(m_particle_list_lock);
697 m_particles.push_back(toadd);