Repository: godotengine/godot (language: cpp)

Issue:
Graphics shake violently if physics interpolation is enabled at runtime
### Tested versions

Reproducible in dev 216b3302

### System information

Godot v4.4.dev (7d645fbea) - Windows 11 (build 22631) - Multi-window, 2 monitors - OpenGL 3 (Compatibility) - NVIDIA GeForce RTX 3070 (NVIDIA; 32.0.15.6636) - AMD Ryzen 7 5800X3D 8-Core Processor (16 threads)

### Issue description

If physics interpolation is initially disabled, and then enabled at runtime, some graphics will start shaking violently as if they're constantly interpolating between two positions but the old and new positions don't get updated.

https://github.com/user-attachments/assets/9a9ecdb4-0bf2-4bf6-bb1c-e760586e7aa0


### Steps to reproduce

- Have `physics/common/physics_interpolation` disabled in Project Settings
- Call `get_tree().set_physics_interpolation_enabled(true);` at some point during runtime

Workaround is to call `get_tree().root.reset_physics_interpolation();` *after* the call to `set_physics_interpolation_enabled()`

Update: This workaround will not work for invisible objects. In fact there is currently no way to automatically reset interpolation on all invisible nodes without additional manual work for every affected object.

### Minimal reproduction project (MRP)

[PhysicsLerpIssue.zip](https://github.com/user-attachments/files/18323729/PhysicsLerpIssue.zip)



Discussion / hints:
On examining the code, there's a check in the canvas item code for whether physics interpolation is enabled, but also for whether the object is visible:

https://github.com/godotengine/godot/blob/1aaf20b1f1f3ab59f04db74b166540327a3f1f90/scene/main/canvas_item.cpp#L378-L382

This unfortunately means that the reset workaround will fail for objects that are initially invisible. I'm assuming that this is done for performance reasons, but it seems like there's currently no way for the user to override that and force a reset of all nodes regardless of their current status.
Updated minimum reproduction showing where the workaround fails:
[PhysicsLerpIssueV2.zip](https://github.com/user-attachments/files/18325744/PhysicsLerpIssueV2.zip)

To reproduce:
- Uncheck "Make Visible"
- Check "Enable Workaround"
- Enable Physics Lerp
- Disable Physics Lerp
- Uncheck "Enable Workaround"
- Check "Make Visible"
- Enable Physics Lerp

Generally switching the global on / off isn't well supported intentionally, and the `set_physics_interpolation_mode()` as these aren't primarily intended to be used at runtime (except for debugging). The idea is generally that you would build your scenes in the editor and set their interpolation modes etc (physics interpolation is always off in the editor).

So getting that to be seamless at runtime is more of a feature request, and would need a use case, but it can probably be done simply with a reset on the root node as you are doing.

In general, any situation where the data flow is not continuous, `reset_physics_interpolation()` will need to be called to prevent streaking. In some limited situations, this is done for you, but it should be your first port of call.

This is mentioned in the docs:
https://docs.godotengine.org/en/3.6/tutorials/physics/interpolation/physics_interpolation_quick_start_guide.html
(I'm not sure about 4.x docs, as I'm not maintaining 4.x, indeed there are several particle features which aren't yet implemented in 4.x.)

Some details are not gone into, because it is subject to change where the auto-resets will happen.

We've added some _minimal_ auto-resets, from memory this occurs when first adding to the tree (there may be more), but most other circumstances you are expected to call reset.

Auto-resets is more complex than you would think to implement without regressions:
* Godot nodes have complex logic for deferring transform updates, and resetting stale transforms would cause glitches
* Different nodes have different order of operations and require special cases and regression testing
* Auto-resets have complex logic and extra maintenance burden (although obviously if they save a lot of user work they are worth it)
* Once we add an auto-reset, it is difficult to remove in terms of backward compatibility
* In some cases auto-resets can _interfere_ or _prevent_ the ability to achieve smooth motion where "priming" is required

For these reasons we've been very conservative adding auto-resets so far, are seeing where the pain points are, and will add them for these cases were possible.

_As an aside, there is actually an auto-reset when calling set_physics_interpolation_mode(). But it currently only occurs on going from interpolated to non-interpolated. I'm investigating whether this was to cure a glitch that way around, or it is simply a historical bug(!) and I maybe intended it to auto-reset here (it is 3 years old lol 😀 )._

Fix patch:
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index a5d2b9c39957..0658122f71b2 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -294,6 +294,7 @@ void Camera2D::_notification(int p_what) {
 			// Force the limits etc to update.
 			_interpolation_data.xform_curr = get_camera_transform();
 			_interpolation_data.xform_prev = _interpolation_data.xform_curr;
+			_update_process_mode();
 		} break;
 		case NOTIFICATION_PAUSED: {
 			if (is_physics_interpolated_and_enabled()) {
diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp
index 218e3dcb3bee..8645e8567ee4 100644
--- a/scene/3d/camera.cpp
+++ b/scene/3d/camera.cpp
@@ -224,6 +224,7 @@ void Camera::_notification(int p_what) {
 			if (is_inside_tree()) {
 				_interpolation_data.xform_curr = get_global_transform();
 				_interpolation_data.xform_prev = _interpolation_data.xform_curr;
+				_update_process_mode();
 			}
 		} break;
 		case NOTIFICATION_PAUSED: {
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 163dc698c51d..0ead3088c871 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -894,13 +894,12 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
 		} break;
 	}
 
-	// if swapping from interpolated to non-interpolated, use this as
-	// an extra means to cause a reset
-	if (is_physics_interpolated() && !interpolate && is_inside_tree()) {
+	_propagate_physics_interpolated(interpolate);
+
+	// Auto-reset on changing interpolation mode.
+	if (is_physics_interpolated() && is_inside_tree()) {
 		propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
 	}
-
-	_propagate_physics_interpolated(interpolate);
 }
 
 void Node::reset_physics_interpolation() {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 3c1809c3586b..1b6bfde0a819 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -537,6 +537,11 @@ void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
 
 	_physics_interpolation_enabled = p_enabled;
 	VisualServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
+
+	// Perform an auto reset on the root node for convenience for the user.
+	if (root) {
+		root->reset_physics_interpolation();
+	}
 }
 
 bool SceneTree::is_physics_interpolation_enabled() const {


Test patch: