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,
264 const struct TileAnimationParams &anim,
266 ParticleManager *p_manager
268 m_particlemanager(p_manager)
280 m_minexptime = minexptime;
281 m_maxexptime = maxexptime;
284 m_collisiondetection = collisiondetection;
285 m_collision_removal = collision_removal;
286 m_object_collision = object_collision;
287 m_attached_id = attached_id;
288 m_vertical = vertical;
294 for (u16 i = 0; i<=m_amount; i++)
296 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
297 m_spawntimes.push_back(spawntime);
301 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
302 bool is_attached, const v3f &attached_pos, float attached_yaw)
304 v3f ppos = m_player->getPosition() / BS;
305 v3f pos = random_v3f(m_minpos, m_maxpos);
307 // Need to apply this first or the following check
308 // will be wrong for attached spawners
310 pos.rotateXZBy(attached_yaw);
314 if (pos.getDistanceFrom(ppos) > radius)
317 v3f vel = random_v3f(m_minvel, m_maxvel);
318 v3f acc = random_v3f(m_minacc, m_maxacc);
321 // Apply attachment yaw
322 vel.rotateXZBy(attached_yaw);
323 acc.rotateXZBy(attached_yaw);
326 float exptime = rand() / (float)RAND_MAX
327 * (m_maxexptime - m_minexptime)
329 float size = rand() / (float)RAND_MAX
330 * (m_maxsize - m_minsize)
333 m_particlemanager->addParticle(new Particle(
342 m_collisiondetection,
354 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
358 static thread_local const float radius =
359 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
361 bool unloaded = false;
362 bool is_attached = false;
363 v3f attached_pos = v3f(0,0,0);
364 float attached_yaw = 0;
365 if (m_attached_id != 0) {
366 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
367 attached_pos = attached->getPosition() / BS;
368 attached_yaw = attached->getYaw();
375 if (m_spawntime != 0) {
376 // Spawner exists for a predefined timespan
377 for (std::vector<float>::iterator i = m_spawntimes.begin();
378 i != m_spawntimes.end();) {
379 if ((*i) <= m_time && m_amount > 0) {
382 // Pretend to, but don't actually spawn a particle if it is
383 // attached to an unloaded object or distant from player.
385 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
387 i = m_spawntimes.erase(i);
393 // Spawner exists for an infinity timespan, spawn on a per-second base
395 // Skip this step if attached to an unloaded object
399 for (int i = 0; i <= m_amount; i++) {
400 if (rand() / (float)RAND_MAX < dtime)
401 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
407 ParticleManager::ParticleManager(ClientEnvironment* env) :
411 ParticleManager::~ParticleManager()
416 void ParticleManager::step(float dtime)
418 stepParticles (dtime);
419 stepSpawners (dtime);
422 void ParticleManager::stepSpawners (float dtime)
424 MutexAutoLock lock(m_spawner_list_lock);
425 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
426 if (i->second->get_expired()) {
428 m_particle_spawners.erase(i++);
430 i->second->step(dtime, m_env);
436 void ParticleManager::stepParticles (float dtime)
438 MutexAutoLock lock(m_particle_list_lock);
439 for (auto i = m_particles.begin(); i != m_particles.end();) {
440 if ((*i)->get_expired()) {
443 i = m_particles.erase(i);
451 void ParticleManager::clearAll ()
453 MutexAutoLock lock(m_spawner_list_lock);
454 MutexAutoLock lock2(m_particle_list_lock);
455 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
457 m_particle_spawners.erase(i++);
460 for(std::vector<Particle*>::iterator i =
462 i != m_particles.end();)
466 i = m_particles.erase(i);
470 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
473 switch (event->type) {
474 case CE_DELETE_PARTICLESPAWNER: {
475 MutexAutoLock lock(m_spawner_list_lock);
476 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
477 m_particle_spawners.end()) {
478 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
479 m_particle_spawners.erase(event->delete_particlespawner.id);
481 // no allocated memory in delete event
484 case CE_ADD_PARTICLESPAWNER: {
486 MutexAutoLock lock(m_spawner_list_lock);
487 if (m_particle_spawners.find(event->add_particlespawner.id) !=
488 m_particle_spawners.end()) {
489 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
490 m_particle_spawners.erase(event->add_particlespawner.id);
494 video::ITexture *texture =
495 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
497 auto toadd = new ParticleSpawner(client, player,
498 event->add_particlespawner.amount,
499 event->add_particlespawner.spawntime,
500 *event->add_particlespawner.minpos,
501 *event->add_particlespawner.maxpos,
502 *event->add_particlespawner.minvel,
503 *event->add_particlespawner.maxvel,
504 *event->add_particlespawner.minacc,
505 *event->add_particlespawner.maxacc,
506 event->add_particlespawner.minexptime,
507 event->add_particlespawner.maxexptime,
508 event->add_particlespawner.minsize,
509 event->add_particlespawner.maxsize,
510 event->add_particlespawner.collisiondetection,
511 event->add_particlespawner.collision_removal,
512 event->add_particlespawner.object_collision,
513 event->add_particlespawner.attached_id,
514 event->add_particlespawner.vertical,
516 event->add_particlespawner.animation,
517 event->add_particlespawner.glow,
520 /* delete allocated content of event */
521 delete event->add_particlespawner.minpos;
522 delete event->add_particlespawner.maxpos;
523 delete event->add_particlespawner.minvel;
524 delete event->add_particlespawner.maxvel;
525 delete event->add_particlespawner.minacc;
526 delete event->add_particlespawner.texture;
527 delete event->add_particlespawner.maxacc;
530 MutexAutoLock lock(m_spawner_list_lock);
531 m_particle_spawners[event->add_particlespawner.id] = toadd;
535 case CE_SPAWN_PARTICLE: {
536 video::ITexture *texture =
537 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
539 Particle *toadd = new Particle(client, player, m_env,
540 *event->spawn_particle.pos,
541 *event->spawn_particle.vel,
542 *event->spawn_particle.acc,
543 event->spawn_particle.expirationtime,
544 event->spawn_particle.size,
545 event->spawn_particle.collisiondetection,
546 event->spawn_particle.collision_removal,
547 event->spawn_particle.object_collision,
548 event->spawn_particle.vertical,
552 event->spawn_particle.animation,
553 event->spawn_particle.glow);
557 delete event->spawn_particle.pos;
558 delete event->spawn_particle.vel;
559 delete event->spawn_particle.acc;
560 delete event->spawn_particle.texture;
568 // The final burst of particles when a node is finally dug, *not* particles
569 // spawned during the digging of a node.
571 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
572 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
574 // No particles for "airlike" nodes
575 if (f.drawtype == NDT_AIRLIKE)
578 for (u16 j = 0; j < 16; j++) {
579 addNodeParticle(gamedef, player, pos, n, f);
583 // During the digging of a node particles are spawned individually by this
584 // function, called from Game::handleDigging() in game.cpp.
586 void ParticleManager::addNodeParticle(IGameDef* gamedef,
587 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
589 // No particles for "airlike" nodes
590 if (f.drawtype == NDT_AIRLIKE)
594 u8 texid = myrand_range(0, 5);
595 const TileLayer &tile = f.tiles[texid].layers[0];
596 video::ITexture *texture;
597 struct TileAnimationParams anim;
598 anim.type = TAT_NONE;
600 // Only use first frame of animated texture
601 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
602 texture = (*tile.frames)[0].texture;
604 texture = tile.texture;
606 float size = (rand() % 8) / 64.0f;
607 float visual_size = BS * size;
610 v2f texsize(size * 2.0f, size * 2.0f);
612 texpos.X = (rand() % 64) / 64.0f - texsize.X;
613 texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
617 (rand() % 150) / 50.0f - 1.5f,
618 (rand() % 150) / 50.0f,
619 (rand() % 150) / 50.0f - 1.5f
623 -player->movement_gravity * player->physics_override_gravity / BS,
626 v3f particlepos = v3f(
627 (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
628 (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
629 (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
636 n.getColor(f, &color);
638 Particle* toadd = new Particle(
645 (rand() % 100) / 100.0f, // expiration time
661 void ParticleManager::addParticle(Particle* toadd)
663 MutexAutoLock lock(m_particle_list_lock);
664 m_particles.push_back(toadd);