From 5a5057859a145dc78fdeae6d934654dbcda20daf Mon Sep 17 00:00:00 2001 From: Hume2 Date: Sat, 27 Jul 2024 21:22:30 +0200 Subject: [PATCH] Scale parallax layers perspectively-correctly (#2954) I implemented it for both tilemaps and backgrounds. DEMO: https://www.youtube.com/watch?v=GnFArTYBL4E Scaling now works so that it imitates a perspective camera. The layers scale differently based on their scrolling speed. Also note that the z-position based on which the scaling is calculated, does not depend on the "z-pos" value at all, so it is nicely compatible with the old levels. It also fixes the bug which allows players to see around paralax layers when the viewport is scaled down. If the layer is large enough to cover the level in the default scale, it now works in all scales. --- src/object/background.cpp | 31 ++++++++++++++++++------------- src/object/tilemap.cpp | 13 ++++++++++--- src/video/drawing_context.cpp | 23 +++++++++++++++++++++++ src/video/drawing_context.hpp | 3 +++ 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/object/background.cpp b/src/object/background.cpp index 1808274671d..7008e00cb4d 100644 --- a/src/object/background.cpp +++ b/src/object/background.cpp @@ -316,8 +316,7 @@ void Background::draw_image(DrawingContext& context, const Vector& pos_) { const Sizef level(d_gameobject_manager->get_width(), d_gameobject_manager->get_height()); - const Sizef screen(context.get_width(), - context.get_height()); + const Sizef screen = context.get_viewport().get_size(); const Sizef parallax_image_size((1.0f - m_parallax_speed.x) * screen.width + level.width * m_parallax_speed.x, (1.0f - m_parallax_speed.y) * screen.height + level.height * m_parallax_speed.y); @@ -334,7 +333,6 @@ Background::draw_image(DrawingContext& context, const Vector& pos_) const int end_y = static_cast(ceilf((cliprect.get_bottom() - (pos_.y + img_h/2.0f)) / img_h)) + 1; Canvas& canvas = context.get_canvas(m_target); - context.set_flip(context.get_flip() ^ m_flip); if (m_fill) { @@ -407,7 +405,6 @@ Background::draw_image(DrawingContext& context, const Vector& pos_) break; } } - context.set_flip(context.get_flip() ^ m_flip); } void @@ -418,19 +415,27 @@ Background::draw(DrawingContext& context) if (!m_image) return; + + context.push_transform(); + if (!context.perspective_scale(m_parallax_speed.x, m_parallax_speed.y)) { + //The background is placed behind the camera. + context.pop_transform(); + return; + } + context.set_flip(context.get_flip() ^ m_flip); - Sizef level_size(d_gameobject_manager->get_width(), + const Sizef level_size(d_gameobject_manager->get_width(), d_gameobject_manager->get_height()); - Sizef screen(context.get_width(), - context.get_height()); - Sizef translation_range = level_size - screen; - Vector center_offset(context.get_translation().x - translation_range.width / 2.0f, - context.get_translation().y - translation_range.height / 2.0f); - - Vector pos(level_size.width / 2, - level_size.height / 2); + const Sizef screen = context.get_viewport().get_size(); + const Sizef translation_range = level_size - screen; + const Vector center_offset(context.get_translation().x - translation_range.width / 2.0f, + context.get_translation().y - translation_range.height / 2.0f); + + const Vector pos(level_size.width / 2, + level_size.height / 2); draw_image(context, pos + m_scroll_offset + Vector(center_offset.x * (1.0f - m_parallax_speed.x), center_offset.y * (1.0f - m_parallax_speed.y))); + context.pop_transform(); } namespace { diff --git a/src/object/tilemap.cpp b/src/object/tilemap.cpp index 422474fc05e..1c457abc4f0 100644 --- a/src/object/tilemap.cpp +++ b/src/object/tilemap.cpp @@ -442,6 +442,15 @@ TileMap::draw(DrawingContext& context) context.push_transform(); + const bool normal_speed = m_editor_active && Editor::is_active(); + const float speed_x = normal_speed ? 1.0f : m_speed_x; + const float speed_y = normal_speed ? 1.0f : m_speed_y; + if (!context.perspective_scale(speed_x, speed_y)) { + //The tilemap is placed behind the camera. + context.pop_transform(); + return; + } + if (m_flip != NO_FLIP) context.set_flip(m_flip); if (m_editor_active) { @@ -454,9 +463,7 @@ TileMap::draw(DrawingContext& context) const float trans_x = context.get_translation().x; const float trans_y = context.get_translation().y; - const bool normal_speed = m_editor_active && Editor::is_active(); - context.set_translation(Vector(trans_x * (normal_speed ? 1.0f : m_speed_x), - trans_y * (normal_speed ? 1.0f : m_speed_y))); + context.set_translation(Vector(trans_x * speed_x, trans_y * speed_y)); Rectf draw_rect = context.get_cliprect(); Rect t_draw_rect = get_tiles_overlapping(draw_rect); diff --git a/src/video/drawing_context.cpp b/src/video/drawing_context.cpp index c33a6d6570f..66bbd62b9fc 100644 --- a/src/video/drawing_context.cpp +++ b/src/video/drawing_context.cpp @@ -133,4 +133,27 @@ DrawingContext::get_size() const return Vector(get_width(), get_height()) * transform().scale; } +bool +DrawingContext::perspective_scale(float speed_x, float speed_y) +{ + DrawingTransform& tfm = transform(); + if (tfm.scale == 1 || speed_x < 0 || speed_y < 0) { + //Trivial or unreal situation: Do not apply perspective. + return true; + } + const float speed = sqrt(speed_x * speed_y); + if (speed == 0) { + //Special case: The object appears to be infinitely far. + tfm.scale = 1.0; + return true; + } + const float t = tfm.scale * (1 / speed - 1) + 1; + if (t <= 0) { + //The object will appear behind the camera, therefore we shall not see it. + return false; + } + tfm.scale /= speed * t; + return true; +} + /* EOF */ diff --git a/src/video/drawing_context.hpp b/src/video/drawing_context.hpp index ad250db5756..737e7b8d16d 100644 --- a/src/video/drawing_context.hpp +++ b/src/video/drawing_context.hpp @@ -77,6 +77,9 @@ class DrawingContext final float get_scale() const { return transform().scale; } void scale(float scale) { transform().scale *= scale; } + + /** Recalculates the scaling factor for parallax layers.*/ + bool perspective_scale(float speed_x, float speed_y); /** Apply that flip in the next draws (flips are listed on surface.h). */ void set_flip(Flip flip);