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/content_cao.h"
25 #include "client/clientevent.h"
26 #include "client/renderingengine.h"
27 #include "util/numeric.h"
29 #include "environment.h"
30 #include "clientmap.h"
40 v3f random_v3f(v3f min, v3f max)
43 rand() / (float)RAND_MAX * (max.X - min.X) + min.X,
44 rand() / (float)RAND_MAX * (max.Y - min.Y) + min.Y,
45 rand() / (float)RAND_MAX * (max.Z - min.Z) + min.Z);
51 ClientEnvironment *env,
57 bool collisiondetection,
58 bool collision_removal,
59 bool object_collision,
61 video::ITexture *texture,
64 const struct TileAnimationParams &anim,
68 scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
69 RenderingEngine::get_scene_manager())
76 m_material.setFlag(video::EMF_LIGHTING, false);
77 m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
78 m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
79 m_material.setFlag(video::EMF_FOG_ENABLE, true);
80 m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
81 m_material.setTexture(0, texture);
92 m_velocity = velocity;
93 m_acceleration = acceleration;
94 m_expiration = expirationtime;
97 m_collisiondetection = collisiondetection;
98 m_collision_removal = collision_removal;
99 m_object_collision = object_collision;
100 m_vertical = vertical;
104 m_collisionbox = aabb3f(
111 this->setAutomaticCulling(scene::EAC_OFF);
120 void Particle::OnRegisterSceneNode()
123 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
125 ISceneNode::OnRegisterSceneNode();
128 void Particle::render()
130 video::IVideoDriver *driver = SceneManager->getVideoDriver();
131 driver->setMaterial(m_material);
132 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
134 u16 indices[] = {0,1,2, 2,3,0};
135 driver->drawVertexPrimitiveList(m_vertices, 4,
136 indices, 2, video::EVT_STANDARD,
137 scene::EPT_TRIANGLES, video::EIT_16BIT);
140 void Particle::step(float dtime)
143 if (m_collisiondetection) {
144 aabb3f box = m_collisionbox;
145 v3f p_pos = m_pos * BS;
146 v3f p_velocity = m_velocity * BS;
147 collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
148 box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
150 if (m_collision_removal && r.collides) {
151 // force expiration of the particle
155 m_velocity = p_velocity / BS;
158 m_velocity += m_acceleration * dtime;
159 m_pos += m_velocity * dtime;
161 if (m_animation.type != TAT_NONE) {
162 m_animation_time += dtime;
163 int frame_length_i, frame_count;
164 m_animation.determineParams(
165 m_material.getTexture(0)->getSize(),
166 &frame_count, &frame_length_i, NULL);
167 float frame_length = frame_length_i / 1000.0;
168 while (m_animation_time > frame_length) {
170 m_animation_time -= frame_length;
181 void Particle::updateLight()
191 MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
193 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
195 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
197 u8 m_light = decode_light(light + m_glow);
199 m_light * m_base_color.getRed() / 255,
200 m_light * m_base_color.getGreen() / 255,
201 m_light * m_base_color.getBlue() / 255);
204 void Particle::updateVertices()
206 f32 tx0, tx1, ty0, ty1;
208 if (m_animation.type != TAT_NONE) {
209 const v2u32 texsize = m_material.getTexture(0)->getSize();
210 v2f texcoord, framesize_f;
212 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
213 m_animation.determineParams(texsize, NULL, NULL, &framesize);
214 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
216 tx0 = m_texpos.X + texcoord.X;
217 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
218 ty0 = m_texpos.Y + texcoord.Y;
219 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
222 tx1 = m_texpos.X + m_texsize.X;
224 ty1 = m_texpos.Y + m_texsize.Y;
227 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
228 0, 0, 0, 0, m_color, tx0, ty1);
229 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
230 0, 0, 0, 0, m_color, tx1, ty1);
231 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
232 0, 0, 0, 0, m_color, tx1, ty0);
233 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
234 0, 0, 0, 0, m_color, tx0, ty0);
236 v3s16 camera_offset = m_env->getCameraOffset();
237 for (video::S3DVertex &vertex : m_vertices) {
239 v3f ppos = m_player->getPosition()/BS;
240 vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
241 core::DEGTORAD + 90);
243 vertex.Pos.rotateYZBy(m_player->getPitch());
244 vertex.Pos.rotateXZBy(m_player->getYaw());
246 m_box.addInternalPoint(vertex.Pos);
247 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
255 ParticleSpawner::ParticleSpawner(
260 v3f minpos, v3f maxpos,
261 v3f minvel, v3f maxvel,
262 v3f minacc, v3f maxacc,
263 float minexptime, float maxexptime,
264 float minsize, float maxsize,
265 bool collisiondetection,
266 bool collision_removal,
267 bool object_collision,
270 video::ITexture *texture,
271 const struct TileAnimationParams &anim,
273 ParticleManager *p_manager
275 m_particlemanager(p_manager)
287 m_minexptime = minexptime;
288 m_maxexptime = maxexptime;
291 m_collisiondetection = collisiondetection;
292 m_collision_removal = collision_removal;
293 m_object_collision = object_collision;
294 m_attached_id = attached_id;
295 m_vertical = vertical;
301 for (u16 i = 0; i <= m_amount; i++)
303 float spawntime = (float)rand() / (float)RAND_MAX * m_spawntime;
304 m_spawntimes.push_back(spawntime);
308 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
309 const core::matrix4 *attached_absolute_pos_rot_matrix)
311 v3f ppos = m_player->getPosition() / BS;
312 v3f pos = random_v3f(m_minpos, m_maxpos);
314 // Need to apply this first or the following check
315 // will be wrong for attached spawners
316 if (attached_absolute_pos_rot_matrix) {
318 attached_absolute_pos_rot_matrix->transformVect(pos);
320 v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
321 pos.X += camera_offset.X;
322 pos.Y += camera_offset.Y;
323 pos.Z += camera_offset.Z;
326 if (pos.getDistanceFrom(ppos) > radius)
329 v3f vel = random_v3f(m_minvel, m_maxvel);
330 v3f acc = random_v3f(m_minacc, m_maxacc);
332 if (attached_absolute_pos_rot_matrix) {
333 // Apply attachment rotation
334 attached_absolute_pos_rot_matrix->rotateVect(vel);
335 attached_absolute_pos_rot_matrix->rotateVect(acc);
338 float exptime = rand() / (float)RAND_MAX
339 * (m_maxexptime - m_minexptime)
342 float size = rand() / (float)RAND_MAX
343 * (m_maxsize - m_minsize)
346 m_particlemanager->addParticle(new Particle(
355 m_collisiondetection,
367 void ParticleSpawner::step(float dtime, ClientEnvironment *env)
371 static thread_local const float radius =
372 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
374 bool unloaded = false;
375 const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
377 if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
378 attached_absolute_pos_rot_matrix = &attached->getAbsolutePosRotMatrix();
384 if (m_spawntime != 0) {
385 // Spawner exists for a predefined timespan
386 for (std::vector<float>::iterator i = m_spawntimes.begin();
387 i != m_spawntimes.end();) {
388 if ((*i) <= m_time && m_amount > 0) {
391 // Pretend to, but don't actually spawn a particle if it is
392 // attached to an unloaded object or distant from player.
394 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
396 i = m_spawntimes.erase(i);
402 // Spawner exists for an infinity timespan, spawn on a per-second base
404 // Skip this step if attached to an unloaded object
408 for (int i = 0; i <= m_amount; i++) {
409 if (rand() / (float)RAND_MAX < dtime)
410 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
416 ParticleManager::ParticleManager(ClientEnvironment *env) :
420 ParticleManager::~ParticleManager()
425 void ParticleManager::step(float dtime)
427 stepParticles (dtime);
428 stepSpawners (dtime);
431 void ParticleManager::stepSpawners(float dtime)
433 MutexAutoLock lock(m_spawner_list_lock);
434 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
435 if (i->second->get_expired()) {
437 m_particle_spawners.erase(i++);
439 i->second->step(dtime, m_env);
445 void ParticleManager::stepParticles(float dtime)
447 MutexAutoLock lock(m_particle_list_lock);
448 for (auto i = m_particles.begin(); i != m_particles.end();) {
449 if ((*i)->get_expired()) {
452 i = m_particles.erase(i);
460 void ParticleManager::clearAll()
462 MutexAutoLock lock(m_spawner_list_lock);
463 MutexAutoLock lock2(m_particle_list_lock);
464 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
466 m_particle_spawners.erase(i++);
469 for(auto i = m_particles.begin(); i != m_particles.end();)
473 i = m_particles.erase(i);
477 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
480 switch (event->type) {
481 case CE_DELETE_PARTICLESPAWNER: {
482 MutexAutoLock lock(m_spawner_list_lock);
483 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
484 m_particle_spawners.end()) {
485 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
486 m_particle_spawners.erase(event->delete_particlespawner.id);
488 // no allocated memory in delete event
491 case CE_ADD_PARTICLESPAWNER: {
493 MutexAutoLock lock(m_spawner_list_lock);
494 if (m_particle_spawners.find(event->add_particlespawner.id) !=
495 m_particle_spawners.end()) {
496 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
497 m_particle_spawners.erase(event->add_particlespawner.id);
501 video::ITexture *texture =
502 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
504 auto toadd = new ParticleSpawner(client, player,
505 event->add_particlespawner.amount,
506 event->add_particlespawner.spawntime,
507 *event->add_particlespawner.minpos,
508 *event->add_particlespawner.maxpos,
509 *event->add_particlespawner.minvel,
510 *event->add_particlespawner.maxvel,
511 *event->add_particlespawner.minacc,
512 *event->add_particlespawner.maxacc,
513 event->add_particlespawner.minexptime,
514 event->add_particlespawner.maxexptime,
515 event->add_particlespawner.minsize,
516 event->add_particlespawner.maxsize,
517 event->add_particlespawner.collisiondetection,
518 event->add_particlespawner.collision_removal,
519 event->add_particlespawner.object_collision,
520 event->add_particlespawner.attached_id,
521 event->add_particlespawner.vertical,
523 event->add_particlespawner.animation,
524 event->add_particlespawner.glow,
527 /* delete allocated content of event */
528 delete event->add_particlespawner.minpos;
529 delete event->add_particlespawner.maxpos;
530 delete event->add_particlespawner.minvel;
531 delete event->add_particlespawner.maxvel;
532 delete event->add_particlespawner.minacc;
533 delete event->add_particlespawner.texture;
534 delete event->add_particlespawner.maxacc;
537 MutexAutoLock lock(m_spawner_list_lock);
538 m_particle_spawners[event->add_particlespawner.id] = toadd;
542 case CE_SPAWN_PARTICLE: {
543 video::ITexture *texture =
544 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
546 Particle *toadd = new Particle(client, player, m_env,
547 *event->spawn_particle.pos,
548 *event->spawn_particle.vel,
549 *event->spawn_particle.acc,
550 event->spawn_particle.expirationtime,
551 event->spawn_particle.size,
552 event->spawn_particle.collisiondetection,
553 event->spawn_particle.collision_removal,
554 event->spawn_particle.object_collision,
555 event->spawn_particle.vertical,
559 event->spawn_particle.animation,
560 event->spawn_particle.glow);
564 delete event->spawn_particle.pos;
565 delete event->spawn_particle.vel;
566 delete event->spawn_particle.acc;
567 delete event->spawn_particle.texture;
575 // The final burst of particles when a node is finally dug, *not* particles
576 // spawned during the digging of a node.
578 void ParticleManager::addDiggingParticles(IGameDef *gamedef,
579 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
581 // No particles for "airlike" nodes
582 if (f.drawtype == NDT_AIRLIKE)
585 for (u16 j = 0; j < 16; j++) {
586 addNodeParticle(gamedef, player, pos, n, f);
590 // During the digging of a node particles are spawned individually by this
591 // function, called from Game::handleDigging() in game.cpp.
593 void ParticleManager::addNodeParticle(IGameDef *gamedef,
594 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
596 // No particles for "airlike" nodes
597 if (f.drawtype == NDT_AIRLIKE)
601 u8 texid = myrand_range(0, 5);
602 const TileLayer &tile = f.tiles[texid].layers[0];
603 video::ITexture *texture;
604 struct TileAnimationParams anim;
605 anim.type = TAT_NONE;
607 // Only use first frame of animated texture
608 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
609 texture = (*tile.frames)[0].texture;
611 texture = tile.texture;
613 float size = (rand() % 8) / 64.0f;
614 float visual_size = BS * size;
617 v2f texsize(size * 2.0f, size * 2.0f);
619 texpos.X = (rand() % 64) / 64.0f - texsize.X;
620 texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
624 (rand() % 150) / 50.0f - 1.5f,
625 (rand() % 150) / 50.0f,
626 (rand() % 150) / 50.0f - 1.5f
630 -player->movement_gravity * player->physics_override_gravity / BS,
633 v3f particlepos = v3f(
634 (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
635 (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
636 (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
643 n.getColor(f, &color);
645 Particle *toadd = new Particle(
652 (rand() % 100) / 100.0f, // expiration time
668 void ParticleManager::addParticle(Particle *toadd)
670 MutexAutoLock lock(m_particle_list_lock);
671 m_particles.push_back(toadd);