rain_particle_system.cpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  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/rain_particle_system.hpp"
  17. #include <assert.h>
  18. #include <math.h>
  19. #include "math/easing.hpp"
  20. #include "math/random.hpp"
  21. #include "object/camera.hpp"
  22. #include "object/rainsplash.hpp"
  23. #include "supertux/sector.hpp"
  24. #include "util/reader.hpp"
  25. #include "util/reader_mapping.hpp"
  26. #include "video/drawing_context.hpp"
  27. #include "video/surface.hpp"
  28. #include "video/video_system.hpp"
  29. #include "video/viewport.hpp"
  30. RainParticleSystem::RainParticleSystem() :
  31. ExposedObject<RainParticleSystem, scripting::Rain>(this),
  32. m_current_speed(1.f),
  33. m_target_speed(1.f),
  34. m_speed_fade_time_remaining(0.f),
  35. m_begin_angle(45.f),
  36. m_current_angle(45.f),
  37. m_target_angle(45.f),
  38. m_angle_fade_time_remaining(0.f),
  39. m_angle_fade_time_total(0.f),
  40. m_angle_easing(getEasingByName(EaseNone)),
  41. m_current_amount(1.f),
  42. m_target_amount(1.f),
  43. m_amount_fade_time_remaining(0.f),
  44. m_current_real_amount(0.f)
  45. {
  46. init();
  47. }
  48. RainParticleSystem::RainParticleSystem(const ReaderMapping& reader) :
  49. ParticleSystem_Interactive(reader),
  50. ExposedObject<RainParticleSystem, scripting::Rain>(this),
  51. m_current_speed(1.f),
  52. m_target_speed(1.f),
  53. m_speed_fade_time_remaining(0.f),
  54. m_begin_angle(45.f),
  55. m_current_angle(45.f),
  56. m_target_angle(45.f),
  57. m_angle_fade_time_remaining(0.f),
  58. m_angle_fade_time_total(0.f),
  59. m_angle_easing(getEasingByName(EaseNone)),
  60. m_current_amount(1.f),
  61. m_target_amount(1.f),
  62. m_amount_fade_time_remaining(0.f),
  63. m_current_real_amount(0.f)
  64. {
  65. reader.get("intensity", m_current_amount, 1.f);
  66. reader.get("angle", m_current_angle, 1.f);
  67. reader.get("speed", m_current_speed, 1.f);
  68. init();
  69. }
  70. RainParticleSystem::~RainParticleSystem()
  71. {
  72. }
  73. void RainParticleSystem::init()
  74. {
  75. rainimages[0] = Surface::from_file("images/particles/rain0.png");
  76. rainimages[1] = Surface::from_file("images/particles/rain1.png");
  77. virtual_width = static_cast<float>(SCREEN_WIDTH) * 2.0f;
  78. // create some random raindrops
  79. set_amount(m_current_amount);
  80. }
  81. ObjectSettings
  82. RainParticleSystem::get_settings()
  83. {
  84. ObjectSettings result = ParticleSystem::get_settings();
  85. result.add_float(_("Intensity"), &m_current_amount, "intensity", 1.f);
  86. result.add_float(_("Angle"), &m_current_angle, "angle", 1.f);
  87. result.add_float(_("Speed"), &m_current_speed, "speed", 1.f);
  88. result.reorder({"intensity", "angle", "speed", "enabled", "name"});
  89. return result;
  90. }
  91. void RainParticleSystem::set_amount(float amount)
  92. {
  93. // Don't spawn too many particles to avoid destroying the player's computer
  94. float real_amount = amount < min_amount ? min_amount
  95. : amount > max_amount ? max_amount
  96. : amount;
  97. int old_raindropcount = static_cast<int>(virtual_width*m_current_real_amount/6.0f);
  98. int new_raindropcount = static_cast<int>(virtual_width*real_amount/6.0f);
  99. int delta = new_raindropcount - old_raindropcount;
  100. if (delta > 0) {
  101. for (int i=0; i<delta; ++i) {
  102. auto particle = std::make_unique<RainParticle>();
  103. particle->pos.x = static_cast<float>(graphicsRandom.rand(int(virtual_width)));
  104. particle->pos.y = static_cast<float>(graphicsRandom.rand(int(virtual_height)));
  105. int rainsize = graphicsRandom.rand(2);
  106. particle->texture = rainimages[rainsize];
  107. do {
  108. particle->speed = ((static_cast<float>(rainsize) + 1.0f) * 45.0f + graphicsRandom.randf(3.6f));
  109. } while(particle->speed < 1);
  110. particles.push_back(std::move(particle));
  111. }
  112. } else if (delta < 0) {
  113. for (int i=0; i>delta; --i) {
  114. particles.pop_back();
  115. }
  116. }
  117. m_current_real_amount = real_amount;
  118. }
  119. void RainParticleSystem::set_angle(float angle)
  120. {
  121. for (const auto& particle : particles)
  122. particle->angle = angle;
  123. }
  124. void RainParticleSystem::update(float dt_sec)
  125. {
  126. if (!enabled)
  127. return;
  128. // Update amount
  129. if (m_amount_fade_time_remaining > 0.f) {
  130. if (dt_sec >= m_amount_fade_time_remaining) {
  131. m_current_amount = m_target_amount;
  132. m_amount_fade_time_remaining = 0.f;
  133. // Test below
  134. /*if (m_current_amount > 1.1f) {
  135. m_target_amount = 0.1f;
  136. } else {
  137. m_target_amount = 5.f;
  138. }
  139. m_amount_fade_time_remaining = 2.f;*/
  140. // Test above
  141. } else {
  142. float amount = dt_sec / m_amount_fade_time_remaining;
  143. m_current_amount += (m_target_amount - m_current_amount) * amount;
  144. m_amount_fade_time_remaining -= dt_sec;
  145. }
  146. }
  147. set_amount(m_current_amount);
  148. // Update speed
  149. if (m_speed_fade_time_remaining > 0.f) {
  150. if (dt_sec >= m_speed_fade_time_remaining) {
  151. m_current_speed = m_target_speed;
  152. m_speed_fade_time_remaining = 0.f;
  153. } else {
  154. float amount = dt_sec / m_speed_fade_time_remaining;
  155. m_current_speed += (m_target_speed - m_current_speed) * amount;
  156. m_speed_fade_time_remaining -= dt_sec;
  157. }
  158. }
  159. // Update angle
  160. if (m_angle_fade_time_remaining > 0.f) {
  161. if (dt_sec >= m_angle_fade_time_remaining) {
  162. m_current_angle = m_target_angle;
  163. m_angle_fade_time_remaining = 0.f;
  164. } else {
  165. m_angle_fade_time_remaining -= dt_sec;
  166. float progress = 1.f - m_angle_fade_time_remaining / m_angle_fade_time_total;
  167. progress = static_cast<float>(m_angle_easing(static_cast<double>(progress)));
  168. m_current_angle = progress * (m_target_angle - m_begin_angle) + m_begin_angle;
  169. }
  170. set_angle(m_current_angle);
  171. }
  172. const auto& cam_translation = Sector::get().get_camera().get_translation();
  173. float movement_multiplier = dt_sec * Sector::get().get_gravity() * m_current_speed * 1.41421353f;
  174. float abs_x = cam_translation.x;
  175. float abs_y = cam_translation.y;
  176. for (auto& it : particles) {
  177. auto particle = dynamic_cast<RainParticle*>(it.get());
  178. assert(particle);
  179. float movement = particle->speed * movement_multiplier;
  180. particle->pos.y += movement * cosf((particle->angle + 45.f) * 3.14159265f / 180.f);
  181. particle->pos.x -= movement * sinf((particle->angle + 45.f) * 3.14159265f / 180.f);
  182. int col = collision(particle, Vector(-movement, movement));
  183. if ((particle->pos.y > static_cast<float>(SCREEN_HEIGHT) + abs_y) || (col >= 0)) {
  184. //Create rainsplash
  185. if ((particle->pos.y <= static_cast<float>(SCREEN_HEIGHT) + abs_y) && (col >= 1)){
  186. bool vertical = (col == 2);
  187. if (!vertical) { //check if collision happened from above
  188. int splash_x, splash_y; // move outside if statement when
  189. // uncommenting the else statement below.
  190. splash_x = int(particle->pos.x);
  191. splash_y = int(particle->pos.y) - (int(particle->pos.y) % 32) + 32;
  192. Sector::get().add<RainSplash>(Vector(static_cast<float>(splash_x), static_cast<float>(splash_y)),
  193. vertical);
  194. }
  195. // Uncomment the following to display vertical splashes, too
  196. /* else {
  197. splash_x = int(particle->pos.x) - (int(particle->pos.x) % 32) + 32;
  198. splash_y = int(particle->pos.y);
  199. Sector::get().add<RainSplash>(Vector(splash_x, splash_y),vertical);
  200. } */
  201. }
  202. int new_x = graphicsRandom.rand(int(virtual_width)) + int(abs_x);
  203. int new_y = 0;
  204. //FIXME: Don't move particles over solid tiles
  205. particle->pos.x = static_cast<float>(new_x);
  206. particle->pos.y = static_cast<float>(new_y);
  207. }
  208. }
  209. }
  210. void RainParticleSystem::fade_speed(float new_speed, float fade_time)
  211. {
  212. // No check to enabled; change the fading even if it's disabled
  213. // If time is 0 (or smaller?), then update() will never change m_current_speed
  214. if (fade_time <= 0.f)
  215. {
  216. m_current_speed = new_speed;
  217. }
  218. m_target_speed = new_speed;
  219. m_speed_fade_time_remaining = fade_time;
  220. }
  221. void RainParticleSystem::fade_angle(float new_angle, float fade_time, easing ease_func)
  222. {
  223. // No check to enabled; change the fading even if it's disabled
  224. // If time is 0 (or smaller?), then update() will never change m_current_amount
  225. if (fade_time <= 0.f)
  226. {
  227. m_current_angle = new_angle - 45.f;
  228. }
  229. m_begin_angle = m_current_angle;
  230. m_target_angle = new_angle - 45.f;
  231. m_angle_fade_time_total = fade_time;
  232. m_angle_fade_time_remaining = fade_time;
  233. m_angle_easing = ease_func;
  234. }
  235. void RainParticleSystem::fade_amount(float new_amount, float fade_time)
  236. {
  237. // No check to enabled; change the fading even if it's disabled
  238. // If time is 0 (or smaller?), then update() will never change m_current_amount
  239. if (fade_time <= 0.f)
  240. {
  241. m_current_amount = new_amount;
  242. }
  243. m_target_amount = new_amount;
  244. m_amount_fade_time_remaining = fade_time;
  245. }
  246. void RainParticleSystem::draw(DrawingContext& context)
  247. {
  248. ParticleSystem_Interactive::draw(context);
  249. if (!enabled)
  250. return;
  251. float opacity = fog_max_value * (m_current_amount - fog_start_amount) / (max_amount - fog_start_amount);
  252. if (opacity < 0.f)
  253. opacity = 0.f;
  254. if (opacity > 1.f)
  255. opacity = 1.f;
  256. context.push_transform();
  257. context.set_translation(Vector(0, 0));
  258. context.color().draw_filled_rect(context.get_rect(),
  259. Color(0.3f, 0.38f, 0.4f, opacity),
  260. 199); // TODO: Change the hardcoded layer value with the rain's layer
  261. context.pop_transform();
  262. }
  263. /* EOF */