Skip to content

Commit 3758735

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 6b5506c commit 3758735

File tree

5 files changed

+66
-42
lines changed

5 files changed

+66
-42
lines changed

src/editor/editor.cpp

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

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

src/object/camera.cpp

Lines changed: 45 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,33 @@ Camera::check_state()
207212
PathObject::check_state();
208213
}
209214

215+
void Camera::reset_prediction_state() {
216+
m_last_translation = get_translation();
217+
m_last_scale = get_current_scale();
218+
}
219+
220+
Vector
221+
Camera::get_predicted_translation(float time_offset) const {
222+
if (g_config->frame_prediction) {
223+
// TODO: extrapolate forwards instead of interpolating over the last frame
224+
float x = time_offset * LOGICAL_FPS;
225+
return get_translation() * x + (1 - x) * m_last_translation;
226+
} else {
227+
return get_translation();
228+
}
229+
}
230+
231+
float
232+
Camera::get_predicted_scale(float time_offset) const {
233+
if (g_config->frame_prediction) {
234+
// TODO: extrapolate forwards instead of interpolating over the last frame
235+
float x = time_offset * LOGICAL_FPS;
236+
return get_current_scale() * x + (1 - x) * m_last_scale;
237+
} else {
238+
return get_current_scale();
239+
}
240+
}
241+
210242
const Vector
211243
Camera::get_translation() const
212244
{
@@ -218,6 +250,7 @@ void
218250
Camera::set_translation_centered(const Vector& translation)
219251
{
220252
m_translation = translation - m_screen_size.as_vector() / 2;
253+
reset_prediction_state();
221254
}
222255

223256
Rectf
@@ -237,6 +270,7 @@ Camera::reset(const Vector& tuxpos)
237270
keep_in_bounds(m_translation);
238271

239272
m_cached_translation = m_translation;
273+
reset_prediction_state();
240274
}
241275

242276
void
@@ -286,6 +320,7 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
286320
m_translation.x = goal.x;
287321
m_translation.y = goal.y;
288322
m_mode = Mode::MANUAL;
323+
reset_prediction_state();
289324
return;
290325
}
291326

@@ -313,6 +348,10 @@ Camera::draw(DrawingContext& context)
313348
void
314349
Camera::update(float dt_sec)
315350
{
351+
// For use by camera position prediction
352+
m_last_scale = get_current_scale();
353+
m_last_translation = get_translation();
354+
316355
// Minimum scale should be set during the update sequence; else, reset it.
317356
m_enfore_minimum_scale = false;
318357

@@ -361,6 +400,8 @@ Camera::keep_in_bounds(const Rectf& bounds)
361400

362401
// Remove any scale factor we may have added in the checks above.
363402
m_translation -= scale_factor;
403+
404+
reset_prediction_state();
364405
}
365406

366407
void
@@ -754,6 +795,8 @@ Camera::ease_scale(float scale, float time, easing ease, AnchorPoint anchor)
754795
m_scale = scale;
755796
if (m_mode == Mode::MANUAL)
756797
m_translation = m_scale_target_translation;
798+
799+
reset_prediction_state();
757800
}
758801
}
759802

src/object/camera.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,16 @@ class Camera final : public LayerObject,
8787
/** reset camera position */
8888
void reset(const Vector& tuxpos);
8989

90+
/** Get the predicted position of the camera, time_offset seconds from now. */
91+
Vector get_predicted_translation(float time_offset) const;
92+
/** Get the predicted scale of the camera, time_offset seconds from now. */
93+
float get_predicted_scale(float time_offset) const;
94+
9095
/** return camera position */
9196
const Vector get_translation() const;
92-
inline void set_translation(const Vector& translation) { m_translation = translation; }
97+
inline void set_translation(const Vector& translation) {
98+
m_translation = translation; reset_prediction_state();
99+
}
93100
void set_translation_centered(const Vector& translation);
94101

95102
void keep_in_bounds(const Rectf& bounds);
@@ -271,6 +278,10 @@ class Camera final : public LayerObject,
271278
Vector get_scale_anchor_target() const;
272279
void reload_scale();
273280

281+
/** This function should be called whenever the last updates/changes
282+
* to the camera should not be interpolated or extrapolated. */
283+
void reset_prediction_state();
284+
274285
private:
275286
Mode m_mode;
276287
Mode m_defaultmode;
@@ -318,6 +329,13 @@ class Camera final : public LayerObject,
318329
float m_minimum_scale;
319330
bool m_enfore_minimum_scale;
320331

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

src/supertux/sector.cpp

Lines changed: 2 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,8 @@ 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+
context.set_translation(camera.get_predicted_translation(context.get_time_offset()));
478+
context.scale(camera.get_predicted_scale(context.get_time_offset()));
501479

502480
GameObjectManager::draw(context);
503481

@@ -742,12 +720,6 @@ Sector::stop_looping_sounds()
742720
}
743721
}
744722

745-
void
746-
Sector::pause_camera_interpolation()
747-
{
748-
m_last_dt = 0.;
749-
}
750-
751723
void Sector::play_looping_sounds()
752724
{
753725
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)