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
105 (-size/2,-size/2,-size/2,size/2,size/2,size/2);
106 this->setAutomaticCulling(scene::EAC_OFF);
115 void Particle::OnRegisterSceneNode()
118 SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
120 ISceneNode::OnRegisterSceneNode();
123 void Particle::render()
125 video::IVideoDriver* driver = SceneManager->getVideoDriver();
126 driver->setMaterial(m_material);
127 driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
129 u16 indices[] = {0,1,2, 2,3,0};
130 driver->drawVertexPrimitiveList(m_vertices, 4,
131 indices, 2, video::EVT_STANDARD,
132 scene::EPT_TRIANGLES, video::EIT_16BIT);
135 void Particle::step(float dtime)
138 if (m_collisiondetection) {
139 aabb3f box = m_collisionbox;
140 v3f p_pos = m_pos * BS;
141 v3f p_velocity = m_velocity * BS;
142 collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
143 box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
145 if (m_collision_removal && r.collides) {
146 // force expiration of the particle
150 m_velocity = p_velocity / BS;
153 m_velocity += m_acceleration * dtime;
154 m_pos += m_velocity * dtime;
156 if (m_animation.type != TAT_NONE) {
157 m_animation_time += dtime;
158 int frame_length_i, frame_count;
159 m_animation.determineParams(
160 m_material.getTexture(0)->getSize(),
161 &frame_count, &frame_length_i, NULL);
162 float frame_length = frame_length_i / 1000.0;
163 while (m_animation_time > frame_length) {
165 m_animation_time -= frame_length;
176 void Particle::updateLight()
186 MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
188 light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
190 light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
192 u8 m_light = decode_light(light + m_glow);
194 m_light * m_base_color.getRed() / 255,
195 m_light * m_base_color.getGreen() / 255,
196 m_light * m_base_color.getBlue() / 255);
199 void Particle::updateVertices()
201 f32 tx0, tx1, ty0, ty1;
203 if (m_animation.type != TAT_NONE) {
204 const v2u32 texsize = m_material.getTexture(0)->getSize();
205 v2f texcoord, framesize_f;
207 texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
208 m_animation.determineParams(texsize, NULL, NULL, &framesize);
209 framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
211 tx0 = m_texpos.X + texcoord.X;
212 tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
213 ty0 = m_texpos.Y + texcoord.Y;
214 ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
217 tx1 = m_texpos.X + m_texsize.X;
219 ty1 = m_texpos.Y + m_texsize.Y;
222 m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
223 0, 0, 0, 0, m_color, tx0, ty1);
224 m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
225 0, 0, 0, 0, m_color, tx1, ty1);
226 m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
227 0, 0, 0, 0, m_color, tx1, ty0);
228 m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
229 0, 0, 0, 0, m_color, tx0, ty0);
231 v3s16 camera_offset = m_env->getCameraOffset();
232 for (video::S3DVertex &vertex : m_vertices) {
234 v3f ppos = m_player->getPosition()/BS;
235 vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
236 core::DEGTORAD + 90);
238 vertex.Pos.rotateYZBy(m_player->getPitch());
239 vertex.Pos.rotateXZBy(m_player->getYaw());
241 m_box.addInternalPoint(vertex.Pos);
242 vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
250 ParticleSpawner::ParticleSpawner(
255 v3f minpos, v3f maxpos,
256 v3f minvel, v3f maxvel,
257 v3f minacc, v3f maxacc,
258 float minexptime, float maxexptime,
259 float minsize, float maxsize,
260 bool collisiondetection,
261 bool collision_removal,
262 bool object_collision,
265 video::ITexture *texture,
266 const struct TileAnimationParams &anim,
268 ParticleManager *p_manager
270 m_particlemanager(p_manager)
282 m_minexptime = minexptime;
283 m_maxexptime = maxexptime;
286 m_collisiondetection = collisiondetection;
287 m_collision_removal = collision_removal;
288 m_object_collision = object_collision;
289 m_attached_id = attached_id;
290 m_vertical = vertical;
296 for (u16 i = 0; i<=m_amount; i++)
298 float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
299 m_spawntimes.push_back(spawntime);
303 void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
304 const core::matrix4 *attached_absolute_pos_rot_matrix)
306 v3f ppos = m_player->getPosition() / BS;
307 v3f pos = random_v3f(m_minpos, m_maxpos);
309 // Need to apply this first or the following check
310 // will be wrong for attached spawners
311 if (attached_absolute_pos_rot_matrix) {
313 attached_absolute_pos_rot_matrix->transformVect(pos);
315 v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
316 pos.X += camera_offset.X;
317 pos.Y += camera_offset.Y;
318 pos.Z += camera_offset.Z;
321 if (pos.getDistanceFrom(ppos) > radius)
324 v3f vel = random_v3f(m_minvel, m_maxvel);
325 v3f acc = random_v3f(m_minacc, m_maxacc);
327 if (attached_absolute_pos_rot_matrix) {
328 // Apply attachment rotation
329 attached_absolute_pos_rot_matrix->rotateVect(vel);
330 attached_absolute_pos_rot_matrix->rotateVect(acc);
333 float exptime = rand() / (float)RAND_MAX
334 * (m_maxexptime - m_minexptime)
337 float size = rand() / (float)RAND_MAX
338 * (m_maxsize - m_minsize)
341 m_particlemanager->addParticle(new Particle(
350 m_collisiondetection,
362 void ParticleSpawner::step(float dtime, ClientEnvironment* env)
366 static thread_local const float radius =
367 g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
369 bool unloaded = false;
370 const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
372 if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
373 attached_absolute_pos_rot_matrix = &attached->getAbsolutePosRotMatrix();
379 if (m_spawntime != 0) {
380 // Spawner exists for a predefined timespan
381 for (std::vector<float>::iterator i = m_spawntimes.begin();
382 i != m_spawntimes.end();) {
383 if ((*i) <= m_time && m_amount > 0) {
386 // Pretend to, but don't actually spawn a particle if it is
387 // attached to an unloaded object or distant from player.
389 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
391 i = m_spawntimes.erase(i);
397 // Spawner exists for an infinity timespan, spawn on a per-second base
399 // Skip this step if attached to an unloaded object
403 for (int i = 0; i <= m_amount; i++) {
404 if (rand() / (float)RAND_MAX < dtime)
405 spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
411 ParticleManager::ParticleManager(ClientEnvironment* env) :
415 ParticleManager::~ParticleManager()
420 void ParticleManager::step(float dtime)
422 stepParticles (dtime);
423 stepSpawners (dtime);
426 void ParticleManager::stepSpawners(float dtime)
428 MutexAutoLock lock(m_spawner_list_lock);
429 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
430 if (i->second->get_expired()) {
432 m_particle_spawners.erase(i++);
434 i->second->step(dtime, m_env);
440 void ParticleManager::stepParticles(float dtime)
442 MutexAutoLock lock(m_particle_list_lock);
443 for (auto i = m_particles.begin(); i != m_particles.end();) {
444 if ((*i)->get_expired()) {
447 i = m_particles.erase(i);
455 void ParticleManager::clearAll()
457 MutexAutoLock lock(m_spawner_list_lock);
458 MutexAutoLock lock2(m_particle_list_lock);
459 for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
461 m_particle_spawners.erase(i++);
464 for(auto i = m_particles.begin(); i != m_particles.end();)
468 i = m_particles.erase(i);
472 void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
475 switch (event->type) {
476 case CE_DELETE_PARTICLESPAWNER: {
477 MutexAutoLock lock(m_spawner_list_lock);
478 if (m_particle_spawners.find(event->delete_particlespawner.id) !=
479 m_particle_spawners.end()) {
480 delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
481 m_particle_spawners.erase(event->delete_particlespawner.id);
483 // no allocated memory in delete event
486 case CE_ADD_PARTICLESPAWNER: {
488 MutexAutoLock lock(m_spawner_list_lock);
489 if (m_particle_spawners.find(event->add_particlespawner.id) !=
490 m_particle_spawners.end()) {
491 delete m_particle_spawners.find(event->add_particlespawner.id)->second;
492 m_particle_spawners.erase(event->add_particlespawner.id);
496 video::ITexture *texture =
497 client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
499 auto toadd = new ParticleSpawner(client, player,
500 event->add_particlespawner.amount,
501 event->add_particlespawner.spawntime,
502 *event->add_particlespawner.minpos,
503 *event->add_particlespawner.maxpos,
504 *event->add_particlespawner.minvel,
505 *event->add_particlespawner.maxvel,
506 *event->add_particlespawner.minacc,
507 *event->add_particlespawner.maxacc,
508 event->add_particlespawner.minexptime,
509 event->add_particlespawner.maxexptime,
510 event->add_particlespawner.minsize,
511 event->add_particlespawner.maxsize,
512 event->add_particlespawner.collisiondetection,
513 event->add_particlespawner.collision_removal,
514 event->add_particlespawner.object_collision,
515 event->add_particlespawner.attached_id,
516 event->add_particlespawner.vertical,
518 event->add_particlespawner.animation,
519 event->add_particlespawner.glow,
522 /* delete allocated content of event */
523 delete event->add_particlespawner.minpos;
524 delete event->add_particlespawner.maxpos;
525 delete event->add_particlespawner.minvel;
526 delete event->add_particlespawner.maxvel;
527 delete event->add_particlespawner.minacc;
528 delete event->add_particlespawner.texture;
529 delete event->add_particlespawner.maxacc;
532 MutexAutoLock lock(m_spawner_list_lock);
533 m_particle_spawners[event->add_particlespawner.id] = toadd;
537 case CE_SPAWN_PARTICLE: {
538 video::ITexture *texture =
539 client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
541 Particle *toadd = new Particle(client, player, m_env,
542 *event->spawn_particle.pos,
543 *event->spawn_particle.vel,
544 *event->spawn_particle.acc,
545 event->spawn_particle.expirationtime,
546 event->spawn_particle.size,
547 event->spawn_particle.collisiondetection,
548 event->spawn_particle.collision_removal,
549 event->spawn_particle.object_collision,
550 event->spawn_particle.vertical,
554 event->spawn_particle.animation,
555 event->spawn_particle.glow);
559 delete event->spawn_particle.pos;
560 delete event->spawn_particle.vel;
561 delete event->spawn_particle.acc;
562 delete event->spawn_particle.texture;
570 // The final burst of particles when a node is finally dug, *not* particles
571 // spawned during the digging of a node.
573 void ParticleManager::addDiggingParticles(IGameDef* gamedef,
574 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
576 // No particles for "airlike" nodes
577 if (f.drawtype == NDT_AIRLIKE)
580 for (u16 j = 0; j < 16; j++) {
581 addNodeParticle(gamedef, player, pos, n, f);
585 // During the digging of a node particles are spawned individually by this
586 // function, called from Game::handleDigging() in game.cpp.
588 void ParticleManager::addNodeParticle(IGameDef* gamedef,
589 LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
591 // No particles for "airlike" nodes
592 if (f.drawtype == NDT_AIRLIKE)
596 u8 texid = myrand_range(0, 5);
597 const TileLayer &tile = f.tiles[texid].layers[0];
598 video::ITexture *texture;
599 struct TileAnimationParams anim;
600 anim.type = TAT_NONE;
602 // Only use first frame of animated texture
603 if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
604 texture = (*tile.frames)[0].texture;
606 texture = tile.texture;
608 float size = (rand() % 8) / 64.0f;
609 float visual_size = BS * size;
612 v2f texsize(size * 2.0f, size * 2.0f);
614 texpos.X = (rand() % 64) / 64.0f - texsize.X;
615 texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
619 (rand() % 150) / 50.0f - 1.5f,
620 (rand() % 150) / 50.0f,
621 (rand() % 150) / 50.0f - 1.5f
625 -player->movement_gravity * player->physics_override_gravity / BS,
628 v3f particlepos = v3f(
629 (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
630 (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
631 (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
638 n.getColor(f, &color);
640 Particle* toadd = new Particle(
647 (rand() % 100) / 100.0f, // expiration time
663 void ParticleManager::addParticle(Particle* toadd)
665 MutexAutoLock lock(m_particle_list_lock);
666 m_particles.push_back(toadd);