Skip to content

Commit 606b90b

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 606b90b

File tree

5 files changed

+58
-42
lines changed

5 files changed

+58
-42
lines changed

src/editor/editor.cpp

-2
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

+37-2
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,25 @@ 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+
// TODO: extrapolate forwards instead of interpolating over the last frame
223+
float x = time_offset * LOGICAL_FPS;
224+
return get_translation() * x + (1 - x) * m_last_translation;
225+
}
226+
227+
float
228+
Camera::get_predicted_scale(float time_offset) const {
229+
// TODO: extrapolate forwards instead of interpolating over the last frame
230+
float x = time_offset * LOGICAL_FPS;
231+
return get_current_scale() * x + (1 - x) * m_last_scale;
232+
}
233+
210234
const Vector
211235
Camera::get_translation() const
212236
{
@@ -218,6 +242,7 @@ void
218242
Camera::set_translation_centered(const Vector& translation)
219243
{
220244
m_translation = translation - m_screen_size.as_vector() / 2;
245+
reset_prediction_state();
221246
}
222247

223248
Rectf
@@ -237,6 +262,7 @@ Camera::reset(const Vector& tuxpos)
237262
keep_in_bounds(m_translation);
238263

239264
m_cached_translation = m_translation;
265+
reset_prediction_state();
240266
}
241267

242268
void
@@ -286,6 +312,7 @@ Camera::scroll_to(const Vector& goal, float scrolltime)
286312
m_translation.x = goal.x;
287313
m_translation.y = goal.y;
288314
m_mode = Mode::MANUAL;
315+
reset_prediction_state();
289316
return;
290317
}
291318

@@ -313,6 +340,10 @@ Camera::draw(DrawingContext& context)
313340
void
314341
Camera::update(float dt_sec)
315342
{
343+
// For use by camera position prediction
344+
m_last_scale = get_current_scale();
345+
m_last_translation = get_translation();
346+
316347
// Minimum scale should be set during the update sequence; else, reset it.
317348
m_enfore_minimum_scale = false;
318349

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

362393
// Remove any scale factor we may have added in the checks above.
363394
m_translation -= scale_factor;
395+
396+
reset_prediction_state();
364397
}
365398

366399
void
@@ -754,6 +787,8 @@ Camera::ease_scale(float scale, float time, easing ease, AnchorPoint anchor)
754787
m_scale = scale;
755788
if (m_mode == Mode::MANUAL)
756789
m_translation = m_scale_target_translation;
790+
791+
reset_prediction_state();
757792
}
758793
}
759794

src/object/camera.hpp

+19-1
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

+2-30
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

-7
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)