Skip to content

Commit 0c882f1

Browse files
committed
Move frame prediction logic for camera from Sector to Camera
This makes it possible to avoid interpolating the camera position when a Camera::move or other discontinuous transition is requested.
1 parent cebb15f commit 0c882f1

File tree

5 files changed

+59
-42
lines changed

5 files changed

+59
-42
lines changed

src/editor/editor.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,6 @@ Editor::draw(Compositor& compositor)
178178
m_new_scale = 0.f;
179179
}
180180

181-
m_sector->pause_camera_interpolation();
182-
183181
// Avoid drawing the sector if we're about to test it, as there is a dangling pointer
184182
// issue with the PlayerStatus.
185183
if (!m_leveltested)

src/object/camera.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "math/random.hpp"
2626
#include "math/util.hpp"
2727
#include "object/player.hpp"
28+
#include "supertux/constants.hpp"
2829
#include "supertux/gameconfig.hpp"
2930
#include "supertux/globals.hpp"
3031
#include "supertux/level.hpp"
@@ -82,7 +83,9 @@ Camera::Camera(const std::string& name) :
8283
m_scale_easing(),
8384
m_scale_anchor(),
8485
m_minimum_scale(1.f),
85-
m_enfore_minimum_scale(false)
86+
m_enfore_minimum_scale(false),
87+
m_last_translation(0.0f, 0.0f),
88+
m_last_scale(1.0f)
8689
{
8790
}
8891

@@ -118,7 +121,9 @@ Camera::Camera(const ReaderMapping& reader) :
118121
m_scale_easing(),
119122
m_scale_anchor(),
120123
m_minimum_scale(1.f),
121-
m_enfore_minimum_scale(false)
124+
m_enfore_minimum_scale(false),
125+
m_last_translation(0.0f, 0.0f),
126+
m_last_scale(1.0f)
122127
{
123128
std::string modename;
124129

@@ -207,6 +212,26 @@ Camera::check_state()
207212
PathObject::check_state();
208213
}
209214

215+
void Camera::reset_prediction_state()
216+
{
217+
m_last_translation = get_translation();
218+
m_last_scale = get_current_scale();
219+
}
220+
221+
std::pair<Vector, float>
222+
Camera::get_predicted_transform(float time_offset) const
223+
{
224+
if (g_config->frame_prediction) {
225+
// TODO: extrapolate forwards instead of interpolating over the last frame
226+
float x = time_offset * LOGICAL_FPS;
227+
Vector translation = get_translation() * x + (1.f - x) * m_last_translation;
228+
float scale = get_current_scale() * x + (1.f - x) * m_last_scale;
229+
return std::pair(translation, scale);
230+
} else {
231+
return std::pair(get_translation(), get_current_scale());
232+
}
233+
}
234+
210235
const Vector
211236
Camera::get_translation() const
212237
{
@@ -218,6 +243,7 @@ void
218243
Camera::set_translation_centered(const Vector& translation)
219244
{
220245
m_translation = translation - m_screen_size.as_vector() / 2;
246+
reset_prediction_state();
221247
}
222248

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

239265
m_cached_translation = m_translation;
266+
reset_prediction_state();
240267
}
241268

242269
void
@@ -286,6 +313,7 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
286313
m_translation.x = goal.x;
287314
m_translation.y = goal.y;
288315
m_mode = Mode::MANUAL;
316+
reset_prediction_state();
289317
return;
290318
}
291319

@@ -301,6 +329,10 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
301329
void
302330
Camera::update(float dt_sec)
303331
{
332+
// For use by camera position prediction
333+
m_last_scale = get_current_scale();
334+
m_last_translation = get_translation();
335+
304336
// Fetch the current screen size from the VideoSystem. The main loop always
305337
// processes input and window events, updates game logic, and then draws zero
306338
// or more frames, in that order, so this screen size will be used by the next
@@ -357,6 +389,8 @@ Camera::keep_in_bounds(const Rectf& bounds)
357389

358390
// Remove any scale factor we may have added in the checks above.
359391
m_translation -= scale_factor;
392+
393+
reset_prediction_state();
360394
}
361395

362396
void
@@ -750,6 +784,8 @@ Camera::ease_scale(float scale, float time, easing ease, AnchorPoint anchor)
750784
m_scale = scale;
751785
if (m_mode == Mode::MANUAL)
752786
m_translation = m_scale_target_translation;
787+
788+
reset_prediction_state();
753789
}
754790
}
755791

src/object/camera.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "editor/layer_object.hpp"
2222

2323
#include <string>
24+
#include <utility>
2425

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

91+
/** Get the predicted translation and scale of the camera, time_offset seconds from now. */
92+
std::pair<Vector, float> get_predicted_transform(float time_offset) const;
93+
9094
/** return camera position */
9195
const Vector get_translation() const;
92-
inline void set_translation(const Vector& translation) { m_translation = translation; }
96+
inline void set_translation(const Vector& translation) {
97+
m_translation = translation; reset_prediction_state();
98+
}
9399
void set_translation_centered(const Vector& translation);
94100

95101
void keep_in_bounds(const Rectf& bounds);
@@ -271,6 +277,10 @@ class Camera final : public LayerObject,
271277
Vector get_scale_anchor_target() const;
272278
void reload_scale();
273279

280+
/** This function should be called whenever the last updates/changes
281+
* to the camera should not be interpolated or extrapolated. */
282+
void reset_prediction_state();
283+
274284
private:
275285
Mode m_mode;
276286
Mode m_defaultmode;
@@ -318,6 +328,13 @@ class Camera final : public LayerObject,
318328
float m_minimum_scale;
319329
bool m_enfore_minimum_scale;
320330

331+
// Remember last camera position, to linearly interpolate camera position
332+
// when drawing frames that are predicted forward a fraction of a game step.
333+
// This is somewhat of a hack: ideally these variables would not be necessary
334+
// and one could predict the next camera scale/translation directly from the
335+
// current camera member variable values.
336+
Vector m_last_translation;
337+
float m_last_scale;
321338
private:
322339
Camera(const Camera&) = delete;
323340
Camera& operator=(const Camera&) = delete;

src/supertux/sector.cpp

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,6 @@ Sector::activate(const Vector& player_pos)
287287
if (!m_init_script.empty() && !Editor::is_active()) {
288288
run_script(m_init_script, "init-script");
289289
}
290-
291-
// Do not interpolate camera after it has been warped
292-
pause_camera_interpolation();
293290
}
294291

295292
void
@@ -361,12 +358,6 @@ Sector::update(float dt_sec)
361358

362359
BIND_SECTOR(*this);
363360

364-
// Record last camera parameters, to allow for camera interpolation
365-
Camera& camera = get_camera();
366-
m_last_scale = camera.get_current_scale();
367-
m_last_translation = camera.get_translation();
368-
m_last_dt = dt_sec;
369-
370361
m_squirrel_environment->update(dt_sec);
371362

372363
GameObjectManager::update(dt_sec);
@@ -483,21 +474,9 @@ Sector::draw(DrawingContext& context)
483474
context.push_transform();
484475

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

502481
GameObjectManager::draw(context);
503482

@@ -742,12 +721,6 @@ Sector::stop_looping_sounds()
742721
}
743722
}
744723

745-
void
746-
Sector::pause_camera_interpolation()
747-
{
748-
m_last_dt = 0.;
749-
}
750-
751724
void Sector::play_looping_sounds()
752725
{
753726
for (const auto& object : get_objects()) {

src/supertux/sector.hpp

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ class Sector final : public Base::Sector
9999
/** stops all looping sounds in whole sector. */
100100
void stop_looping_sounds();
101101

102-
/** Freeze camera position for this frame, preventing camera interpolation jumps and loops */
103-
void pause_camera_interpolation();
104-
105102
/** continues the looping sounds in whole sector. */
106103
void play_looping_sounds();
107104

@@ -263,10 +260,6 @@ class Sector final : public Base::Sector
263260

264261
TextObject& m_text_object;
265262

266-
Vector m_last_translation; // For camera interpolation at high frame rates
267-
float m_last_scale;
268-
float m_last_dt;
269-
270263
private:
271264
Sector(const Sector&) = delete;
272265
Sector& operator=(const Sector&) = delete;

0 commit comments

Comments
 (0)