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::spawnParticle(ClientEnvironment *env, float radius,
285 bool is_attached, const v3f &attached_pos, float attached_yaw)
287 v3f ppos = m_player->getPosition() / BS;
288 v3f pos = random_v3f(m_minpos, m_maxpos);
290 // Need to apply this first or the following check
291 // will be wrong for attached spawners
293 pos.rotateXZBy(attached_yaw);
297 if (pos.getDistanceFrom(ppos) > radius)
300 v3f vel = random_v3f(m_minvel, m_maxvel);
301 v3f acc = random_v3f(m_minacc, m_maxacc);
304 // Apply attachment yaw
305 vel.rotateXZBy(attached_yaw);
306 acc.rotateXZBy(attached_yaw);
309 float exptime = rand() / (float)RAND_MAX
310 * (m_maxexptime - m_minexptime)
312 float size = rand() / (float)RAND_MAX
313 * (m_maxsize - m_minsize)
316 m_particlemanager->addParticle(new Particle(
325 m_collisiondetection,
336 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
340 static thread_local const float radius =
341 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
343 bool unloaded = false;
344 bool is_attached = false;
345 v3f attached_pos = v3f(0,0,0);
346 float attached_yaw = 0;
347 if (m_attached_id != 0) {
348 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
349 attached_pos = attached->getPosition() / BS;
350 attached_yaw = attached->getYaw();
357 if (m_spawntime != 0) {
358 // Spawner exists for a predefined timespan
359 for (std::vector<float>::iterator i = m_spawntimes.begin();
360 i != m_spawntimes.end();) {
361 if ((*i) <= m_time && m_amount > 0) {
364 // Pretend to, but don't actually spawn a particle if it is
365 // attached to an unloaded object or distant from player.
367 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
369 i = m_spawntimes.erase(i);
375 // Spawner exists for an infinity timespan, spawn on a per-second base
377 // Skip this step if attached to an unloaded object
381 for (int i = 0; i <= m_amount; i++) {
382 if (rand() / (float)RAND_MAX < dtime)
383 spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
389 ParticleManager::ParticleManager(ClientEnvironment* env) :
393 ParticleManager::~ParticleManager()
398 void ParticleManager::step(float dtime)
400 stepParticles (dtime);
401 stepSpawners (dtime);
404 void ParticleManager::stepSpawners (float dtime)
406 MutexAutoLock lock(m_spawner_list_lock);
407 for (std::map<u32, ParticleSpawner*>::iterator i =
408 m_particle_spawners.begin();
409 i != m_particle_spawners.end();)
411 if (i->second->get_expired())
414 m_particle_spawners.erase(i++);
418 i->second->step(dtime, m_env);
424 void ParticleManager::stepParticles (float dtime)
426 MutexAutoLock lock(m_particle_list_lock);
427 for(std::vector<Particle*>::iterator i = m_particles.begin();
428 i != m_particles.end();)
430 if ((*i)->get_expired())
434 i = m_particles.erase(i);
444 void ParticleManager::clearAll ()
446 MutexAutoLock lock(m_spawner_list_lock);
447 MutexAutoLock lock2(m_particle_list_lock);
448 for(std::map<u32, ParticleSpawner*>::iterator i =
449 m_particle_spawners.begin();
450 i != m_particle_spawners.end();)
453 m_particle_spawners.erase(i++);
456 for(std::vector<Particle*>::iterator i =
458 i != m_particles.end();)
462 i = m_particles.erase(i);
466 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
469 switch (event->type) {
470 case CE_DELETE_PARTICLESPAWNER: {
471 MutexAutoLock lock(m_spawner_list_lock);
472 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
473 m_particle_spawners.end()) {
474 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
475 m_particle_spawners.erase(event->delete_particlespawner.id);
477 // no allocated memory in delete event
480 case CE_ADD_PARTICLESPAWNER: {
482 MutexAutoLock lock(m_spawner_list_lock);
483 if (m_particle_spawners.find(event->add_particlespawner.id) !=
484 m_particle_spawners.end()) {
485 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
486 m_particle_spawners.erase(event->add_particlespawner.id);
490 video::ITexture *texture =
491 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
493 ParticleSpawner *toadd = new ParticleSpawner(client, player,
494 event->add_particlespawner.amount,
495 event->add_particlespawner.spawntime,
496 *event->add_particlespawner.minpos,
497 *event->add_particlespawner.maxpos,
498 *event->add_particlespawner.minvel,
499 *event->add_particlespawner.maxvel,
500 *event->add_particlespawner.minacc,
501 *event->add_particlespawner.maxacc,
502 event->add_particlespawner.minexptime,
503 event->add_particlespawner.maxexptime,
504 event->add_particlespawner.minsize,
505 event->add_particlespawner.maxsize,
506 event->add_particlespawner.collisiondetection,
507 event->add_particlespawner.collision_removal,
508 event->add_particlespawner.attached_id,
509 event->add_particlespawner.vertical,
511 event->add_particlespawner.id,
512 event->add_particlespawner.animation,
513 event->add_particlespawner.glow,
516 /* delete allocated content of event */
517 delete event->add_particlespawner.minpos;
518 delete event->add_particlespawner.maxpos;
519 delete event->add_particlespawner.minvel;
520 delete event->add_particlespawner.maxvel;
521 delete event->add_particlespawner.minacc;
522 delete event->add_particlespawner.texture;
523 delete event->add_particlespawner.maxacc;
526 MutexAutoLock lock(m_spawner_list_lock);
527 m_particle_spawners.insert(
528 std::pair<u32, ParticleSpawner*>(
529 event->add_particlespawner.id,
534 case CE_SPAWN_PARTICLE: {
535 video::ITexture *texture =
536 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
538 Particle *toadd = new Particle(client, player, m_env,
539 *event->spawn_particle.pos,
540 *event->spawn_particle.vel,
541 *event->spawn_particle.acc,
542 event->spawn_particle.expirationtime,
543 event->spawn_particle.size,
544 event->spawn_particle.collisiondetection,
545 event->spawn_particle.collision_removal,
546 event->spawn_particle.vertical,
550 event->spawn_particle.animation,
551 event->spawn_particle.glow);
555 delete event->spawn_particle.pos;
556 delete event->spawn_particle.vel;
557 delete event->spawn_particle.acc;
558 delete event->spawn_particle.texture;
566 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
567 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
569 // No particles for "airlike" nodes
570 if (f.drawtype == NDT_AIRLIKE)
573 // set the amount of particles here
574 for (u16 j = 0; j < 32; j++) {
575 addNodeParticle(gamedef, player, pos, n, f);
579 void ParticleManager::addNodeParticle(IGameDef* gamedef,
580 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
582 // No particles for "airlike" nodes
583 if (f.drawtype == NDT_AIRLIKE)
587 u8 texid = myrand_range(0, 5);
588 const TileLayer &tile = f.tiles[texid].layers[0];
589 video::ITexture *texture;
590 struct TileAnimationParams anim;
591 anim.type = TAT_NONE;
593 // Only use first frame of animated texture
594 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
595 texture = (*tile.frames)[0].texture;
597 texture = tile.texture;
599 float size = rand() % 64 / 512.;
600 float visual_size = BS * size;
603 v2f texsize(size * 2, size * 2);
605 texpos.X = ((rand() % 64) / 64. - texsize.X);
606 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
609 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
611 (rand() % 100 / 50. - 1) / 1.5);
613 v3f acceleration(0,-9,0);
614 v3f particlepos = v3f(
615 (f32) pos.X + rand() %100 /200. - 0.25,
616 (f32) pos.Y + rand() %100 /200. - 0.25,
617 (f32) pos.Z + rand() %100 /200. - 0.25
624 n.getColor(f, &color);
626 Particle* toadd = new Particle(
633 rand() % 100 / 100., // expiration time
648 void ParticleManager::addParticle(Particle* toadd)
650 MutexAutoLock lock(m_particle_list_lock);
651 m_particles.push_back(toadd);