Skip to content

Commit 0d074e6

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 d6609a2 commit 0d074e6

File tree

5 files changed

+59
-41
lines changed

5 files changed

+59
-41
lines changed

src/editor/editor.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ Editor::draw(Compositor& compositor)
185185
m_new_scale = 0.f;
186186
}
187187

188-
m_sector->pause_camera_interpolation();
189-
190188
// Avoid drawing the sector if we're about to test it, as there is a dangling pointer
191189
// issue with the PlayerStatus.
192190
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
@@ -20,6 +20,7 @@
2020
#include "editor/layer_object.hpp"
2121

2222
#include <string>
23+
#include <utility>
2324

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

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

94100
void keep_in_bounds(const Rectf& bounds);
@@ -270,6 +276,10 @@ class Camera final : public LayerObject,
270276
Vector get_scale_anchor_target() const;
271277
void reload_scale();
272278

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

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

src/supertux/sector.cpp

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,6 @@ Sector::activate(const Vector& player_pos)
291291
if (m_init_script_run_once)
292292
m_init_script_run = true;
293293
}
294-
295-
// Do not interpolate camera after it has been warped
296-
pause_camera_interpolation();
297294
}
298295

299296
void
@@ -365,12 +362,6 @@ Sector::update(float dt_sec)
365362

366363
BIND_SECTOR(*this);
367364

368-
// Record last camera parameters, to allow for camera interpolation
369-
Camera& camera = get_camera();
370-
m_last_scale = camera.get_current_scale();
371-
m_last_translation = camera.get_translation();
372-
m_last_dt = dt_sec;
373-
374365
m_squirrel_environment->update(dt_sec);
375366

376367
GameObjectManager::update(dt_sec);
@@ -487,21 +478,9 @@ Sector::draw(DrawingContext& context)
487478
context.push_transform();
488479

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

506485
GameObjectManager::draw(context);
507486

@@ -747,12 +726,6 @@ Sector::stop_looping_sounds()
747726
}
748727
}
749728

750-
void
751-
Sector::pause_camera_interpolation()
752-
{
753-
m_last_dt = 0.;
754-
}
755-
756729
void Sector::play_looping_sounds()
757730
{
758731
for (const auto& object : get_objects()) {

src/supertux/sector.hpp

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

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

@@ -265,9 +262,6 @@ class Sector final : public Base::Sector
265262

266263
TextObject& m_text_object;
267264

268-
Vector m_last_translation; // For camera interpolation at high frame rates
269-
float m_last_scale;
270-
float m_last_dt;
271265
bool m_init_script_run;
272266
bool m_init_script_run_once;
273267

0 commit comments

Comments
 (0)