custom_particle_system.cpp 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. // SuperTux
  2. // Copyright (C) 2020 A. Semphris <semphris@protonmail.com>
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. #include "object/custom_particle_system.hpp"
  17. #include <assert.h>
  18. #include <math.h>
  19. #include "collision/collision.hpp"
  20. #include "editor/particle_editor.hpp"
  21. #include "gui/menu_manager.hpp"
  22. #include "math/aatriangle.hpp"
  23. #include "math/easing.hpp"
  24. #include "math/random.hpp"
  25. #include "math/util.hpp"
  26. #include "object/camera.hpp"
  27. #include "object/tilemap.hpp"
  28. #include "supertux/fadetoblack.hpp"
  29. #include "supertux/game_session.hpp"
  30. #include "supertux/screen_manager.hpp"
  31. #include "supertux/sector.hpp"
  32. #include "supertux/tile.hpp"
  33. #include "util/reader.hpp"
  34. #include "util/reader_document.hpp"
  35. #include "util/reader_mapping.hpp"
  36. #include "video/drawing_context.hpp"
  37. #include "video/surface.hpp"
  38. #include "video/surface_batch.hpp"
  39. #include "video/video_system.hpp"
  40. #include "video/viewport.hpp"
  41. CustomParticleSystem::CustomParticleSystem() :
  42. ExposedObject<CustomParticleSystem, scripting::CustomParticles>(this),
  43. texture_sum_odds(0.f),
  44. time_last_remaining(0.f),
  45. script_easings(),
  46. m_textures(),
  47. custom_particles(),
  48. m_particle_main_texture("/images/engine/editor/particle.png"),
  49. m_max_amount(25),
  50. m_delay(0.1f),
  51. m_particle_lifetime(5.f),
  52. m_particle_lifetime_variation(0.f),
  53. m_particle_birth_time(0.f),
  54. m_particle_birth_time_variation(0.f),
  55. m_particle_death_time(0.f),
  56. m_particle_death_time_variation(0.f),
  57. m_particle_birth_mode(),
  58. m_particle_death_mode(),
  59. m_particle_birth_easing(),
  60. m_particle_death_easing(),
  61. m_particle_speed_x(0.f),
  62. m_particle_speed_y(0.f),
  63. m_particle_speed_variation_x(0.f),
  64. m_particle_speed_variation_y(0.f),
  65. m_particle_acceleration_x(0.f),
  66. m_particle_acceleration_y(0.f),
  67. m_particle_friction_x(0.f),
  68. m_particle_friction_y(0.f),
  69. m_particle_feather_factor(0.f),
  70. m_particle_rotation(0.f),
  71. m_particle_rotation_variation(0.f),
  72. m_particle_rotation_speed(0.f),
  73. m_particle_rotation_speed_variation(0.f),
  74. m_particle_rotation_acceleration(0.f),
  75. m_particle_rotation_decceleration(0.f),
  76. m_particle_rotation_mode(),
  77. m_particle_collision_mode(),
  78. m_particle_offscreen_mode(),
  79. m_cover_screen(true)
  80. {
  81. reinit_textures();
  82. }
  83. CustomParticleSystem::CustomParticleSystem(const ReaderMapping& reader) :
  84. ParticleSystem_Interactive(reader),
  85. ExposedObject<CustomParticleSystem, scripting::CustomParticles>(this),
  86. texture_sum_odds(0.f),
  87. time_last_remaining(0.f),
  88. script_easings(),
  89. m_textures(),
  90. custom_particles(),
  91. m_particle_main_texture("/images/engine/editor/particle.png"),
  92. m_max_amount(25),
  93. m_delay(0.1f),
  94. m_particle_lifetime(5.f),
  95. m_particle_lifetime_variation(0.f),
  96. m_particle_birth_time(0.f),
  97. m_particle_birth_time_variation(0.f),
  98. m_particle_death_time(0.f),
  99. m_particle_death_time_variation(0.f),
  100. m_particle_birth_mode(),
  101. m_particle_death_mode(),
  102. m_particle_birth_easing(),
  103. m_particle_death_easing(),
  104. m_particle_speed_x(0.f),
  105. m_particle_speed_y(0.f),
  106. m_particle_speed_variation_x(0.f),
  107. m_particle_speed_variation_y(0.f),
  108. m_particle_acceleration_x(0.f),
  109. m_particle_acceleration_y(0.f),
  110. m_particle_friction_x(0.f),
  111. m_particle_friction_y(0.f),
  112. m_particle_feather_factor(0.f),
  113. m_particle_rotation(0.f),
  114. m_particle_rotation_variation(0.f),
  115. m_particle_rotation_speed(0.f),
  116. m_particle_rotation_speed_variation(0.f),
  117. m_particle_rotation_acceleration(0.f),
  118. m_particle_rotation_decceleration(0.f),
  119. m_particle_rotation_mode(),
  120. m_particle_collision_mode(),
  121. m_particle_offscreen_mode(),
  122. m_cover_screen(true)
  123. {
  124. reader.get("main-texture", m_particle_main_texture, "/images/engine/editor/particle.png");
  125. // FIXME: Is there a cleaner way to get a list of textures?
  126. auto iter = reader.get_iter();
  127. while (iter.next())
  128. {
  129. if (iter.get_key() == "texture")
  130. {
  131. ReaderMapping mapping = iter.as_mapping();
  132. std::string tex;
  133. if (!mapping.get("surface", tex))
  134. {
  135. log_warning << "Texture without surface data ('surface') in " <<
  136. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  137. continue;
  138. }
  139. float color_r, color_g, color_b, color_a;
  140. if (!mapping.get("color_r", color_r))
  141. {
  142. log_warning << "Texture without red color field ('color_r') in " <<
  143. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  144. continue;
  145. }
  146. if (!mapping.get("color_g", color_g))
  147. {
  148. log_warning << "Texture without green color field ('color_g') in " <<
  149. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  150. continue;
  151. }
  152. if (!mapping.get("color_b", color_b))
  153. {
  154. log_warning << "Texture without blue color field ('color_b') in " <<
  155. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  156. continue;
  157. }
  158. if (!mapping.get("color_a", color_a))
  159. {
  160. log_warning << "Texture without alpha channel field ('color_a') in " <<
  161. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  162. continue;
  163. }
  164. float likeliness;
  165. if (!mapping.get("likeliness", likeliness))
  166. {
  167. log_warning << "Texture without likeliness field in " <<
  168. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  169. continue;
  170. }
  171. float scale_x, scale_y;
  172. if (!mapping.get("scale_x", scale_x))
  173. {
  174. log_warning << "Texture without horizontal scale ('scale_x') field in " <<
  175. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  176. continue;
  177. }
  178. if (!mapping.get("scale_y", scale_y))
  179. {
  180. log_warning << "Texture without vertical scale ('scale_y') field in " <<
  181. mapping.get_doc().get_filename() << ", skipping" << std::endl;
  182. continue;
  183. }
  184. auto props = SpriteProperties(Surface::from_file(tex));
  185. props.likeliness = likeliness;
  186. props.color = Color(color_r, color_g, color_b, color_a);
  187. props.scale = Vector(scale_x, scale_y);
  188. m_textures.push_back(props);
  189. }
  190. }
  191. reader.get("amount", m_max_amount, 25);
  192. reader.get("delay", m_delay, 0.1f);
  193. reader.get("lifetime", m_particle_lifetime, 5.f);
  194. reader.get("lifetime-variation", m_particle_lifetime_variation, 0.f);
  195. reader.get("birth-time", m_particle_birth_time, 0.f);
  196. reader.get("birth-time-variation", m_particle_birth_time_variation, 0.f);
  197. reader.get("death-time", m_particle_death_time, 0.f);
  198. reader.get("death-time-variation", m_particle_death_time_variation, 0.f);
  199. reader.get("speed-x", m_particle_speed_x, 0.f);
  200. reader.get("speed-y", m_particle_speed_y, 0.f);
  201. reader.get("speed-var-x", m_particle_speed_variation_x, 0.f);
  202. reader.get("speed-var-y", m_particle_speed_variation_y, 0.f);
  203. reader.get("acceleration-x", m_particle_acceleration_x, 0.f);
  204. reader.get("acceleration-y", m_particle_acceleration_y, 0.f);
  205. reader.get("friction-x", m_particle_friction_x, 0.f);
  206. reader.get("friction-y", m_particle_friction_y, 0.f);
  207. reader.get("feather-factor", m_particle_feather_factor, 0.f);
  208. reader.get("rotation", m_particle_rotation, 0.f);
  209. reader.get("rotation-variation", m_particle_rotation_variation, 0.f);
  210. reader.get("rotation-speed", m_particle_rotation_speed, 0.f);
  211. reader.get("rotation-speed-variation", m_particle_rotation_speed_variation, 0.f);
  212. reader.get("rotation-acceleration", m_particle_rotation_acceleration, 0.f);
  213. reader.get("rotation-decceleration", m_particle_rotation_decceleration, 0.f);
  214. reader.get("cover-screen", m_cover_screen, true);
  215. std::string rotation_mode;
  216. if (reader.get("rotation-mode", rotation_mode))
  217. {
  218. if (rotation_mode == "wiggling")
  219. {
  220. m_particle_rotation_mode = RotationMode::Wiggling;
  221. }
  222. else if (rotation_mode == "facing")
  223. {
  224. m_particle_rotation_mode = RotationMode::Facing;
  225. }
  226. else
  227. {
  228. m_particle_rotation_mode = RotationMode::Fixed;
  229. }
  230. }
  231. else
  232. {
  233. m_particle_rotation_mode = RotationMode::Fixed;
  234. }
  235. std::string birth_mode;
  236. if (reader.get("birth-mode", birth_mode))
  237. {
  238. if (birth_mode == "shrink")
  239. {
  240. m_particle_birth_mode = FadeMode::Shrink;
  241. }
  242. else if (birth_mode == "fade")
  243. {
  244. m_particle_birth_mode = FadeMode::Fade;
  245. }
  246. else
  247. {
  248. m_particle_birth_mode = FadeMode::None;
  249. }
  250. }
  251. else
  252. {
  253. m_particle_birth_mode = FadeMode::None;
  254. }
  255. std::string death_mode;
  256. if (reader.get("death-mode", death_mode))
  257. {
  258. if (death_mode == "shrink")
  259. {
  260. m_particle_death_mode = FadeMode::Shrink;
  261. }
  262. else if (death_mode == "fade")
  263. {
  264. m_particle_death_mode = FadeMode::Fade;
  265. }
  266. else
  267. {
  268. m_particle_death_mode = FadeMode::None;
  269. }
  270. }
  271. else
  272. {
  273. m_particle_death_mode = FadeMode::None;
  274. }
  275. std::string birth_easing;
  276. if (reader.get("birth-easing", birth_easing))
  277. {
  278. m_particle_birth_easing = EasingMode_from_string(birth_easing);
  279. }
  280. else
  281. {
  282. m_particle_birth_easing = EasingMode_from_string("");
  283. }
  284. std::string death_easing;
  285. if (reader.get("death-easing", death_easing))
  286. {
  287. m_particle_death_easing = EasingMode_from_string(death_easing);
  288. }
  289. else
  290. {
  291. m_particle_death_easing = EasingMode_from_string("");
  292. }
  293. std::string collision_mode;
  294. if (reader.get("collision-mode", collision_mode))
  295. {
  296. if (collision_mode == "stick")
  297. {
  298. m_particle_collision_mode = CollisionMode::Stick;
  299. }
  300. else if (collision_mode == "stick-forever")
  301. {
  302. m_particle_collision_mode = CollisionMode::StickForever;
  303. }
  304. else if (collision_mode == "bounce-heavy")
  305. {
  306. m_particle_collision_mode = CollisionMode::BounceHeavy;
  307. }
  308. else if (collision_mode == "bounce-light")
  309. {
  310. m_particle_collision_mode = CollisionMode::BounceLight;
  311. }
  312. else if (collision_mode == "destroy")
  313. {
  314. m_particle_collision_mode = CollisionMode::Destroy;
  315. }
  316. else if (collision_mode == "fade-out")
  317. {
  318. m_particle_collision_mode = CollisionMode::FadeOut;
  319. }
  320. else
  321. {
  322. m_particle_collision_mode = CollisionMode::Ignore;
  323. }
  324. }
  325. else
  326. {
  327. m_particle_collision_mode = CollisionMode::Ignore;
  328. }
  329. std::string offscreen_mode;
  330. if (reader.get("offscreen-mode", offscreen_mode))
  331. {
  332. if (offscreen_mode == "always")
  333. {
  334. m_particle_offscreen_mode = OffscreenMode::Always;
  335. }
  336. else if (offscreen_mode == "only-on-exit")
  337. {
  338. m_particle_offscreen_mode = OffscreenMode::OnlyOnExit;
  339. }
  340. else
  341. {
  342. m_particle_offscreen_mode = OffscreenMode::Never;
  343. }
  344. }
  345. else
  346. {
  347. m_particle_offscreen_mode = OffscreenMode::Never;
  348. }
  349. reinit_textures();
  350. }
  351. CustomParticleSystem::~CustomParticleSystem()
  352. {
  353. }
  354. void
  355. CustomParticleSystem::reinit_textures()
  356. {
  357. if (!m_textures.size())
  358. {
  359. auto props = SpriteProperties(Surface::from_file(m_particle_main_texture));
  360. props.likeliness = 1.f;
  361. props.color = Color(1.f, 1.f, 1.f, 1.f);
  362. props.scale = Vector(1.f, 1.f);
  363. m_textures.push_back(props);
  364. }
  365. texture_sum_odds = 0.f;
  366. for (const auto& texture : m_textures)
  367. {
  368. texture_sum_odds += texture.likeliness;
  369. }
  370. }
  371. void
  372. CustomParticleSystem::save(Writer& writer)
  373. {
  374. for (const auto& tex : m_textures)
  375. {
  376. writer.start_list("texture");
  377. writer.write("surface", tex.texture->get_filename());
  378. writer.write("color_r", tex.color.red);
  379. writer.write("color_g", tex.color.green);
  380. writer.write("color_b", tex.color.blue);
  381. writer.write("color_a", tex.color.alpha);
  382. writer.write("likeliness", tex.likeliness);
  383. writer.write("scale_x", tex.scale.x);
  384. writer.write("scale_y", tex.scale.y);
  385. writer.end_list("texture");
  386. }
  387. GameObject::save(writer);
  388. }
  389. ObjectSettings
  390. CustomParticleSystem::get_settings()
  391. {
  392. ObjectSettings result = ParticleSystem::get_settings();
  393. result.add_surface(_("Texture"), &m_particle_main_texture, "main-texture");
  394. result.add_int(_("Amount"), &m_max_amount, "amount", 25);
  395. result.add_float(_("Delay"), &m_delay, "delay", 0.1f);
  396. result.add_float(_("Lifetime"), &m_particle_lifetime, "lifetime", 5.f);
  397. result.add_float(_("Lifetime variation"), &m_particle_lifetime_variation, "lifetime-variation", 0.f);
  398. result.add_enum(_("Birth mode"), reinterpret_cast<int*>(&m_particle_birth_mode),
  399. {_("None"), _("Fade"), _("Shrink")},
  400. {"none", "fade", "shrink"},
  401. static_cast<int>(FadeMode::None),
  402. "birth-mode");
  403. result.add_enum(_("Birth easing"), reinterpret_cast<int*>(&m_particle_birth_easing),
  404. {
  405. _("No easing"),
  406. _("Quad in"), _("Quad out"), _("Quad in/out"),
  407. _("Cubic in"), _("Cubic out"), _("Cubic in/out"),
  408. _("Quart in"), _("Quart out"), _("Quart in/out"),
  409. _("Quint in"), _("Quint out"), _("Quint in/out"),
  410. _("Sine in"), _("Sine out"), _("Sine in/out"),
  411. _("Circular in"), _("Circular out"), _("Circular in/out"),
  412. _("Exponential in"), _("Exponential out"), _("Exponential in/out"),
  413. _("Elastic in"), _("Elastic out"), _("Elastic in/out"),
  414. _("Back in"), _("Back out"), _("Back in/out"),
  415. _("Bounce in"), _("Bounce out"), _("Bounce in/out")
  416. },
  417. {
  418. "EaseNone",
  419. "EaseQuadIn", "EaseQuadOut", "EaseQuadInOut",
  420. "EaseCubicIn", "EaseCubicOut", "EaseCubicInOut",
  421. "EaseQuartIn", "EaseQuartOut", "EaseQuartInOut",
  422. "EaseQuintIn", "EaseQuintOut", "EaseQuintInOut",
  423. "EaseSineIn", "EaseSineOut", "EaseSineInOut",
  424. "EaseCircularIn", "EaseCircularOut", "EaseCircularInOut",
  425. "EaseExponentialIn", "EaseExponentialOut", "EaseExponentialInOut",
  426. "EaseElasticIn", "EaseElasticOut", "EaseElasticInOut",
  427. "EaseBackIn", "EaseBackOut", "EaseBackInOut",
  428. "EaseBounceIn", "EaseBounceOut", "EaseBounceInOut"
  429. },
  430. 0, "birth-easing");
  431. result.add_float(_("Birth time"), &m_particle_birth_time, "birth-time", 5.f);
  432. result.add_float(_("Birth time variation"), &m_particle_birth_time_variation, "birth-time-variation", 0.f);
  433. result.add_enum(_("Death mode"), reinterpret_cast<int*>(&m_particle_death_mode),
  434. {_("None"), _("Fade"), _("Shrink")},
  435. {"none", "fade", "shrink"},
  436. static_cast<int>(FadeMode::None),
  437. "death-mode");
  438. result.add_enum(_("Death easing"), reinterpret_cast<int*>(&m_particle_death_easing),
  439. {
  440. _("No easing"),
  441. _("Quad in"), _("Quad out"), _("Quad in/out"),
  442. _("Cubic in"), _("Cubic out"), _("Cubic in/out"),
  443. _("Quart in"), _("Quart out"), _("Quart in/out"),
  444. _("Quint in"), _("Quint out"), _("Quint in/out"),
  445. _("Sine in"), _("Sine out"), _("Sine in/out"),
  446. _("Circular in"), _("Circular out"), _("Circular in/out"),
  447. _("Exponential in"), _("Exponential out"), _("Exponential in/out"),
  448. _("Elastic in"), _("Elastic out"), _("Elastic in/out"),
  449. _("Back in"), _("Back out"), _("Back in/out"),
  450. _("Bounce in"), _("Bounce out"), _("Bounce in/out")
  451. },
  452. {
  453. "EaseNone",
  454. "EaseQuadIn", "EaseQuadOut", "EaseQuadInOut",
  455. "EaseCubicIn", "EaseCubicOut", "EaseCubicInOut",
  456. "EaseQuartIn", "EaseQuartOut", "EaseQuartInOut",
  457. "EaseQuintIn", "EaseQuintOut", "EaseQuintInOut",
  458. "EaseSineIn", "EaseSineOut", "EaseSineInOut",
  459. "EaseCircularIn", "EaseCircularOut", "EaseCircularInOut",
  460. "EaseExponentialIn", "EaseExponentialOut", "EaseExponentialInOut",
  461. "EaseElasticIn", "EaseElasticOut", "EaseElasticInOut",
  462. "EaseBackIn", "EaseBackOut", "EaseBackInOut",
  463. "EaseBounceIn", "EaseBounceOut", "EaseBounceInOut"
  464. },
  465. 0, "death-easing");
  466. result.add_float(_("Death time"), &m_particle_death_time, "death-time", 5.f);
  467. result.add_float(_("Death time variation"), &m_particle_death_time_variation, "death-time-variation", 0.f);
  468. result.add_float(_("Speed X"), &m_particle_speed_x, "speed-x", 0.f);
  469. result.add_float(_("Speed Y"), &m_particle_speed_y, "speed-y", 0.f);
  470. result.add_float(_("Speed X (variation)"), &m_particle_speed_variation_x, "speed-var-x", 0.f);
  471. result.add_float(_("Speed Y (variation)"), &m_particle_speed_variation_y, "speed-var-y", 0.f);
  472. result.add_float(_("Acceleration X"), &m_particle_acceleration_x, "acceleration-x", 0.f);
  473. result.add_float(_("Acceleration Y"), &m_particle_acceleration_y, "acceleration-y", 0.f);
  474. result.add_float(_("Friction X"), &m_particle_friction_x, "friction-x", 0.f);
  475. result.add_float(_("Friction Y"), &m_particle_friction_y, "friction-y", 0.f);
  476. result.add_float(_("Feather factor"), &m_particle_feather_factor, "feather-factor", 0.f);
  477. result.add_float(_("Rotation"), &m_particle_rotation, "rotation", 0.f);
  478. result.add_float(_("Rotation (variation)"), &m_particle_rotation_variation, "rotation-variation", 0.f);
  479. result.add_float(_("Rotation speed"), &m_particle_rotation_speed, "rotation-speed", 0.f);
  480. result.add_float(_("Rotation speed (variation)"), &m_particle_rotation_speed_variation, "rotation-speed-variation", 0.f);
  481. result.add_float(_("Rotation acceleration"), &m_particle_rotation_acceleration, "rotation-acceleration", 0.f);
  482. result.add_float(_("Rotation friction"), &m_particle_rotation_decceleration, "rotation-decceleration", 0.f);
  483. result.add_enum(_("Rotation mode"), reinterpret_cast<int*>(&m_particle_rotation_mode),
  484. {_("Fixed"), _("Facing"), _("Wiggling")},
  485. {"fixed", "facing", "wiggling"},
  486. static_cast<int>(RotationMode::Fixed),
  487. "rotation-mode");
  488. result.add_enum(_("Collision mode"), reinterpret_cast<int*>(&m_particle_collision_mode),
  489. {_("None (pass through)"), _("Stick"), _("Stick Forever"), _("Bounce (heavy)"), _("Bounce (light)"), _("Kill particle"), _("Fade out particle")},
  490. {"ignore", "stick", "stick-forever", "bounce-heavy", "bounce-light", "destroy", "fade-out"},
  491. static_cast<int>(CollisionMode::Ignore),
  492. "collision-mode");
  493. result.add_enum(_("Delete if off-screen"), reinterpret_cast<int*>(&m_particle_offscreen_mode),
  494. {_("Never"), _("Only on exit"), _("Always")},
  495. {"never", "only-on-exit", "always"},
  496. static_cast<int>(OffscreenMode::Never),
  497. "offscreen-mode");
  498. result.add_bool(_("Cover screen"), &m_cover_screen, "cover-screen", true);
  499. //result.reorder({"amount", "delay", "lifetime", "lifetime-variation", "enabled", "name"});
  500. result.add_particle_editor();
  501. return result;
  502. }
  503. void
  504. CustomParticleSystem::update(float dt_sec)
  505. {
  506. // The "enabled" flag being false only indicates that new particles shouldn't spawn.
  507. // However, we still need to update the existing particles, if any.
  508. // Handle script-based easings.
  509. for (auto& req : script_easings)
  510. {
  511. req.time_remain -= dt_sec;
  512. if (req.time_remain <= 0)
  513. {
  514. req.time_remain = 0;
  515. *(req.value) = req.end;
  516. }
  517. else
  518. {
  519. float progress = 1.f - (req.time_remain / req.time_total);
  520. progress = static_cast<float>(req.func(static_cast<double>(progress)));
  521. *(req.value) = req.begin + progress * (req.end - req.begin);
  522. }
  523. }
  524. // Update existing particles.
  525. for (auto& it : custom_particles) {
  526. auto particle = dynamic_cast<CustomParticle*>(it.get());
  527. assert(particle);
  528. if (particle->birth_time > dt_sec) {
  529. switch(particle->birth_mode) {
  530. case FadeMode::Shrink:
  531. particle->scale = static_cast<float>(
  532. getEasingByName(particle->birth_easing)(
  533. static_cast<double>(
  534. 1.f - (particle->birth_time / particle->total_birth)
  535. )
  536. ));
  537. break;
  538. case FadeMode::Fade:
  539. particle->props = SpriteProperties(particle->original_props,
  540. 1.f - (particle->birth_time /
  541. particle->total_birth));
  542. break;
  543. default:
  544. break;
  545. }
  546. particle->birth_time -= dt_sec;
  547. } else if (particle->birth_time > 0.f) {
  548. particle->birth_time = 0.f;
  549. switch(particle->birth_mode) {
  550. case FadeMode::Shrink:
  551. particle->scale = 1.f;
  552. break;
  553. case FadeMode::Fade:
  554. particle->props = particle->original_props;
  555. break;
  556. default:
  557. break;
  558. }
  559. }
  560. particle->lifetime -= dt_sec;
  561. if (particle->lifetime < 0.f) {
  562. particle->lifetime = 0.f;
  563. }
  564. if (particle->birth_time <= 0.f && particle->lifetime <= 0.f) {
  565. if (particle->death_time > dt_sec) {
  566. switch(particle->death_mode) {
  567. case FadeMode::Shrink:
  568. particle->scale = 1.f - static_cast<float>(
  569. getEasingByName(particle->death_easing)(
  570. static_cast<double>(
  571. 1.f - (particle->death_time / particle->total_death)
  572. )
  573. ));
  574. break;
  575. case FadeMode::Fade:
  576. particle->props = SpriteProperties(particle->original_props,
  577. (particle->death_time /
  578. particle->total_death));
  579. break;
  580. default:
  581. break;
  582. }
  583. particle->death_time -= dt_sec;
  584. } else {
  585. particle->death_time = 0.f;
  586. switch(particle->death_mode) {
  587. case FadeMode::Shrink:
  588. particle->scale = 0.f;
  589. break;
  590. case FadeMode::Fade:
  591. particle->props = SpriteProperties(particle->original_props, 0.f);
  592. break;
  593. default:
  594. break;
  595. }
  596. particle->ready_for_deletion = true;
  597. }
  598. }
  599. float abs_x = get_abs_x();
  600. float abs_y = get_abs_y();
  601. if (!particle->has_been_on_screen) {
  602. if (particle->pos.y <= static_cast<float>(SCREEN_HEIGHT) + abs_y
  603. && particle->pos.y >= abs_y
  604. && particle->pos.x <= static_cast<float>(SCREEN_WIDTH) + abs_x
  605. && particle->pos.x >= abs_x) {
  606. particle->has_been_on_screen = true;
  607. }
  608. }
  609. switch(particle->offscreen_mode) {
  610. case OffscreenMode::Always:
  611. if (particle->pos.y > static_cast<float>(SCREEN_HEIGHT) + abs_y
  612. || particle->pos.y < abs_y
  613. || particle->pos.x > static_cast<float>(SCREEN_WIDTH) + abs_x
  614. || particle->pos.x < abs_x) {
  615. particle->ready_for_deletion = true;
  616. }
  617. break;
  618. case OffscreenMode::OnlyOnExit:
  619. if ((particle->pos.y > static_cast<float>(SCREEN_HEIGHT) + abs_y
  620. || particle->pos.y < abs_y
  621. || particle->pos.x > static_cast<float>(SCREEN_WIDTH) + abs_x
  622. || particle->pos.x < abs_x)
  623. && particle->has_been_on_screen) {
  624. particle->ready_for_deletion = true;
  625. }
  626. break;
  627. case OffscreenMode::Never:
  628. break;
  629. }
  630. bool is_in_life_zone = false;
  631. for (auto& zone : get_zones()) {
  632. if (zone.get_rect().contains(particle->pos) && zone.get_particle_name() == m_name) {
  633. switch(zone.get_type()) {
  634. case ParticleZone::ParticleZoneType::Killer:
  635. particle->lifetime = 0.f;
  636. particle->birth_time = 0.f;
  637. break;
  638. case ParticleZone::ParticleZoneType::Destroyer:
  639. particle->ready_for_deletion = true;
  640. break;
  641. case ParticleZone::ParticleZoneType::LifeClear:
  642. particle->last_life_zone_required_instakill = true;
  643. particle->has_been_in_life_zone = true;
  644. is_in_life_zone = true;
  645. break;
  646. case ParticleZone::ParticleZoneType::Life:
  647. particle->last_life_zone_required_instakill = false;
  648. particle->has_been_in_life_zone = true;
  649. is_in_life_zone = true;
  650. break;
  651. // This case is intentionally empty; it serves as a placeholder to prevent a warning.
  652. case ParticleZone::ParticleZoneType::Spawn:
  653. break;
  654. }
  655. }
  656. } // For each ParticleZone object.
  657. if (!is_in_life_zone && particle->has_been_in_life_zone) {
  658. if (particle->last_life_zone_required_instakill) {
  659. particle->ready_for_deletion = true;
  660. } else {
  661. particle->lifetime = 0.f;
  662. particle->birth_time = 0.f;
  663. }
  664. }
  665. if (!particle->stuck) {
  666. particle->speedX += graphicsRandom.randf(-particle->feather_factor,
  667. particle->feather_factor) * dt_sec * 1000.f;
  668. particle->speedY += graphicsRandom.randf(-particle->feather_factor,
  669. particle->feather_factor) * dt_sec * 1000.f;
  670. particle->speedX += particle->accX * dt_sec;
  671. particle->speedY += particle->accY * dt_sec;
  672. particle->speedX *= 1.f - particle->frictionX * dt_sec;
  673. particle->speedY *= 1.f - particle->frictionY * dt_sec;
  674. if (Sector::current() && collision(particle,
  675. Vector(particle->speedX,particle->speedY) * dt_sec) > 0) {
  676. switch(particle->collision_mode) {
  677. case CollisionMode::Ignore:
  678. particle->pos.x += particle->speedX * dt_sec;
  679. particle->pos.y += particle->speedY * dt_sec;
  680. break;
  681. case CollisionMode::Stick:
  682. // Just don't move
  683. break;
  684. case CollisionMode::StickForever:
  685. particle->stuck = true;
  686. break;
  687. case CollisionMode::BounceHeavy:
  688. case CollisionMode::BounceLight:
  689. {
  690. auto c = get_collision(particle, Vector(particle->speedX, particle->speedY) * dt_sec);
  691. float speed_angle = atanf(-particle->speedY / particle->speedX);
  692. if (c.slope_normal.x == 0.f && c.slope_normal.y == 0.f) {
  693. auto cX = get_collision(particle, Vector(particle->speedX, 0) * dt_sec);
  694. if (cX.left != cX.right)
  695. particle->speedX *= -1;
  696. auto cY = get_collision(particle, Vector(0, particle->speedY) * dt_sec);
  697. if (cY.top != cY.bottom)
  698. particle->speedY *= -1;
  699. } else {
  700. float face_angle = atanf(c.slope_normal.y / c.slope_normal.x);
  701. float dest_angle = face_angle * 2.f - speed_angle; // Reflect the angle around face_angle.
  702. float dX = cosf(dest_angle),
  703. dY = sinf(dest_angle);
  704. float true_speed = static_cast<float>(sqrt(pow(particle->speedY, 2)
  705. + pow(particle->speedX, 2)));
  706. particle->speedX = dX * true_speed;
  707. particle->speedY = dY * true_speed;
  708. }
  709. switch(particle->collision_mode) {
  710. case CollisionMode::BounceHeavy:
  711. particle->speedX *= .2f;
  712. particle->speedY *= .2f;
  713. break;
  714. case CollisionMode::BounceLight:
  715. particle->speedX *= .7f;
  716. particle->speedY *= .7f;
  717. break;
  718. default:
  719. assert(false);
  720. }
  721. particle->pos.x += particle->speedX * dt_sec;
  722. particle->pos.y += particle->speedY * dt_sec;
  723. }
  724. break;
  725. case CollisionMode::Destroy:
  726. particle->ready_for_deletion = true;
  727. break;
  728. case CollisionMode::FadeOut:
  729. particle->lifetime = 0.f;
  730. break;
  731. }
  732. } else {
  733. particle->pos.x += particle->speedX * dt_sec;
  734. particle->pos.y += particle->speedY * dt_sec;
  735. }
  736. switch(particle->angle_mode) {
  737. case RotationMode::Facing:
  738. particle->angle = atanf(particle->speedY / particle->speedX) * 180.f / math::PI;
  739. break;
  740. case RotationMode::Wiggling:
  741. particle->angle += graphicsRandom.randf(-particle->angle_speed / 2.f,
  742. particle->angle_speed / 2.f) * dt_sec;
  743. break;
  744. case RotationMode::Fixed:
  745. default:
  746. particle->angle_speed += particle->angle_acc * dt_sec;
  747. particle->angle_speed *= 1.f - particle->angle_decc * dt_sec;
  748. particle->angle += particle->angle_speed * dt_sec;
  749. }
  750. }
  751. } // For each particle.
  752. // Clear dead particles
  753. // We iterate through the vector backwards because removing an element affects
  754. // the index of all elements after it, which can lead to buggy behavior.
  755. for (int i = static_cast<int>(custom_particles.size()) - 1; i >= 0; --i) {
  756. auto particle = dynamic_cast<CustomParticle*>(custom_particles.at(i).get());
  757. if (particle->ready_for_deletion) {
  758. custom_particles.erase(custom_particles.begin()+i);
  759. }
  760. }
  761. // Add necessary particles.
  762. float remaining = dt_sec + time_last_remaining;
  763. if (enabled) {
  764. int real_max = m_max_amount;
  765. if (!m_cover_screen) {
  766. int i = 0;
  767. for (auto& zone : get_zones()) {
  768. if (zone.get_type() == ParticleZone::ParticleZoneType::Spawn && zone.get_particle_name() == m_name) {
  769. i++;
  770. }
  771. }
  772. real_max *= i;
  773. }
  774. while (remaining > m_delay && int(custom_particles.size()) < real_max)
  775. {
  776. spawn_particles(remaining);
  777. remaining -= m_delay;
  778. }
  779. }
  780. // Maxes to m_delay, so that if there's already the max amount of particles,
  781. // it won't store all the time waiting for some particles to go and then
  782. // spawn a bazillion particles instantly. (Bacisally it means : This will
  783. // help guarantee there will be at least m_delay between each particle
  784. // spawn as long as m_delay >= dt_sec).
  785. time_last_remaining = (remaining > m_delay) ? m_delay : remaining;
  786. }
  787. void
  788. CustomParticleSystem::draw(DrawingContext& context)
  789. {
  790. // The "enabled" flag being false only indicates that new particles shouldn't spawn.
  791. // However, we still need to update the existing particles, if any.
  792. context.push_transform();
  793. std::unordered_map<SpriteProperties*, SurfaceBatch> batches;
  794. for (const auto& particle : custom_particles) {
  795. auto it = batches.find(&(particle->props));
  796. if (it == batches.end()) {
  797. const auto& batch_it = batches.emplace(&(particle->props),
  798. SurfaceBatch(particle->props.texture, particle->props.color));
  799. batch_it.first->second.draw(Rectf(Vector(
  800. particle->pos.x - particle->scale
  801. * static_cast<float>(
  802. particle->props.texture->get_width()
  803. ) * particle->props.scale.x / 2,
  804. particle->pos.y - particle->scale
  805. * static_cast<float>(
  806. particle->props.texture->get_height()
  807. ) * particle->props.scale.y / 2
  808. ),
  809. Vector(
  810. particle->pos.x + particle->scale
  811. * static_cast<float>(
  812. particle->props.texture->get_width()
  813. ) * particle->props.scale.x / 2,
  814. particle->pos.y + particle->scale
  815. * static_cast<float>(
  816. particle->props.texture->get_height()
  817. ) * particle->props.scale.y / 2
  818. )
  819. ), particle->angle);
  820. } else {
  821. it->second.draw(Rectf(particle->pos,
  822. Vector(
  823. particle->pos.x + particle->scale
  824. * static_cast<float>(
  825. particle->texture->get_width()
  826. ) * particle->props.scale.x,
  827. particle->pos.y + particle->scale
  828. * static_cast<float>(
  829. particle->texture->get_height()
  830. ) * particle->props.scale.y
  831. )
  832. ), particle->angle);
  833. }
  834. }
  835. for(auto& it : batches) {
  836. auto& surface = it.first->texture;
  837. auto& batch = it.second;
  838. context.color().draw_surface_batch(surface, batch.move_srcrects(),
  839. batch.move_dstrects(), batch.move_angles(), it.first->color, z_pos);
  840. }
  841. context.pop_transform();
  842. }
  843. // Duplicated from ParticleSystem_Interactive because I intend to bring edits
  844. // sometime in the future, for even more flexibility with particles. (Semphris).
  845. int
  846. CustomParticleSystem::collision(Particle* object, const Vector& movement)
  847. {
  848. using namespace collision;
  849. CustomParticle* particle = dynamic_cast<CustomParticle*>(object);
  850. assert(particle);
  851. // Calculate rectangle where the object will move.
  852. float x1, x2;
  853. float y1, y2;
  854. x1 = object->pos.x - particle->props.hb_scale.x * static_cast<float>(particle->props.texture->get_width()) / 2
  855. + particle->props.hb_offset.x * static_cast<float>(particle->props.texture->get_width());
  856. x2 = x1 + particle->props.hb_scale.x * static_cast<float>(particle->props.texture->get_width()) + movement.x;
  857. if (x2 < x1) {
  858. float temp_x = x1;
  859. x1 = x2;
  860. x2 = temp_x;
  861. }
  862. y1 = object->pos.y - particle->props.hb_scale.y * static_cast<float>(particle->props.texture->get_height()) / 2
  863. + particle->props.hb_offset.y * static_cast<float>(particle->props.texture->get_height());
  864. y2 = y1 + particle->props.hb_scale.y * static_cast<float>(particle->props.texture->get_height()) + movement.y;
  865. if (y2 < y1) {
  866. float temp_y = y1;
  867. y1 = y2;
  868. y2 = temp_y;
  869. }
  870. bool water = false;
  871. // Test with all tiles in this rectangle.
  872. int starttilex = int(x1-1) / 32;
  873. int starttiley = int(y1-1) / 32;
  874. int max_x = int(x2+1);
  875. int max_y = int(y2+1);
  876. Rectf dest(x1, y1, x2, y2);
  877. dest.move(movement);
  878. Constraints constraints;
  879. for (const auto& solids : Sector::get().get_solid_tilemaps()) {
  880. // FIXME: Handle a nonzero tilemap offset.
  881. // Check if it gets fixed in particlesystem_interactive.cpp.
  882. for (int x = starttilex; x*32 < max_x; ++x) {
  883. for (int y = starttiley; y*32 < max_y; ++y) {
  884. const Tile& tile = solids->get_tile(x, y);
  885. // Skip non-solid tiles, except water.
  886. if (! (tile.get_attributes() & (Tile::WATER | Tile::SOLID)))
  887. continue;
  888. Rectf rect = solids->get_tile_bbox(x, y);
  889. if (tile.is_slope ()) { // Slope tile.
  890. AATriangle triangle = AATriangle(rect, tile.get_data());
  891. if (rectangle_aatriangle(&constraints, dest, triangle)) {
  892. if (tile.get_attributes() & Tile::WATER)
  893. water = true;
  894. }
  895. } else { // Normal rectangular tile.
  896. if (dest.overlaps(rect)) {
  897. if (tile.get_attributes() & Tile::WATER)
  898. water = true;
  899. set_rectangle_rectangle_constraints(&constraints, dest, rect);
  900. }
  901. }
  902. }
  903. }
  904. }
  905. // TODO: Avoid using magic numbers in this section.
  906. // Check if any constraints exist for collision.
  907. if (!constraints.has_constraints())
  908. return -1;
  909. const CollisionHit& hit = constraints.hit;
  910. if (water) {
  911. return 0; // Collision with water tile - no need to draw splash.
  912. } else {
  913. if (hit.right || hit.left) {
  914. return 2; // Collision from the right.
  915. } else {
  916. return 1; // Collision from above.
  917. }
  918. }
  919. }
  920. CollisionHit
  921. CustomParticleSystem::get_collision(Particle* object, const Vector& movement)
  922. {
  923. using namespace collision;
  924. CustomParticle* particle = dynamic_cast<CustomParticle*>(object);
  925. assert(particle);
  926. // Calculate rectangle where the object will move.
  927. float x1, x2;
  928. float y1, y2;
  929. x1 = object->pos.x - particle->props.scale.x * static_cast<float>(particle->props.texture->get_width()) / 2;
  930. x2 = x1 + particle->props.scale.x * static_cast<float>(particle->props.texture->get_width()) + movement.x;
  931. if (x2 < x1) {
  932. float temp_x = x1;
  933. x1 = x2;
  934. x2 = temp_x;
  935. }
  936. y1 = object->pos.y - particle->props.scale.y * static_cast<float>(particle->props.texture->get_height()) / 2;
  937. y2 = y1 + particle->props.scale.y * static_cast<float>(particle->props.texture->get_height()) + movement.y;
  938. if (y2 < y1) {
  939. float temp_y = y1;
  940. y1 = y2;
  941. y2 = temp_y;
  942. }
  943. // Test with all tiles in this rectangle.
  944. int starttilex = int(x1-1) / 32;
  945. int starttiley = int(y1-1) / 32;
  946. int max_x = int(x2+1);
  947. int max_y = int(y2+1);
  948. Rectf dest(x1, y1, x2, y2);
  949. dest.move(movement);
  950. Constraints constraints;
  951. for (const auto& solids : Sector::get().get_solid_tilemaps()) {
  952. // FIXME: Handle a nonzero tilemap offset.
  953. // Check if it gets fixed in particlesystem_interactive.cpp.
  954. for (int x = starttilex; x*32 < max_x; ++x) {
  955. for (int y = starttiley; y*32 < max_y; ++y) {
  956. const Tile& tile = solids->get_tile(x, y);
  957. // Skip non-solid tiles.
  958. if (! (tile.get_attributes() & (/*Tile::WATER |*/ Tile::SOLID)))
  959. continue;
  960. Rectf rect = solids->get_tile_bbox(x, y);
  961. if (tile.is_slope ()) { // Slope tile.
  962. AATriangle triangle = AATriangle(rect, tile.get_data());
  963. rectangle_aatriangle(&constraints, dest, triangle);
  964. } else { // Normal rectangular tile.
  965. if (dest.overlaps(rect)) {
  966. set_rectangle_rectangle_constraints(&constraints, dest, rect);
  967. }
  968. }
  969. }
  970. }
  971. }
  972. return constraints.hit;
  973. }
  974. // =============================================================================
  975. // LOCAL
  976. CustomParticleSystem::SpriteProperties
  977. CustomParticleSystem::get_random_texture()
  978. {
  979. float val = graphicsRandom.randf(texture_sum_odds);
  980. for (const auto& texture : m_textures)
  981. {
  982. val -= texture.likeliness;
  983. if (val <= 0)
  984. {
  985. return texture;
  986. }
  987. }
  988. return m_textures.at(0);
  989. }
  990. std::vector<ParticleZone::ZoneDetails>
  991. CustomParticleSystem::get_zones()
  992. {
  993. std::vector<ParticleZone::ZoneDetails> list;
  994. //if (!!GameSession::current() && Sector::current()) {
  995. if (!ParticleEditor::current()) {
  996. // In game or in level editor.
  997. for (auto& zone : GameSession::current()->get_current_sector().get_objects_by_type<ParticleZone>()) {
  998. list.push_back(zone.get_details());
  999. }
  1000. } else {
  1001. // In particle editor.
  1002. list.push_back(ParticleZone::ZoneDetails(m_name,
  1003. ParticleZone::ParticleZoneType::Spawn,
  1004. Rectf(virtual_width / 2 - 16.f,
  1005. virtual_height / 2 - 16.f,
  1006. virtual_width / 2 + 16.f,
  1007. virtual_height / 2 + 16.f)
  1008. ));
  1009. }
  1010. return list;
  1011. }
  1012. float
  1013. CustomParticleSystem::get_abs_x()
  1014. {
  1015. return (Sector::current()) ? Sector::get().get_camera().get_translation().x : 0.f;
  1016. }
  1017. float
  1018. CustomParticleSystem::get_abs_y()
  1019. {
  1020. return (Sector::current()) ? Sector::get().get_camera().get_translation().y : 0.f;
  1021. }
  1022. /** Initializes and adds a single particle to the stack. Performs
  1023. * no check regarding the maximum amount of total particles.
  1024. * @param lifetime The time elapsed since the moment the particle should have been born.
  1025. */
  1026. void
  1027. CustomParticleSystem::add_particle(float lifetime, float x, float y)
  1028. {
  1029. auto particle = std::make_unique<CustomParticle>();
  1030. particle->original_props = get_random_texture();
  1031. particle->props = particle->original_props;
  1032. particle->pos.x = x;
  1033. particle->pos.y = y;
  1034. float life_elapsed = lifetime;
  1035. float birth_delta = m_particle_birth_time_variation / 2;
  1036. particle->total_birth = m_particle_birth_time + graphicsRandom.randf(-birth_delta, birth_delta);
  1037. particle->birth_time = particle->total_birth - life_elapsed;
  1038. if (particle->birth_time < 0.f) {
  1039. life_elapsed = -particle->birth_time;
  1040. particle->birth_time = 0.f;
  1041. } else {
  1042. life_elapsed = 0.f;
  1043. }
  1044. float life_delta = m_particle_lifetime_variation / 2;
  1045. particle->lifetime = m_particle_lifetime - life_elapsed + graphicsRandom.randf(-life_delta, life_delta);
  1046. if (particle->lifetime < 0.f) {
  1047. life_elapsed = -particle->lifetime;
  1048. particle->lifetime = 0.f;
  1049. } else {
  1050. life_elapsed = 0.f;
  1051. }
  1052. float death_delta = m_particle_death_time_variation / 2;
  1053. particle->total_death = m_particle_death_time + graphicsRandom.randf(-death_delta, death_delta);
  1054. particle->death_time = particle->total_death - life_elapsed;
  1055. particle->birth_mode = m_particle_birth_mode;
  1056. particle->death_mode = m_particle_death_mode;
  1057. particle->birth_easing = m_particle_birth_easing;
  1058. particle->death_easing = m_particle_death_easing;
  1059. switch(particle->birth_mode) {
  1060. case FadeMode::Shrink:
  1061. particle->scale = 0.f;
  1062. break;
  1063. default:
  1064. break;
  1065. }
  1066. float speedx_delta = m_particle_speed_variation_x / 2;
  1067. particle->speedX = m_particle_speed_x + graphicsRandom.randf(-speedx_delta, speedx_delta);
  1068. float speedy_delta = m_particle_speed_variation_y / 2;
  1069. particle->speedY = m_particle_speed_y + graphicsRandom.randf(-speedy_delta, speedy_delta);
  1070. particle->accX = m_particle_acceleration_x;
  1071. particle->accY = m_particle_acceleration_y;
  1072. particle->frictionX = m_particle_friction_x;
  1073. particle->frictionY = m_particle_friction_y;
  1074. particle->feather_factor = m_particle_feather_factor;
  1075. float angle_delta = m_particle_rotation_variation / 2;
  1076. particle->angle = m_particle_rotation + graphicsRandom.randf(-angle_delta, angle_delta);
  1077. float angle_speed_delta = m_particle_rotation_speed_variation / 2;
  1078. particle->angle_speed = m_particle_rotation_speed + graphicsRandom.randf(-angle_speed_delta, angle_speed_delta);
  1079. particle->angle_acc = m_particle_rotation_acceleration;
  1080. particle->angle_decc = m_particle_rotation_decceleration;
  1081. particle->angle_mode = m_particle_rotation_mode;
  1082. particle->collision_mode = m_particle_collision_mode;
  1083. particle->offscreen_mode = m_particle_offscreen_mode;
  1084. custom_particles.push_back(std::move(particle));
  1085. }
  1086. void
  1087. CustomParticleSystem::spawn_particles(float lifetime)
  1088. {
  1089. if (!m_cover_screen) {
  1090. for (auto& zone : get_zones()) {
  1091. if (zone.get_type() == ParticleZone::ParticleZoneType::Spawn && zone.get_particle_name() == m_name) {
  1092. Rectf rect = zone.get_rect();
  1093. add_particle(lifetime,
  1094. graphicsRandom.randf(rect.get_width()) + rect.get_left(),
  1095. graphicsRandom.randf(rect.get_height()) + rect.get_top());
  1096. }
  1097. }
  1098. } else {
  1099. float abs_x = get_abs_x();
  1100. float abs_y = get_abs_y();
  1101. add_particle(lifetime,
  1102. graphicsRandom.randf(virtual_width) + abs_x,
  1103. graphicsRandom.randf(virtual_height) + abs_y);
  1104. }
  1105. }
  1106. // SCRIPTING
  1107. void
  1108. CustomParticleSystem::ease_value(float* value, float target, float time, easing func) {
  1109. assert(value);
  1110. ease_request req;
  1111. req.value = value;
  1112. req.begin = *value;
  1113. req.end = target;
  1114. req.time_total = time;
  1115. req.time_remain = time;
  1116. req.func = func;
  1117. script_easings.push_back(req);
  1118. }
  1119. /* EOF */