Skip to content

Commit

Permalink
Scale parallax layers perspectively-correctly (#2954)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Hume2 authored Jul 27, 2024
1 parent d700288 commit 5a50578
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 16 deletions.
31 changes: 18 additions & 13 deletions src/object/background.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -334,7 +333,6 @@ Background::draw_image(DrawingContext& context, const Vector& pos_)
const int end_y = static_cast<int>(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)
{
Expand Down Expand Up @@ -407,7 +405,6 @@ Background::draw_image(DrawingContext& context, const Vector& pos_)
break;
}
}
context.set_flip(context.get_flip() ^ m_flip);
}

void
Expand All @@ -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 {
Expand Down
13 changes: 10 additions & 3 deletions src/object/tilemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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);
Expand Down
23 changes: 23 additions & 0 deletions src/video/drawing_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
3 changes: 3 additions & 0 deletions src/video/drawing_context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 5a50578

Please sign in to comment.