Fixed Timestep Interpolation (2D)

Adds fixed timestep interpolation to the rendering server (2D only).
Switchable on and off with a project setting (default is off).

Co-authored-by: lawnjelly <lawnjelly@gmail.com>
This commit is contained in:
Ricardo Buring
2024-02-17 00:57:32 +01:00
parent fe01776f05
commit 2ed2ccc2d8
39 changed files with 1040 additions and 75 deletions

View File

@@ -54,7 +54,12 @@ void Camera2D::_update_scroll() {
if (is_current()) {
ERR_FAIL_COND(custom_viewport && !ObjectDB::get_instance(custom_viewport_id));
Transform2D xform = get_camera_transform();
Transform2D xform;
if (is_physics_interpolated_and_enabled()) {
xform = _interpolation_data.xform_prev.interpolate_with(_interpolation_data.xform_curr, Engine::get_singleton()->get_physics_interpolation_fraction());
} else {
xform = get_camera_transform();
}
viewport->set_canvas_transform(xform);
@@ -68,15 +73,26 @@ void Camera2D::_update_scroll() {
}
void Camera2D::_update_process_callback() {
if (_is_editing_in_editor()) {
if (is_physics_interpolated_and_enabled()) {
set_process_internal(is_current());
set_physics_process_internal(is_current());
#ifdef TOOLS_ENABLED
if (process_callback == CAMERA2D_PROCESS_IDLE) {
WARN_PRINT_ONCE("Camera2D overridden to physics process mode due to use of physics interpolation.");
}
#endif
} else if (_is_editing_in_editor()) {
set_process_internal(false);
set_physics_process_internal(false);
} else if (process_callback == CAMERA2D_PROCESS_IDLE) {
set_process_internal(true);
set_physics_process_internal(false);
} else {
set_process_internal(false);
set_physics_process_internal(true);
if (process_callback == CAMERA2D_PROCESS_IDLE) {
set_process_internal(true);
set_physics_process_internal(false);
} else {
set_process_internal(false);
set_physics_process_internal(true);
}
}
}
@@ -161,8 +177,15 @@ Transform2D Camera2D::get_camera_transform() {
}
}
// FIXME: There is a bug here, introduced before physics interpolation.
// Smoothing occurs rather confusingly during the call to get_camera_transform().
// It may be called MULTIPLE TIMES on certain frames,
// therefore smoothing is not currently applied only once per frame / tick,
// which will result in some haphazard results.
if (position_smoothing_enabled && !_is_editing_in_editor()) {
real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
bool physics_process = (process_callback == CAMERA2D_PROCESS_PHYSICS) || is_physics_interpolated_and_enabled();
real_t delta = physics_process ? get_physics_process_delta_time() : get_process_delta_time();
real_t c = position_smoothing_speed * delta;
smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
ret_camera_pos = smoothed_camera_pos;
//camera_pos=camera_pos*(1.0-position_smoothing_speed)+new_camera_pos*position_smoothing_speed;
@@ -223,19 +246,54 @@ Transform2D Camera2D::get_camera_transform() {
return xform.affine_inverse();
}
void Camera2D::_ensure_update_interpolation_data() {
// The "curr -> previous" update can either occur
// on NOTIFICATION_INTERNAL_PHYSICS_PROCESS, OR
// on NOTIFICATION_TRANSFORM_CHANGED,
// if NOTIFICATION_TRANSFORM_CHANGED takes place earlier than
// NOTIFICATION_INTERNAL_PHYSICS_PROCESS on a tick.
// This is to ensure that the data keeps flowing, but the new data
// doesn't overwrite before prev has been set.
// Keep the data flowing.
uint64_t tick = Engine::get_singleton()->get_physics_frames();
if (_interpolation_data.last_update_physics_tick != tick) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
_interpolation_data.last_update_physics_tick = tick;
}
}
void Camera2D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_INTERNAL_PROCESS:
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
case NOTIFICATION_INTERNAL_PROCESS: {
_update_scroll();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if (!position_smoothing_enabled || _is_editing_in_editor()) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
} else {
_update_scroll();
}
} break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
// Force the limits etc. to update.
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
if ((!position_smoothing_enabled && !is_physics_interpolated_and_enabled()) || _is_editing_in_editor()) {
_update_scroll();
}
if (is_physics_interpolated_and_enabled()) {
_ensure_update_interpolation_data();
_interpolation_data.xform_curr = get_camera_transform();
}
} break;
case NOTIFICATION_ENTER_TREE: {
ERR_FAIL_COND(!is_inside_tree());
if (custom_viewport && ObjectDB::get_instance(custom_viewport_id)) {
@@ -260,6 +318,15 @@ void Camera2D::_notification(int p_what) {
_update_process_callback();
first = true;
_update_scroll();
// Note that NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// is automatically called before this because Camera2D is inherited
// from CanvasItem. However, the camera transform is not up to date
// until this point, so we do an extra manual reset.
if (is_physics_interpolated_and_enabled()) {
_interpolation_data.xform_curr = get_camera_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
}
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -431,12 +498,17 @@ void Camera2D::_make_current(Object *p_which) {
queue_redraw();
if (p_which == this) {
bool was_current = viewport->get_camera_2d() == this;
bool is_current = p_which == this;
if (is_current) {
viewport->_camera_2d_set(this);
} else {
if (viewport->get_camera_2d() == this) {
viewport->_camera_2d_set(nullptr);
}
} else if (was_current) {
viewport->_camera_2d_set(nullptr);
}
if (is_current != was_current) {
_update_process_callback();
}
}
@@ -456,6 +528,7 @@ void Camera2D::make_current() {
_make_current(this);
}
_update_scroll();
_update_process_callback();
}
void Camera2D::clear_current() {
@@ -468,6 +541,8 @@ void Camera2D::clear_current() {
if (!custom_viewport || ObjectDB::get_instance(custom_viewport_id)) {
viewport->assign_next_enabled_camera_2d(group_name);
}
_update_process_callback();
}
bool Camera2D::is_current() const {