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"
23 #include "client/clientevent.h"
24 #include "client/renderingengine.h"
25 #include "util/numeric.h"
27 #include "environment.h"
28 #include "clientmap.h"
38 v3f random_v3f(v3f min, v3f max)
40 return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
41 rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
42 rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
48 ClientEnvironment *env,
54 bool collisiondetection,
55 bool collision_removal,
57 video::ITexture *texture,
60 const struct TileAnimationParams &anim,
64 scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
65 RenderingEngine::get_scene_manager())
72 m_material.setFlag(video::EMF_LIGHTING, false);
73 m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
74 m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
75 m_material.setFlag(video::EMF_FOG_ENABLE, true);
76 m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
77 m_material.setTexture(0, texture);
88 m_velocity = velocity;
89 m_acceleration = acceleration;
90 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 void Particle::OnRegisterSceneNode()
113 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
115 ISceneNode::OnRegisterSceneNode();
118 void Particle::render()
120 video::IVideoDriver* driver = SceneManager->getVideoDriver();
121 driver->setMaterial(m_material);
122 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
124 u16 indices[] = {0,1,2, 2,3,0};
125 driver->drawVertexPrimitiveList(m_vertices, 4,
126 indices, 2, video::EVT_STANDARD,
127 scene::EPT_TRIANGLES, video::EIT_16BIT);
130 void Particle::step(float dtime)
133 if (m_collisiondetection) {
134 aabb3f box = m_collisionbox;
135 v3f p_pos = m_pos * BS;
136 v3f p_velocity = m_velocity * BS;
137 collisionMoveResult r = collisionMoveSimple(m_env,
138 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
139 &p_velocity, m_acceleration * BS);
140 if (m_collision_removal && r.collides) {
141 // force expiration of the particle
145 m_velocity = p_velocity / BS;
148 m_velocity += m_acceleration * dtime;
149 m_pos += m_velocity * dtime;
151 if (m_animation.type != TAT_NONE) {
152 m_animation_time += dtime;
153 int frame_length_i, frame_count;
154 m_animation.determineParams(
155 m_material.getTexture(0)->getSize(),
156 &frame_count, &frame_length_i, NULL);
157 float frame_length = frame_length_i / 1000.0;
158 while (m_animation_time > frame_length) {
160 m_animation_time -= frame_length;
171 void Particle::updateLight()
181 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
183 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
185 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
187 u8 m_light = decode_light(light + m_glow);
189 m_light * m_base_color.getRed() / 255,
190 m_light * m_base_color.getGreen() / 255,
191 m_light * m_base_color.getBlue() / 255);
194 void Particle::updateVertices()
196 f32 tx0, tx1, ty0, ty1;
198 if (m_animation.type != TAT_NONE) {
199 const v2u32 texsize = m_material.getTexture(0)->getSize();
200 v2f texcoord, framesize_f;
202 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
203 m_animation.determineParams(texsize, NULL, NULL, &framesize);
204 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
206 tx0 = m_texpos.X + texcoord.X;
207 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
208 ty0 = m_texpos.Y + texcoord.Y;
209 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
212 tx1 = m_texpos.X + m_texsize.X;
214 ty1 = m_texpos.Y + m_texsize.Y;
217 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
218 0, 0, 0, 0, m_color, tx0, ty1);
219 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
220 0, 0, 0, 0, m_color, tx1, ty1);
221 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
222 0, 0, 0, 0, m_color, tx1, ty0);
223 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
224 0, 0, 0, 0, m_color, tx0, ty0);
226 v3s16 camera_offset = m_env->getCameraOffset();
227 for (video::S3DVertex &vertex : m_vertices) {
229 v3f ppos = m_player->getPosition()/BS;
230 vertex.Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
232 vertex.Pos.rotateYZBy(m_player->getPitch());
233 vertex.Pos.rotateXZBy(m_player->getYaw());
235 m_box.addInternalPoint(vertex.Pos);
236 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
244 ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player,
245 u16 amount, float time,
246 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
247 float minexptime, float maxexptime, float minsize, float maxsize,
248 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
249 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
251 ParticleManager *p_manager) :
252 m_particlemanager(p_manager)
264 m_minexptime = minexptime;
265 m_maxexptime = maxexptime;
268 m_collisiondetection = collisiondetection;
269 m_collision_removal = collision_removal;
270 m_attached_id = attached_id;
271 m_vertical = vertical;
277 for (u16 i = 0; i<=m_amount; i++)
279 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
280 m_spawntimes.push_back(spawntime);
284 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
288 static thread_local const float radius =
289 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
291 bool unloaded = false;
292 bool is_attached = false;
293 v3f attached_pos = v3f(0,0,0);
294 float attached_yaw = 0;
295 if (m_attached_id != 0) {
296 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
297 attached_pos = attached->getPosition() / BS;
298 attached_yaw = attached->getYaw();
305 if (m_spawntime != 0) // Spawner exists for a predefined timespan
307 for(std::vector<float>::iterator i = m_spawntimes.begin();
308 i != m_spawntimes.end();)
310 if ((*i) <= m_time && m_amount > 0)
314 // Pretend to, but don't actually spawn a particle if it is
315 // attached to an unloaded object or distant from player.
317 v3f ppos = m_player->getPosition() / BS;
318 v3f pos = random_v3f(m_minpos, m_maxpos);
320 // Need to apply this first or the following check
321 // will be wrong for attached spawners
325 if (pos.getDistanceFrom(ppos) <= radius) {
326 v3f vel = random_v3f(m_minvel, m_maxvel);
327 v3f acc = random_v3f(m_minacc, m_maxacc);
330 // Apply attachment yaw and position
331 pos.rotateXZBy(attached_yaw);
332 vel.rotateXZBy(attached_yaw);
333 acc.rotateXZBy(attached_yaw);
336 float exptime = rand()/(float)RAND_MAX
337 *(m_maxexptime-m_minexptime)
339 float size = rand()/(float)RAND_MAX
340 *(m_maxsize-m_minsize)
343 Particle* toadd = new Particle(
352 m_collisiondetection,
360 m_particlemanager->addParticle(toadd);
363 i = m_spawntimes.erase(i);
371 else // Spawner exists for an infinity timespan, spawn on a per-second base
373 // Skip this step if attached to an unloaded object
376 for (int i = 0; i <= m_amount; i++)
378 if (rand()/(float)RAND_MAX < dtime)
380 // Do not spawn particle if distant from player
381 v3f ppos = m_player->getPosition() / BS;
382 v3f pos = random_v3f(m_minpos, m_maxpos);
384 // Need to apply this first or the following check
385 // will be wrong for attached spawners
389 if (pos.getDistanceFrom(ppos) <= radius) {
390 v3f vel = random_v3f(m_minvel, m_maxvel);
391 v3f acc = random_v3f(m_minacc, m_maxacc);
394 // Apply attachment yaw and position
395 pos.rotateXZBy(attached_yaw);
396 vel.rotateXZBy(attached_yaw);
397 acc.rotateXZBy(attached_yaw);
400 float exptime = rand()/(float)RAND_MAX
401 *(m_maxexptime-m_minexptime)
403 float size = rand()/(float)RAND_MAX
404 *(m_maxsize-m_minsize)
407 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,
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, 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, 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 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
612 // No particles for "airlike" nodes
613 if (f.drawtype == NDT_AIRLIKE)
616 // set the amount of particles here
617 for (u16 j = 0; j < 32; j++) {
618 addNodeParticle(gamedef, player, pos, n, f);
622 void ParticleManager::addNodeParticle(IGameDef* gamedef,
623 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
625 // No particles for "airlike" nodes
626 if (f.drawtype == NDT_AIRLIKE)
630 u8 texid = myrand_range(0, 5);
631 const TileLayer &tile = f.tiles[texid].layers[0];
632 video::ITexture *texture;
633 struct TileAnimationParams anim;
634 anim.type = TAT_NONE;
636 // Only use first frame of animated texture
637 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
638 texture = (*tile.frames)[0].texture;
640 texture = tile.texture;
642 float size = rand() % 64 / 512.;
643 float visual_size = BS * size;
644 v2f texsize(size * 2, size * 2);
646 texpos.X = ((rand() % 64) / 64. - texsize.X);
647 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
650 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
652 (rand() % 100 / 50. - 1) / 1.5);
654 v3f acceleration(0,-9,0);
655 v3f particlepos = v3f(
656 (f32) pos.X + rand() %100 /200. - 0.25,
657 (f32) pos.Y + rand() %100 /200. - 0.25,
658 (f32) pos.Z + rand() %100 /200. - 0.25
665 n.getColor(f, &color);
667 Particle* toadd = new Particle(
674 rand() % 100 / 100., // expiration time
689 void ParticleManager::addParticle(Particle* toadd)
691 MutexAutoLock lock(m_particle_list_lock);
692 m_particles.push_back(toadd);