Skip to content

Move frame prediction logic for camera from Sector to Camera #3173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/editor/editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,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 All @@ -301,6 +329,10 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
void
Camera::update(float dt_sec)
{
// For use by camera position prediction
m_last_scale = get_current_scale();
m_last_translation = get_translation();

// Fetch the current screen size from the VideoSystem. The main loop always
// processes input and window events, updates game logic, and then draws zero
// or more frames, in that order, so this screen size will be used by the next
Expand Down Expand Up @@ -357,6 +389,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 @@ -750,6 +784,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
Loading