particles.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  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/clientevent.h"
  21. #include "client/renderingengine.h"
  22. #include "util/numeric.h"
  23. #include "light.h"
  24. #include "environment.h"
  25. #include "clientmap.h"
  26. #include "mapnode.h"
  27. #include "nodedef.h"
  28. #include "client.h"
  29. #include "settings.h"
  30. /*
  31. Utility
  32. */
  33. v3f random_v3f(v3f min, v3f max)
  34. {
  35. return v3f( rand()/(float)RAND_MAX*(max.X-min.X)+min.X,
  36. rand()/(float)RAND_MAX*(max.Y-min.Y)+min.Y,
  37. rand()/(float)RAND_MAX*(max.Z-min.Z)+min.Z);
  38. }
  39. Particle::Particle(
  40. IGameDef *gamedef,
  41. LocalPlayer *player,
  42. ClientEnvironment *env,
  43. v3f pos,
  44. v3f velocity,
  45. v3f acceleration,
  46. float expirationtime,
  47. float size,
  48. bool collisiondetection,
  49. bool collision_removal,
  50. bool vertical,
  51. video::ITexture *texture,
  52. v2f texpos,
  53. v2f texsize,
  54. const struct TileAnimationParams &anim,
  55. u8 glow,
  56. video::SColor color
  57. ):
  58. scene::ISceneNode(RenderingEngine::get_scene_manager()->getRootSceneNode(),
  59. RenderingEngine::get_scene_manager())
  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 = anim;
  74. // Color
  75. m_base_color = color;
  76. m_color = color;
  77. // Particle related
  78. m_pos = pos;
  79. m_velocity = velocity;
  80. m_acceleration = acceleration;
  81. m_expiration = expirationtime;
  82. m_player = player;
  83. m_size = size;
  84. m_collisiondetection = collisiondetection;
  85. m_collision_removal = collision_removal;
  86. m_vertical = vertical;
  87. m_glow = glow;
  88. // Irrlicht stuff
  89. m_collisionbox = aabb3f
  90. (-size/2,-size/2,-size/2,size/2,size/2,size/2);
  91. this->setAutomaticCulling(scene::EAC_OFF);
  92. // Init lighting
  93. updateLight();
  94. // Init model
  95. updateVertices();
  96. }
  97. void Particle::OnRegisterSceneNode()
  98. {
  99. if (IsVisible)
  100. SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
  101. ISceneNode::OnRegisterSceneNode();
  102. }
  103. void Particle::render()
  104. {
  105. video::IVideoDriver* driver = SceneManager->getVideoDriver();
  106. driver->setMaterial(m_material);
  107. driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
  108. u16 indices[] = {0,1,2, 2,3,0};
  109. driver->drawVertexPrimitiveList(m_vertices, 4,
  110. indices, 2, video::EVT_STANDARD,
  111. scene::EPT_TRIANGLES, video::EIT_16BIT);
  112. }
  113. void Particle::step(float dtime)
  114. {
  115. m_time += dtime;
  116. if (m_collisiondetection) {
  117. aabb3f box = m_collisionbox;
  118. v3f p_pos = m_pos * BS;
  119. v3f p_velocity = m_velocity * BS;
  120. collisionMoveResult r = collisionMoveSimple(m_env,
  121. m_gamedef, BS * 0.5, box, 0, dtime, &p_pos,
  122. &p_velocity, m_acceleration * BS);
  123. if (m_collision_removal && r.collides) {
  124. // force expiration of the particle
  125. m_expiration = -1.0;
  126. } else {
  127. m_pos = p_pos / BS;
  128. m_velocity = p_velocity / BS;
  129. }
  130. } else {
  131. m_velocity += m_acceleration * dtime;
  132. m_pos += m_velocity * dtime;
  133. }
  134. if (m_animation.type != TAT_NONE) {
  135. m_animation_time += dtime;
  136. int frame_length_i, frame_count;
  137. m_animation.determineParams(
  138. m_material.getTexture(0)->getSize(),
  139. &frame_count, &frame_length_i, NULL);
  140. float frame_length = frame_length_i / 1000.0;
  141. while (m_animation_time > frame_length) {
  142. m_animation_frame++;
  143. m_animation_time -= frame_length;
  144. }
  145. }
  146. // Update lighting
  147. updateLight();
  148. // Update model
  149. updateVertices();
  150. }
  151. void Particle::updateLight()
  152. {
  153. u8 light = 0;
  154. bool pos_ok;
  155. v3s16 p = v3s16(
  156. floor(m_pos.X+0.5),
  157. floor(m_pos.Y+0.5),
  158. floor(m_pos.Z+0.5)
  159. );
  160. MapNode n = m_env->getClientMap().getNodeNoEx(p, &pos_ok);
  161. if (pos_ok)
  162. light = n.getLightBlend(m_env->getDayNightRatio(), m_gamedef->ndef());
  163. else
  164. light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
  165. u8 m_light = decode_light(light + m_glow);
  166. m_color.set(255,
  167. m_light * m_base_color.getRed() / 255,
  168. m_light * m_base_color.getGreen() / 255,
  169. m_light * m_base_color.getBlue() / 255);
  170. }
  171. void Particle::updateVertices()
  172. {
  173. f32 tx0, tx1, ty0, ty1;
  174. if (m_animation.type != TAT_NONE) {
  175. const v2u32 texsize = m_material.getTexture(0)->getSize();
  176. v2f texcoord, framesize_f;
  177. v2u32 framesize;
  178. texcoord = m_animation.getTextureCoords(texsize, m_animation_frame);
  179. m_animation.determineParams(texsize, NULL, NULL, &framesize);
  180. framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
  181. tx0 = m_texpos.X + texcoord.X;
  182. tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
  183. ty0 = m_texpos.Y + texcoord.Y;
  184. ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
  185. } else {
  186. tx0 = m_texpos.X;
  187. tx1 = m_texpos.X + m_texsize.X;
  188. ty0 = m_texpos.Y;
  189. ty1 = m_texpos.Y + m_texsize.Y;
  190. }
  191. m_vertices[0] = video::S3DVertex(-m_size / 2, -m_size / 2,
  192. 0, 0, 0, 0, m_color, tx0, ty1);
  193. m_vertices[1] = video::S3DVertex(m_size / 2, -m_size / 2,
  194. 0, 0, 0, 0, m_color, tx1, ty1);
  195. m_vertices[2] = video::S3DVertex(m_size / 2, m_size / 2,
  196. 0, 0, 0, 0, m_color, tx1, ty0);
  197. m_vertices[3] = video::S3DVertex(-m_size / 2, m_size / 2,
  198. 0, 0, 0, 0, m_color, tx0, ty0);
  199. v3s16 camera_offset = m_env->getCameraOffset();
  200. for (video::S3DVertex &vertex : m_vertices) {
  201. if (m_vertical) {
  202. v3f ppos = m_player->getPosition()/BS;
  203. vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
  204. core::DEGTORAD + 90);
  205. } else {
  206. vertex.Pos.rotateYZBy(m_player->getPitch());
  207. vertex.Pos.rotateXZBy(m_player->getYaw());
  208. }
  209. m_box.addInternalPoint(vertex.Pos);
  210. vertex.Pos += m_pos*BS - intToFloat(camera_offset, BS);
  211. }
  212. }
  213. /*
  214. ParticleSpawner
  215. */
  216. ParticleSpawner::ParticleSpawner(IGameDef *gamedef, LocalPlayer *player,
  217. u16 amount, float time,
  218. v3f minpos, v3f maxpos, v3f minvel, v3f maxvel, v3f minacc, v3f maxacc,
  219. float minexptime, float maxexptime, float minsize, float maxsize,
  220. bool collisiondetection, bool collision_removal, u16 attached_id, bool vertical,
  221. video::ITexture *texture, u32 id, const struct TileAnimationParams &anim,
  222. u8 glow,
  223. ParticleManager *p_manager) :
  224. m_particlemanager(p_manager)
  225. {
  226. m_gamedef = gamedef;
  227. m_player = player;
  228. m_amount = amount;
  229. m_spawntime = time;
  230. m_minpos = minpos;
  231. m_maxpos = maxpos;
  232. m_minvel = minvel;
  233. m_maxvel = maxvel;
  234. m_minacc = minacc;
  235. m_maxacc = maxacc;
  236. m_minexptime = minexptime;
  237. m_maxexptime = maxexptime;
  238. m_minsize = minsize;
  239. m_maxsize = maxsize;
  240. m_collisiondetection = collisiondetection;
  241. m_collision_removal = collision_removal;
  242. m_attached_id = attached_id;
  243. m_vertical = vertical;
  244. m_texture = texture;
  245. m_time = 0;
  246. m_animation = anim;
  247. m_glow = glow;
  248. for (u16 i = 0; i<=m_amount; i++)
  249. {
  250. float spawntime = (float)rand()/(float)RAND_MAX*m_spawntime;
  251. m_spawntimes.push_back(spawntime);
  252. }
  253. }
  254. void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
  255. bool is_attached, const v3f &attached_pos, float attached_yaw)
  256. {
  257. v3f ppos = m_player->getPosition() / BS;
  258. v3f pos = random_v3f(m_minpos, m_maxpos);
  259. // Need to apply this first or the following check
  260. // will be wrong for attached spawners
  261. if (is_attached) {
  262. pos.rotateXZBy(attached_yaw);
  263. pos += attached_pos;
  264. }
  265. if (pos.getDistanceFrom(ppos) > radius)
  266. return;
  267. v3f vel = random_v3f(m_minvel, m_maxvel);
  268. v3f acc = random_v3f(m_minacc, m_maxacc);
  269. if (is_attached) {
  270. // Apply attachment yaw
  271. vel.rotateXZBy(attached_yaw);
  272. acc.rotateXZBy(attached_yaw);
  273. }
  274. float exptime = rand() / (float)RAND_MAX
  275. * (m_maxexptime - m_minexptime)
  276. + m_minexptime;
  277. float size = rand() / (float)RAND_MAX
  278. * (m_maxsize - m_minsize)
  279. + m_minsize;
  280. m_particlemanager->addParticle(new Particle(
  281. m_gamedef,
  282. m_player,
  283. env,
  284. pos,
  285. vel,
  286. acc,
  287. exptime,
  288. size,
  289. m_collisiondetection,
  290. m_collision_removal,
  291. m_vertical,
  292. m_texture,
  293. v2f(0.0, 0.0),
  294. v2f(1.0, 1.0),
  295. m_animation,
  296. m_glow
  297. ));
  298. }
  299. void ParticleSpawner::step(float dtime, ClientEnvironment* env)
  300. {
  301. m_time += dtime;
  302. static thread_local const float radius =
  303. g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
  304. bool unloaded = false;
  305. bool is_attached = false;
  306. v3f attached_pos = v3f(0,0,0);
  307. float attached_yaw = 0;
  308. if (m_attached_id != 0) {
  309. if (ClientActiveObject *attached = env->getActiveObject(m_attached_id)) {
  310. attached_pos = attached->getPosition() / BS;
  311. attached_yaw = attached->getYaw();
  312. is_attached = true;
  313. } else {
  314. unloaded = true;
  315. }
  316. }
  317. if (m_spawntime != 0) {
  318. // Spawner exists for a predefined timespan
  319. for (std::vector<float>::iterator i = m_spawntimes.begin();
  320. i != m_spawntimes.end();) {
  321. if ((*i) <= m_time && m_amount > 0) {
  322. m_amount--;
  323. // Pretend to, but don't actually spawn a particle if it is
  324. // attached to an unloaded object or distant from player.
  325. if (!unloaded)
  326. spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
  327. i = m_spawntimes.erase(i);
  328. } else {
  329. ++i;
  330. }
  331. }
  332. } else {
  333. // Spawner exists for an infinity timespan, spawn on a per-second base
  334. // Skip this step if attached to an unloaded object
  335. if (unloaded)
  336. return;
  337. for (int i = 0; i <= m_amount; i++) {
  338. if (rand() / (float)RAND_MAX < dtime)
  339. spawnParticle(env, radius, is_attached, attached_pos, attached_yaw);
  340. }
  341. }
  342. }
  343. ParticleManager::ParticleManager(ClientEnvironment* env) :
  344. m_env(env)
  345. {}
  346. ParticleManager::~ParticleManager()
  347. {
  348. clearAll();
  349. }
  350. void ParticleManager::step(float dtime)
  351. {
  352. stepParticles (dtime);
  353. stepSpawners (dtime);
  354. }
  355. void ParticleManager::stepSpawners (float dtime)
  356. {
  357. MutexAutoLock lock(m_spawner_list_lock);
  358. for (std::map<u32, ParticleSpawner*>::iterator i =
  359. m_particle_spawners.begin();
  360. i != m_particle_spawners.end();)
  361. {
  362. if (i->second->get_expired())
  363. {
  364. delete i->second;
  365. m_particle_spawners.erase(i++);
  366. }
  367. else
  368. {
  369. i->second->step(dtime, m_env);
  370. ++i;
  371. }
  372. }
  373. }
  374. void ParticleManager::stepParticles (float dtime)
  375. {
  376. MutexAutoLock lock(m_particle_list_lock);
  377. for(std::vector<Particle*>::iterator i = m_particles.begin();
  378. i != m_particles.end();)
  379. {
  380. if ((*i)->get_expired())
  381. {
  382. (*i)->remove();
  383. delete *i;
  384. i = m_particles.erase(i);
  385. }
  386. else
  387. {
  388. (*i)->step(dtime);
  389. ++i;
  390. }
  391. }
  392. }
  393. void ParticleManager::clearAll ()
  394. {
  395. MutexAutoLock lock(m_spawner_list_lock);
  396. MutexAutoLock lock2(m_particle_list_lock);
  397. for(std::map<u32, ParticleSpawner*>::iterator i =
  398. m_particle_spawners.begin();
  399. i != m_particle_spawners.end();)
  400. {
  401. delete i->second;
  402. m_particle_spawners.erase(i++);
  403. }
  404. for(std::vector<Particle*>::iterator i =
  405. m_particles.begin();
  406. i != m_particles.end();)
  407. {
  408. (*i)->remove();
  409. delete *i;
  410. i = m_particles.erase(i);
  411. }
  412. }
  413. void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
  414. LocalPlayer *player)
  415. {
  416. switch (event->type) {
  417. case CE_DELETE_PARTICLESPAWNER: {
  418. MutexAutoLock lock(m_spawner_list_lock);
  419. if (m_particle_spawners.find(event->delete_particlespawner.id) !=
  420. m_particle_spawners.end()) {
  421. delete m_particle_spawners.find(event->delete_particlespawner.id)->second;
  422. m_particle_spawners.erase(event->delete_particlespawner.id);
  423. }
  424. // no allocated memory in delete event
  425. break;
  426. }
  427. case CE_ADD_PARTICLESPAWNER: {
  428. {
  429. MutexAutoLock lock(m_spawner_list_lock);
  430. if (m_particle_spawners.find(event->add_particlespawner.id) !=
  431. m_particle_spawners.end()) {
  432. delete m_particle_spawners.find(event->add_particlespawner.id)->second;
  433. m_particle_spawners.erase(event->add_particlespawner.id);
  434. }
  435. }
  436. video::ITexture *texture =
  437. client->tsrc()->getTextureForMesh(*(event->add_particlespawner.texture));
  438. ParticleSpawner *toadd = new ParticleSpawner(client, player,
  439. event->add_particlespawner.amount,
  440. event->add_particlespawner.spawntime,
  441. *event->add_particlespawner.minpos,
  442. *event->add_particlespawner.maxpos,
  443. *event->add_particlespawner.minvel,
  444. *event->add_particlespawner.maxvel,
  445. *event->add_particlespawner.minacc,
  446. *event->add_particlespawner.maxacc,
  447. event->add_particlespawner.minexptime,
  448. event->add_particlespawner.maxexptime,
  449. event->add_particlespawner.minsize,
  450. event->add_particlespawner.maxsize,
  451. event->add_particlespawner.collisiondetection,
  452. event->add_particlespawner.collision_removal,
  453. event->add_particlespawner.attached_id,
  454. event->add_particlespawner.vertical,
  455. texture,
  456. event->add_particlespawner.id,
  457. event->add_particlespawner.animation,
  458. event->add_particlespawner.glow,
  459. this);
  460. /* delete allocated content of event */
  461. delete event->add_particlespawner.minpos;
  462. delete event->add_particlespawner.maxpos;
  463. delete event->add_particlespawner.minvel;
  464. delete event->add_particlespawner.maxvel;
  465. delete event->add_particlespawner.minacc;
  466. delete event->add_particlespawner.texture;
  467. delete event->add_particlespawner.maxacc;
  468. {
  469. MutexAutoLock lock(m_spawner_list_lock);
  470. m_particle_spawners.insert(
  471. std::pair<u32, ParticleSpawner*>(
  472. event->add_particlespawner.id,
  473. toadd));
  474. }
  475. break;
  476. }
  477. case CE_SPAWN_PARTICLE: {
  478. video::ITexture *texture =
  479. client->tsrc()->getTextureForMesh(*(event->spawn_particle.texture));
  480. Particle *toadd = new Particle(client, player, m_env,
  481. *event->spawn_particle.pos,
  482. *event->spawn_particle.vel,
  483. *event->spawn_particle.acc,
  484. event->spawn_particle.expirationtime,
  485. event->spawn_particle.size,
  486. event->spawn_particle.collisiondetection,
  487. event->spawn_particle.collision_removal,
  488. event->spawn_particle.vertical,
  489. texture,
  490. v2f(0.0, 0.0),
  491. v2f(1.0, 1.0),
  492. event->spawn_particle.animation,
  493. event->spawn_particle.glow);
  494. addParticle(toadd);
  495. delete event->spawn_particle.pos;
  496. delete event->spawn_particle.vel;
  497. delete event->spawn_particle.acc;
  498. delete event->spawn_particle.texture;
  499. break;
  500. }
  501. default: break;
  502. }
  503. }
  504. void ParticleManager::addDiggingParticles(IGameDef* gamedef,
  505. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  506. {
  507. // No particles for "airlike" nodes
  508. if (f.drawtype == NDT_AIRLIKE)
  509. return;
  510. // set the amount of particles here
  511. for (u16 j = 0; j < 32; j++) {
  512. addNodeParticle(gamedef, player, pos, n, f);
  513. }
  514. }
  515. void ParticleManager::addNodeParticle(IGameDef* gamedef,
  516. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  517. {
  518. // No particles for "airlike" nodes
  519. if (f.drawtype == NDT_AIRLIKE)
  520. return;
  521. // Texture
  522. u8 texid = myrand_range(0, 5);
  523. const TileLayer &tile = f.tiles[texid].layers[0];
  524. video::ITexture *texture;
  525. struct TileAnimationParams anim;
  526. anim.type = TAT_NONE;
  527. // Only use first frame of animated texture
  528. if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
  529. texture = (*tile.frames)[0].texture;
  530. else
  531. texture = tile.texture;
  532. float size = rand() % 64 / 512.;
  533. float visual_size = BS * size;
  534. if (tile.scale)
  535. size /= tile.scale;
  536. v2f texsize(size * 2, size * 2);
  537. v2f texpos;
  538. texpos.X = ((rand() % 64) / 64. - texsize.X);
  539. texpos.Y = ((rand() % 64) / 64. - texsize.Y);
  540. // Physics
  541. v3f velocity((rand() % 100 / 50. - 1) / 1.5,
  542. rand() % 100 / 35.,
  543. (rand() % 100 / 50. - 1) / 1.5);
  544. v3f acceleration(0,-9,0);
  545. v3f particlepos = v3f(
  546. (f32) pos.X + rand() %100 /200. - 0.25,
  547. (f32) pos.Y + rand() %100 /200. - 0.25,
  548. (f32) pos.Z + rand() %100 /200. - 0.25
  549. );
  550. video::SColor color;
  551. if (tile.has_color)
  552. color = tile.color;
  553. else
  554. n.getColor(f, &color);
  555. Particle* toadd = new Particle(
  556. gamedef,
  557. player,
  558. m_env,
  559. particlepos,
  560. velocity,
  561. acceleration,
  562. rand() % 100 / 100., // expiration time
  563. visual_size,
  564. true,
  565. false,
  566. false,
  567. texture,
  568. texpos,
  569. texsize,
  570. anim,
  571. 0,
  572. color);
  573. addParticle(toadd);
  574. }
  575. void ParticleManager::addParticle(Particle* toadd)
  576. {
  577. MutexAutoLock lock(m_particle_list_lock);
  578. m_particles.push_back(toadd);
  579. }