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/renderingengine.h"
24 #include "util/numeric.h"
26 #include "environment.h"
27 #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 void Particle::OnRegisterSceneNode()
112 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
114 ISceneNode::OnRegisterSceneNode();
117 void Particle::render()
119 video::IVideoDriver* driver = SceneManager->getVideoDriver();
120 driver->setMaterial(m_material);
121 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
123 u16 indices[] = {0,1,2, 2,3,0};
124 driver->drawVertexPrimitiveList(m_vertices, 4,
125 indices, 2, video::EVT_STANDARD,
126 scene::EPT_TRIANGLES, video::EIT_16BIT);
129 void Particle::step(float dtime)
132 if (m_collisiondetection) {
133 aabb3f box = m_collisionbox;
134 v3f p_pos = m_pos * BS;
135 v3f p_velocity = m_velocity * BS;
136 collisionMoveResult r = collisionMoveSimple(m_env,
137 m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
138 &p_velocity, m_acceleration * BS);
139 if (m_collision_removal && r.collides) {
140 // force expiration of the particle
144 m_velocity = p_velocity / BS;
147 m_velocity += m_acceleration * dtime;
148 m_pos += m_velocity * dtime;
150 if (m_animation.type != TAT_NONE) {
151 m_animation_time += dtime;
152 int frame_length_i, frame_count;
153 m_animation.determineParams(
154 m_material.getTexture(0)->getSize(),
155 &frame_count, &frame_length_i, NULL);
156 float frame_length = frame_length_i / 1000.0;
157 while (m_animation_time > frame_length) {
159 m_animation_time -= frame_length;
170 void Particle::updateLight()
180 MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
182 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
184 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
186 u8 m_light = decode_light(light + m_glow);
188 m_light * m_base_color.getRed() / 255,
189 m_light * m_base_color.getGreen() / 255,
190 m_light * m_base_color.getBlue() / 255);
193 void Particle::updateVertices()
195 f32 tx0, tx1, ty0, ty1;
197 if (m_animation.type != TAT_NONE) {
198 const v2u32 texsize = m_material.getTexture(0)->getSize();
199 v2f texcoord, framesize_f;
201 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
202 m_animation.determineParams(texsize, NULL, NULL, &framesize);
203 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
205 tx0 = m_texpos.X + texcoord.X;
206 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
207 ty0 = m_texpos.Y + texcoord.Y;
208 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
211 tx1 = m_texpos.X + m_texsize.X;
213 ty1 = m_texpos.Y + m_texsize.Y;
216 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
217 0, 0, 0, 0, m_color, tx0, ty1);
218 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
219 0, 0, 0, 0, m_color, tx1, ty1);
220 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
221 0, 0, 0, 0, m_color, tx1, ty0);
222 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
223 0, 0, 0, 0, m_color, tx0, ty0);
225 v3s16 camera_offset = m_env->getCameraOffset();
226 for (video::S3DVertex &vertex : m_vertices) {
228 v3f ppos = m_player->getPosition()/BS;
229 vertex.Pos.rotateXZBy(atan2(ppos.Z-m_pos.Z, ppos.X-m_pos.X)/core::DEGTORAD+90);
231 vertex.Pos.rotateYZBy(m_player->getPitch());
232 vertex.Pos.rotateXZBy(m_player->getYaw());
234 m_box.addInternalPoint(vertex.Pos);
235 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
243 ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player,
244 u16 amount, float time,
245 v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
246 float minexptime, float maxexptime, float minsize, float maxsize,
247 bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
248 video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
250 ParticleManager *p_manager) :
251 m_particlemanager(p_manager)
263 m_minexptime = minexptime;
264 m_maxexptime = maxexptime;
267 m_collisiondetection = collisiondetection;
268 m_collision_removal = collision_removal;
269 m_attached_id = attached_id;
270 m_vertical = vertical;
276 for (u16 i = 0; i<=m_amount; i++)
278 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
279 m_spawntimes.push_back(spawntime);
283 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
287 static thread_local const float radius =
288 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
290 bool unloaded = false;
291 bool is_attached = false;
292 v3f attached_pos = v3f(0,0,0);
293 float attached_yaw = 0;
294 if (m_attached_id != 0) {
295 if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
296 attached_pos = attached->getPosition() / BS;
297 attached_yaw = attached->getYaw();
304 if (m_spawntime != 0) // Spawner exists for a predefined timespan
306 for(std::vector<float>::iterator i = m_spawntimes.begin();
307 i != m_spawntimes.end();)
309 if ((*i) <= m_time && m_amount > 0)
313 // Pretend to, but don't actually spawn a particle if it is
314 // attached to an unloaded object or distant from player.
316 v3f ppos = m_player->getPosition() / BS;
317 v3f pos = random_v3f(m_minpos, m_maxpos);
319 if (pos.getDistanceFrom(ppos) <= radius) {
320 v3f vel = random_v3f(m_minvel, m_maxvel);
321 v3f acc = random_v3f(m_minacc, m_maxacc);
324 // Apply attachment yaw and position
325 pos.rotateXZBy(attached_yaw);
327 vel.rotateXZBy(attached_yaw);
328 acc.rotateXZBy(attached_yaw);
331 float exptime = rand()/(float)RAND_MAX
332 *(m_maxexptime-m_minexptime)
334 float size = rand()/(float)RAND_MAX
335 *(m_maxsize-m_minsize)
338 Particle* toadd = new Particle(
347 m_collisiondetection,
355 m_particlemanager->addParticle(toadd);
358 i = m_spawntimes.erase(i);
366 else // Spawner exists for an infinity timespan, spawn on a per-second base
368 // Skip this step if attached to an unloaded object
371 for (int i = 0; i <= m_amount; i++)
373 if (rand()/(float)RAND_MAX < dtime)
375 // Do not spawn particle if distant from player
376 v3f ppos = m_player->getPosition() / BS;
377 v3f pos = random_v3f(m_minpos, m_maxpos);
379 if (pos.getDistanceFrom(ppos) <= radius) {
380 v3f vel = random_v3f(m_minvel, m_maxvel);
381 v3f acc = random_v3f(m_minacc, m_maxacc);
384 // Apply attachment yaw and position
385 pos.rotateXZBy(attached_yaw);
387 vel.rotateXZBy(attached_yaw);
388 acc.rotateXZBy(attached_yaw);
391 float exptime = rand()/(float)RAND_MAX
392 *(m_maxexptime-m_minexptime)
394 float size = rand()/(float)RAND_MAX
395 *(m_maxsize-m_minsize)
398 Particle* toadd = new Particle(
407 m_collisiondetection,
415 m_particlemanager->addParticle(toadd);
423 ParticleManager::ParticleManager(ClientEnvironment* env) :
427 ParticleManager::~ParticleManager()
432 void ParticleManager::step(float dtime)
434 stepParticles (dtime);
435 stepSpawners (dtime);
438 void ParticleManager::stepSpawners (float dtime)
440 MutexAutoLock lock(m_spawner_list_lock);
441 for (std::map<u32, ParticleSpawner*>::iterator i =
442 m_particle_spawners.begin();
443 i != m_particle_spawners.end();)
445 if (i->second->get_expired())
448 m_particle_spawners.erase(i++);
452 i->second->step(dtime, m_env);
458 void ParticleManager::stepParticles (float dtime)
460 MutexAutoLock lock(m_particle_list_lock);
461 for(std::vector<Particle*>::iterator i = m_particles.begin();
462 i != m_particles.end();)
464 if ((*i)->get_expired())
468 i = m_particles.erase(i);
478 void ParticleManager::clearAll ()
480 MutexAutoLock lock(m_spawner_list_lock);
481 MutexAutoLock lock2(m_particle_list_lock);
482 for(std::map<u32, ParticleSpawner*>::iterator i =
483 m_particle_spawners.begin();
484 i != m_particle_spawners.end();)
487 m_particle_spawners.erase(i++);
490 for(std::vector<Particle*>::iterator i =
492 i != m_particles.end();)
496 i = m_particles.erase(i);
500 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
503 switch (event->type) {
504 case CE_DELETE_PARTICLESPAWNER: {
505 MutexAutoLock lock(m_spawner_list_lock);
506 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
507 m_particle_spawners.end()) {
508 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
509 m_particle_spawners.erase(event->delete_particlespawner.id);
511 // no allocated memory in delete event
514 case CE_ADD_PARTICLESPAWNER: {
516 MutexAutoLock lock(m_spawner_list_lock);
517 if (m_particle_spawners.find(event->add_particlespawner.id) !=
518 m_particle_spawners.end()) {
519 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
520 m_particle_spawners.erase(event->add_particlespawner.id);
524 video::ITexture *texture =
525 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
527 ParticleSpawner *toadd = new ParticleSpawner(client, player,
528 event->add_particlespawner.amount,
529 event->add_particlespawner.spawntime,
530 *event->add_particlespawner.minpos,
531 *event->add_particlespawner.maxpos,
532 *event->add_particlespawner.minvel,
533 *event->add_particlespawner.maxvel,
534 *event->add_particlespawner.minacc,
535 *event->add_particlespawner.maxacc,
536 event->add_particlespawner.minexptime,
537 event->add_particlespawner.maxexptime,
538 event->add_particlespawner.minsize,
539 event->add_particlespawner.maxsize,
540 event->add_particlespawner.collisiondetection,
541 event->add_particlespawner.collision_removal,
542 event->add_particlespawner.attached_id,
543 event->add_particlespawner.vertical,
545 event->add_particlespawner.id,
546 event->add_particlespawner.animation,
547 event->add_particlespawner.glow,
550 /* delete allocated content of event */
551 delete event->add_particlespawner.minpos;
552 delete event->add_particlespawner.maxpos;
553 delete event->add_particlespawner.minvel;
554 delete event->add_particlespawner.maxvel;
555 delete event->add_particlespawner.minacc;
556 delete event->add_particlespawner.texture;
557 delete event->add_particlespawner.maxacc;
560 MutexAutoLock lock(m_spawner_list_lock);
561 m_particle_spawners.insert(
562 std::pair<u32, ParticleSpawner*>(
563 event->add_particlespawner.id,
568 case CE_SPAWN_PARTICLE: {
569 video::ITexture *texture =
570 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
572 Particle *toadd = new Particle(client, player, m_env,
573 *event->spawn_particle.pos,
574 *event->spawn_particle.vel,
575 *event->spawn_particle.acc,
576 event->spawn_particle.expirationtime,
577 event->spawn_particle.size,
578 event->spawn_particle.collisiondetection,
579 event->spawn_particle.collision_removal,
580 event->spawn_particle.vertical,
584 event->spawn_particle.animation,
585 event->spawn_particle.glow);
589 delete event->spawn_particle.pos;
590 delete event->spawn_particle.vel;
591 delete event->spawn_particle.acc;
592 delete event->spawn_particle.texture;
600 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
601 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
603 // set the amount of particles here
604 for (u16 j = 0; j < 32; j++) {
605 addNodeParticle(gamedef, player, pos, n, f);
609 void ParticleManager::addPunchingParticles(IGameDef* gamedef,
610 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
612 addNodeParticle(gamedef, player, pos, n, f);
615 void ParticleManager::addNodeParticle(IGameDef* gamedef,
616 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
619 u8 texid = myrand_range(0, 5);
620 const TileLayer &tile = f.tiles[texid].layers[0];
621 video::ITexture *texture;
622 struct TileAnimationParams anim;
623 anim.type = TAT_NONE;
625 // Only use first frame of animated texture
626 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
627 texture = (*tile.frames)[0].texture;
629 texture = tile.texture;
631 float size = rand() % 64 / 512.;
632 float visual_size = BS * size;
633 v2f texsize(size * 2, size * 2);
635 texpos.X = ((rand() % 64) / 64. - texsize.X);
636 texpos.Y = ((rand() % 64) / 64. - texsize.Y);
639 v3f velocity((rand() % 100 / 50. - 1) / 1.5,
641 (rand() % 100 / 50. - 1) / 1.5);
643 v3f acceleration(0,-9,0);
644 v3f particlepos = v3f(
645 (f32) pos.X + rand() %100 /200. - 0.25,
646 (f32) pos.Y + rand() %100 /200. - 0.25,
647 (f32) pos.Z + rand() %100 /200. - 0.25
654 n.getColor(f, &color);
656 Particle* toadd = new Particle(
663 rand() % 100 / 100., // expiration time
678 void ParticleManager::addParticle(Particle* toadd)
680 MutexAutoLock lock(m_particle_list_lock);
681 m_particles.push_back(toadd);