particles.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  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. Particle
  33. */
  34. Particle::Particle(
  35. IGameDef *gamedef,
  36. LocalPlayer *player,
  37. ClientEnvironment *env,
  38. const ParticleParameters &p,
  39. const ClientParticleTexRef &texture,
  40. v2f texpos,
  41. v2f texsize,
  42. video::SColor color,
  43. ParticleSpawner *parent,
  44. std::unique_ptr<ClientParticleTexture> owned_texture
  45. ) :
  46. scene::ISceneNode(((Client *)gamedef)->getSceneManager()->getRootSceneNode(),
  47. ((Client *)gamedef)->getSceneManager()),
  48. m_expiration(p.expirationtime),
  49. m_env(env),
  50. m_gamedef(gamedef),
  51. m_collisionbox(aabb3f(v3f(-p.size / 2.0f), v3f(p.size / 2.0f))),
  52. m_texture(texture),
  53. m_texpos(texpos),
  54. m_texsize(texsize),
  55. m_pos(p.pos),
  56. m_velocity(p.vel),
  57. m_acceleration(p.acc),
  58. m_p(p),
  59. m_player(player),
  60. m_base_color(color),
  61. m_color(color),
  62. m_parent(parent),
  63. m_owned_texture(std::move(owned_texture))
  64. {
  65. // Set material
  66. {
  67. // translate blend modes to GL blend functions
  68. video::E_BLEND_FACTOR bfsrc, bfdst;
  69. video::E_BLEND_OPERATION blendop;
  70. const auto blendmode = texture.tex != nullptr
  71. ? texture.tex->blendmode
  72. : ParticleParamTypes::BlendMode::alpha;
  73. switch (blendmode) {
  74. case ParticleParamTypes::BlendMode::add:
  75. bfsrc = video::EBF_SRC_ALPHA;
  76. bfdst = video::EBF_DST_ALPHA;
  77. blendop = video::EBO_ADD;
  78. break;
  79. case ParticleParamTypes::BlendMode::sub:
  80. bfsrc = video::EBF_SRC_ALPHA;
  81. bfdst = video::EBF_DST_ALPHA;
  82. blendop = video::EBO_REVSUBTRACT;
  83. break;
  84. case ParticleParamTypes::BlendMode::screen:
  85. bfsrc = video::EBF_ONE;
  86. bfdst = video::EBF_ONE_MINUS_SRC_COLOR;
  87. blendop = video::EBO_ADD;
  88. break;
  89. default: // includes ParticleParamTypes::BlendMode::alpha
  90. bfsrc = video::EBF_SRC_ALPHA;
  91. bfdst = video::EBF_ONE_MINUS_SRC_ALPHA;
  92. blendop = video::EBO_ADD;
  93. break;
  94. }
  95. // Texture
  96. m_material.Lighting = false;
  97. m_material.BackfaceCulling = false;
  98. m_material.FogEnable = true;
  99. m_material.forEachTexture([] (auto &tex) {
  100. tex.MinFilter = video::ETMINF_NEAREST_MIPMAP_NEAREST;
  101. tex.MagFilter = video::ETMAGF_NEAREST;
  102. });
  103. // correctly render layered transparent particles -- see #10398
  104. m_material.ZWriteEnable = video::EZW_AUTO;
  105. // enable alpha blending and set blend mode
  106. m_material.MaterialType = video::EMT_ONETEXTURE_BLEND;
  107. m_material.MaterialTypeParam = video::pack_textureBlendFunc(
  108. bfsrc, bfdst,
  109. video::EMFN_MODULATE_1X,
  110. video::EAS_TEXTURE | video::EAS_VERTEX_COLOR);
  111. m_material.BlendOperation = blendop;
  112. m_material.setTexture(0, m_texture.ref);
  113. }
  114. // Irrlicht stuff
  115. this->setAutomaticCulling(scene::EAC_OFF);
  116. // Init lighting
  117. updateLight();
  118. // Init model
  119. updateVertices();
  120. }
  121. void Particle::OnRegisterSceneNode()
  122. {
  123. if (IsVisible)
  124. SceneManager->registerNodeForRendering(this, scene::ESNRP_TRANSPARENT_EFFECT);
  125. ISceneNode::OnRegisterSceneNode();
  126. }
  127. void Particle::render()
  128. {
  129. video::IVideoDriver *driver = SceneManager->getVideoDriver();
  130. driver->setMaterial(m_material);
  131. driver->setTransform(video::ETS_WORLD, AbsoluteTransformation);
  132. u16 indices[] = {0,1,2, 2,3,0};
  133. driver->drawVertexPrimitiveList(m_vertices, 4,
  134. indices, 2, video::EVT_STANDARD,
  135. scene::EPT_TRIANGLES, video::EIT_16BIT);
  136. }
  137. void Particle::step(float dtime)
  138. {
  139. m_time += dtime;
  140. // apply drag (not handled by collisionMoveSimple) and brownian motion
  141. v3f av = vecAbsolute(m_velocity);
  142. av -= av * (m_p.drag * dtime);
  143. m_velocity = av*vecSign(m_velocity) + v3f(m_p.jitter.pickWithin())*dtime;
  144. if (m_p.collisiondetection) {
  145. aabb3f box = m_collisionbox;
  146. v3f p_pos = m_pos * BS;
  147. v3f p_velocity = m_velocity * BS;
  148. collisionMoveResult r = collisionMoveSimple(m_env, m_gamedef, BS * 0.5f,
  149. box, 0.0f, dtime, &p_pos, &p_velocity, m_acceleration * BS, nullptr,
  150. m_p.object_collision);
  151. f32 bounciness = m_p.bounce.pickWithin();
  152. if (r.collides && (m_p.collision_removal || bounciness > 0)) {
  153. if (m_p.collision_removal) {
  154. // force expiration of the particle
  155. m_expiration = -1.0f;
  156. } else if (bounciness > 0) {
  157. /* cheap way to get a decent bounce effect is to only invert the
  158. * largest component of the velocity vector, so e.g. you don't
  159. * have a rock immediately bounce back in your face when you try
  160. * to skip it across the water (as would happen if we simply
  161. * downscaled and negated the velocity vector). this means
  162. * bounciness will work properly for cubic objects, but meshes
  163. * with diagonal angles and entities will not yield the correct
  164. * visual. this is probably unavoidable */
  165. if (av.Y > av.X && av.Y > av.Z) {
  166. m_velocity.Y = -(m_velocity.Y * bounciness);
  167. } else if (av.X > av.Y && av.X > av.Z) {
  168. m_velocity.X = -(m_velocity.X * bounciness);
  169. } else if (av.Z > av.Y && av.Z > av.X) {
  170. m_velocity.Z = -(m_velocity.Z * bounciness);
  171. } else { // well now we're in a bit of a pickle
  172. m_velocity = -(m_velocity * bounciness);
  173. }
  174. }
  175. } else {
  176. m_velocity = p_velocity / BS;
  177. }
  178. m_pos = p_pos / BS;
  179. } else {
  180. // apply velocity and acceleration to position
  181. m_pos += (m_velocity + m_acceleration * 0.5f * dtime) * dtime;
  182. // apply acceleration to velocity
  183. m_velocity += m_acceleration * dtime;
  184. }
  185. if (m_p.animation.type != TAT_NONE) {
  186. m_animation_time += dtime;
  187. int frame_length_i = 0;
  188. m_p.animation.determineParams(
  189. m_material.getTexture(0)->getSize(),
  190. NULL, &frame_length_i, NULL);
  191. float frame_length = frame_length_i / 1000.0;
  192. while (m_animation_time > frame_length) {
  193. m_animation_frame++;
  194. m_animation_time -= frame_length;
  195. }
  196. }
  197. // animate particle alpha in accordance with settings
  198. if (m_texture.tex != nullptr)
  199. m_alpha = m_texture.tex -> alpha.blend(m_time / (m_expiration+0.1f));
  200. else
  201. m_alpha = 1.f;
  202. // Update lighting
  203. updateLight();
  204. // Update model
  205. updateVertices();
  206. // Update position -- see #10398
  207. v3s16 camera_offset = m_env->getCameraOffset();
  208. setPosition(m_pos*BS - intToFloat(camera_offset, BS));
  209. }
  210. void Particle::updateLight()
  211. {
  212. u8 light = 0;
  213. bool pos_ok;
  214. v3s16 p = v3s16(
  215. floor(m_pos.X+0.5),
  216. floor(m_pos.Y+0.5),
  217. floor(m_pos.Z+0.5)
  218. );
  219. MapNode n = m_env->getClientMap().getNode(p, &pos_ok);
  220. if (pos_ok)
  221. light = n.getLightBlend(m_env->getDayNightRatio(),
  222. m_gamedef->ndef()->getLightingFlags(n));
  223. else
  224. light = blend_light(m_env->getDayNightRatio(), LIGHT_SUN, 0);
  225. u8 m_light = decode_light(light + m_p.glow);
  226. m_color.set(m_alpha*255,
  227. m_light * m_base_color.getRed() / 255,
  228. m_light * m_base_color.getGreen() / 255,
  229. m_light * m_base_color.getBlue() / 255);
  230. }
  231. void Particle::updateVertices()
  232. {
  233. f32 tx0, tx1, ty0, ty1;
  234. v2f scale;
  235. if (m_texture.tex != nullptr)
  236. scale = m_texture.tex -> scale.blend(m_time / (m_expiration+0.1));
  237. else
  238. scale = v2f(1.f, 1.f);
  239. if (m_p.animation.type != TAT_NONE) {
  240. const v2u32 texsize = m_material.getTexture(0)->getSize();
  241. v2f texcoord, framesize_f;
  242. v2u32 framesize;
  243. texcoord = m_p.animation.getTextureCoords(texsize, m_animation_frame);
  244. m_p.animation.determineParams(texsize, NULL, NULL, &framesize);
  245. framesize_f = v2f(framesize.X / (float) texsize.X, framesize.Y / (float) texsize.Y);
  246. tx0 = m_texpos.X + texcoord.X;
  247. tx1 = m_texpos.X + texcoord.X + framesize_f.X * m_texsize.X;
  248. ty0 = m_texpos.Y + texcoord.Y;
  249. ty1 = m_texpos.Y + texcoord.Y + framesize_f.Y * m_texsize.Y;
  250. } else {
  251. tx0 = m_texpos.X;
  252. tx1 = m_texpos.X + m_texsize.X;
  253. ty0 = m_texpos.Y;
  254. ty1 = m_texpos.Y + m_texsize.Y;
  255. }
  256. auto half = m_p.size * .5f,
  257. hx = half * scale.X,
  258. hy = half * scale.Y;
  259. m_vertices[0] = video::S3DVertex(-hx, -hy,
  260. 0, 0, 0, 0, m_color, tx0, ty1);
  261. m_vertices[1] = video::S3DVertex(hx, -hy,
  262. 0, 0, 0, 0, m_color, tx1, ty1);
  263. m_vertices[2] = video::S3DVertex(hx, hy,
  264. 0, 0, 0, 0, m_color, tx1, ty0);
  265. m_vertices[3] = video::S3DVertex(-hx, hy,
  266. 0, 0, 0, 0, m_color, tx0, ty0);
  267. // see #10398
  268. // v3s16 camera_offset = m_env->getCameraOffset();
  269. // particle position is now handled by step()
  270. m_box.reset(v3f());
  271. for (video::S3DVertex &vertex : m_vertices) {
  272. if (m_p.vertical) {
  273. v3f ppos = m_player->getPosition()/BS;
  274. vertex.Pos.rotateXZBy(std::atan2(ppos.Z - m_pos.Z, ppos.X - m_pos.X) /
  275. core::DEGTORAD + 90);
  276. } else {
  277. vertex.Pos.rotateYZBy(m_player->getPitch());
  278. vertex.Pos.rotateXZBy(m_player->getYaw());
  279. }
  280. m_box.addInternalPoint(vertex.Pos);
  281. }
  282. }
  283. /*
  284. ParticleSpawner
  285. */
  286. ParticleSpawner::ParticleSpawner(
  287. IGameDef *gamedef,
  288. LocalPlayer *player,
  289. const ParticleSpawnerParameters &params,
  290. u16 attached_id,
  291. std::vector<ClientParticleTexture> &&texpool,
  292. ParticleManager *p_manager
  293. ) :
  294. m_active(0),
  295. m_particlemanager(p_manager),
  296. m_time(0.0f),
  297. m_gamedef(gamedef),
  298. m_player(player),
  299. p(params),
  300. m_texpool(std::move(texpool)),
  301. m_attached_id(attached_id)
  302. {
  303. m_spawntimes.reserve(p.amount + 1);
  304. for (u16 i = 0; i <= p.amount; i++) {
  305. float spawntime = myrand_float() * p.time;
  306. m_spawntimes.push_back(spawntime);
  307. }
  308. size_t max_particles = 0; // maximum number of particles likely to be visible at any given time
  309. if (p.time != 0) {
  310. auto maxGenerations = p.time / std::min(p.exptime.start.min, p.exptime.end.min);
  311. max_particles = p.amount / maxGenerations;
  312. } else {
  313. auto longestLife = std::max(p.exptime.start.max, p.exptime.end.max);
  314. max_particles = p.amount * longestLife;
  315. }
  316. p_manager->reserveParticleSpace(max_particles * 1.2);
  317. }
  318. namespace {
  319. GenericCAO *findObjectByID(ClientEnvironment *env, u16 id) {
  320. if (id == 0)
  321. return nullptr;
  322. return env->getGenericCAO(id);
  323. }
  324. }
  325. void ParticleSpawner::spawnParticle(ClientEnvironment *env, float radius,
  326. const core::matrix4 *attached_absolute_pos_rot_matrix)
  327. {
  328. float fac = 0;
  329. if (p.time != 0) { // ensure safety from divide-by-zeroes
  330. fac = m_time / (p.time+0.1f);
  331. }
  332. auto r_pos = p.pos.blend(fac);
  333. auto r_vel = p.vel.blend(fac);
  334. auto r_acc = p.acc.blend(fac);
  335. auto r_drag = p.drag.blend(fac);
  336. auto r_radius = p.radius.blend(fac);
  337. auto r_jitter = p.jitter.blend(fac);
  338. auto r_bounce = p.bounce.blend(fac);
  339. v3f attractor_origin = p.attractor_origin.blend(fac);
  340. v3f attractor_direction = p.attractor_direction.blend(fac);
  341. auto attractor_obj = findObjectByID(env, p.attractor_attachment);
  342. auto attractor_direction_obj = findObjectByID(env, p.attractor_direction_attachment);
  343. auto r_exp = p.exptime.blend(fac);
  344. auto r_size = p.size.blend(fac);
  345. auto r_attract = p.attract.blend(fac);
  346. auto attract = r_attract.pickWithin();
  347. v3f ppos = m_player->getPosition() / BS;
  348. v3f pos = r_pos.pickWithin();
  349. v3f sphere_radius = r_radius.pickWithin();
  350. // Need to apply this first or the following check
  351. // will be wrong for attached spawners
  352. if (attached_absolute_pos_rot_matrix) {
  353. pos *= BS;
  354. attached_absolute_pos_rot_matrix->transformVect(pos);
  355. pos /= BS;
  356. v3s16 camera_offset = m_particlemanager->m_env->getCameraOffset();
  357. pos.X += camera_offset.X;
  358. pos.Y += camera_offset.Y;
  359. pos.Z += camera_offset.Z;
  360. }
  361. if (pos.getDistanceFromSQ(ppos) > radius*radius)
  362. return;
  363. // Parameters for the single particle we're about to spawn
  364. ParticleParameters pp;
  365. pp.pos = pos;
  366. pp.vel = r_vel.pickWithin();
  367. pp.acc = r_acc.pickWithin();
  368. pp.drag = r_drag.pickWithin();
  369. pp.jitter = r_jitter;
  370. pp.bounce = r_bounce;
  371. if (attached_absolute_pos_rot_matrix) {
  372. // Apply attachment rotation
  373. attached_absolute_pos_rot_matrix->rotateVect(pp.vel);
  374. attached_absolute_pos_rot_matrix->rotateVect(pp.acc);
  375. }
  376. if (attractor_obj)
  377. attractor_origin += attractor_obj->getPosition() / BS;
  378. if (attractor_direction_obj) {
  379. auto *attractor_absolute_pos_rot_matrix = attractor_direction_obj->getAbsolutePosRotMatrix();
  380. if (attractor_absolute_pos_rot_matrix)
  381. attractor_absolute_pos_rot_matrix->rotateVect(attractor_direction);
  382. }
  383. pp.expirationtime = r_exp.pickWithin();
  384. if (sphere_radius != v3f()) {
  385. f32 l = sphere_radius.getLength();
  386. v3f mag = sphere_radius;
  387. mag.normalize();
  388. v3f ofs = v3f(l,0,0);
  389. ofs.rotateXZBy(myrand_range(0.f,360.f));
  390. ofs.rotateYZBy(myrand_range(0.f,360.f));
  391. ofs.rotateXYBy(myrand_range(0.f,360.f));
  392. pp.pos += ofs * mag;
  393. }
  394. if (p.attractor_kind != ParticleParamTypes::AttractorKind::none && attract != 0) {
  395. v3f dir;
  396. f32 dist = 0; /* =0 necessary to silence warning */
  397. switch (p.attractor_kind) {
  398. case ParticleParamTypes::AttractorKind::none:
  399. break;
  400. case ParticleParamTypes::AttractorKind::point: {
  401. dist = pp.pos.getDistanceFrom(attractor_origin);
  402. dir = pp.pos - attractor_origin;
  403. dir.normalize();
  404. break;
  405. }
  406. case ParticleParamTypes::AttractorKind::line: {
  407. // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
  408. const auto& lorigin = attractor_origin;
  409. v3f ldir = attractor_direction;
  410. ldir.normalize();
  411. auto origin_to_point = pp.pos - lorigin;
  412. auto scalar_projection = origin_to_point.dotProduct(ldir);
  413. auto point_on_line = lorigin + (ldir * scalar_projection);
  414. dist = pp.pos.getDistanceFrom(point_on_line);
  415. dir = (point_on_line - pp.pos);
  416. dir.normalize();
  417. dir *= -1; // flip it around so strength=1 attracts, not repulses
  418. break;
  419. }
  420. case ParticleParamTypes::AttractorKind::plane: {
  421. // https://github.com/minetest/minetest/issues/11505#issuecomment-915612700
  422. const v3f& porigin = attractor_origin;
  423. v3f normal = attractor_direction;
  424. normal.normalize();
  425. v3f point_to_origin = porigin - pp.pos;
  426. f32 factor = normal.dotProduct(point_to_origin);
  427. if (numericAbsolute(factor) == 0.0f) {
  428. dir = normal;
  429. } else {
  430. factor = numericSign(factor);
  431. dir = normal * factor;
  432. }
  433. dist = numericAbsolute(normal.dotProduct(pp.pos - porigin));
  434. dir *= -1; // flip it around so strength=1 attracts, not repulses
  435. break;
  436. }
  437. }
  438. f32 speedTowards = numericAbsolute(attract) * dist;
  439. v3f avel = dir * speedTowards;
  440. if (attract > 0 && speedTowards > 0) {
  441. avel *= -1;
  442. if (p.attractor_kill) {
  443. // make sure the particle dies after crossing the attractor threshold
  444. f32 timeToCenter = dist / speedTowards;
  445. if (timeToCenter < pp.expirationtime)
  446. pp.expirationtime = timeToCenter;
  447. }
  448. }
  449. pp.vel += avel;
  450. }
  451. p.copyCommon(pp);
  452. ClientParticleTexRef texture;
  453. v2f texpos, texsize;
  454. video::SColor color(0xFFFFFFFF);
  455. if (p.node.getContent() != CONTENT_IGNORE) {
  456. const ContentFeatures &f =
  457. m_particlemanager->m_env->getGameDef()->ndef()->get(p.node);
  458. if (!ParticleManager::getNodeParticleParams(p.node, f, pp, &texture.ref,
  459. texpos, texsize, &color, p.node_tile))
  460. return;
  461. } else {
  462. if (m_texpool.size() == 0)
  463. return;
  464. texture = ClientParticleTexRef(m_texpool[m_texpool.size() == 1 ? 0
  465. : myrand_range(0, m_texpool.size()-1)]);
  466. texpos = v2f(0.0f, 0.0f);
  467. texsize = v2f(1.0f, 1.0f);
  468. if (texture.tex->animated)
  469. pp.animation = texture.tex->animation;
  470. }
  471. // synchronize animation length with particle life if desired
  472. if (pp.animation.type != TAT_NONE) {
  473. // FIXME: this should be moved into a TileAnimationParams class method
  474. if (pp.animation.type == TAT_VERTICAL_FRAMES &&
  475. pp.animation.vertical_frames.length < 0) {
  476. auto& a = pp.animation.vertical_frames;
  477. // we add a tiny extra value to prevent the first frame
  478. // from flickering back on just before the particle dies
  479. a.length = (pp.expirationtime / -a.length) + 0.1;
  480. } else if (pp.animation.type == TAT_SHEET_2D &&
  481. pp.animation.sheet_2d.frame_length < 0) {
  482. auto& a = pp.animation.sheet_2d;
  483. auto frames = a.frames_w * a.frames_h;
  484. auto runtime = (pp.expirationtime / -a.frame_length) + 0.1;
  485. pp.animation.sheet_2d.frame_length = frames / runtime;
  486. }
  487. }
  488. // Allow keeping default random size
  489. if (p.size.start.max > 0.0f || p.size.end.max > 0.0f)
  490. pp.size = r_size.pickWithin();
  491. ++m_active;
  492. m_particlemanager->addParticle(std::make_unique<Particle>(
  493. m_gamedef,
  494. m_player,
  495. env,
  496. pp,
  497. texture,
  498. texpos,
  499. texsize,
  500. color,
  501. this
  502. ));
  503. }
  504. void ParticleSpawner::step(float dtime, ClientEnvironment *env)
  505. {
  506. m_time += dtime;
  507. static thread_local const float radius =
  508. g_settings->getS16("max_block_send_distance") * MAP_BLOCKSIZE;
  509. bool unloaded = false;
  510. const core::matrix4 *attached_absolute_pos_rot_matrix = nullptr;
  511. if (m_attached_id) {
  512. if (GenericCAO *attached = env->getGenericCAO(m_attached_id)) {
  513. attached_absolute_pos_rot_matrix = attached->getAbsolutePosRotMatrix();
  514. } else {
  515. unloaded = true;
  516. }
  517. }
  518. if (p.time != 0) {
  519. // Spawner exists for a predefined timespan
  520. for (auto i = m_spawntimes.begin(); i != m_spawntimes.end(); ) {
  521. if ((*i) <= m_time && p.amount > 0) {
  522. --p.amount;
  523. // Pretend to, but don't actually spawn a particle if it is
  524. // attached to an unloaded object or distant from player.
  525. if (!unloaded)
  526. spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
  527. i = m_spawntimes.erase(i);
  528. } else {
  529. ++i;
  530. }
  531. }
  532. } else {
  533. // Spawner exists for an infinity timespan, spawn on a per-second base
  534. // Skip this step if attached to an unloaded object
  535. if (unloaded)
  536. return;
  537. for (int i = 0; i <= p.amount; i++) {
  538. if (myrand_float() < dtime)
  539. spawnParticle(env, radius, attached_absolute_pos_rot_matrix);
  540. }
  541. }
  542. }
  543. /*
  544. ParticleManager
  545. */
  546. ParticleManager::ParticleManager(ClientEnvironment *env) :
  547. m_env(env)
  548. {}
  549. ParticleManager::~ParticleManager()
  550. {
  551. clearAll();
  552. }
  553. void ParticleManager::step(float dtime)
  554. {
  555. stepParticles (dtime);
  556. stepSpawners (dtime);
  557. }
  558. void ParticleManager::stepSpawners(float dtime)
  559. {
  560. MutexAutoLock lock(m_spawner_list_lock);
  561. for (size_t i = 0; i < m_dying_particle_spawners.size();) {
  562. // the particlespawner owns the textures, so we need to make
  563. // sure there are no active particles before we free it
  564. if (!m_dying_particle_spawners[i]->hasActive()) {
  565. m_dying_particle_spawners[i] = std::move(m_dying_particle_spawners.back());
  566. m_dying_particle_spawners.pop_back();
  567. } else {
  568. ++i;
  569. }
  570. }
  571. for (auto it = m_particle_spawners.begin(); it != m_particle_spawners.end();) {
  572. auto &ps = it->second;
  573. if (ps->getExpired()) {
  574. // same as above
  575. if (ps->hasActive())
  576. m_dying_particle_spawners.push_back(std::move(ps));
  577. it = m_particle_spawners.erase(it);
  578. } else {
  579. ps->step(dtime, m_env);
  580. ++it;
  581. }
  582. }
  583. }
  584. void ParticleManager::stepParticles(float dtime)
  585. {
  586. MutexAutoLock lock(m_particle_list_lock);
  587. for (size_t i = 0; i < m_particles.size();) {
  588. Particle &p = *m_particles[i];
  589. if (p.isExpired()) {
  590. ParticleSpawner *parent = p.getParent();
  591. if (parent) {
  592. assert(parent->hasActive());
  593. parent->decrActive();
  594. }
  595. // remove scene node
  596. p.remove();
  597. // delete
  598. m_particles[i] = std::move(m_particles.back());
  599. m_particles.pop_back();
  600. } else {
  601. p.step(dtime);
  602. ++i;
  603. }
  604. }
  605. }
  606. void ParticleManager::clearAll()
  607. {
  608. MutexAutoLock lock(m_spawner_list_lock);
  609. MutexAutoLock lock2(m_particle_list_lock);
  610. // clear particle spawners
  611. m_particle_spawners.clear();
  612. m_dying_particle_spawners.clear();
  613. // clear particles
  614. for (std::unique_ptr<Particle> &p : m_particles) {
  615. // remove scene node
  616. p->remove();
  617. // delete
  618. p.reset();
  619. }
  620. m_particles.clear();
  621. }
  622. void ParticleManager::handleParticleEvent(ClientEvent *event, Client *client,
  623. LocalPlayer *player)
  624. {
  625. switch (event->type) {
  626. case CE_DELETE_PARTICLESPAWNER: {
  627. deleteParticleSpawner(event->delete_particlespawner.id);
  628. // no allocated memory in delete event
  629. break;
  630. }
  631. case CE_ADD_PARTICLESPAWNER: {
  632. deleteParticleSpawner(event->add_particlespawner.id);
  633. const ParticleSpawnerParameters &p = *event->add_particlespawner.p;
  634. // texture pool
  635. std::vector<ClientParticleTexture> texpool;
  636. if (!p.texpool.empty()) {
  637. size_t txpsz = p.texpool.size();
  638. texpool.reserve(txpsz);
  639. for (size_t i = 0; i < txpsz; ++i) {
  640. texpool.emplace_back(p.texpool[i], client->tsrc());
  641. }
  642. } else {
  643. // no texpool in use, use fallback texture
  644. texpool.emplace_back(p.texture, client->tsrc());
  645. }
  646. addParticleSpawner(event->add_particlespawner.id,
  647. std::make_unique<ParticleSpawner>(
  648. client,
  649. player,
  650. p,
  651. event->add_particlespawner.attached_id,
  652. std::move(texpool),
  653. this)
  654. );
  655. delete event->add_particlespawner.p;
  656. break;
  657. }
  658. case CE_SPAWN_PARTICLE: {
  659. ParticleParameters &p = *event->spawn_particle;
  660. ClientParticleTexRef texture;
  661. std::unique_ptr<ClientParticleTexture> texstore;
  662. v2f texpos, texsize;
  663. video::SColor color(0xFFFFFFFF);
  664. f32 oldsize = p.size;
  665. if (p.node.getContent() != CONTENT_IGNORE) {
  666. const ContentFeatures &f = m_env->getGameDef()->ndef()->get(p.node);
  667. getNodeParticleParams(p.node, f, p, &texture.ref, texpos,
  668. texsize, &color, p.node_tile);
  669. } else {
  670. /* with no particlespawner to own the texture, we need
  671. * to save it on the heap. it will be freed when the
  672. * particle is destroyed */
  673. texstore = std::make_unique<ClientParticleTexture>(p.texture, client->tsrc());
  674. texture = ClientParticleTexRef(*texstore);
  675. texpos = v2f(0.0f, 0.0f);
  676. texsize = v2f(1.0f, 1.0f);
  677. }
  678. // Allow keeping default random size
  679. if (oldsize > 0.0f)
  680. p.size = oldsize;
  681. if (texture.ref) {
  682. addParticle(std::make_unique<Particle>(client, player, m_env,
  683. p, texture, texpos, texsize, color, nullptr,
  684. std::move(texstore)));
  685. }
  686. delete event->spawn_particle;
  687. break;
  688. }
  689. default: break;
  690. }
  691. }
  692. bool ParticleManager::getNodeParticleParams(const MapNode &n,
  693. const ContentFeatures &f, ParticleParameters &p, video::ITexture **texture,
  694. v2f &texpos, v2f &texsize, video::SColor *color, u8 tilenum)
  695. {
  696. // No particles for "airlike" nodes
  697. if (f.drawtype == NDT_AIRLIKE)
  698. return false;
  699. // Texture
  700. u8 texid;
  701. if (tilenum > 0 && tilenum <= 6)
  702. texid = tilenum - 1;
  703. else
  704. texid = myrand_range(0,5);
  705. const TileLayer &tile = f.tiles[texid].layers[0];
  706. p.animation.type = TAT_NONE;
  707. // Only use first frame of animated texture
  708. if (tile.material_flags & MATERIAL_FLAG_ANIMATION)
  709. *texture = (*tile.frames)[0].texture;
  710. else
  711. *texture = tile.texture;
  712. float size = (myrand_range(0,8)) / 64.0f;
  713. p.size = BS * size;
  714. if (tile.scale)
  715. size /= tile.scale;
  716. texsize = v2f(size * 2.0f, size * 2.0f);
  717. texpos.X = (myrand_range(0,64)) / 64.0f - texsize.X;
  718. texpos.Y = (myrand_range(0,64)) / 64.0f - texsize.Y;
  719. if (tile.has_color)
  720. *color = tile.color;
  721. else
  722. n.getColor(f, color);
  723. return true;
  724. }
  725. // The final burst of particles when a node is finally dug, *not* particles
  726. // spawned during the digging of a node.
  727. void ParticleManager::addDiggingParticles(IGameDef *gamedef,
  728. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  729. {
  730. // No particles for "airlike" nodes
  731. if (f.drawtype == NDT_AIRLIKE)
  732. return;
  733. for (u16 j = 0; j < 16; j++) {
  734. addNodeParticle(gamedef, player, pos, n, f);
  735. }
  736. }
  737. // During the digging of a node particles are spawned individually by this
  738. // function, called from Game::handleDigging() in game.cpp.
  739. void ParticleManager::addNodeParticle(IGameDef *gamedef,
  740. LocalPlayer *player, v3s16 pos, const MapNode &n, const ContentFeatures &f)
  741. {
  742. ParticleParameters p;
  743. video::ITexture *ref = nullptr;
  744. v2f texpos, texsize;
  745. video::SColor color;
  746. if (!getNodeParticleParams(n, f, p, &ref, texpos, texsize, &color))
  747. return;
  748. p.expirationtime = myrand_range(0, 100) / 100.0f;
  749. // Physics
  750. p.vel = v3f(
  751. myrand_range(-1.5f,1.5f),
  752. myrand_range(0.f,3.f),
  753. myrand_range(-1.5f,1.5f)
  754. );
  755. p.acc = v3f(
  756. 0.0f,
  757. -player->movement_gravity * player->physics_override.gravity / BS,
  758. 0.0f
  759. );
  760. p.pos = v3f(
  761. (f32)pos.X + myrand_range(0.f, .5f) - .25f,
  762. (f32)pos.Y + myrand_range(0.f, .5f) - .25f,
  763. (f32)pos.Z + myrand_range(0.f, .5f) - .25f
  764. );
  765. addParticle(std::make_unique<Particle>(
  766. gamedef,
  767. player,
  768. m_env,
  769. p,
  770. ClientParticleTexRef(ref),
  771. texpos,
  772. texsize,
  773. color));
  774. }
  775. void ParticleManager::reserveParticleSpace(size_t max_estimate)
  776. {
  777. MutexAutoLock lock(m_particle_list_lock);
  778. m_particles.reserve(m_particles.size() + max_estimate);
  779. }
  780. void ParticleManager::addParticle(std::unique_ptr<Particle> toadd)
  781. {
  782. MutexAutoLock lock(m_particle_list_lock);
  783. m_particles.push_back(std::move(toadd));
  784. }
  785. void ParticleManager::addParticleSpawner(u64 id, std::unique_ptr<ParticleSpawner> toadd)
  786. {
  787. MutexAutoLock lock(m_spawner_list_lock);
  788. auto &slot = m_particle_spawners[id];
  789. if (slot) {
  790. // do not kill spawners here. children are still alive
  791. errorstream << "ParticleManager: Failed to add spawner with id " << id
  792. << ". Id already in use." << std::endl;
  793. return;
  794. }
  795. slot = std::move(toadd);
  796. }
  797. void ParticleManager::deleteParticleSpawner(u64 id)
  798. {
  799. MutexAutoLock lock(m_spawner_list_lock);
  800. auto it = m_particle_spawners.find(id);
  801. if (it != m_particle_spawners.end()) {
  802. m_dying_particle_spawners.push_back(std::move(it->second));
  803. m_particle_spawners.erase(it);
  804. }
  805. }