background.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. // SuperTux
  2. // Copyright (C) 2006 Matthias Braun <matze@braunis.de>
  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/background.hpp"
  17. #include <physfs.h>
  18. #include <utility>
  19. #include "editor/editor.hpp"
  20. #include "supertux/d_scope.hpp"
  21. #include "supertux/flip_level_transformer.hpp"
  22. #include "supertux/gameconfig.hpp"
  23. #include "supertux/globals.hpp"
  24. #include "util/reader.hpp"
  25. #include "util/reader_mapping.hpp"
  26. #include "util/writer.hpp"
  27. #include "video/drawing_context.hpp"
  28. #include "video/surface.hpp"
  29. Background::Background() :
  30. ExposedObject<Background, scripting::Background>(this),
  31. m_alignment(NO_ALIGNMENT),
  32. m_fill(false),
  33. m_layer(LAYER_BACKGROUND0),
  34. m_imagefile_top(),
  35. m_imagefile(),
  36. m_imagefile_bottom(),
  37. m_pos(0.0f, 0.0f),
  38. m_parallax_speed(0.0f, 0.0f),
  39. m_scroll_speed(0.0f, 0.0f),
  40. m_scroll_offset(0.0f, 0.0f),
  41. m_image_top(),
  42. m_image(),
  43. m_image_bottom(),
  44. m_blend(),
  45. m_color(1.f, 1.f, 1.f),
  46. m_target(DrawingTarget::COLORMAP),
  47. m_timer_color(),
  48. m_src_color(),
  49. m_dst_color(),
  50. m_flip(NO_FLIP)
  51. {
  52. }
  53. Background::Background(const ReaderMapping& reader) :
  54. GameObject(reader),
  55. ExposedObject<Background, scripting::Background>(this),
  56. m_alignment(NO_ALIGNMENT),
  57. m_fill(false),
  58. m_layer(LAYER_BACKGROUND0),
  59. m_imagefile_top(),
  60. m_imagefile(),
  61. m_imagefile_bottom(),
  62. m_pos(0.0f, 0.0f),
  63. m_parallax_speed(1.0f, 1.0f),
  64. m_scroll_speed(0.0f, 0.0f),
  65. m_scroll_offset(0.0f, 0.0f),
  66. m_image_top(),
  67. m_image(),
  68. m_image_bottom(),
  69. m_blend(),
  70. m_color(),
  71. m_target(DrawingTarget::COLORMAP),
  72. m_timer_color(),
  73. m_src_color(),
  74. m_dst_color(),
  75. m_flip(NO_FLIP)
  76. {
  77. reader.get("fill", m_fill);
  78. std::string alignment_str;
  79. if (reader.get("alignment", alignment_str))
  80. {
  81. if (alignment_str == "left")
  82. {
  83. m_alignment = LEFT_ALIGNMENT;
  84. }
  85. else if (alignment_str == "right")
  86. {
  87. m_alignment = RIGHT_ALIGNMENT;
  88. }
  89. else if (alignment_str == "top")
  90. {
  91. m_alignment = TOP_ALIGNMENT;
  92. }
  93. else if (alignment_str == "bottom")
  94. {
  95. m_alignment = BOTTOM_ALIGNMENT;
  96. }
  97. else if (alignment_str == "none")
  98. {
  99. m_alignment = NO_ALIGNMENT;
  100. }
  101. else
  102. {
  103. log_warning << "Background: invalid alignment: '" << alignment_str << "'" << std::endl;
  104. m_alignment = NO_ALIGNMENT;
  105. }
  106. }
  107. reader.get("scroll-offset-x", m_scroll_offset.x, 0.0f);
  108. reader.get("scroll-offset-y", m_scroll_offset.y, 0.0f);
  109. // For backward compatibility, add position to scroll offset.
  110. float px;
  111. float py;
  112. if (reader.get("x", px))
  113. m_scroll_offset.x += px;
  114. if (reader.get("y", py))
  115. m_scroll_offset.y += py;
  116. reader.get("scroll-speed-x", m_scroll_speed.x, 0.0f);
  117. reader.get("scroll-speed-y", m_scroll_speed.y, 0.0f);
  118. m_layer = reader_get_layer(reader, LAYER_BACKGROUND0);
  119. reader.get("image", m_imagefile, "images/background/misc/transparent_up.png");
  120. m_image = load_background(m_imagefile);
  121. if(!reader.get("speed-x", m_parallax_speed.x))
  122. {
  123. // For backward compatibility.
  124. reader.get("speed", m_parallax_speed.x, 0.5f);
  125. }
  126. reader.get("speed-y", m_parallax_speed.y, m_parallax_speed.x);
  127. if (reader.get("image-top", m_imagefile_top)) {
  128. m_image_top = load_background(m_imagefile_top);
  129. } else {
  130. if (!Editor::is_active()) {
  131. m_imagefile_top = m_imagefile;
  132. }
  133. }
  134. if (reader.get("image-bottom", m_imagefile_bottom)) {
  135. m_image_bottom = load_background(m_imagefile_bottom);
  136. } else {
  137. if (!Editor::is_active()) {
  138. m_imagefile_bottom = m_imagefile;
  139. }
  140. }
  141. std::vector<float> color;
  142. if (reader.get("color", color))
  143. {
  144. m_color = Color(color);
  145. }
  146. else
  147. {
  148. m_color = Color(1, 1, 1);
  149. }
  150. reader.get_custom("blend", m_blend, Blend_from_string);
  151. reader.get_custom("target", m_target, DrawingTarget_from_string);
  152. }
  153. Background::~Background()
  154. {
  155. }
  156. ObjectSettings
  157. Background::get_settings()
  158. {
  159. ObjectSettings result = GameObject::get_settings();
  160. result.add_float(_("X"), &m_pos.x, "x", 0.0f, OPTION_HIDDEN);
  161. result.add_float(_("Y"), &m_pos.y, "y", 0.0f, OPTION_HIDDEN);
  162. result.add_bool(_("Fill"), &m_fill, "fill", false);
  163. result.add_int(_("Z-pos"), &m_layer, "z-pos", LAYER_BACKGROUND0);
  164. result.add_enum(_("Alignment"), reinterpret_cast<int*>(&m_alignment),
  165. {_("none"), _("left"), _("right"), _("top"), _("bottom")},
  166. {"none", "left", "right", "top", "bottom"},
  167. static_cast<int>(NO_ALIGNMENT), "alignment");
  168. result.add_float(_("Scroll offset x"), &m_scroll_offset.x, "scroll-offset-x", 0.0f);
  169. result.add_float(_("Scroll offset y"), &m_scroll_offset.y, "scroll-offset-y", 0.0f);
  170. result.add_float(_("Scroll speed x"), &m_scroll_speed.x, "scroll-speed-x", 0.0f);
  171. result.add_float(_("Scroll speed y"), &m_scroll_speed.y, "scroll-speed-y", 0.0f);
  172. result.add_float(_("Parallax Speed x"), &m_parallax_speed.x, "speed", std::nullopt);
  173. result.add_float(_("Parallax Speed y"), &m_parallax_speed.y, "speed-y", m_parallax_speed.x);
  174. result.add_surface(_("Top image"), &m_imagefile_top, "image-top", "");
  175. result.add_surface(_("Image"), &m_imagefile, "image");
  176. result.add_surface(_("Bottom image"), &m_imagefile_bottom, "image-bottom", "");
  177. result.add_rgba(_("Colour"), &m_color, "color");
  178. result.add_enum(_("Draw target"), reinterpret_cast<int*>(&m_target),
  179. {_("Normal"), _("Lightmap")},
  180. {"normal", "lightmap"},
  181. static_cast<int>(DrawingTarget::COLORMAP),
  182. "target");
  183. result.reorder({"x", "y", "alignment", "scroll-speed-x", "scroll-speed-y", "speed", "speed-y", "fill", "target", "image-top", "image", "image-bottom", "z-pos"});
  184. result.add_remove();
  185. return result;
  186. }
  187. void
  188. Background::after_editor_set()
  189. {
  190. m_image_top = load_background(m_imagefile_top);
  191. m_image = load_background(m_imagefile);
  192. m_image_bottom = load_background(m_imagefile_bottom);
  193. }
  194. void
  195. Background::update(float dt_sec)
  196. {
  197. m_scroll_offset += m_scroll_speed * dt_sec;
  198. if (m_timer_color.check())
  199. {
  200. m_color = m_dst_color;
  201. m_timer_color.stop(); // To reset the "check()" value.
  202. }
  203. else if (m_timer_color.started())
  204. {
  205. float progress = m_timer_color.get_progress();
  206. m_color = (m_src_color + (m_dst_color - m_src_color) * progress).validate();
  207. }
  208. }
  209. void
  210. Background::fade_color(Color color, float time)
  211. {
  212. m_src_color = m_color;
  213. m_dst_color = color;
  214. m_timer_color.start(time, false);
  215. m_color = m_src_color;
  216. }
  217. void
  218. Background::set_image(const std::string& name)
  219. {
  220. m_imagefile = name;
  221. m_image = load_background(name);
  222. }
  223. void
  224. Background::set_images(const std::string& name_top,
  225. const std::string& name_middle,
  226. const std::string& name_bottom)
  227. {
  228. m_image_top = load_background(name_top);
  229. m_imagefile_top = name_top;
  230. m_image = load_background(name_middle);
  231. m_imagefile = name_middle;
  232. m_image_bottom = load_background(name_bottom);
  233. m_imagefile_bottom = name_bottom;
  234. }
  235. void
  236. Background::set_speed(float speed)
  237. {
  238. m_parallax_speed.x = speed;
  239. m_parallax_speed.y = speed;
  240. }
  241. void
  242. Background::draw_image(DrawingContext& context, const Vector& pos_)
  243. {
  244. const Sizef level(d_gameobject_manager->get_width(), d_gameobject_manager->get_height());
  245. const Sizef screen(context.get_width(),
  246. context.get_height());
  247. const Sizef parallax_image_size((1.0f - m_parallax_speed.x) * screen.width + level.width * m_parallax_speed.x,
  248. (1.0f - m_parallax_speed.y) * screen.height + level.height * m_parallax_speed.y);
  249. const Rectf cliprect = context.get_cliprect();
  250. const float img_w = static_cast<float>(m_image->get_width());
  251. const float img_h = static_cast<float>(m_image->get_height());
  252. const float img_w_2 = img_w / 2.0f;
  253. const float img_h_2 = img_h / 2.0f;
  254. const int start_x = static_cast<int>(floorf((cliprect.get_left() - (pos_.x - img_w /2.0f)) / img_w));
  255. const int end_x = static_cast<int>(ceilf((cliprect.get_right() - (pos_.x + img_w /2.0f)) / img_w)) + 1;
  256. const int start_y = static_cast<int>(floorf((cliprect.get_top() - (pos_.y - img_h/2.0f)) / img_h));
  257. const int end_y = static_cast<int>(ceilf((cliprect.get_bottom() - (pos_.y + img_h/2.0f)) / img_h)) + 1;
  258. Canvas& canvas = context.get_canvas(m_target);
  259. context.set_flip(context.get_flip() ^ m_flip);
  260. if (m_fill)
  261. {
  262. Rectf dstrect(Vector(pos_.x - context.get_width() / 2.0f,
  263. pos_.y - context.get_height() / 2.0f),
  264. Sizef(context.get_width(),
  265. context.get_height()));
  266. canvas.draw_surface_scaled(m_image, dstrect, m_layer);
  267. }
  268. else
  269. {
  270. switch (m_alignment)
  271. {
  272. case LEFT_ALIGNMENT:
  273. for (int y = start_y; y < end_y; ++y)
  274. {
  275. Vector p(pos_.x - parallax_image_size.width / 2.0f,
  276. pos_.y + static_cast<float>(y) * img_h - img_h_2);
  277. canvas.draw_surface(m_image, p, 0.f, m_color, m_blend, m_layer);
  278. }
  279. break;
  280. case RIGHT_ALIGNMENT:
  281. for (int y = start_y; y < end_y; ++y)
  282. {
  283. Vector p(pos_.x + parallax_image_size.width / 2.0f - img_w,
  284. pos_.y + static_cast<float>(y) * img_h - img_h_2);
  285. canvas.draw_surface(m_image, p, 0.f, m_color, m_blend, m_layer);
  286. }
  287. break;
  288. case TOP_ALIGNMENT:
  289. for (int x = start_x; x < end_x; ++x)
  290. {
  291. Vector p(pos_.x + static_cast<float>(x) * img_w - img_w_2,
  292. pos_.y - parallax_image_size.height / 2.0f);
  293. canvas.draw_surface(m_image, p, 0.f, m_color, m_blend, m_layer);
  294. }
  295. break;
  296. case BOTTOM_ALIGNMENT:
  297. for (int x = start_x; x < end_x; ++x)
  298. {
  299. Vector p(pos_.x + static_cast<float>(x) * img_w - img_w_2,
  300. pos_.y - img_h + parallax_image_size.height / 2.0f);
  301. canvas.draw_surface(m_image, p, 0.f, m_color, m_blend, m_layer);
  302. }
  303. break;
  304. case NO_ALIGNMENT:
  305. for (int y = start_y; y < end_y; ++y)
  306. for (int x = start_x; x < end_x; ++x)
  307. {
  308. Vector p(pos_.x + static_cast<float>(x) * img_w - img_w_2,
  309. pos_.y + static_cast<float>(y) * img_h - img_h_2);
  310. if (m_image_top && (y < 0))
  311. {
  312. canvas.draw_surface(m_image_top, p, 0.f, m_color, m_blend, m_layer);
  313. }
  314. else if (m_image_bottom && (y > 0))
  315. {
  316. canvas.draw_surface(m_image_bottom, p, 0.f, m_color, m_blend, m_layer);
  317. }
  318. else
  319. {
  320. canvas.draw_surface(m_image, p, 0.f, m_color, m_blend, m_layer);
  321. }
  322. }
  323. break;
  324. }
  325. }
  326. context.set_flip(context.get_flip() ^ m_flip);
  327. }
  328. void
  329. Background::draw(DrawingContext& context)
  330. {
  331. if (Editor::is_active() && !g_config->editor_render_background)
  332. return;
  333. if (!m_image)
  334. return;
  335. Sizef level_size(d_gameobject_manager->get_width(),
  336. d_gameobject_manager->get_height());
  337. Sizef screen(context.get_width(),
  338. context.get_height());
  339. Sizef translation_range = level_size - screen;
  340. Vector center_offset(context.get_translation().x - translation_range.width / 2.0f,
  341. context.get_translation().y - translation_range.height / 2.0f);
  342. Vector pos(level_size.width / 2,
  343. level_size.height / 2);
  344. draw_image(context, pos + m_scroll_offset + Vector(center_offset.x * (1.0f - m_parallax_speed.x),
  345. center_offset.y * (1.0f - m_parallax_speed.y)));
  346. }
  347. namespace {
  348. std::unordered_map<std::string, std::string> fallback_paths = {
  349. {"arctis2.png", "antarctic/arctis2.png"},
  350. {"misty_snowhills_small.png", "antarctic/misty_snowhills_small.png"},
  351. {"semi_arctic.jpg", "antarctic/semi_arctic.jpg"},
  352. {"bridgecloud-dark.png", "arctic_bridge/bridgecloud-dark.png"},
  353. {"bridgecloud-light.png", "arctic_bridge/bridgecloud-light.png"},
  354. {"bridgeocean-fade.png", "arctic_bridge/bridgeocean-fade.png"},
  355. {"bridgeocean-nofade.png", "arctic_bridge/bridgeocean-nofade.png"},
  356. {"bridgeocean-original.png", "arctic_bridge/bridgeocean-original.png"},
  357. {"arcticskies1.png", "arcticskies/arcticskies1.png"},
  358. {"arcticskies2.png", "arcticskies/arcticskies2.png"},
  359. {"arcticskies3.png", "arcticskies/arcticskies3.png"},
  360. {"arcticskies35.png", "arcticskies/arcticskies35.png"},
  361. {"arcticskies4.png", "arcticskies/arcticskies4.png"},
  362. {"block-snow-background.png", "block_snow/block-snow-background.png"},
  363. {"block-snow-midground.png", "block_snow/block-snow-midground.png"},
  364. {"block-snow-top.png", "block_snow/block-snow-top.png"},
  365. {"bluemountain-bottom.png", "bluemountain/bluemountain-bottom.png"},
  366. {"bluemountain-middle.png", "bluemountain/bluemountain-middle.png"},
  367. {"bluemountain-top.png", "bluemountain/bluemountain-top.png"},
  368. {"bluemountain2.png", "bluemountain/bluemountain2.png"},
  369. {"castle_foreground.png", "castle/castle_foreground.png"},
  370. {"snowcastle.png", "castle/snowcastle.png"},
  371. {"cloud-mountains-background.png", "cloud_mountains/cloud-mountains-background.png"},
  372. {"cloud-mountains-bottom.png", "cloud_mountains/cloud-mountains-bottom.png"},
  373. {"cloud-mountains-forground.png", "cloud_mountains/cloud-mountains-forground.png"},
  374. {"cloud-mountains-midground.png", "cloud_mountains/cloud-mountains-midground.png"},
  375. {"dawn_hill_para_blur.png", "forest/dawn_hill_para_blur.png"},
  376. {"forest2_para.png", "forest/forest2_para.png"},
  377. {"forest_para2.png", "forest/forest_para2.png"},
  378. {"forest_para3.png", "forest/forest_para3.png"},
  379. {"forest_para3_bottom.png", "forest/forest_para3_bottom.png"},
  380. {"nighthills.png", "forest/nighthills.png"},
  381. {"ghostforest.jpg", "ghostforest/ghostforest.jpg"},
  382. {"ghostforest_grave.png", "ghostforest/ghostforest_grave.png"},
  383. {"ghostforest_para.png", "ghostforest/ghostforest_para.png"},
  384. {"cave2.jpg", "ice_cave/cave2.jpg"},
  385. {"darkcave-background.png", "ice_cave/darkcave-background.png"},
  386. {"darkcave-middle.png", "ice_cave/darkcave-middle.png"},
  387. {"darkcave-preview.png", "ice_cave/darkcave-preview.png"},
  388. {"darkcave-top_and_bottom.png", "ice_cave/darkcave-top_and_bottom.png"},
  389. {"darkcavemidground-middle.png", "ice_cave/darkcavemidground-middle.png"},
  390. {"darkcavemidground-top_and_bottom.png", "ice_cave/darkcavemidground-top_and_bottom.png"},
  391. {"black_800px.png", "misc/black_800px.png"},
  392. {"fog.png", "misc/fog.png"},
  393. {"grid.png", "misc/grid.png"},
  394. {"grid.surface", "misc/grid.surface"},
  395. {"heatshimmer-displacement.png", "misc/heatshimmer-displacement.png"},
  396. {"heatshimmer.png", "misc/heatshimmer.png"},
  397. {"heatshimmer.surface", "misc/heatshimmer.surface"},
  398. {"leaves.png", "misc/leaves.png"},
  399. {"oiltux.jpg", "misc/oiltux.jpg"},
  400. {"transparent_up.png", "misc/transparent_up.png"},
  401. {"nightsky.png", "nightsky/nightsky.png"},
  402. {"nightsky_bottom.png", "nightsky/nightsky_bottom.png"},
  403. {"nightsky_middle.png", "nightsky/nightsky_middle.png"},
  404. {"nightsky_para.png", "nightsky/nightsky_para.png"},
  405. {"nightsky_top.png", "nightsky/nightsky_top.png"},
  406. };
  407. } // namespace
  408. SurfacePtr
  409. Background::load_background(const std::string& image_path)
  410. {
  411. if (image_path.empty())
  412. return nullptr;
  413. if (PHYSFS_exists(image_path.c_str()))
  414. // No need to search fallback paths.
  415. return Surface::from_file(image_path);
  416. // Search for a fallback image in fallback_paths.
  417. const std::string& default_dir = "images/background/";
  418. const std::string& default_dir2 = "/images/background/";
  419. std::string new_path = image_path;
  420. if (image_path.substr(0, default_dir.length()) == default_dir)
  421. new_path.erase(0, default_dir.length());
  422. else if (image_path.substr(0, default_dir2.length()) == default_dir2)
  423. new_path.erase(0, default_dir2.length());
  424. auto it = fallback_paths.find(new_path);
  425. if (it == fallback_paths.end())
  426. // Unknown image, let the texture manager select the dummy texture.
  427. return Surface::from_file(image_path);
  428. new_path = default_dir + it->second;
  429. return Surface::from_file(new_path);
  430. }
  431. void
  432. Background::on_flip(float height)
  433. {
  434. GameObject::on_flip(height);
  435. std::swap(m_image_bottom, m_image_top);
  436. m_pos.y = height - m_pos.y - static_cast<float>(m_image->get_height());
  437. m_scroll_offset.y = -m_scroll_offset.y;
  438. if (m_alignment == BOTTOM_ALIGNMENT)
  439. m_alignment = TOP_ALIGNMENT;
  440. else if (m_alignment == TOP_ALIGNMENT)
  441. m_alignment = BOTTOM_ALIGNMENT;
  442. FlipLevelTransformer::transform_flip(m_flip);
  443. }
  444. /* EOF */