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 "client/renderingengine.h"
25 #include "util/numeric.h"
27 #include "environment.h"
28 #include "clientmap.h"
37 v3f random_v3f(v3f min, v3f max)
39 return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
40 rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
41 rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
47 ClientEnvironment *env,
53 bool collisiondetection,
54 bool collision_removal,
56 video::ITexture *texture,
59 const struct TileAnimationParams &anim,
63 scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
64 RenderingEngine::get_scene_manager())
71 m_material.setFlag(video::EMF_LIGHTING, false);
72 m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
73 m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
74 m_material.setFlag(video::EMF_FOG_ENABLE, true);
75 m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
76 m_material.setTexture(0, texture);
87 m_velocity = velocity;
88 m_acceleration = acceleration;
89 m_expiration = expirationtime;
92 m_collisiondetection = collisiondetection;
93 m_collision_removal = collision_removal;
94 m_vertical = vertical;
98 m_collisionbox = aabb3f
99 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
100 this->setAutomaticCulling(scene::EAC_OFF);
109 Particle::~Particle()
113 void Particle::OnRegisterSceneNode()
116 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
118 ISceneNode::OnRegisterSceneNode();
121 void Particle::render()
123 video::IVideoDriver* driver = SceneManager->getVideoDriver();
124 driver->setMaterial(m_material);
125 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
127 u16 indices[] = {0,1,2, 2,3,0};
128 driver->drawVertexPrimitiveList(m_vertices, 4,
129 indices, 2, video::EVT_STANDARD,
130 scene::EPT_TRIANGLES, video::EIT_16BIT);
133 void Particle::step(float dtime)
136 if (m_collisiondetection) {
137 aabb3f box = m_collisionbox;
138 v3f p_pos = m_pos * BS;
139 v3f p_velocity = m_velocity * BS;
140 collisionMoveResult r = collisionMoveSimple(m_env,
141 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
142 &p_velocity, m_acceleration * BS);
143 if (m_collision_removal && r.collides) {
144 // force expiration of the particle
148 m_velocity = p_velocity / BS;
151 m_velocity += m_acceleration * dtime;
152 m_pos += m_velocity * dtime;
154 if (m_animation.type != TAT_NONE) {
155 m_animation_time += dtime;
156 int frame_length_i, frame_count;
157 m_animation.determineParams(
158 m_material.getTexture(0)->getSize(),
159 &frame_count, &frame_length_i, NULL);
160 float frame_length = frame_length_i / 1000.0;
161 while (m_animation_time > frame_length) {
163 m_animation_time -= frame_length;
174 void Particle::updateLight()
184 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
186 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
188 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
190 u8 m_light = decode_light(light + m_glow);
192 m_light * m_base_color.getRed() / 255,
193 m_light * m_base_color.getGreen() / 255,
194 m_light * m_base_color.getBlue() / 255);
197 void Particle::updateVertices()
199 f32 tx0, tx1, ty0, ty1;
201 if (m_animation.type != TAT_NONE) {
202 const v2u32 texsize = m_material.getTexture(0)->getSize();
203 v2f texcoord, framesize_f;
205 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
206 m_animation.determineParams(texsize, NULL, NULL, &framesize);
207 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
209 tx0 = m_texpos.X + texcoord.X;
210 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
211 ty0 = m_texpos.Y + texcoord.Y;
212 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
215 tx1 = m_texpos.X + m_texsize.X;
217 ty1 = m_texpos.Y + m_texsize.Y;
220 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
221 0, 0, 0, 0, m_color, tx0, ty1);
222 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
223 0, 0, 0, 0, m_color, tx1, ty1);
224 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
225 0, 0, 0, 0, m_color, tx1, ty0);
226 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
227 0, 0, 0, 0, m_color, tx0, ty0);
229 v3s16 camera_offset = m_env->getCameraOffset();
230 for(u16 i=0; i<4; i++)
233 v3f ppos = m_player->getPosition()/BS;
234 m_vertices[i].Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
236 m_vertices[i].Pos.rotateYZBy(m_player->getPitch());
237 m_vertices[i].Pos.rotateXZBy(m_player->getYaw());
239 m_box.addInternalPoint(m_vertices[i].Pos);
240 m_vertices[i].Pos += m_pos*BS - intToFloat(camera_offset, BS);
248 ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player,
249 u16 amount, float time,
250 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
251 float minexptime, float maxexptime, float minsize, float maxsize,
252 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
253 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
255 ParticleManager *p_manager) :
256 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(
354 m_collisiondetection,
362 m_particlemanager->addParticle(toadd);
365 i = m_spawntimes.erase(i);
373 else // Spawner exists for an infinity timespan, spawn on a per-second base
375 // Skip this step if attached to an unloaded object
378 for (int i = 0; i <= m_amount; i++)
380 if (rand()/(float)RAND_MAX < dtime)
382 // Do not spawn particle if distant from player
383 v3f ppos = m_player->getPosition() / BS;
384 v3f pos = random_v3f(m_minpos, m_maxpos);
386 if (pos.getDistanceFrom(ppos) <= radius) {
387 v3f vel = random_v3f(m_minvel, m_maxvel);
388 v3f acc = random_v3f(m_minacc, m_maxacc);
391 // Apply attachment yaw and position
392 pos.rotateXZBy(attached_yaw);
394 vel.rotateXZBy(attached_yaw);
395 acc.rotateXZBy(attached_yaw);
398 float exptime = rand()/(float)RAND_MAX
399 *(m_maxexptime-m_minexptime)
401 float size = rand()/(float)RAND_MAX
402 *(m_maxsize-m_minsize)
405 Particle* toadd = new Particle(
414 m_collisiondetection,
422 m_particlemanager->addParticle(toadd);
430 ParticleManager::ParticleManager(ClientEnvironment* env) :
434 ParticleManager::~ParticleManager()
439 void ParticleManager::step(float dtime)
441 stepParticles (dtime);
442 stepSpawners (dtime);
445 void ParticleManager::stepSpawners (float dtime)
447 MutexAutoLock lock(m_spawner_list_lock);
448 for (std::map<u32, ParticleSpawner*>::iterator i =
449 m_particle_spawners.begin();
450 i != m_particle_spawners.end();)
452 if (i->second->get_expired())
455 m_particle_spawners.erase(i++);
459 i->second->step(dtime, m_env);
465 void ParticleManager::stepParticles (float dtime)
467 MutexAutoLock lock(m_particle_list_lock);
468 for(std::vector<Particle*>::iterator i = m_particles.begin();
469 i != m_particles.end();)
471 if ((*i)->get_expired())
475 i = m_particles.erase(i);
485 void ParticleManager::clearAll ()
487 MutexAutoLock lock(m_spawner_list_lock);
488 MutexAutoLock lock2(m_particle_list_lock);
489 for(std::map<u32, ParticleSpawner*>::iterator i =
490 m_particle_spawners.begin();
491 i != m_particle_spawners.end();)
494 m_particle_spawners.erase(i++);
497 for(std::vector<Particle*>::iterator i =
499 i != m_particles.end();)
503 i = m_particles.erase(i);
507 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
510 switch (event->type) {
511 case CE_DELETE_PARTICLESPAWNER: {
512 MutexAutoLock lock(m_spawner_list_lock);
513 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
514 m_particle_spawners.end()) {
515 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
516 m_particle_spawners.erase(event->delete_particlespawner.id);
518 // no allocated memory in delete event
521 case CE_ADD_PARTICLESPAWNER: {
523 MutexAutoLock lock(m_spawner_list_lock);
524 if (m_particle_spawners.find(event->add_particlespawner.id) !=
525 m_particle_spawners.end()) {
526 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
527 m_particle_spawners.erase(event->add_particlespawner.id);
531 video::ITexture *texture =
532 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
534 ParticleSpawner *toadd = new ParticleSpawner(client, player,
535 event->add_particlespawner.amount,
536 event->add_particlespawner.spawntime,
537 *event->add_particlespawner.minpos,
538 *event->add_particlespawner.maxpos,
539 *event->add_particlespawner.minvel,
540 *event->add_particlespawner.maxvel,
541 *event->add_particlespawner.minacc,
542 *event->add_particlespawner.maxacc,
543 event->add_particlespawner.minexptime,
544 event->add_particlespawner.maxexptime,
545 event->add_particlespawner.minsize,
546 event->add_particlespawner.maxsize,
547 event->add_particlespawner.collisiondetection,
548 event->add_particlespawner.collision_removal,
549 event->add_particlespawner.attached_id,
550 event->add_particlespawner.vertical,
552 event->add_particlespawner.id,
553 event->add_particlespawner.animation,
554 event->add_particlespawner.glow,
557 /* delete allocated content of event */
558 delete event->add_particlespawner.minpos;
559 delete event->add_particlespawner.maxpos;
560 delete event->add_particlespawner.minvel;
561 delete event->add_particlespawner.maxvel;
562 delete event->add_particlespawner.minacc;
563 delete event->add_particlespawner.texture;
564 delete event->add_particlespawner.maxacc;
567 MutexAutoLock lock(m_spawner_list_lock);
568 m_particle_spawners.insert(
569 std::pair<u32, ParticleSpawner*>(
570 event->add_particlespawner.id,
575 case CE_SPAWN_PARTICLE: {
576 video::ITexture *texture =
577 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
579 Particle *toadd = new Particle(client, player, m_env,
580 *event->spawn_particle.pos,
581 *event->spawn_particle.vel,
582 *event->spawn_particle.acc,
583 event->spawn_particle.expirationtime,
584 event->spawn_particle.size,
585 event->spawn_particle.collisiondetection,
586 event->spawn_particle.collision_removal,
587 event->spawn_particle.vertical,
591 event->spawn_particle.animation,
592 event->spawn_particle.glow);
596 delete event->spawn_particle.pos;
597 delete event->spawn_particle.vel;
598 delete event->spawn_particle.acc;
599 delete event->spawn_particle.texture;
607 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
608 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
610 // set the amount of particles here
611 for (u16 j = 0; j < 32; j++) {
612 addNodeParticle(gamedef, player, pos, n, f);
616 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
617 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
619 addNodeParticle(gamedef, player, pos, n, f);
622 void ParticleManager::addNodeParticle(IGameDef* gamedef,
623 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
626 u8 texid = myrand_range(0, 5);
627 const TileLayer &tile = f.tiles[texid].layers[0];
628 video::ITexture *texture;
629 struct TileAnimationParams anim;
630 anim.type = TAT_NONE;
632 // Only use first frame of animated texture
633 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
634 texture = tile.frames[0].texture;
636 texture = tile.texture;
638 float size = rand() % 64 / 512.;
639 float visual_size = BS * size;
640 v2f texsize(size * 2, size * 2);
642 texpos.X = ((rand() % 64) / 64. - texsize.X);
643 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
646 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
648 (rand() % 100 / 50. - 1) / 1.5);
650 v3f acceleration(0,-9,0);
651 v3f particlepos = v3f(
652 (f32) pos.X + rand() %100 /200. - 0.25,
653 (f32) pos.Y + rand() %100 /200. - 0.25,
654 (f32) pos.Z + rand() %100 /200. - 0.25
661 n.getColor(f, &color);
663 Particle* toadd = new Particle(
670 rand() % 100 / 100., // expiration time
685 void ParticleManager::addParticle(Particle* toadd)
687 MutexAutoLock lock(m_particle_list_lock);
688 m_particles.push_back(toadd);