Skip to content

Commit

Permalink
Move frame prediction logic for camera from Sector to Camera
Browse files Browse the repository at this point in the history
This makes it possible to avoid interpolating the camera position
when a Camera::move or other discontinuous transition is requested.
  • Loading branch information
mstoeckl committed Jan 18, 2025
1 parent c7420cf commit 566c7ed
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 42 deletions.
2 changes: 0 additions & 2 deletions src/editor/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,6 @@ Editor::draw(Compositor& compositor)
m_new_scale = 0.f;
}

m_sector->pause_camera_interpolation();

// Avoid drawing the sector if we're about to test it, as there is a dangling pointer
// issue with the PlayerStatus.
if (!m_leveltested)
Expand Down
40 changes: 38 additions & 2 deletions src/object/camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "math/random.hpp"
#include "math/util.hpp"
#include "object/player.hpp"
#include "supertux/constants.hpp"
#include "supertux/gameconfig.hpp"
#include "supertux/globals.hpp"
#include "supertux/level.hpp"
Expand Down Expand Up @@ -82,7 +83,9 @@ Camera::Camera(const std::string& name) :
m_scale_easing(),
m_scale_anchor(),
m_minimum_scale(1.f),
m_enfore_minimum_scale(false)
m_enfore_minimum_scale(false),
m_last_translation(0.0f, 0.0f),
m_last_scale(1.0f)
{
}

Expand Down Expand Up @@ -118,7 +121,9 @@ Camera::Camera(const ReaderMapping& reader) :
m_scale_easing(),
m_scale_anchor(),
m_minimum_scale(1.f),
m_enfore_minimum_scale(false)
m_enfore_minimum_scale(false),
m_last_translation(0.0f, 0.0f),
m_last_scale(1.0f)
{
std::string modename;

Expand Down Expand Up @@ -207,6 +212,26 @@ Camera::check_state()
PathObject::check_state();
}

void Camera::reset_prediction_state()
{
m_last_translation = get_translation();
m_last_scale = get_current_scale();
}

std::pair<Vector, float>
Camera::get_predicted_transform(float time_offset) const
{
if (g_config->frame_prediction) {
// TODO: extrapolate forwards instead of interpolating over the last frame
float x = time_offset * LOGICAL_FPS;
Vector translation = get_translation() * x + (1.f - x) * m_last_translation;
float scale = get_current_scale() * x + (1.f - x) * m_last_scale;
return std::pair(translation, scale);
} else {
return std::pair(get_translation(), get_current_scale());
}
}

const Vector
Camera::get_translation() const
{
Expand All @@ -218,6 +243,7 @@ void
Camera::set_translation_centered(const Vector& translation)
{
m_translation = translation - m_screen_size.as_vector() / 2;
reset_prediction_state();
}

Rectf
Expand All @@ -237,6 +263,7 @@ Camera::reset(const Vector& tuxpos)
keep_in_bounds(m_translation);

m_cached_translation = m_translation;
reset_prediction_state();
}

void
Expand Down Expand Up @@ -286,6 +313,7 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
m_translation.x = goal.x;
m_translation.y = goal.y;
m_mode = Mode::MANUAL;
reset_prediction_state();
return;
}

Expand Down Expand Up @@ -313,6 +341,10 @@ Camera::draw(DrawingContext& context)
void
Camera::update(float dt_sec)
{
// For use by camera position prediction
m_last_scale = get_current_scale();
m_last_translation = get_translation();

// Minimum scale should be set during the update sequence; else, reset it.
m_enfore_minimum_scale = false;

Expand Down Expand Up @@ -361,6 +393,8 @@ Camera::keep_in_bounds(const Rectf& bounds)

// Remove any scale factor we may have added in the checks above.
m_translation -= scale_factor;

reset_prediction_state();
}

void
Expand Down Expand Up @@ -754,6 +788,8 @@ Camera::ease_scale(float scale, float time, easing ease, AnchorPoint anchor)
m_scale = scale;
if (m_mode == Mode::MANUAL)
m_translation = m_scale_target_translation;

reset_prediction_state();
}
}

Expand Down
19 changes: 18 additions & 1 deletion src/object/camera.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "editor/layer_object.hpp"

#include <string>
#include <utility>

#include "math/anchor_point.hpp"
#include "math/size.hpp"
Expand Down Expand Up @@ -87,9 +88,14 @@ class Camera final : public LayerObject,
/** reset camera position */
void reset(const Vector& tuxpos);

/** Get the predicted translation and scale of the camera, time_offset seconds from now. */
std::pair<Vector, float> get_predicted_transform(float time_offset) const;

/** return camera position */
const Vector get_translation() const;
inline void set_translation(const Vector& translation) { m_translation = translation; }
inline void set_translation(const Vector& translation) {
m_translation = translation; reset_prediction_state();
}
void set_translation_centered(const Vector& translation);

void keep_in_bounds(const Rectf& bounds);
Expand Down Expand Up @@ -271,6 +277,10 @@ class Camera final : public LayerObject,
Vector get_scale_anchor_target() const;
void reload_scale();

/** This function should be called whenever the last updates/changes
* to the camera should not be interpolated or extrapolated. */
void reset_prediction_state();

private:
Mode m_mode;
Mode m_defaultmode;
Expand Down Expand Up @@ -318,6 +328,13 @@ class Camera final : public LayerObject,
float m_minimum_scale;
bool m_enfore_minimum_scale;

// Remember last camera position, to linearly interpolate camera position
// when drawing frames that are predicted forward a fraction of a game step.
// This is somewhat of a hack: ideally these variables would not be necessary
// and one could predict the next camera scale/translation directly from the
// current camera member variable values.
Vector m_last_translation;
float m_last_scale;
private:
Camera(const Camera&) = delete;
Camera& operator=(const Camera&) = delete;
Expand Down
33 changes: 3 additions & 30 deletions src/supertux/sector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,9 +287,6 @@ Sector::activate(const Vector& player_pos)
if (!m_init_script.empty() && !Editor::is_active()) {
run_script(m_init_script, "init-script");
}

// Do not interpolate camera after it has been warped
pause_camera_interpolation();
}

void
Expand Down Expand Up @@ -361,12 +358,6 @@ Sector::update(float dt_sec)

BIND_SECTOR(*this);

// Record last camera parameters, to allow for camera interpolation
Camera& camera = get_camera();
m_last_scale = camera.get_current_scale();
m_last_translation = camera.get_translation();
m_last_dt = dt_sec;

m_squirrel_environment->update(dt_sec);

GameObjectManager::update(dt_sec);
Expand Down Expand Up @@ -483,21 +474,9 @@ Sector::draw(DrawingContext& context)
context.push_transform();

Camera& camera = get_camera();

if (g_config->frame_prediction && m_last_dt > 0.f) {
// Interpolate between two camera settings; there are many possible ways to do this, but on
// short time scales all look about the same. This delays the camera position by one frame.
// (The proper thing to do, of course, would be not to interpolate, but instead to adjust
// the Camera class to extrapolate, and provide scale/translation at a given time; done
// right, this would make it possible to, for example, exactly sinusoidally shake the
// camera instead of piecewise linearly.)
float x = std::min(1.f, context.get_time_offset() / m_last_dt);
context.set_translation(camera.get_translation() * x + (1 - x) * m_last_translation);
context.scale(camera.get_current_scale() * x + (1 - x) * m_last_scale);
} else {
context.set_translation(camera.get_translation());
context.scale(camera.get_current_scale());
}
std::pair<Vector, float> transform = camera.get_predicted_transform(context.get_time_offset());
context.set_translation(transform.first);
context.scale(transform.second);

GameObjectManager::draw(context);

Expand Down Expand Up @@ -742,12 +721,6 @@ Sector::stop_looping_sounds()
}
}

void
Sector::pause_camera_interpolation()
{
m_last_dt = 0.;
}

void Sector::play_looping_sounds()
{
for (const auto& object : get_objects()) {
Expand Down
7 changes: 0 additions & 7 deletions src/supertux/sector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,6 @@ class Sector final : public Base::Sector
/** stops all looping sounds in whole sector. */
void stop_looping_sounds();

/** Freeze camera position for this frame, preventing camera interpolation jumps and loops */
void pause_camera_interpolation();

/** continues the looping sounds in whole sector. */
void play_looping_sounds();

Expand Down Expand Up @@ -263,10 +260,6 @@ class Sector final : public Base::Sector

TextObject& m_text_object;

Vector m_last_translation; // For camera interpolation at high frame rates
float m_last_scale;
float m_last_dt;

private:
Sector(const Sector&) = delete;
Sector& operator=(const Sector&) = delete;
Expand Down

0 comments on commit 566c7ed

Please sign in to comment.