particles.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. /*
  2. Minetest
  3. Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU Lesser General Public License as published by
  6. the Free Software Foundation; either version 2.1 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU Lesser General Public License for more details.
  12. You should have received a copy of the GNU Lesser General Public License along
  13. with this program; if not, write to the Free Software Foundation, Inc.,
  14. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  15. */
  16. #include "particles.h"
  17. #include <cmath>
  18. #include "client.h"
  19. #include "collision.h"
  20. #include "client/content_cao.h"
  21. #include "client/clientevent.h"
  22. #include "client/renderingengine.h"
  23. #include "util/numeric.h"
  24. #include "light.h"
  25. #include "environment.h"
  26. #include "clientmap.h"
  27. #include "mapnode.h"
  28. #include "nodedef.h"
  29. #include "client.h"
  30. #include "settings.h"
  31. /*
  32. Utility
  33. */
  34. static f32 random_f32(f32 min, f32 max)
  35. {
  36. return rand() / (float)RAND_MAX * (max - min) + min;
  37. }
  38. static v3f random_v3f(v3f min, v3f max)
  39. {
  40. return v3f(
  41. random_f32(min.X, max.X),
  42. random_f32(min.Y, max.Y),
  43. random_f32(min.Z, max.Z));
  44. }
  45. /*
  46. Particle
  47. */
  48. Particle::Particle(
  49. IGameDef *gamedef,
  50. LocalPlayer *player,
  51. ClientEnvironment *env,
  52. const ParticleParameters &p,
  53. video::ITexture *texture,
  54. v2f texpos,
  55. v2f texsize,
  56. video::SColor color
  57. ):
  58. scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
  59. ((Client *)gamedef)->getSceneManager())
  60. {
  61. // Misc
  62. m_gamedef = gamedef;
  63. m_env = env;
  64. // Texture
  65. m_material.setFlag(video::EMF_LIGHTING, false);
  66. m_material.setFlag(video::EMF_BACK_FACE_CULLING, false);
  67. m_material.setFlag(video::EMF_BILINEAR_FILTER, false);
  68. m_material.setFlag(video::EMF_FOG_ENABLE, true);
  69. m_material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
  70. m_material.setTexture(0, texture);
  71. m_texpos = texpos;
  72. m_texsize = texsize;
  73. m_animation = p.animation;
  74. // Color
  75. m_base_color = color;
  76. m_color = color;
  77. // Particle related
  78. m_pos = p.pos;
  79. m_velocity = p.vel;
  80. m_acceleration = p.acc;
  81. m_expiration = p.expirationtime;
  82. m_player = player;
  83. m_size = p.size;
  84. m_collisiondetection = p.collisiondetection;
  85. m_collision_removal = p.collision_removal;
  86. m_object_collision = p.object_collision;
  87. m_vertical = p.vertical;
  88. m_glow = p.glow;
  89. // Irrlicht stuff
  90. const float c = p.size / 2;
  91. m_collisionbox = aabb3f(-c, -c, -c, c, c, c);
  92. this->setAutomaticCulling(scene::EAC_OFF);
  93. // Init lighting
  94. updateLight();
  95. // Init model
  96. updateVertices();
  97. }
  98. void Particle::OnRegisterSceneNode()
  99. {
  100. if (IsVisible)
  101. SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
  102. ISceneNode::OnRegisterSceneNode();
  103. }
  104. void Particle::render()
  105. {
  106. video::IVideoDriver *driver = SceneManager->getVideoDriver();
  107. driver->setMaterial(m_material);
  108. driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
  109. u16 indices[] = {0,1,2, 2,3,0};
  110. driver->drawVertexPrimitiveList(m_vertices, 4,
  111. indices, 2, video::EVT_STANDARD,
  112. scene::EPT_TRIANGLES, video::EIT_16BIT);
  113. }
  114. void Particle::step(float dtime)
  115. {
  116. m_time += dtime;
  117. if (m_collisiondetection) {
  118. aabb3f box = m_collisionbox;
  119. v3f p_pos = m_pos * BS;
  120. v3f p_velocity = m_velocity * BS;
  121. collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
  122. box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
  123. m_object_collision);
  124. if (m_collision_removal && r.collides) {
  125. // force expiration of the particle
  126. m_expiration = -1.0;
  127. } else {
  128. m_pos = p_pos / BS;
  129. m_velocity = p_velocity / BS;
  130. }
  131. } else {
  132. m_velocity += m_acceleration * dtime;
  133. m_pos += m_velocity * dtime;
  134. }
  135. if (m_animation.type != TAT_NONE) {
  136. m_animation_time += dtime;
  137. int frame_length_i, frame_count;
  138. m_animation.determineParams(
  139. m_material.getTexture(0)->getSize(),
  140. &frame_count, &frame_length_i, NULL);
  141. float frame_length = frame_length_i / 1000.0;
  142. while (m_animation_time > frame_length) {
  143. m_animation_frame++;
  144. m_animation_time -= frame_length;
  145. }
  146. }
  147. // Update lighting
  148. updateLight();
  149. // Update model
  150. updateVertices();
  151. }
  152. void Particle::updateLight()
  153. {
  154. u8 light = 0;
  155. bool pos_ok;
  156. v3s16 p = v3s16(
  157. floor(m_pos.X+0.5),
  158. floor(m_pos.Y+0.5),
  159. floor(m_pos.Z+0.5)
  160. );
  161. MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
  162. if (pos_ok)
  163. light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
  164. else
  165. light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
  166. u8 m_light = decode_light(light + m_glow);
  167. m_color.set(255,
  168. m_light * m_base_color.getRed() / 255,
  169. m_light * m_base_color.getGreen() / 255,
  170. m_light * m_base_color.getBlue() / 255);
  171. }
  172. void Particle::updateVertices()
  173. {
  174. f32 tx0, tx1, ty0, ty1;
  175. if (m_animation.type != TAT_NONE) {
  176. const v2u32 texsize = m_material.getTexture(0)->getSize();
  177. v2f texcoord, framesize_f;
  178. v2u32 framesize;
  179. texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
  180. m_animation.determineParams(texsize, NULL, NULL, &framesize);
  181. framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
  182. tx0 = m_texpos.X + texcoord.X;
  183. tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
  184. ty0 = m_texpos.Y + texcoord.Y;
  185. ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
  186. } else {
  187. tx0 = m_texpos.X;
  188. tx1 = m_texpos.X + m_texsize.X;
  189. ty0 = m_texpos.Y;
  190. ty1 = m_texpos.Y + m_texsize.Y;
  191. }
  192. m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
  193. 0, 0, 0, 0, m_color, tx0, ty1);
  194. m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
  195. 0, 0, 0, 0, m_color, tx1, ty1);
  196. m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
  197. 0, 0, 0, 0, m_color, tx1, ty0);
  198. m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
  199. 0, 0, 0, 0, m_color, tx0, ty0);
  200. v3s16 camera_offset = m_env->getCameraOffset();
  201. for (video::S3DVertex &vertex : m_vertices) {
  202. if (m_vertical) {
  203. v3f ppos = m_player->getPosition()/BS;
  204. vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
  205. core::DEGTORAD + 90);
  206. } else {
  207. vertex.Pos.rotateYZBy(m_player->getPitch());
  208. vertex.Pos.rotateXZBy(m_player->getYaw());
  209. }
  210. m_box.addInternalPoint(vertex.Pos);
  211. vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
  212. }
  213. }
  214. /*
  215. ParticleSpawner
  216. */
  217. ParticleSpawner::ParticleSpawner(
  218. IGameDef *gamedef,
  219. LocalPlayer *player,
  220. const ParticleSpawnerParameters &p,
  221. u16 attached_id,
  222. video::ITexture *texture,
  223. ParticleManager *p_manager
  224. ):
  225. m_particlemanager(p_manager), p(p)
  226. {
  227. m_gamedef = gamedef;
  228. m_player = player;
  229. m_attached_id = attached_id;
  230. m_texture = texture;
  231. m_time = 0;
  232. m_spawntimes.reserve(p.amount + 1);
  233. for (u16 i = 0; i <= p.amount; i++) {
  234. float spawntime = rand() / (float)RAND_MAX * p.time;
  235. m_spawntimes.push_back(spawntime);
  236. }
  237. }
  238. void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
  239. const core::matrix4 *attached_absolute_pos_rot_matrix)
  240. {
  241. v3f ppos = m_player->getPosition() / BS;
  242. v3f pos = random_v3f(p.minpos, p.maxpos);
  243. // Need to apply this first or the following check
  244. // will be wrong for attached spawners
  245. if (attached_absolute_pos_rot_matrix) {
  246. pos *= BS;
  247. attached_absolute_pos_rot_matrix->transformVect(pos);
  248. pos /= BS;
  249. v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
  250. pos.X += camera_offset.X;
  251. pos.Y += camera_offset.Y;
  252. pos.Z += camera_offset.Z;
  253. }
  254. if (pos.getDistanceFrom(ppos) > radius)
  255. return;
  256. // Parameters for the single particle we're about to spawn
  257. ParticleParameters pp;
  258. pp.pos = pos;
  259. pp.vel = random_v3f(p.minvel, p.maxvel);
  260. pp.acc = random_v3f(p.minacc, p.maxacc);
  261. if (attached_absolute_pos_rot_matrix) {
  262. // Apply attachment rotation
  263. attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
  264. attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
  265. }
  266. pp.expirationtime = random_f32(p.minexptime, p.maxexptime);
  267. p.copyCommon(pp);
  268. video::ITexture *texture;
  269. v2f texpos, texsize;
  270. video::SColor color(0xFFFFFFFF);
  271. if (p.node.getContent() != CONTENT_IGNORE) {
  272. const ContentFeatures &f =
  273. m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
  274. if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture,
  275. texpos, texsize, &color, p.node_tile))
  276. return;
  277. } else {
  278. texture = m_texture;
  279. texpos = v2f(0.0f, 0.0f);
  280. texsize = v2f(1.0f, 1.0f);
  281. }
  282. // Allow keeping default random size
  283. if (p.maxsize > 0.0f)
  284. pp.size = random_f32(p.minsize, p.maxsize);
  285. m_particlemanager->addParticle(new Particle(
  286. m_gamedef,
  287. m_player,
  288. env,
  289. pp,
  290. texture,
  291. texpos,
  292. texsize,
  293. color
  294. ));
  295. }
  296. void ParticleSpawner::step(float dtime, ClientEnvironment *env)
  297. {
  298. m_time += dtime;
  299. static thread_local const float radius =
  300. g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
  301. bool unloaded = false;
  302. const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
  303. if (m_attached_id) {
  304. if (GenericCAO *attached = dynamic_cast<GenericCAO *>(env->getActiveObject(m_attached_id))) {
  305. attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
  306. } else {
  307. unloaded = true;
  308. }
  309. }
  310. if (p.time != 0) {
  311. // Spawner exists for a predefined timespan
  312. for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) {
  313. if ((*i) <= m_time && p.amount > 0) {
  314. --p.amount;
  315. // Pretend to, but don't actually spawn a particle if it is
  316. // attached to an unloaded object or distant from player.
  317. if (!unloaded)
  318. spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
  319. i = m_spawntimes.erase(i);
  320. } else {
  321. ++i;
  322. }
  323. }
  324. } else {
  325. // Spawner exists for an infinity timespan, spawn on a per-second base
  326. // Skip this step if attached to an unloaded object
  327. if (unloaded)
  328. return;
  329. for (int i = 0; i <= p.amount; i++) {
  330. if (rand() / (float)RAND_MAX < dtime)
  331. spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
  332. }
  333. }
  334. }
  335. /*
  336. ParticleManager
  337. */
  338. ParticleManager::ParticleManager(ClientEnvironment *env) :
  339. m_env(env)
  340. {}
  341. ParticleManager::~ParticleManager()
  342. {
  343. clearAll();
  344. }
  345. void ParticleManager::step(float dtime)
  346. {
  347. stepParticles (dtime);
  348. stepSpawners (dtime);
  349. }
  350. void ParticleManager::stepSpawners(float dtime)
  351. {
  352. MutexAutoLock lock(m_spawner_list_lock);
  353. for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
  354. if (i->second->get_expired()) {
  355. delete i->second;
  356. m_particle_spawners.erase(i++);
  357. } else {
  358. i->second->step(dtime, m_env);
  359. ++i;
  360. }
  361. }
  362. }
  363. void ParticleManager::stepParticles(float dtime)
  364. {
  365. MutexAutoLock lock(m_particle_list_lock);
  366. for (auto i = m_particles.begin(); i != m_particles.end();) {
  367. if ((*i)->get_expired()) {
  368. (*i)->remove();
  369. delete *i;
  370. i = m_particles.erase(i);
  371. } else {
  372. (*i)->step(dtime);
  373. ++i;
  374. }
  375. }
  376. }
  377. void ParticleManager::clearAll()
  378. {
  379. MutexAutoLock lock(m_spawner_list_lock);
  380. MutexAutoLock lock2(m_particle_list_lock);
  381. for (auto i = m_particle_spawners.begin(); i != m_particle_spawners.end();) {
  382. delete i->second;
  383. m_particle_spawners.erase(i++);
  384. }
  385. for(auto i = m_particles.begin(); i != m_particles.end();)
  386. {
  387. (*i)->remove();
  388. delete *i;
  389. i = m_particles.erase(i);
  390. }
  391. }
  392. void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
  393. LocalPlayer *player)
  394. {
  395. switch (event->type) {
  396. case CE_DELETE_PARTICLESPAWNER: {
  397. deleteParticleSpawner(event->delete_particlespawner.id);
  398. // no allocated memory in delete event
  399. break;
  400. }
  401. case CE_ADD_PARTICLESPAWNER: {
  402. deleteParticleSpawner(event->add_particlespawner.id);
  403. const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
  404. video::ITexture *texture =
  405. client->tsrc()->getTextureForMesh(p.texture);
  406. auto toadd = new ParticleSpawner(client, player,
  407. p,
  408. event->add_particlespawner.attached_id,
  409. texture,
  410. this);
  411. addParticleSpawner(event->add_particlespawner.id, toadd);
  412. delete event->add_particlespawner.p;
  413. break;
  414. }
  415. case CE_SPAWN_PARTICLE: {
  416. ParticleParameters &p = *event->spawn_particle;
  417. video::ITexture *texture;
  418. v2f texpos, texsize;
  419. video::SColor color(0xFFFFFFFF);
  420. f32 oldsize = p.size;
  421. if (p.node.getContent() != CONTENT_IGNORE) {
  422. const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
  423. if (!getNodeParticleParams(p.node, f, p, &texture, texpos,
  424. texsize, &color, p.node_tile))
  425. texture = nullptr;
  426. } else {
  427. texture = client->tsrc()->getTextureForMesh(p.texture);
  428. texpos = v2f(0.0f, 0.0f);
  429. texsize = v2f(1.0f, 1.0f);
  430. }
  431. // Allow keeping default random size
  432. if (oldsize > 0.0f)
  433. p.size = oldsize;
  434. if (texture) {
  435. Particle *toadd = new Particle(client, player, m_env,
  436. p, texture, texpos, texsize, color);
  437. addParticle(toadd);
  438. }
  439. delete event->spawn_particle;
  440. break;
  441. }
  442. default: break;
  443. }
  444. }
  445. bool ParticleManager::getNodeParticleParams(const MapNode &n,
  446. const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture,
  447. v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum)
  448. {
  449. // No particles for "airlike" nodes
  450. if (f.drawtype == NDT_AIRLIKE)
  451. return false;
  452. // Texture
  453. u8 texid;
  454. if (tilenum > 0 && tilenum <= 6)
  455. texid = tilenum - 1;
  456. else
  457. texid = rand() % 6;
  458. const TileLayer &tile = f.tiles[texid].layers[0];
  459. p.animation.type = TAT_NONE;
  460. // Only use first frame of animated texture
  461. if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
  462. *texture = (*tile.frames)[0].texture;
  463. else
  464. *texture = tile.texture;
  465. float size = (rand() % 8) / 64.0f;
  466. p.size = BS * size;
  467. if (tile.scale)
  468. size /= tile.scale;
  469. texsize = v2f(size * 2.0f, size * 2.0f);
  470. texpos.X = (rand() % 64) / 64.0f - texsize.X;
  471. texpos.Y = (rand() % 64) / 64.0f - texsize.Y;
  472. if (tile.has_color)
  473. *color = tile.color;
  474. else
  475. n.getColor(f, color);
  476. return true;
  477. }
  478. // The final burst of particles when a node is finally dug, *not* particles
  479. // spawned during the digging of a node.
  480. void ParticleManager::addDiggingParticles(IGameDef *gamedef,
  481. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  482. {
  483. // No particles for "airlike" nodes
  484. if (f.drawtype == NDT_AIRLIKE)
  485. return;
  486. for (u16 j = 0; j < 16; j++) {
  487. addNodeParticle(gamedef, player, pos, n, f);
  488. }
  489. }
  490. // During the digging of a node particles are spawned individually by this
  491. // function, called from Game::handleDigging() in game.cpp.
  492. void ParticleManager::addNodeParticle(IGameDef *gamedef,
  493. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  494. {
  495. ParticleParameters p;
  496. video::ITexture *texture;
  497. v2f texpos, texsize;
  498. video::SColor color;
  499. if (!getNodeParticleParams(n, f, p, &texture, texpos, texsize, &color))
  500. return;
  501. p.expirationtime = (rand() % 100) / 100.0f;
  502. // Physics
  503. p.vel = v3f(
  504. (rand() % 150) / 50.0f - 1.5f,
  505. (rand() % 150) / 50.0f,
  506. (rand() % 150) / 50.0f - 1.5f
  507. );
  508. p.acc = v3f(
  509. 0.0f,
  510. -player->movement_gravity * player->physics_override_gravity / BS,
  511. 0.0f
  512. );
  513. p.pos = v3f(
  514. (f32)pos.X + (rand() % 100) / 200.0f - 0.25f,
  515. (f32)pos.Y + (rand() % 100) / 200.0f - 0.25f,
  516. (f32)pos.Z + (rand() % 100) / 200.0f - 0.25f
  517. );
  518. Particle *toadd = new Particle(
  519. gamedef,
  520. player,
  521. m_env,
  522. p,
  523. texture,
  524. texpos,
  525. texsize,
  526. color);
  527. addParticle(toadd);
  528. }
  529. void ParticleManager::addParticle(Particle *toadd)
  530. {
  531. MutexAutoLock lock(m_particle_list_lock);
  532. m_particles.push_back(toadd);
  533. }
  534. void ParticleManager::addParticleSpawner(u64 id, ParticleSpawner *toadd)
  535. {
  536. MutexAutoLock lock(m_spawner_list_lock);
  537. m_particle_spawners[id] = toadd;
  538. }
  539. void ParticleManager::deleteParticleSpawner(u64 id)
  540. {
  541. MutexAutoLock lock(m_spawner_list_lock);
  542. auto it = m_particle_spawners.find(id);
  543. if (it != m_particle_spawners.end()) {
  544. delete it->second;
  545. m_particle_spawners.erase(it);
  546. }
  547. }