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"
23 #include "collision.h"
24 #include "client/clientevent.h"
25 #include "client/renderingengine.h"
26 #include "util/numeric.h"
28 #include "environment.h"
29 #include "clientmap.h"
39 v3f random_v3f(v3f min, v3f max)
41 return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
42 rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
43 rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
49 ClientEnvironment *env,
55 bool collisiondetection,
56 bool collision_removal,
57 bool object_collision,
59 video::ITexture *texture,
62 const struct TileAnimationParams &anim,
66 scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
67 RenderingEngine::get_scene_manager())
74 m_material.setFlag(video::EMF_LIGHTING, false);
75 m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
76 m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
77 m_material.setFlag(video::EMF_FOG_ENABLE, true);
78 m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
79 m_material.setTexture(0, texture);
90 m_velocity = velocity;
91 m_acceleration = acceleration;
92 m_expiration = expirationtime;
95 m_collisiondetection = collisiondetection;
96 m_collision_removal = collision_removal;
97 m_object_collision = object_collision;
98 m_vertical = vertical;
102 m_collisionbox = aabb3f
103 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
104 this->setAutomaticCulling(scene::EAC_OFF);
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, m_gamedef, BS * 0.5f,
141 box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
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 (video::S3DVertex &vertex : m_vertices) {
232 v3f ppos = m_player->getPosition()/BS;
233 vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
234 core::DEGTORAD + 90);
236 vertex.Pos.rotateYZBy(m_player->getPitch());
237 vertex.Pos.rotateXZBy(m_player->getYaw());
239 m_box.addInternalPoint(vertex.Pos);
240 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
248 ParticleSpawner::ParticleSpawner(
253 v3f minpos, v3f maxpos,
254 v3f minvel, v3f maxvel,
255 v3f minacc, v3f maxacc,
256 float minexptime, float maxexptime,
257 float minsize, float maxsize,
258 bool collisiondetection,
259 bool collision_removal,
260 bool object_collision,
263 video::ITexture *texture,
265 const struct TileAnimationParams &anim,
267 ParticleManager *p_manager
269 m_particlemanager(p_manager)
281 m_minexptime = minexptime;
282 m_maxexptime = maxexptime;
285 m_collisiondetection = collisiondetection;
286 m_collision_removal = collision_removal;
287 m_object_collision = object_collision;
288 m_attached_id = attached_id;
289 m_vertical = vertical;
295 for (u16 i = 0; i<=m_amount; i++)
297 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
298 m_spawntimes.push_back(spawntime);
302 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
303 bool is_attached, const v3f &attached_pos, float attached_yaw)
305 v3f ppos = m_player->getPosition() / BS;
306 v3f pos = random_v3f(m_minpos, m_maxpos);
308 // Need to apply this first or the following check
309 // will be wrong for attached spawners
311 pos.rotateXZBy(attached_yaw);
315 if (pos.getDistanceFrom(ppos) > radius)
318 v3f vel = random_v3f(m_minvel, m_maxvel);
319 v3f acc = random_v3f(m_minacc, m_maxacc);
322 // Apply attachment yaw
323 vel.rotateXZBy(attached_yaw);
324 acc.rotateXZBy(attached_yaw);
327 float exptime = rand() / (float)RAND_MAX
328 * (m_maxexptime - m_minexptime)
330 float size = rand() / (float)RAND_MAX
331 * (m_maxsize - m_minsize)
334 m_particlemanager->addParticle(new Particle(
343 m_collisiondetection,
355 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
359 static thread_local const float radius =
360 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
362 bool unloaded = false;
363 bool is_attached = false;
364 v3f attached_pos = v3f(0,0,0);
365 float attached_yaw = 0;
366 if (m_attached_id != 0) {
367 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
368 attached_pos = attached->getPosition() / BS;
369 attached_yaw = attached->getYaw();
376 if (m_spawntime != 0) {
377 // Spawner exists for a predefined timespan
378 for (std::vector<float>::iterator i = m_spawntimes.begin();
379 i != m_spawntimes.end();) {
380 if ((*i) <= m_time && m_amount > 0) {
383 // Pretend to, but don't actually spawn a particle if it is
384 // attached to an unloaded object or distant from player.
386 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
388 i = m_spawntimes.erase(i);
394 // Spawner exists for an infinity timespan, spawn on a per-second base
396 // Skip this step if attached to an unloaded object
400 for (int i = 0; i <= m_amount; i++) {
401 if (rand() / (float)RAND_MAX < dtime)
402 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
408 ParticleManager::ParticleManager(ClientEnvironment* env) :
412 ParticleManager::~ParticleManager()
417 void ParticleManager::step(float dtime)
419 stepParticles (dtime);
420 stepSpawners (dtime);
423 void ParticleManager::stepSpawners (float dtime)
425 MutexAutoLock lock(m_spawner_list_lock);
426 for (std::map<u32, ParticleSpawner*>::iterator i =
427 m_particle_spawners.begin();
428 i != m_particle_spawners.end();)
430 if (i->second->get_expired())
433 m_particle_spawners.erase(i++);
437 i->second->step(dtime, m_env);
443 void ParticleManager::stepParticles (float dtime)
445 MutexAutoLock lock(m_particle_list_lock);
446 for(std::vector<Particle*>::iterator i = m_particles.begin();
447 i != m_particles.end();)
449 if ((*i)->get_expired())
453 i = m_particles.erase(i);
463 void ParticleManager::clearAll ()
465 MutexAutoLock lock(m_spawner_list_lock);
466 MutexAutoLock lock2(m_particle_list_lock);
467 for(std::map<u32, ParticleSpawner*>::iterator i =
468 m_particle_spawners.begin();
469 i != m_particle_spawners.end();)
472 m_particle_spawners.erase(i++);
475 for(std::vector<Particle*>::iterator i =
477 i != m_particles.end();)
481 i = m_particles.erase(i);
485 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
488 switch (event->type) {
489 case CE_DELETE_PARTICLESPAWNER: {
490 MutexAutoLock lock(m_spawner_list_lock);
491 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
492 m_particle_spawners.end()) {
493 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
494 m_particle_spawners.erase(event->delete_particlespawner.id);
496 // no allocated memory in delete event
499 case CE_ADD_PARTICLESPAWNER: {
501 MutexAutoLock lock(m_spawner_list_lock);
502 if (m_particle_spawners.find(event->add_particlespawner.id) !=
503 m_particle_spawners.end()) {
504 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
505 m_particle_spawners.erase(event->add_particlespawner.id);
509 video::ITexture *texture =
510 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
512 ParticleSpawner *toadd = new ParticleSpawner(client, player,
513 event->add_particlespawner.amount,
514 event->add_particlespawner.spawntime,
515 *event->add_particlespawner.minpos,
516 *event->add_particlespawner.maxpos,
517 *event->add_particlespawner.minvel,
518 *event->add_particlespawner.maxvel,
519 *event->add_particlespawner.minacc,
520 *event->add_particlespawner.maxacc,
521 event->add_particlespawner.minexptime,
522 event->add_particlespawner.maxexptime,
523 event->add_particlespawner.minsize,
524 event->add_particlespawner.maxsize,
525 event->add_particlespawner.collisiondetection,
526 event->add_particlespawner.collision_removal,
527 event->add_particlespawner.object_collision,
528 event->add_particlespawner.attached_id,
529 event->add_particlespawner.vertical,
531 event->add_particlespawner.id,
532 event->add_particlespawner.animation,
533 event->add_particlespawner.glow,
536 /* delete allocated content of event */
537 delete event->add_particlespawner.minpos;
538 delete event->add_particlespawner.maxpos;
539 delete event->add_particlespawner.minvel;
540 delete event->add_particlespawner.maxvel;
541 delete event->add_particlespawner.minacc;
542 delete event->add_particlespawner.texture;
543 delete event->add_particlespawner.maxacc;
546 MutexAutoLock lock(m_spawner_list_lock);
547 m_particle_spawners.insert(
548 std::pair<u32, ParticleSpawner*>(
549 event->add_particlespawner.id,
554 case CE_SPAWN_PARTICLE: {
555 video::ITexture *texture =
556 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
558 Particle *toadd = new Particle(client, player, m_env,
559 *event->spawn_particle.pos,
560 *event->spawn_particle.vel,
561 *event->spawn_particle.acc,
562 event->spawn_particle.expirationtime,
563 event->spawn_particle.size,
564 event->spawn_particle.collisiondetection,
565 event->spawn_particle.collision_removal,
566 event->spawn_particle.object_collision,
567 event->spawn_particle.vertical,
571 event->spawn_particle.animation,
572 event->spawn_particle.glow);
576 delete event->spawn_particle.pos;
577 delete event->spawn_particle.vel;
578 delete event->spawn_particle.acc;
579 delete event->spawn_particle.texture;
587 // The final burst of particles when a node is finally dug, *not* particles
588 // spawned during the digging of a node.
590 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
591 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
593 // No particles for "airlike" nodes
594 if (f.drawtype == NDT_AIRLIKE)
597 for (u16 j = 0; j < 16; j++) {
598 addNodeParticle(gamedef, player, pos, n, f);
602 // During the digging of a node particles are spawned individually by this
603 // function, called from Game::handleDigging() in game.cpp.
605 void ParticleManager::addNodeParticle(IGameDef* gamedef,
606 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
608 // No particles for "airlike" nodes
609 if (f.drawtype == NDT_AIRLIKE)
613 u8 texid = myrand_range(0, 5);
614 const TileLayer &tile = f.tiles[texid].layers[0];
615 video::ITexture *texture;
616 struct TileAnimationParams anim;
617 anim.type = TAT_NONE;
619 // Only use first frame of animated texture
620 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
621 texture = (*tile.frames)[0].texture;
623 texture = tile.texture;
625 float size = (rand() % 8) / 64.0f;
626 float visual_size = BS * size;
629 v2f texsize(size * 2.0f, size * 2.0f);
631 texpos.X = (rand() % 64) / 64.0f - texsize.X;
632 texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
636 (rand() % 150) / 50.0f - 1.5f,
637 (rand() % 150) / 50.0f,
638 (rand() % 150) / 50.0f - 1.5f
642 -player->movement_gravity * player->physics_override_gravity / BS,
645 v3f particlepos = v3f(
646 (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
647 (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
648 (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
655 n.getColor(f, &color);
657 Particle* toadd = new Particle(
664 (rand() % 100) / 100.0f, // expiration time
680 void ParticleManager::addParticle(Particle* toadd)
682 MutexAutoLock lock(m_particle_list_lock);
683 m_particles.push_back(toadd);