Browse Source

Fixed Timestep Interpolation (3D)

Adds 3D fixed timestep interpolation to the rendering server.
This does not yet include support for multimeshes or particles.

Co-authored-by: lawnjelly <[email protected]>
Ricardo Buring 1 year ago
parent
commit
2f8ab4a654

+ 51 - 0
core/error/error_macros.cpp

@@ -34,6 +34,12 @@
 #include "core/os/os.h"
 #include "core/os/os.h"
 #include "core/string/ustring.h"
 #include "core/string/ustring.h"
 
 
+// Optional physics interpolation warnings try to include the path to the relevant node.
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+#include "core/config/project_settings.h"
+#include "scene/main/node.h"
+#endif
+
 static ErrorHandlerList *error_handler_list = nullptr;
 static ErrorHandlerList *error_handler_list = nullptr;
 
 
 void add_error_handler(ErrorHandlerList *p_handler) {
 void add_error_handler(ErrorHandlerList *p_handler) {
@@ -128,3 +134,48 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
 void _err_flush_stdout() {
 void _err_flush_stdout() {
 	fflush(stdout);
 	fflush(stdout);
 }
 }
+
+// Prevent error spam by limiting the warnings to a certain frequency.
+void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string) {
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+	const uint32_t warn_max = 2048;
+	const uint32_t warn_timeout_seconds = 15;
+
+	static uint32_t warn_count = warn_max;
+	static uint32_t warn_timeout = warn_timeout_seconds;
+
+	uint32_t time_now = UINT32_MAX;
+
+	if (warn_count) {
+		warn_count--;
+	}
+
+	if (!warn_count) {
+		time_now = OS::get_singleton()->get_ticks_msec() / 1000;
+	}
+
+	if ((warn_count == 0) && (time_now >= warn_timeout)) {
+		warn_count = warn_max;
+		warn_timeout = time_now + warn_timeout_seconds;
+
+		if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) {
+			// UINT64_MAX means unused.
+			if (p_id.operator uint64_t() == UINT64_MAX) {
+				_err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + " (possibly benign).", false, ERR_HANDLER_WARNING);
+			} else {
+				String node_name;
+				if (p_id.is_valid()) {
+					Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+					if (node && node->is_inside_tree()) {
+						node_name = "\"" + String(node->get_path()) + "\"";
+					} else {
+						node_name = "\"unknown\"";
+					}
+				}
+
+				_err_print_error(p_function, p_file, p_line, "[Physics interpolation] " + String(p_warn_string) + ": " + node_name + " (possibly benign).", false, ERR_HANDLER_WARNING);
+			}
+		}
+	}
+#endif
+}

+ 13 - 0
core/error/error_macros.h

@@ -31,6 +31,7 @@
 #ifndef ERROR_MACROS_H
 #ifndef ERROR_MACROS_H
 #define ERROR_MACROS_H
 #define ERROR_MACROS_H
 
 
+#include "core/object/object_id.h"
 #include "core/typedefs.h"
 #include "core/typedefs.h"
 
 
 #include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes.
 #include <atomic> // We'd normally use safe_refcount.h, but that would cause circular includes.
@@ -71,6 +72,8 @@ void _err_print_index_error(const char *p_function, const char *p_file, int p_li
 void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false);
 void _err_print_index_error(const char *p_function, const char *p_file, int p_line, int64_t p_index, int64_t p_size, const char *p_index_str, const char *p_size_str, const String &p_message, bool p_editor_notify = false, bool fatal = false);
 void _err_flush_stdout();
 void _err_flush_stdout();
 
 
+void _physics_interpolation_warning(const char *p_function, const char *p_file, int p_line, ObjectID p_id, const char *p_warn_string);
+
 #ifdef __GNUC__
 #ifdef __GNUC__
 //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying
 //#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying
 #define FUNCTION_STR __FUNCTION__
 #define FUNCTION_STR __FUNCTION__
@@ -832,4 +835,14 @@ void _err_flush_stdout();
 #define DEV_CHECK_ONCE(m_cond)
 #define DEV_CHECK_ONCE(m_cond)
 #endif
 #endif
 
 
+/**
+ * Physics Interpolation warnings.
+ * These are spam protection warnings.
+ */
+#define PHYSICS_INTERPOLATION_NODE_WARNING(m_object_id, m_string) \
+	_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, m_object_id, m_string)
+
+#define PHYSICS_INTERPOLATION_WARNING(m_string) \
+	_physics_interpolation_warning(FUNCTION_STR, __FILE__, __LINE__, UINT64_MAX, m_string)
+
 #endif // ERROR_MACROS_H
 #endif // ERROR_MACROS_H

+ 338 - 0
core/math/transform_interpolator.cpp

@@ -31,6 +31,7 @@
 #include "transform_interpolator.h"
 #include "transform_interpolator.h"
 
 
 #include "core/math/transform_2d.h"
 #include "core/math/transform_2d.h"
+#include "core/math/transform_3d.h"
 
 
 void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
 void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction) {
 	// Special case for physics interpolation, if flipping, don't interpolate basis.
 	// Special case for physics interpolation, if flipping, don't interpolate basis.
@@ -44,3 +45,340 @@ void TransformInterpolator::interpolate_transform_2d(const Transform2D &p_prev,
 
 
 	r_result = p_prev.interpolate_with(p_curr, p_fraction);
 	r_result = p_prev.interpolate_with(p_curr, p_fraction);
 }
 }
+
+void TransformInterpolator::interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction) {
+	r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction);
+	interpolate_basis(p_prev.basis, p_curr.basis, r_result.basis, p_fraction);
+}
+
+void TransformInterpolator::interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) {
+	Method method = find_method(p_prev, p_curr);
+	interpolate_basis_via_method(p_prev, p_curr, r_result, p_fraction, method);
+}
+
+void TransformInterpolator::interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method) {
+	r_result.origin = p_prev.origin + ((p_curr.origin - p_prev.origin) * p_fraction);
+	interpolate_basis_via_method(p_prev.basis, p_curr.basis, r_result.basis, p_fraction, p_method);
+}
+
+void TransformInterpolator::interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method) {
+	switch (p_method) {
+		default: {
+			interpolate_basis_linear(p_prev, p_curr, r_result, p_fraction);
+		} break;
+		case INTERP_SLERP: {
+			r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction);
+		} break;
+		case INTERP_SCALED_SLERP: {
+			interpolate_basis_scaled_slerp(p_prev, p_curr, r_result, p_fraction);
+		} break;
+	}
+}
+
+Quaternion TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) {
+	Basis m = p_basis;
+	real_t trace = m.rows[0][0] + m.rows[1][1] + m.rows[2][2];
+	real_t temp[4];
+
+	if (trace > 0.0) {
+		real_t s = Math::sqrt(trace + 1.0f);
+		temp[3] = (s * 0.5f);
+		s = 0.5f / s;
+
+		temp[0] = ((m.rows[2][1] - m.rows[1][2]) * s);
+		temp[1] = ((m.rows[0][2] - m.rows[2][0]) * s);
+		temp[2] = ((m.rows[1][0] - m.rows[0][1]) * s);
+	} else {
+		int i = m.rows[0][0] < m.rows[1][1]
+				? (m.rows[1][1] < m.rows[2][2] ? 2 : 1)
+				: (m.rows[0][0] < m.rows[2][2] ? 2 : 0);
+		int j = (i + 1) % 3;
+		int k = (i + 2) % 3;
+
+		real_t s = Math::sqrt(m.rows[i][i] - m.rows[j][j] - m.rows[k][k] + 1.0f);
+		temp[i] = s * 0.5f;
+		s = 0.5f / s;
+
+		temp[3] = (m.rows[k][j] - m.rows[j][k]) * s;
+		temp[j] = (m.rows[j][i] + m.rows[i][j]) * s;
+		temp[k] = (m.rows[k][i] + m.rows[i][k]) * s;
+	}
+
+	return Quaternion(temp[0], temp[1], temp[2], temp[3]);
+}
+
+Quaternion TransformInterpolator::_quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction) {
+	Quaternion to1;
+	real_t omega, cosom, sinom, scale0, scale1;
+
+	// Calculate cosine.
+	cosom = p_from.dot(p_to);
+
+	// Adjust signs (if necessary)
+	if (cosom < 0.0f) {
+		cosom = -cosom;
+		to1.x = -p_to.x;
+		to1.y = -p_to.y;
+		to1.z = -p_to.z;
+		to1.w = -p_to.w;
+	} else {
+		to1.x = p_to.x;
+		to1.y = p_to.y;
+		to1.z = p_to.z;
+		to1.w = p_to.w;
+	}
+
+	// Calculate coefficients.
+
+	// This check could possibly be removed as we dealt with this
+	// case in the find_method() function, but is left for safety, it probably
+	// isn't a bottleneck.
+	if ((1.0f - cosom) > (real_t)CMP_EPSILON) {
+		// standard case (slerp)
+		omega = Math::acos(cosom);
+		sinom = Math::sin(omega);
+		scale0 = Math::sin((1.0f - p_fraction) * omega) / sinom;
+		scale1 = Math::sin(p_fraction * omega) / sinom;
+	} else {
+		// "from" and "to" quaternions are very close
+		//  ... so we can do a linear interpolation
+		scale0 = 1.0f - p_fraction;
+		scale1 = p_fraction;
+	}
+	// Calculate final values.
+	return Quaternion(
+			scale0 * p_from.x + scale1 * to1.x,
+			scale0 * p_from.y + scale1 * to1.y,
+			scale0 * p_from.z + scale1 * to1.z,
+			scale0 * p_from.w + scale1 * to1.w);
+}
+
+Basis TransformInterpolator::_basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction) {
+	Quaternion from = _basis_to_quat_unchecked(p_from);
+	Quaternion to = _basis_to_quat_unchecked(p_to);
+
+	Basis b(_quat_slerp_unchecked(from, to, p_fraction));
+	return b;
+}
+
+void TransformInterpolator::interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction) {
+	// Normalize both and find lengths.
+	Vector3 lengths_prev = _basis_orthonormalize(p_prev);
+	Vector3 lengths_curr = _basis_orthonormalize(p_curr);
+
+	r_result = _basis_slerp_unchecked(p_prev, p_curr, p_fraction);
+
+	// Now the result is unit length basis, we need to scale.
+	Vector3 lengths_lerped = lengths_prev + ((lengths_curr - lengths_prev) * p_fraction);
+
+	// Keep a note that the column / row order of the basis is weird,
+	// so keep an eye for bugs with this.
+	r_result[0] *= lengths_lerped;
+	r_result[1] *= lengths_lerped;
+	r_result[2] *= lengths_lerped;
+}
+
+void TransformInterpolator::interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction) {
+	// Interpolate basis.
+	r_result = p_prev.lerp(p_curr, p_fraction);
+
+	// It turns out we need to guard against zero scale basis.
+	// This is kind of silly, as we should probably fix the bugs elsewhere in Godot that can't deal with
+	// zero scale, but until that time...
+	for (int n = 0; n < 3; n++) {
+		Vector3 &axis = r_result[n];
+
+		// Not ok, this could cause errors due to bugs elsewhere,
+		// so we will bodge set this to a small value.
+		const real_t smallest = 0.0001f;
+		const real_t smallest_squared = smallest * smallest;
+		if (axis.length_squared() < smallest_squared) {
+			// Setting a different component to the smallest
+			// helps prevent the situation where all the axes are pointing in the same direction,
+			// which could be a problem for e.g. cross products...
+			axis[n] = smallest;
+		}
+	}
+}
+
+// Returns length.
+real_t TransformInterpolator::_vec3_normalize(Vector3 &p_vec) {
+	real_t lengthsq = p_vec.length_squared();
+	if (lengthsq == 0.0f) {
+		p_vec.x = p_vec.y = p_vec.z = 0.0f;
+		return 0.0f;
+	}
+	real_t length = Math::sqrt(lengthsq);
+	p_vec.x /= length;
+	p_vec.y /= length;
+	p_vec.z /= length;
+	return length;
+}
+
+// Returns lengths.
+Vector3 TransformInterpolator::_basis_orthonormalize(Basis &r_basis) {
+	// Gram-Schmidt Process.
+
+	Vector3 x = r_basis.get_column(0);
+	Vector3 y = r_basis.get_column(1);
+	Vector3 z = r_basis.get_column(2);
+
+	Vector3 lengths;
+
+	lengths.x = _vec3_normalize(x);
+	y = (y - x * (x.dot(y)));
+	lengths.y = _vec3_normalize(y);
+	z = (z - x * (x.dot(z)) - y * (y.dot(z)));
+	lengths.z = _vec3_normalize(z);
+
+	r_basis.set_column(0, x);
+	r_basis.set_column(1, y);
+	r_basis.set_column(2, z);
+
+	return lengths;
+}
+
+TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat) {
+	// Axis lengths.
+	Vector3 al = Vector3(p_basis.get_column(0).length_squared(),
+			p_basis.get_column(1).length_squared(),
+			p_basis.get_column(2).length_squared());
+
+	// Non unit scale?
+	if (r_needed_normalize || !_vec3_is_equal_approx(al, Vector3(1.0, 1.0, 1.0), (real_t)0.001f)) {
+		// If the basis is not normalized (at least approximately), it will fail the checks needed for slerp.
+		// So we try to detect a scaled (but not sheared) basis, which we *can* slerp by normalizing first,
+		// and lerping the scales separately.
+
+		// If any of the axes are really small, it is unlikely to be a valid rotation, or is scaled too small to deal with float error.
+		const real_t sl_epsilon = 0.00001f;
+		if ((al.x < sl_epsilon) ||
+				(al.y < sl_epsilon) ||
+				(al.z < sl_epsilon)) {
+			return INTERP_LERP;
+		}
+
+		// Normalize the basis.
+		Basis norm_basis = p_basis;
+
+		al.x = Math::sqrt(al.x);
+		al.y = Math::sqrt(al.y);
+		al.z = Math::sqrt(al.z);
+
+		norm_basis.set_column(0, norm_basis.get_column(0) / al.x);
+		norm_basis.set_column(1, norm_basis.get_column(1) / al.y);
+		norm_basis.set_column(2, norm_basis.get_column(2) / al.z);
+
+		// This doesn't appear necessary, as the later checks will catch it.
+		// if (!_basis_is_orthogonal_any_scale(norm_basis)) {
+		// return INTERP_LERP;
+		// }
+
+		p_basis = norm_basis;
+
+		// Orthonormalize not necessary as normal normalization(!) works if the
+		// axes are orthonormal.
+		// p_basis.orthonormalize();
+
+		// If we needed to normalize one of the two bases, we will need to normalize both,
+		// regardless of whether the 2nd needs it, just to make sure it takes the path to return
+		// INTERP_SCALED_LERP on the 2nd call of _test_basis.
+		r_needed_normalize = true;
+	}
+
+	// Apply less stringent tests than the built in slerp, the standard Godot slerp
+	// is too susceptible to float error to be useful.
+	real_t det = p_basis.determinant();
+	if (!Math::is_equal_approx(det, 1, (real_t)0.01f)) {
+		return INTERP_LERP;
+	}
+
+	if (!_basis_is_orthogonal(p_basis)) {
+		return INTERP_LERP;
+	}
+
+	// TODO: This could possibly be less stringent too, check this.
+	r_quat = _basis_to_quat_unchecked(p_basis);
+	if (!r_quat.is_normalized()) {
+		return INTERP_LERP;
+	}
+
+	return r_needed_normalize ? INTERP_SCALED_SLERP : INTERP_SLERP;
+}
+
+// This check doesn't seem to be needed but is preserved in case of bugs.
+bool TransformInterpolator::_basis_is_orthogonal_any_scale(const Basis &p_basis) {
+	Vector3 cross = p_basis.get_column(0).cross(p_basis.get_column(1));
+	real_t l = _vec3_normalize(cross);
+	// Too small numbers, revert to lerp.
+	if (l < 0.001f) {
+		return false;
+	}
+
+	const real_t epsilon = 0.9995f;
+
+	real_t dot = cross.dot(p_basis.get_column(2));
+	if (dot < epsilon) {
+		return false;
+	}
+
+	cross = p_basis.get_column(1).cross(p_basis.get_column(2));
+	l = _vec3_normalize(cross);
+	// Too small numbers, revert to lerp.
+	if (l < 0.001f) {
+		return false;
+	}
+
+	dot = cross.dot(p_basis.get_column(0));
+	if (dot < epsilon) {
+		return false;
+	}
+
+	return true;
+}
+
+bool TransformInterpolator::_basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon) {
+	Basis identity;
+	Basis m = p_basis * p_basis.transposed();
+
+	// Less stringent tests than the standard Godot slerp.
+	if (!_vec3_is_equal_approx(m[0], identity[0], p_epsilon) || !_vec3_is_equal_approx(m[1], identity[1], p_epsilon) || !_vec3_is_equal_approx(m[2], identity[2], p_epsilon)) {
+		return false;
+	}
+	return true;
+}
+
+real_t TransformInterpolator::checksum_transform_3d(const Transform3D &p_transform) {
+	// just a really basic checksum, this can probably be improved
+	real_t sum = _vec3_sum(p_transform.origin);
+	sum -= _vec3_sum(p_transform.basis.rows[0]);
+	sum += _vec3_sum(p_transform.basis.rows[1]);
+	sum -= _vec3_sum(p_transform.basis.rows[2]);
+	return sum;
+}
+
+TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) {
+	bool needed_normalize = false;
+
+	Quaternion q0;
+	Method method = _test_basis(p_a, needed_normalize, q0);
+	if (method == INTERP_LERP) {
+		return method;
+	}
+
+	Quaternion q1;
+	method = _test_basis(p_b, needed_normalize, q1);
+	if (method == INTERP_LERP) {
+		return method;
+	}
+
+	// Are they close together?
+	// Apply the same test that will revert to lerp as is present in the slerp routine.
+	// Calculate cosine.
+	real_t cosom = Math::abs(q0.dot(q1));
+	if ((1.0f - cosom) <= (real_t)CMP_EPSILON) {
+		return INTERP_LERP;
+	}
+
+	return method;
+}

+ 50 - 1
core/math/transform_interpolator.h

@@ -32,15 +32,64 @@
 #define TRANSFORM_INTERPOLATOR_H
 #define TRANSFORM_INTERPOLATOR_H
 
 
 #include "core/math/math_defs.h"
 #include "core/math/math_defs.h"
+#include "core/math/vector3.h"
+
+// Keep all the functions for fixed timestep interpolation together.
+// There are two stages involved:
+// Finding a method, for determining the interpolation method between two
+// keyframes (which are physics ticks).
+// And applying that pre-determined method.
+
+// Pre-determining the method makes sense because it is expensive and often
+// several frames may occur between each physics tick, which will make it cheaper
+// than performing every frame.
 
 
 struct Transform2D;
 struct Transform2D;
+struct Transform3D;
+struct Basis;
+struct Quaternion;
 
 
 class TransformInterpolator {
 class TransformInterpolator {
+public:
+	enum Method {
+		INTERP_LERP,
+		INTERP_SLERP,
+		INTERP_SCALED_SLERP,
+	};
+
 private:
 private:
-	static bool _sign(real_t p_val) { return p_val >= 0; }
+	_FORCE_INLINE_ static bool _sign(real_t p_val) { return p_val >= 0; }
+	static real_t _vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; }
+	static real_t _vec3_normalize(Vector3 &p_vec);
+	_FORCE_INLINE_ static bool _vec3_is_equal_approx(const Vector3 &p_a, const Vector3 &p_b, real_t p_tolerance) {
+		return Math::is_equal_approx(p_a.x, p_b.x, p_tolerance) && Math::is_equal_approx(p_a.y, p_b.y, p_tolerance) && Math::is_equal_approx(p_a.z, p_b.z, p_tolerance);
+	}
+	static Vector3 _basis_orthonormalize(Basis &r_basis);
+	static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quaternion &r_quat);
+	static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction);
+	static Quaternion _quat_slerp_unchecked(const Quaternion &p_from, const Quaternion &p_to, real_t p_fraction);
+	static Quaternion _basis_to_quat_unchecked(const Basis &p_basis);
+	static bool _basis_is_orthogonal(const Basis &p_basis, real_t p_epsilon = 0.01f);
+	static bool _basis_is_orthogonal_any_scale(const Basis &p_basis);
+
+	static void interpolate_basis_linear(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction);
+	static void interpolate_basis_scaled_slerp(Basis p_prev, Basis p_curr, Basis &r_result, real_t p_fraction);
 
 
 public:
 public:
 	static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
 	static void interpolate_transform_2d(const Transform2D &p_prev, const Transform2D &p_curr, Transform2D &r_result, real_t p_fraction);
+
+	// Generic functions, use when you don't know what method should be used, e.g. from GDScript.
+	// These will be slower.
+	static void interpolate_transform_3d(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction);
+	static void interpolate_basis(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction);
+
+	// Optimized function when you know ahead of time the method.
+	static void interpolate_transform_3d_via_method(const Transform3D &p_prev, const Transform3D &p_curr, Transform3D &r_result, real_t p_fraction, Method p_method);
+	static void interpolate_basis_via_method(const Basis &p_prev, const Basis &p_curr, Basis &r_result, real_t p_fraction, Method p_method);
+
+	static real_t checksum_transform_3d(const Transform3D &p_transform);
+
+	static Method find_method(const Basis &p_a, const Basis &p_b);
 };
 };
 
 
 #endif // TRANSFORM_INTERPOLATOR_H
 #endif // TRANSFORM_INTERPOLATOR_H

+ 1 - 0
core/os/main_loop.h

@@ -64,6 +64,7 @@ public:
 	virtual void initialize();
 	virtual void initialize();
 	virtual void iteration_prepare() {}
 	virtual void iteration_prepare() {}
 	virtual bool physics_process(double p_time);
 	virtual bool physics_process(double p_time);
+	virtual void iteration_end() {}
 	virtual bool process(double p_time);
 	virtual bool process(double p_time);
 	virtual void finalize();
 	virtual void finalize();
 
 

+ 8 - 0
doc/classes/Node3D.xml

@@ -46,6 +46,14 @@
 				Returns all the gizmos attached to this [Node3D].
 				Returns all the gizmos attached to this [Node3D].
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_global_transform_interpolated">
+			<return type="Transform3D" />
+			<description>
+				When using physics interpolation, there will be circumstances in which you want to know the interpolated (displayed) transform of a node rather than the standard transform (which may only be accurate to the most recent physics tick).
+				This is particularly important for frame-based operations that take place in [method Node._process], rather than [method Node._physics_process]. Examples include [Camera3D]s focusing on a node, or finding where to fire lasers from on a frame rather than physics tick.
+				[b]Note:[/b] This function creates an interpolation pump on the [Node3D] the first time it is called, which can respond to physics interpolation resets. If you get problems with "streaking" when initially following a [Node3D], be sure to call [method get_global_transform_interpolated] at least once [i]before[/i] resetting the [Node3D] physics interpolation.
+			</description>
+		</method>
 		<method name="get_parent_node_3d" qualifiers="const">
 		<method name="get_parent_node_3d" qualifiers="const">
 			<return type="Node3D" />
 			<return type="Node3D" />
 			<description>
 			<description>

+ 6 - 1
doc/classes/ProjectSettings.xml

@@ -630,6 +630,10 @@
 		<member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024">
 		<member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024">
 			Maximum call stack allowed for debugging GDScript.
 			Maximum call stack allowed for debugging GDScript.
 		</member>
 		</member>
+		<member name="debug/settings/physics_interpolation/enable_warnings" type="bool" setter="" getter="" default="true">
+			If [code]true[/code], enables warnings which can help pinpoint where nodes are being incorrectly updated, which will result in incorrect interpolation and visual glitches.
+			When a node is being interpolated, it is essential that the transform is set during [method Node._physics_process] (during a physics tick) rather than [method Node._process] (during a frame).
+		</member>
 		<member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384">
 		<member name="debug/settings/profiler/max_functions" type="int" setter="" getter="" default="16384">
 			Maximum number of functions per frame allowed when profiling.
 			Maximum number of functions per frame allowed when profiling.
 		</member>
 		</member>
@@ -2325,7 +2329,8 @@
 		</member>
 		</member>
 		<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
 		<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
 			Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
 			Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be good enough for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended.
-			[b]Note:[/b] When using a physics interpolation solution (such as enabling [member physics/common/physics_interpolation] or using a custom solution), the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
+			[b]Note:[/b] Jitter fix is automatically disabled at runtime when [member physics/common/physics_interpolation] is enabled.
+			[b]Note:[/b] When using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0.0[/code].
 			[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
 			[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
 		</member>
 		</member>
 		<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">
 		<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">

+ 16 - 0
doc/classes/RenderingServer.xml

@@ -1855,6 +1855,14 @@
 				Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties.
 				Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="instance_reset_physics_interpolation">
+			<return type="void" />
+			<param index="0" name="instance" type="RID" />
+			<description>
+				Prevents physics interpolation for the current physics tick.
+				This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location.
+			</description>
+		</method>
 		<method name="instance_set_base">
 		<method name="instance_set_base">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="instance" type="RID" />
 			<param index="0" name="instance" type="RID" />
@@ -1896,6 +1904,14 @@
 				If [code]true[/code], ignores both frustum and occlusion culling on the specified 3D geometry instance. This is not the same as [member GeometryInstance3D.ignore_occlusion_culling], which only ignores occlusion culling and leaves frustum culling intact.
 				If [code]true[/code], ignores both frustum and occlusion culling on the specified 3D geometry instance. This is not the same as [member GeometryInstance3D.ignore_occlusion_culling], which only ignores occlusion culling and leaves frustum culling intact.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="instance_set_interpolated">
+			<return type="void" />
+			<param index="0" name="instance" type="RID" />
+			<param index="1" name="interpolated" type="bool" />
+			<description>
+				Turns on and off physics interpolation for the instance.
+			</description>
+		</method>
 		<method name="instance_set_layer_mask">
 		<method name="instance_set_layer_mask">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="instance" type="RID" />
 			<param index="0" name="instance" type="RID" />

+ 1 - 0
doc/classes/Viewport.xml

@@ -301,6 +301,7 @@
 		<member name="own_world_3d" type="bool" setter="set_use_own_world_3d" getter="is_using_own_world_3d" default="false">
 		<member name="own_world_3d" type="bool" setter="set_use_own_world_3d" getter="is_using_own_world_3d" default="false">
 			If [code]true[/code], the viewport will use a unique copy of the [World3D] defined in [member world_3d].
 			If [code]true[/code], the viewport will use a unique copy of the [World3D] defined in [member world_3d].
 		</member>
 		</member>
+		<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="1" />
 		<member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false">
 		<member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false">
 			If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process.
 			If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process.
 			[b]Note:[/b] The number of simultaneously pickable objects is limited to 64 and they are selected in a non-deterministic order, which can be different in each picking process.
 			[b]Note:[/b] The number of simultaneously pickable objects is limited to 64 and they are selected in a non-deterministic order, which can be different in each picking process.

+ 9 - 5
main/main.cpp

@@ -2380,6 +2380,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 	GLOBAL_DEF("debug/settings/stdout/print_fps", false);
 	GLOBAL_DEF("debug/settings/stdout/print_fps", false);
 	GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false);
 	GLOBAL_DEF("debug/settings/stdout/print_gpu_profile", false);
 	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
 	GLOBAL_DEF("debug/settings/stdout/verbose_stdout", false);
+	GLOBAL_DEF("debug/settings/physics_interpolation/enable_warnings", true);
 
 
 	if (!OS::get_singleton()->_verbose_stdout) { // Not manually overridden.
 	if (!OS::get_singleton()->_verbose_stdout) { // Not manually overridden.
 		OS::get_singleton()->_verbose_stdout = GLOBAL_GET("debug/settings/stdout/verbose_stdout");
 		OS::get_singleton()->_verbose_stdout = GLOBAL_GET("debug/settings/stdout/verbose_stdout");
@@ -4047,16 +4048,16 @@ bool Main::iteration() {
 
 
 		uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
 		uint64_t physics_begin = OS::get_singleton()->get_ticks_usec();
 
 
-#ifndef _3D_DISABLED
-		PhysicsServer3D::get_singleton()->sync();
-		PhysicsServer3D::get_singleton()->flush_queries();
-#endif // _3D_DISABLED
-
 		// Prepare the fixed timestep interpolated nodes BEFORE they are updated
 		// Prepare the fixed timestep interpolated nodes BEFORE they are updated
 		// by the physics server, otherwise the current and previous transforms
 		// by the physics server, otherwise the current and previous transforms
 		// may be the same, and no interpolation takes place.
 		// may be the same, and no interpolation takes place.
 		OS::get_singleton()->get_main_loop()->iteration_prepare();
 		OS::get_singleton()->get_main_loop()->iteration_prepare();
 
 
+#ifndef _3D_DISABLED
+		PhysicsServer3D::get_singleton()->sync();
+		PhysicsServer3D::get_singleton()->flush_queries();
+#endif // _3D_DISABLED
+
 		PhysicsServer2D::get_singleton()->sync();
 		PhysicsServer2D::get_singleton()->sync();
 		PhysicsServer2D::get_singleton()->flush_queries();
 		PhysicsServer2D::get_singleton()->flush_queries();
 
 
@@ -4066,6 +4067,7 @@ bool Main::iteration() {
 #endif // _3D_DISABLED
 #endif // _3D_DISABLED
 			PhysicsServer2D::get_singleton()->end_sync();
 			PhysicsServer2D::get_singleton()->end_sync();
 
 
+			Engine::get_singleton()->_in_physics = false;
 			exit = true;
 			exit = true;
 			break;
 			break;
 		}
 		}
@@ -4089,6 +4091,8 @@ bool Main::iteration() {
 
 
 		message_queue->flush();
 		message_queue->flush();
 
 
+		OS::get_singleton()->get_main_loop()->iteration_end();
+
 		physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference
 		physics_process_ticks = MAX(physics_process_ticks, OS::get_singleton()->get_ticks_usec() - physics_begin); // keep the largest one for reference
 		physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
 		physics_process_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
 
 

+ 0 - 11
main/main_timer_sync.cpp

@@ -299,17 +299,6 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
 // before advance_core considers changing the physics_steps return from
 // before advance_core considers changing the physics_steps return from
 // the typical values as defined by typical_physics_steps
 // the typical values as defined by typical_physics_steps
 double MainTimerSync::get_physics_jitter_fix() {
 double MainTimerSync::get_physics_jitter_fix() {
-	// Turn off jitter fix when using fixed timestep interpolation.
-	// Note this shouldn't be on UNTIL 3d interpolation is implemented,
-	// otherwise we will get people making 3d games with the physics_interpolation
-	// set to on getting jitter fix disabled unexpectedly.
-#if 0
-	if (Engine::get_singleton()->is_physics_interpolation_enabled()) {
-		// Would be better to write a simple bypass for jitter fix but this will do to get started.
-		return 0.0;
-	}
-#endif
-
 	return Engine::get_singleton()->get_physics_jitter_fix();
 	return Engine::get_singleton()->get_physics_jitter_fix();
 }
 }
 
 

+ 135 - 3
scene/3d/camera_3d.cpp

@@ -31,7 +31,9 @@
 #include "camera_3d.h"
 #include "camera_3d.h"
 
 
 #include "core/math/projection.h"
 #include "core/math/projection.h"
+#include "core/math/transform_interpolator.h"
 #include "scene/main/viewport.h"
 #include "scene/main/viewport.h"
+#include "servers/rendering/rendering_server_constants.h"
 
 
 void Camera3D::_update_audio_listener_state() {
 void Camera3D::_update_audio_listener_state() {
 }
 }
@@ -88,7 +90,16 @@ void Camera3D::_update_camera() {
 		return;
 		return;
 	}
 	}
 
 
-	RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
+	if (!is_physics_interpolated_and_enabled()) {
+		RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
+	} else {
+		// Ideally we shouldn't be moving a physics interpolated camera within a frame,
+		// because it will break smooth interpolation, but it may occur on e.g. level load.
+		if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) {
+			_physics_interpolation_ensure_transform_calculated(true);
+			RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+		}
+	}
 
 
 	if (is_part_of_edited_scene() || !is_current()) {
 	if (is_part_of_edited_scene() || !is_current()) {
 		return;
 		return;
@@ -97,6 +108,64 @@ void Camera3D::_update_camera() {
 	get_viewport()->_camera_3d_transform_changed_notify();
 	get_viewport()->_camera_3d_transform_changed_notify();
 }
 }
 
 
+void Camera3D::_physics_interpolated_changed() {
+	_update_process_mode();
+}
+
+void Camera3D::_physics_interpolation_ensure_data_flipped() {
+	// The curr -> previous update can either occur
+	// on the INTERNAL_PHYSICS_PROCESS OR
+	// on NOTIFICATION_TRANSFORM_CHANGED,
+	// if NOTIFICATION_TRANSFORM_CHANGED takes place
+	// earlier than 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;
+		physics_interpolation_flip_data();
+	}
+}
+
+void Camera3D::_physics_interpolation_ensure_transform_calculated(bool p_force) const {
+	DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame());
+
+	InterpolationData &id = _interpolation_data;
+	uint64_t frame = Engine::get_singleton()->get_frames_drawn();
+
+	if (id.last_update_frame != frame || p_force) {
+		id.last_update_frame = frame;
+
+		TransformInterpolator::interpolate_transform_3d(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction());
+
+		Transform3D &tr = id.camera_xform_interpolated;
+		tr = _get_adjusted_camera_transform(id.xform_interpolated);
+	}
+}
+
+void Camera3D::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) {
+	_desired_process_internal = p_process_internal;
+	_desired_physics_process_internal = p_physics_process_internal;
+	_update_process_mode();
+}
+
+void Camera3D::_update_process_mode() {
+	bool process = _desired_process_internal;
+	bool physics_process = _desired_physics_process_internal;
+
+	if (is_physics_interpolated_and_enabled()) {
+		if (is_current()) {
+			process = true;
+			physics_process = true;
+		}
+	}
+	set_process_internal(process);
+	set_physics_process_internal(physics_process);
+}
+
 void Camera3D::_notification(int p_what) {
 void Camera3D::_notification(int p_what) {
 	switch (p_what) {
 	switch (p_what) {
 		case NOTIFICATION_ENTER_WORLD: {
 		case NOTIFICATION_ENTER_WORLD: {
@@ -118,11 +187,58 @@ void Camera3D::_notification(int p_what) {
 #endif
 #endif
 		} break;
 		} break;
 
 
+		case NOTIFICATION_INTERNAL_PROCESS: {
+			if (is_physics_interpolated_and_enabled() && camera.is_valid()) {
+				_physics_interpolation_ensure_transform_calculated();
+
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+				print_line("\t\tinterpolated Camera3D: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
+#endif
+
+				RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+			}
+		} break;
+
+		case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
+			if (is_physics_interpolated_and_enabled()) {
+				_physics_interpolation_ensure_data_flipped();
+				_interpolation_data.xform_curr = get_global_transform();
+			}
+		} break;
+
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 		case NOTIFICATION_TRANSFORM_CHANGED: {
+			if (is_physics_interpolated_and_enabled()) {
+				_physics_interpolation_ensure_data_flipped();
+				_interpolation_data.xform_curr = get_global_transform();
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+				if (!Engine::get_singleton()->is_in_physics_frame()) {
+					PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera3D triggered from outside physics process");
+				}
+#endif
+			}
 			_request_camera_update();
 			_request_camera_update();
 			if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
 			if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
 				velocity_tracker->update_position(get_global_transform().origin);
 				velocity_tracker->update_position(get_global_transform().origin);
 			}
 			}
+			// Allow auto-reset when first adding to the tree, as a convenience.
+			if (_is_physics_interpolation_reset_requested() && is_inside_tree()) {
+				_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+				_set_physics_interpolation_reset_requested(false);
+			}
+		} break;
+
+		case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+			if (is_inside_tree()) {
+				_interpolation_data.xform_curr = get_global_transform();
+				_interpolation_data.xform_prev = _interpolation_data.xform_curr;
+			}
+		} break;
+
+		case NOTIFICATION_PAUSED: {
+			if (is_physics_interpolated_and_enabled() && is_inside_tree() && is_visible_in_tree()) {
+				_physics_interpolation_ensure_transform_calculated(true);
+				RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
+			}
 		} break;
 		} break;
 
 
 		case NOTIFICATION_EXIT_WORLD: {
 		case NOTIFICATION_EXIT_WORLD: {
@@ -151,23 +267,34 @@ void Camera3D::_notification(int p_what) {
 			if (viewport) {
 			if (viewport) {
 				viewport->find_world_3d()->_register_camera(this);
 				viewport->find_world_3d()->_register_camera(this);
 			}
 			}
+			_update_process_mode();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_LOST_CURRENT: {
 		case NOTIFICATION_LOST_CURRENT: {
 			if (viewport) {
 			if (viewport) {
 				viewport->find_world_3d()->_remove_camera(this);
 				viewport->find_world_3d()->_remove_camera(this);
 			}
 			}
+			_update_process_mode();
 		} break;
 		} break;
 	}
 	}
 }
 }
 
 
-Transform3D Camera3D::get_camera_transform() const {
-	Transform3D tr = get_global_transform().orthonormalized();
+Transform3D Camera3D::_get_adjusted_camera_transform(const Transform3D &p_xform) const {
+	Transform3D tr = p_xform.orthonormalized();
 	tr.origin += tr.basis.get_column(1) * v_offset;
 	tr.origin += tr.basis.get_column(1) * v_offset;
 	tr.origin += tr.basis.get_column(0) * h_offset;
 	tr.origin += tr.basis.get_column(0) * h_offset;
 	return tr;
 	return tr;
 }
 }
 
 
+Transform3D Camera3D::get_camera_transform() const {
+	if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) {
+		_physics_interpolation_ensure_transform_calculated();
+		return _interpolation_data.camera_xform_interpolated;
+	}
+
+	return _get_adjusted_camera_transform(get_global_transform());
+}
+
 Projection Camera3D::_get_camera_projection(real_t p_near) const {
 Projection Camera3D::_get_camera_projection(real_t p_near) const {
 	Size2 viewport_size = get_viewport()->get_visible_rect().size;
 	Size2 viewport_size = get_viewport()->get_visible_rect().size;
 	Projection cm;
 	Projection cm;
@@ -379,6 +506,11 @@ Point2 Camera3D::unproject_position(const Vector3 &p_pos) const {
 	Plane p(get_camera_transform().xform_inv(p_pos), 1.0);
 	Plane p(get_camera_transform().xform_inv(p_pos), 1.0);
 
 
 	p = cm.xform4(p);
 	p = cm.xform4(p);
+
+	// Prevent divide by zero.
+	// TODO: Investigate, this was causing NaNs.
+	ERR_FAIL_COND_V(p.d == 0, Point2());
+
 	p.normal /= p.d;
 	p.normal /= p.d;
 
 
 	Point2 res;
 	Point2 res;

+ 32 - 0
scene/3d/camera_3d.h

@@ -98,7 +98,39 @@ private:
 	RID pyramid_shape;
 	RID pyramid_shape;
 	Vector<Vector3> pyramid_shape_points;
 	Vector<Vector3> pyramid_shape_points;
 
 
+	///////////////////////////////////////////////////////
+	// INTERPOLATION FUNCTIONS
+	void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const;
+	void _physics_interpolation_ensure_data_flipped();
+
+	// These can be set by derived Camera3Ds, if they wish to do processing
+	// (while still allowing physics interpolation to function).
+	bool _desired_process_internal = false;
+	bool _desired_physics_process_internal = false;
+
+	mutable struct InterpolationData {
+		Transform3D xform_curr;
+		Transform3D xform_prev;
+		Transform3D xform_interpolated;
+		Transform3D camera_xform_interpolated; // After modification according to camera type.
+		uint32_t last_update_physics_tick = 0;
+		uint32_t last_update_frame = UINT32_MAX;
+	} _interpolation_data;
+
+	void _update_process_mode();
+
 protected:
 protected:
+	// Use from derived classes to set process modes instead of setting directly.
+	// This is because physics interpolation may need to request process modes additionally.
+	void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal);
+
+	// Opportunity for derived classes to interpolate extra attributes.
+	virtual void physics_interpolation_flip_data() {}
+
+	virtual void _physics_interpolated_changed() override;
+	virtual Transform3D _get_adjusted_camera_transform(const Transform3D &p_xform) const;
+	///////////////////////////////////////////////////////
+
 	void _update_camera();
 	void _update_camera();
 	virtual void _request_camera_update();
 	virtual void _request_camera_update();
 	void _update_camera_mode();
 	void _update_camera_mode();

+ 146 - 1
scene/3d/node_3d.cpp

@@ -30,6 +30,7 @@
 
 
 #include "node_3d.h"
 #include "node_3d.h"
 
 
+#include "core/math/transform_interpolator.h"
 #include "scene/3d/visual_instance_3d.h"
 #include "scene/3d/visual_instance_3d.h"
 #include "scene/main/viewport.h"
 #include "scene/main/viewport.h"
 #include "scene/property_utils.h"
 #include "scene/property_utils.h"
@@ -176,6 +177,7 @@ void Node3D::_notification(int p_what) {
 			data.parent = nullptr;
 			data.parent = nullptr;
 			data.C = nullptr;
 			data.C = nullptr;
 			_update_visibility_parent(true);
 			_update_visibility_parent(true);
+			_disable_client_physics_interpolation();
 		} break;
 		} break;
 
 
 		case NOTIFICATION_ENTER_WORLD: {
 		case NOTIFICATION_ENTER_WORLD: {
@@ -226,6 +228,12 @@ void Node3D::_notification(int p_what) {
 			}
 			}
 #endif
 #endif
 		} break;
 		} break;
+
+		case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+			if (data.client_physics_interpolation_data) {
+				data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
+			}
+		} break;
 	}
 	}
 }
 }
 
 
@@ -341,6 +349,119 @@ Transform3D Node3D::get_transform() const {
 	return data.local_transform;
 	return data.local_transform;
 }
 }
 
 
+// Return false to timeout and remove from the client interpolation list.
+bool Node3D::update_client_physics_interpolation_data() {
+	if (!is_inside_tree() || !_is_physics_interpolated_client_side()) {
+		return false;
+	}
+
+	ERR_FAIL_NULL_V(data.client_physics_interpolation_data, false);
+	ClientPhysicsInterpolationData &pid = *data.client_physics_interpolation_data;
+
+	uint64_t tick = Engine::get_singleton()->get_physics_frames();
+
+	// Has this update been done already this tick?
+	// (For instance, get_global_transform_interpolated() could be called multiple times.)
+	if (pid.current_physics_tick != tick) {
+		// Timeout?
+		if (tick >= pid.timeout_physics_tick) {
+			return false;
+		}
+
+		if (pid.current_physics_tick == (tick - 1)) {
+			// Normal interpolation situation, there is a continuous flow of data
+			// from one tick to the next...
+			pid.global_xform_prev = pid.global_xform_curr;
+		} else {
+			// There has been a gap, we cannot sensibly offer interpolation over
+			// a multitick gap, so we will teleport.
+			pid.global_xform_prev = get_global_transform();
+		}
+		pid.current_physics_tick = tick;
+	}
+
+	pid.global_xform_curr = get_global_transform();
+	return true;
+}
+
+void Node3D::_disable_client_physics_interpolation() {
+	// Disable any current client side interpolation.
+	// (This can always restart as normal if you later re-attach the node to the SceneTree.)
+	if (data.client_physics_interpolation_data) {
+		memdelete(data.client_physics_interpolation_data);
+		data.client_physics_interpolation_data = nullptr;
+
+		SceneTree *tree = get_tree();
+		if (tree && _client_physics_interpolation_node_3d_list.in_list()) {
+			tree->client_physics_interpolation_remove_node_3d(&_client_physics_interpolation_node_3d_list);
+		}
+	}
+	_set_physics_interpolated_client_side(false);
+}
+
+Transform3D Node3D::_get_global_transform_interpolated(real_t p_interpolation_fraction) {
+	ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
+
+	// Set in motion the mechanisms for client side interpolation if not already active.
+	if (!_is_physics_interpolated_client_side()) {
+		_set_physics_interpolated_client_side(true);
+
+		ERR_FAIL_COND_V(data.client_physics_interpolation_data != nullptr, Transform3D());
+		data.client_physics_interpolation_data = memnew(ClientPhysicsInterpolationData);
+		data.client_physics_interpolation_data->global_xform_curr = get_global_transform();
+		data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
+		data.client_physics_interpolation_data->current_physics_tick = Engine::get_singleton()->get_physics_frames();
+	}
+
+	// Storing the last tick we requested client interpolation allows us to timeout
+	// and remove client interpolated nodes from the list to save processing.
+	// We use some arbitrary timeout here, but this could potentially be user defined.
+
+	// Note: This timeout has to be larger than the number of ticks in a frame, otherwise the interpolated
+	// data will stop flowing before the next frame is drawn. This should only be relevant at high tick rates.
+	// We could alternatively do this by frames rather than ticks and avoid this problem, but then the behavior
+	// would be machine dependent.
+	data.client_physics_interpolation_data->timeout_physics_tick = Engine::get_singleton()->get_physics_frames() + 256;
+
+	// Make sure data is up to date.
+	update_client_physics_interpolation_data();
+
+	// Interpolate the current data.
+	const Transform3D &xform_curr = data.client_physics_interpolation_data->global_xform_curr;
+	const Transform3D &xform_prev = data.client_physics_interpolation_data->global_xform_prev;
+
+	Transform3D res;
+	TransformInterpolator::interpolate_transform_3d(xform_prev, xform_curr, res, p_interpolation_fraction);
+
+	SceneTree *tree = get_tree();
+
+	// This should not happen, as is_inside_tree() is checked earlier.
+	ERR_FAIL_NULL_V(tree, res);
+	if (!_client_physics_interpolation_node_3d_list.in_list()) {
+		tree->client_physics_interpolation_add_node_3d(&_client_physics_interpolation_node_3d_list);
+	}
+
+	return res;
+}
+
+Transform3D Node3D::get_global_transform_interpolated() {
+	// Pass through if physics interpolation is switched off.
+	// This is a convenience, as it allows you to easy turn off interpolation
+	// without changing any code.
+	if (!is_physics_interpolated_and_enabled()) {
+		return get_global_transform();
+	}
+
+	// If we are in the physics frame, the interpolated global transform is meaningless.
+	// However, there is an exception, we may want to use this as a means of starting off the client
+	// interpolation pump if not already started (when _is_physics_interpolated_client_side() is false).
+	if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) {
+		return get_global_transform();
+	}
+
+	return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction());
+}
+
 Transform3D Node3D::get_global_transform() const {
 Transform3D Node3D::get_global_transform() const {
 	ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
 	ERR_FAIL_COND_V(!is_inside_tree(), Transform3D());
 
 
@@ -1140,6 +1261,7 @@ void Node3D::_bind_methods() {
 
 
 	ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
 	ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Node3D::set_global_transform);
 	ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
 	ClassDB::bind_method(D_METHOD("get_global_transform"), &Node3D::get_global_transform);
+	ClassDB::bind_method(D_METHOD("get_global_transform_interpolated"), &Node3D::get_global_transform_interpolated);
 	ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position);
 	ClassDB::bind_method(D_METHOD("set_global_position", "position"), &Node3D::set_global_position);
 	ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position);
 	ClassDB::bind_method(D_METHOD("get_global_position"), &Node3D::get_global_position);
 	ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis);
 	ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis);
@@ -1236,4 +1358,27 @@ void Node3D::_bind_methods() {
 }
 }
 
 
 Node3D::Node3D() :
 Node3D::Node3D() :
-		xform_change(this) {}
+		xform_change(this), _client_physics_interpolation_node_3d_list(this) {
+	// Default member initializer for bitfield is a C++20 extension, so:
+
+	data.top_level = false;
+	data.inside_world = false;
+
+	data.ignore_notification = false;
+	data.notify_local_transform = false;
+	data.notify_transform = false;
+
+	data.visible = true;
+	data.disable_scale = false;
+	data.vi_visible = true;
+
+#ifdef TOOLS_ENABLED
+	data.gizmos_disabled = false;
+	data.gizmos_dirty = false;
+	data.transform_gizmo_visible = true;
+#endif
+}
+
+Node3D::~Node3D() {
+	_disable_client_physics_interpolation();
+}

+ 34 - 11
scene/3d/node_3d.h

@@ -85,7 +85,15 @@ private:
 		DIRTY_GLOBAL_TRANSFORM = 4
 		DIRTY_GLOBAL_TRANSFORM = 4
 	};
 	};
 
 
+	struct ClientPhysicsInterpolationData {
+		Transform3D global_xform_curr;
+		Transform3D global_xform_prev;
+		uint64_t current_physics_tick = 0;
+		uint64_t timeout_physics_tick = 0;
+	};
+
 	mutable SelfList<Node> xform_change;
 	mutable SelfList<Node> xform_change;
+	SelfList<Node3D> _client_physics_interpolation_node_3d_list;
 
 
 	// This Data struct is to avoid namespace pollution in derived classes.
 	// This Data struct is to avoid namespace pollution in derived classes.
 
 
@@ -101,8 +109,19 @@ private:
 
 
 		Viewport *viewport = nullptr;
 		Viewport *viewport = nullptr;
 
 
-		bool top_level = false;
-		bool inside_world = false;
+		bool top_level : 1;
+		bool inside_world : 1;
+
+		// This is cached, and only currently kept up to date in visual instances.
+		// This is set if a visual instance is (a) in the tree AND (b) visible via is_visible_in_tree() call.
+		bool vi_visible : 1;
+
+		bool ignore_notification : 1;
+		bool notify_local_transform : 1;
+		bool notify_transform : 1;
+
+		bool visible : 1;
+		bool disable_scale : 1;
 
 
 		RID visibility_parent;
 		RID visibility_parent;
 
 
@@ -110,18 +129,13 @@ private:
 		List<Node3D *> children;
 		List<Node3D *> children;
 		List<Node3D *>::Element *C = nullptr;
 		List<Node3D *>::Element *C = nullptr;
 
 
-		bool ignore_notification = false;
-		bool notify_local_transform = false;
-		bool notify_transform = false;
-
-		bool visible = true;
-		bool disable_scale = false;
+		ClientPhysicsInterpolationData *client_physics_interpolation_data = nullptr;
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 		Vector<Ref<Node3DGizmo>> gizmos;
 		Vector<Ref<Node3DGizmo>> gizmos;
-		bool gizmos_disabled = false;
-		bool gizmos_dirty = false;
-		bool transform_gizmo_visible = true;
+		bool gizmos_disabled : 1;
+		bool gizmos_dirty : 1;
+		bool transform_gizmo_visible : 1;
 #endif
 #endif
 
 
 	} data;
 	} data;
@@ -150,6 +164,11 @@ protected:
 	_FORCE_INLINE_ void _update_local_transform() const;
 	_FORCE_INLINE_ void _update_local_transform() const;
 	_FORCE_INLINE_ void _update_rotation_and_scale() const;
 	_FORCE_INLINE_ void _update_rotation_and_scale() const;
 
 
+	void _set_vi_visible(bool p_visible) { data.vi_visible = p_visible; }
+	bool _is_vi_visible() const { return data.vi_visible; }
+	Transform3D _get_global_transform_interpolated(real_t p_interpolation_fraction);
+	void _disable_client_physics_interpolation();
+
 	void _notification(int p_what);
 	void _notification(int p_what);
 	static void _bind_methods();
 	static void _bind_methods();
 
 
@@ -208,6 +227,9 @@ public:
 	Quaternion get_quaternion() const;
 	Quaternion get_quaternion() const;
 	Transform3D get_global_transform() const;
 	Transform3D get_global_transform() const;
 
 
+	Transform3D get_global_transform_interpolated();
+	bool update_client_physics_interpolation_data();
+
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	virtual Transform3D get_global_gizmo_transform() const;
 	virtual Transform3D get_global_gizmo_transform() const;
 	virtual Transform3D get_local_gizmo_transform() const;
 	virtual Transform3D get_local_gizmo_transform() const;
@@ -279,6 +301,7 @@ public:
 	NodePath get_visibility_parent() const;
 	NodePath get_visibility_parent() const;
 
 
 	Node3D();
 	Node3D();
+	~Node3D();
 };
 };
 
 
 VARIANT_ENUM_CAST(Node3D::RotationEditMode)
 VARIANT_ENUM_CAST(Node3D::RotationEditMode)

+ 5 - 1
scene/3d/skeleton_ik_3d.cpp

@@ -503,7 +503,11 @@ Transform3D SkeletonIK3D::_get_target_transform() {
 
 
 	Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object());
 	Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object());
 	if (target_node_override && target_node_override->is_inside_tree()) {
 	if (target_node_override && target_node_override->is_inside_tree()) {
-		return target_node_override->get_global_transform();
+		// Make sure to use the interpolated transform as target.
+		// When physics interpolation is off this will pass through to get_global_transform().
+		// When using interpolation, ensure that the target matches the interpolated visual position
+		// of the target when updating the IK each frame.
+		return target_node_override->get_global_transform_interpolated();
 	} else {
 	} else {
 		return target;
 		return target;
 	}
 	}

+ 75 - 3
scene/3d/visual_instance_3d.cpp

@@ -30,6 +30,8 @@
 
 
 #include "visual_instance_3d.h"
 #include "visual_instance_3d.h"
 
 
+#include "core/config/project_settings.h"
+
 AABB VisualInstance3D::get_aabb() const {
 AABB VisualInstance3D::get_aabb() const {
 	AABB ret;
 	AABB ret;
 	GDVIRTUAL_CALL(_get_aabb, ret);
 	GDVIRTUAL_CALL(_get_aabb, ret);
@@ -41,7 +43,38 @@ void VisualInstance3D::_update_visibility() {
 		return;
 		return;
 	}
 	}
 
 
-	RS::get_singleton()->instance_set_visible(get_instance(), is_visible_in_tree());
+	bool already_visible = _is_vi_visible();
+	bool visible = is_visible_in_tree();
+	_set_vi_visible(visible);
+
+	// If making visible, make sure the rendering server is up to date with the transform.
+	if (visible && !already_visible) {
+		if (!_is_using_identity_transform()) {
+			Transform3D gt = get_global_transform();
+			RS::get_singleton()->instance_set_transform(instance, gt);
+		}
+	}
+
+	RS::get_singleton()->instance_set_visible(instance, visible);
+}
+
+void VisualInstance3D::_physics_interpolated_changed() {
+	RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated());
+}
+
+void VisualInstance3D::set_instance_use_identity_transform(bool p_enable) {
+	// Prevent sending instance transforms when using global coordinates.
+	_set_use_identity_transform(p_enable);
+
+	if (is_inside_tree()) {
+		if (p_enable) {
+			// Want to make sure instance is using identity transform.
+			RS::get_singleton()->instance_set_transform(instance, Transform3D());
+		} else {
+			// Want to make sure instance is up to date.
+			RS::get_singleton()->instance_set_transform(instance, get_global_transform());
+		}
+	}
 }
 }
 
 
 void VisualInstance3D::_notification(int p_what) {
 void VisualInstance3D::_notification(int p_what) {
@@ -53,13 +86,52 @@ void VisualInstance3D::_notification(int p_what) {
 		} break;
 		} break;
 
 
 		case NOTIFICATION_TRANSFORM_CHANGED: {
 		case NOTIFICATION_TRANSFORM_CHANGED: {
-			Transform3D gt = get_global_transform();
-			RenderingServer::get_singleton()->instance_set_transform(instance, gt);
+			if (_is_vi_visible() || is_physics_interpolated_and_enabled()) {
+				if (!_is_using_identity_transform()) {
+					RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
+
+					// For instance when first adding to the tree, when the previous transform is
+					// unset, to prevent streaking from the origin.
+					if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) {
+						if (_is_vi_visible()) {
+							_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+						}
+						_set_physics_interpolation_reset_requested(false);
+					}
+				}
+			}
+		} break;
+
+		case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
+			if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) {
+				// We must ensure the RenderingServer transform is up to date before resetting.
+				// This is because NOTIFICATION_TRANSFORM_CHANGED is deferred,
+				// and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION.
+				if (!_is_using_identity_transform()) {
+					RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
+				}
+
+				RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance);
+			}
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+			else if (GLOBAL_GET("debug/settings/physics_interpolation/enable_warnings")) {
+
+				String node_name = is_inside_tree() ? String(get_path()) : String(get_name());
+				if (!_is_vi_visible()) {
+					WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with unhidden nodes: \"" + node_name + "\".");
+				}
+				if (!is_physics_interpolated()) {
+					WARN_PRINT("[Physics interpolation] NOTIFICATION_RESET_PHYSICS_INTERPOLATION only works with interpolated nodes: \"" + node_name + "\".");
+				}
+			}
+#endif
+
 		} break;
 		} break;
 
 
 		case NOTIFICATION_EXIT_WORLD: {
 		case NOTIFICATION_EXIT_WORLD: {
 			RenderingServer::get_singleton()->instance_set_scenario(instance, RID());
 			RenderingServer::get_singleton()->instance_set_scenario(instance, RID());
 			RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID());
 			RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID());
+			_set_vi_visible(false);
 		} break;
 		} break;
 
 
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 		case NOTIFICATION_VISIBILITY_CHANGED: {

+ 3 - 0
scene/3d/visual_instance_3d.h

@@ -45,6 +45,9 @@ class VisualInstance3D : public Node3D {
 protected:
 protected:
 	void _update_visibility();
 	void _update_visibility();
 
 
+	virtual void _physics_interpolated_changed() override;
+	void set_instance_use_identity_transform(bool p_enable);
+
 	void _notification(int p_what);
 	void _notification(int p_what);
 	static void _bind_methods();
 	static void _bind_methods();
 	void _validate_property(PropertyInfo &p_property) const;
 	void _validate_property(PropertyInfo &p_property) const;

+ 32 - 3
scene/main/node.cpp

@@ -138,6 +138,12 @@ void Node::_notification(int p_notification) {
 
 
 			get_tree()->nodes_in_tree_count++;
 			get_tree()->nodes_in_tree_count++;
 			orphan_node_count--;
 			orphan_node_count--;
+
+			// Allow physics interpolated nodes to automatically reset when added to the tree
+			// (this is to save the user from doing this manually each time).
+			if (get_tree()->is_physics_interpolation_enabled()) {
+				_set_physics_interpolation_reset_requested(true);
+			}
 		} break;
 		} break;
 
 
 		case NOTIFICATION_EXIT_TREE: {
 		case NOTIFICATION_EXIT_TREE: {
@@ -437,6 +443,18 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) {
 	data.blocked--;
 	data.blocked--;
 }
 }
 
 
+void Node::_propagate_physics_interpolation_reset_requested(bool p_requested) {
+	if (is_physics_interpolated()) {
+		data.physics_interpolation_reset_requested = p_requested;
+	}
+
+	data.blocked++;
+	for (KeyValue<StringName, Node *> &K : data.children) {
+		K.value->_propagate_physics_interpolation_reset_requested(p_requested);
+	}
+	data.blocked--;
+}
+
 void Node::move_child(Node *p_child, int p_index) {
 void Node::move_child(Node *p_child, int p_index) {
 	ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
 	ERR_FAIL_COND_MSG(data.inside_tree && !Thread::is_main_thread(), "Moving child node positions inside the SceneTree is only allowed from the main thread. Use call_deferred(\"move_child\",child,index).");
 	ERR_FAIL_NULL(p_child);
 	ERR_FAIL_NULL(p_child);
@@ -890,15 +908,23 @@ void Node::set_physics_interpolation_mode(PhysicsInterpolationMode p_mode) {
 	}
 	}
 
 
 	// If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
 	// If swapping from interpolated to non-interpolated, use this as an extra means to cause a reset.
-	if (is_physics_interpolated() && !interpolate) {
-		reset_physics_interpolation();
+	if (is_physics_interpolated() && !interpolate && is_inside_tree()) {
+		propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
 	}
 	}
 
 
 	_propagate_physics_interpolated(interpolate);
 	_propagate_physics_interpolated(interpolate);
 }
 }
 
 
 void Node::reset_physics_interpolation() {
 void Node::reset_physics_interpolation() {
-	propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+	if (is_inside_tree()) {
+		propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
+
+		// If `reset_physics_interpolation()` is called explicitly by the user
+		// (e.g. from scripts) then we prevent deferred auto-resets taking place.
+		// The user is trusted to call reset in the right order, and auto-reset
+		// will interfere with their control of prev / curr, so should be turned off.
+		_propagate_physics_interpolation_reset_requested(false);
+	}
 }
 }
 
 
 bool Node::_is_enabled() const {
 bool Node::_is_enabled() const {
@@ -3825,6 +3851,9 @@ Node::Node() {
 	data.unhandled_key_input = false;
 	data.unhandled_key_input = false;
 
 
 	data.physics_interpolated = true;
 	data.physics_interpolated = true;
+	data.physics_interpolation_reset_requested = false;
+	data.physics_interpolated_client_side = false;
+	data.use_identity_transform = false;
 
 
 	data.parent_owned = false;
 	data.parent_owned = false;
 	data.in_constructor = true;
 	data.in_constructor = true;

+ 25 - 0
scene/main/node.h

@@ -225,6 +225,21 @@ private:
 		// is switched on.
 		// is switched on.
 		bool physics_interpolated : 1;
 		bool physics_interpolated : 1;
 
 
+		// We can auto-reset physics interpolation when e.g. adding a node for the first time.
+		bool physics_interpolation_reset_requested : 1;
+
+		// Most nodes need not be interpolated in the scene tree, physics interpolation
+		// is normally only needed in the RenderingServer. However if we need to read the
+		// interpolated transform of a node in the SceneTree, it is necessary to duplicate
+		// the interpolation logic client side, in order to prevent stalling the RenderingServer
+		// by reading back.
+		bool physics_interpolated_client_side : 1;
+
+		// For certain nodes (e.g. CPU particles in global mode)
+		// it can be useful to not send the instance transform to the
+		// RenderingServer, and specify the mesh in world space.
+		bool use_identity_transform : 1;
+
 		bool parent_owned : 1;
 		bool parent_owned : 1;
 		bool in_constructor : 1;
 		bool in_constructor : 1;
 		bool use_placeholder : 1;
 		bool use_placeholder : 1;
@@ -263,6 +278,7 @@ private:
 	void _propagate_exit_tree();
 	void _propagate_exit_tree();
 	void _propagate_after_exit_tree();
 	void _propagate_after_exit_tree();
 	void _propagate_physics_interpolated(bool p_interpolated);
 	void _propagate_physics_interpolated(bool p_interpolated);
+	void _propagate_physics_interpolation_reset_requested(bool p_requested);
 	void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
 	void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
 	void _propagate_groups_dirty();
 	void _propagate_groups_dirty();
 	Array _get_node_and_resource(const NodePath &p_path);
 	Array _get_node_and_resource(const NodePath &p_path);
@@ -334,6 +350,15 @@ protected:
 	void _set_owner_nocheck(Node *p_owner);
 	void _set_owner_nocheck(Node *p_owner);
 	void _set_name_nocheck(const StringName &p_name);
 	void _set_name_nocheck(const StringName &p_name);
 
 
+	void _set_physics_interpolated_client_side(bool p_enable) { data.physics_interpolated_client_side = p_enable; }
+	bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; }
+
+	void _set_physics_interpolation_reset_requested(bool p_enable) { data.physics_interpolation_reset_requested = p_enable; }
+	bool _is_physics_interpolation_reset_requested() const { return data.physics_interpolation_reset_requested; }
+
+	void _set_use_identity_transform(bool p_enable) { data.use_identity_transform = p_enable; }
+	bool _is_using_identity_transform() const { return data.use_identity_transform; }
+
 	//call from SceneTree
 	//call from SceneTree
 	void _call_input(const Ref<InputEvent> &p_event);
 	void _call_input(const Ref<InputEvent> &p_event);
 	void _call_shortcut_input(const Ref<InputEvent> &p_event);
 	void _call_shortcut_input(const Ref<InputEvent> &p_event);

+ 65 - 0
scene/main/scene_tree.cpp

@@ -59,6 +59,7 @@
 #include "servers/navigation_server_3d.h"
 #include "servers/navigation_server_3d.h"
 #include "servers/physics_server_2d.h"
 #include "servers/physics_server_2d.h"
 #ifndef _3D_DISABLED
 #ifndef _3D_DISABLED
+#include "scene/3d/node_3d.h"
 #include "scene/resources/3d/world_3d.h"
 #include "scene/resources/3d/world_3d.h"
 #include "servers/physics_server_3d.h"
 #include "servers/physics_server_3d.h"
 #endif // _3D_DISABLED
 #endif // _3D_DISABLED
@@ -118,6 +119,29 @@ void SceneTreeTimer::release_connections() {
 
 
 SceneTreeTimer::SceneTreeTimer() {}
 SceneTreeTimer::SceneTreeTimer() {}
 
 
+#ifndef _3D_DISABLED
+// This should be called once per physics tick, to make sure the transform previous and current
+// is kept up to date on the few Node3Ds that are using client side physics interpolation.
+void SceneTree::ClientPhysicsInterpolation::physics_process() {
+	for (SelfList<Node3D> *E = _node_3d_list.first(); E;) {
+		Node3D *node_3d = E->self();
+
+		SelfList<Node3D> *current = E;
+
+		// Get the next element here BEFORE we potentially delete one.
+		E = E->next();
+
+		// This will return false if the Node3D has timed out ..
+		// i.e. if get_global_transform_interpolated() has not been called
+		// for a few seconds, we can delete from the list to keep processing
+		// to a minimum.
+		if (!node_3d->update_client_physics_interpolation_data()) {
+			_node_3d_list.remove(current);
+		}
+	}
+}
+#endif
+
 void SceneTree::tree_changed() {
 void SceneTree::tree_changed() {
 	emit_signal(tree_changed_name);
 	emit_signal(tree_changed_name);
 }
 }
@@ -466,9 +490,31 @@ bool SceneTree::is_physics_interpolation_enabled() const {
 	return _physics_interpolation_enabled;
 	return _physics_interpolation_enabled;
 }
 }
 
 
+#ifndef _3D_DISABLED
+void SceneTree::client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem) {
+	// This ensures that _update_physics_interpolation_data() will be called at least once every
+	// physics tick, to ensure the previous and current transforms are kept up to date.
+	_client_physics_interpolation._node_3d_list.add(p_elem);
+}
+
+void SceneTree::client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem) {
+	_client_physics_interpolation._node_3d_list.remove(p_elem);
+}
+#endif
+
 void SceneTree::iteration_prepare() {
 void SceneTree::iteration_prepare() {
 	if (_physics_interpolation_enabled) {
 	if (_physics_interpolation_enabled) {
+		// Make sure any pending transforms from the last tick / frame
+		// are flushed before pumping the interpolation prev and currents.
+		flush_transform_notifications();
 		RenderingServer::get_singleton()->tick();
 		RenderingServer::get_singleton()->tick();
+
+#ifndef _3D_DISABLED
+		// Any objects performing client physics interpolation
+		// should be given an opportunity to keep their previous transforms
+		// up to date before each new physics tick.
+		_client_physics_interpolation.physics_process();
+#endif
 	}
 	}
 }
 }
 
 
@@ -503,6 +549,14 @@ bool SceneTree::physics_process(double p_time) {
 	return _quit;
 	return _quit;
 }
 }
 
 
+void SceneTree::iteration_end() {
+	// When physics interpolation is active, we want all pending transforms
+	// to be flushed to the RenderingServer before finishing a physics tick.
+	if (_physics_interpolation_enabled) {
+		flush_transform_notifications();
+	}
+}
+
 bool SceneTree::process(double p_time) {
 bool SceneTree::process(double p_time) {
 	if (MainLoop::process(p_time)) {
 	if (MainLoop::process(p_time)) {
 		_quit = true;
 		_quit = true;
@@ -570,6 +624,10 @@ bool SceneTree::process(double p_time) {
 #endif // _3D_DISABLED
 #endif // _3D_DISABLED
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
+	if (_physics_interpolation_enabled) {
+		RenderingServer::get_singleton()->pre_draw(true);
+	}
+
 	return _quit;
 	return _quit;
 }
 }
 
 
@@ -1761,6 +1819,13 @@ SceneTree::SceneTree() {
 
 
 	set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
 	set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
 
 
+	// Always disable jitter fix if physics interpolation is enabled -
+	// Jitter fix will interfere with interpolation, and is not necessary
+	// when interpolation is active.
+	if (is_physics_interpolation_enabled()) {
+		Engine::get_singleton()->set_physics_jitter_fix(0);
+	}
+
 	// Initialize network state.
 	// Initialize network state.
 	set_multiplayer(MultiplayerAPI::create_default_interface());
 	set_multiplayer(MultiplayerAPI::create_default_interface());
 
 

+ 16 - 0
scene/main/scene_tree.h

@@ -41,6 +41,9 @@
 
 
 class PackedScene;
 class PackedScene;
 class Node;
 class Node;
+#ifndef _3D_DISABLED
+class Node3D;
+#endif
 class Window;
 class Window;
 class Material;
 class Material;
 class Mesh;
 class Mesh;
@@ -120,6 +123,13 @@ private:
 		bool changed = false;
 		bool changed = false;
 	};
 	};
 
 
+#ifndef _3D_DISABLED
+	struct ClientPhysicsInterpolation {
+		SelfList<Node3D>::List _node_3d_list;
+		void physics_process();
+	} _client_physics_interpolation;
+#endif
+
 	Window *root = nullptr;
 	Window *root = nullptr;
 
 
 	double physics_process_time = 0.0;
 	double physics_process_time = 0.0;
@@ -315,6 +325,7 @@ public:
 	virtual void iteration_prepare() override;
 	virtual void iteration_prepare() override;
 
 
 	virtual bool physics_process(double p_time) override;
 	virtual bool physics_process(double p_time) override;
+	virtual void iteration_end() override;
 	virtual bool process(double p_time) override;
 	virtual bool process(double p_time) override;
 
 
 	virtual void finalize() override;
 	virtual void finalize() override;
@@ -423,6 +434,11 @@ public:
 	void set_physics_interpolation_enabled(bool p_enabled);
 	void set_physics_interpolation_enabled(bool p_enabled);
 	bool is_physics_interpolation_enabled() const;
 	bool is_physics_interpolation_enabled() const;
 
 
+#ifndef _3D_DISABLED
+	void client_physics_interpolation_add_node_3d(SelfList<Node3D> *p_elem);
+	void client_physics_interpolation_remove_node_3d(SelfList<Node3D> *p_elem);
+#endif
+
 	SceneTree();
 	SceneTree();
 	~SceneTree();
 	~SceneTree();
 };
 };

+ 7 - 0
scene/main/viewport.cpp

@@ -5024,6 +5024,13 @@ Viewport::Viewport() {
 #endif // _3D_DISABLED
 #endif // _3D_DISABLED
 
 
 	set_sdf_oversize(sdf_oversize); // Set to server.
 	set_sdf_oversize(sdf_oversize); // Set to server.
+
+	// Physics interpolation mode for viewports is a special case.
+	// Typically viewports will be housed within Controls,
+	// and Controls default to PHYSICS_INTERPOLATION_MODE_OFF.
+	// Viewports can thus inherit physics interpolation OFF, which is unexpected.
+	// Setting to ON allows each viewport to have a fresh interpolation state.
+	set_physics_interpolation_mode(Node::PHYSICS_INTERPOLATION_MODE_ON);
 }
 }
 
 
 Viewport::~Viewport() {
 Viewport::~Viewport() {

+ 265 - 16
servers/rendering/renderer_scene_cull.cpp

@@ -34,10 +34,16 @@
 #include "core/object/worker_thread_pool.h"
 #include "core/object/worker_thread_pool.h"
 #include "core/os/os.h"
 #include "core/os/os.h"
 #include "rendering_light_culler.h"
 #include "rendering_light_culler.h"
+#include "rendering_server_constants.h"
 #include "rendering_server_default.h"
 #include "rendering_server_default.h"
 
 
 #include <new>
 #include <new>
 
 
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+// This is used only to obtain node paths for user-friendly physics interpolation warnings.
+#include "scene/main/node.h"
+#endif
+
 /* HALTON SEQUENCE */
 /* HALTON SEQUENCE */
 
 
 #ifndef _3D_DISABLED
 #ifndef _3D_DISABLED
@@ -53,6 +59,20 @@ static float get_halton_value(int p_index, int p_base) {
 }
 }
 #endif // _3D_DISABLED
 #endif // _3D_DISABLED
 
 
+/* EVENT QUEUING */
+
+void RendererSceneCull::tick() {
+	if (_interpolation_data.interpolation_enabled) {
+		update_interpolation_tick(true);
+	}
+}
+
+void RendererSceneCull::pre_draw(bool p_will_draw) {
+	if (_interpolation_data.interpolation_enabled) {
+		update_interpolation_frame(p_will_draw);
+	}
+}
+
 /* CAMERA API */
 /* CAMERA API */
 
 
 RID RendererSceneCull::camera_allocate() {
 RID RendererSceneCull::camera_allocate() {
@@ -93,6 +113,7 @@ void RendererSceneCull::camera_set_frustum(RID p_camera, float p_size, Vector2 p
 void RendererSceneCull::camera_set_transform(RID p_camera, const Transform3D &p_transform) {
 void RendererSceneCull::camera_set_transform(RID p_camera, const Transform3D &p_transform) {
 	Camera *camera = camera_owner.get_or_null(p_camera);
 	Camera *camera = camera_owner.get_or_null(p_camera);
 	ERR_FAIL_NULL(camera);
 	ERR_FAIL_NULL(camera);
+
 	camera->transform = p_transform.orthonormalized();
 	camera->transform = p_transform.orthonormalized();
 }
 }
 
 
@@ -924,8 +945,45 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
 	Instance *instance = instance_owner.get_or_null(p_instance);
 	Instance *instance = instance_owner.get_or_null(p_instance);
 	ERR_FAIL_NULL(instance);
 	ERR_FAIL_NULL(instance);
 
 
-	if (instance->transform == p_transform) {
-		return; //must be checked to avoid worst evil
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+	print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
+#endif
+
+	if (!_interpolation_data.interpolation_enabled || !instance->interpolated || !instance->scenario) {
+		if (instance->transform == p_transform) {
+			return; // Must be checked to avoid worst evil.
+		}
+
+#ifdef DEBUG_ENABLED
+
+		for (int i = 0; i < 4; i++) {
+			const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin;
+			ERR_FAIL_COND(!v.is_finite());
+		}
+
+#endif
+		instance->transform = p_transform;
+		_instance_queue_update(instance, true);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+		if (_interpolation_data.interpolation_enabled && !instance->interpolated && Engine::get_singleton()->is_in_physics_frame()) {
+			PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Non-interpolated instance triggered from physics process");
+		}
+#endif
+
+		return;
+	}
+
+	float new_checksum = TransformInterpolator::checksum_transform_3d(p_transform);
+	bool checksums_match = (instance->transform_checksum_curr == new_checksum) && (instance->transform_checksum_prev == new_checksum);
+
+	// We can't entirely reject no changes because we need the interpolation
+	// system to keep on stewing.
+
+	// Optimized check. First checks the checksums. If they pass it does the slow check at the end.
+	// Alternatively we can do this non-optimized and ignore the checksum... if no change.
+	if (checksums_match && (instance->transform_curr == p_transform) && (instance->transform_prev == p_transform)) {
+		return;
 	}
 	}
 
 
 #ifdef DEBUG_ENABLED
 #ifdef DEBUG_ENABLED
@@ -936,8 +994,69 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
 	}
 	}
 
 
 #endif
 #endif
-	instance->transform = p_transform;
+
+	instance->transform_curr = p_transform;
+
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+	print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
+#endif
+
+	// Keep checksums up to date.
+	instance->transform_checksum_curr = new_checksum;
+
+	if (!instance->on_interpolate_transform_list) {
+		_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
+		instance->on_interpolate_transform_list = true;
+	} else {
+		DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size());
+	}
+
+	// If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated
+	// transform or anything else.
+	// Ideally we would not even call the VisualServer::set_transform() when invisible but that would entail having logic
+	// to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise.
+	if (!instance->visible) {
+		return;
+	}
+
+	// Decide on the interpolation method... slerp if possible.
+	instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
+
+	if (!instance->on_interpolate_list) {
+		_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
+		instance->on_interpolate_list = true;
+	} else {
+		DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size());
+	}
+
 	_instance_queue_update(instance, true);
 	_instance_queue_update(instance, true);
+
+#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
+	if (!Engine::get_singleton()->is_in_physics_frame()) {
+		PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Interpolated instance triggered from outside physics process");
+	}
+#endif
+}
+
+void RendererSceneCull::instance_set_interpolated(RID p_instance, bool p_interpolated) {
+	Instance *instance = instance_owner.get_or_null(p_instance);
+	ERR_FAIL_NULL(instance);
+	instance->interpolated = p_interpolated;
+}
+
+void RendererSceneCull::instance_reset_physics_interpolation(RID p_instance) {
+	Instance *instance = instance_owner.get_or_null(p_instance);
+	ERR_FAIL_NULL(instance);
+
+	if (_interpolation_data.interpolation_enabled && instance->interpolated) {
+		instance->transform_prev = instance->transform_curr;
+		instance->transform_checksum_prev = instance->transform_checksum_curr;
+
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+		print_line("instance_reset_physics_interpolation .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
+		print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
+#endif
+	}
 }
 }
 
 
 void RendererSceneCull::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
 void RendererSceneCull::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
@@ -990,6 +1109,23 @@ void RendererSceneCull::instance_set_visible(RID p_instance, bool p_visible) {
 
 
 	if (p_visible) {
 	if (p_visible) {
 		if (instance->scenario != nullptr) {
 		if (instance->scenario != nullptr) {
+			// Special case for physics interpolation, we want to ensure the interpolated data is up to date
+			if (_interpolation_data.interpolation_enabled && instance->interpolated && !instance->on_interpolate_list) {
+				// Do all the extra work we normally do on instance_set_transform(), because this is optimized out for hidden instances.
+				// This prevents a glitch of stale interpolation transform data when unhiding before the next physics tick.
+				instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
+				_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
+				instance->on_interpolate_list = true;
+
+				// We must also place on the transform update list for a tick, so the system
+				// can auto-detect if the instance is no longer moving, and remove from the interpolate lists again.
+				// If this step is ignored, an unmoving instance could remain on the interpolate lists indefinitely
+				// (or rather until the object is deleted) and cause unnecessary updates and drawcalls.
+				if (!instance->on_interpolate_transform_list) {
+					_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
+					instance->on_interpolate_transform_list = true;
+				}
+			}
 			_instance_queue_update(instance, true, false);
 			_instance_queue_update(instance, true, false);
 		}
 		}
 	} else if (instance->indexer_id.is_valid()) {
 	} else if (instance->indexer_id.is_valid()) {
@@ -1574,11 +1710,22 @@ void RendererSceneCull::instance_geometry_get_shader_parameter_list(RID p_instan
 void RendererSceneCull::_update_instance(Instance *p_instance) {
 void RendererSceneCull::_update_instance(Instance *p_instance) {
 	p_instance->version++;
 	p_instance->version++;
 
 
+	// When not using interpolation the transform is used straight.
+	const Transform3D *instance_xform = &p_instance->transform;
+
+	// Can possibly use the most up to date current transform here when using physics interpolation ...
+	// uncomment the next line for this..
+	//if (_interpolation_data.interpolation_enabled && p_instance->interpolated) {
+	//    instance_xform = &p_instance->transform_curr;
+	//}
+	// However it does seem that using the interpolated transform (transform) works for keeping AABBs
+	// up to date to avoid culling errors.
+
 	if (p_instance->base_type == RS::INSTANCE_LIGHT) {
 	if (p_instance->base_type == RS::INSTANCE_LIGHT) {
 		InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data);
 		InstanceLightData *light = static_cast<InstanceLightData *>(p_instance->base_data);
 
 
-		RSG::light_storage->light_instance_set_transform(light->instance, p_instance->transform);
-		RSG::light_storage->light_instance_set_aabb(light->instance, p_instance->transform.xform(p_instance->aabb));
+		RSG::light_storage->light_instance_set_transform(light->instance, *instance_xform);
+		RSG::light_storage->light_instance_set_aabb(light->instance, instance_xform->xform(p_instance->aabb));
 		light->make_shadow_dirty();
 		light->make_shadow_dirty();
 
 
 		RS::LightBakeMode bake_mode = RSG::light_storage->light_get_bake_mode(p_instance->base);
 		RS::LightBakeMode bake_mode = RSG::light_storage->light_get_bake_mode(p_instance->base);
@@ -1601,7 +1748,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
 	} else if (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE) {
 	} else if (p_instance->base_type == RS::INSTANCE_REFLECTION_PROBE) {
 		InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(p_instance->base_data);
 		InstanceReflectionProbeData *reflection_probe = static_cast<InstanceReflectionProbeData *>(p_instance->base_data);
 
 
-		RSG::light_storage->reflection_probe_instance_set_transform(reflection_probe->instance, p_instance->transform);
+		RSG::light_storage->reflection_probe_instance_set_transform(reflection_probe->instance, *instance_xform);
 
 
 		if (p_instance->scenario && p_instance->array_index >= 0) {
 		if (p_instance->scenario && p_instance->array_index >= 0) {
 			InstanceData &idata = p_instance->scenario->instance_data[p_instance->array_index];
 			InstanceData &idata = p_instance->scenario->instance_data[p_instance->array_index];
@@ -1610,17 +1757,17 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
 	} else if (p_instance->base_type == RS::INSTANCE_DECAL) {
 	} else if (p_instance->base_type == RS::INSTANCE_DECAL) {
 		InstanceDecalData *decal = static_cast<InstanceDecalData *>(p_instance->base_data);
 		InstanceDecalData *decal = static_cast<InstanceDecalData *>(p_instance->base_data);
 
 
-		RSG::texture_storage->decal_instance_set_transform(decal->instance, p_instance->transform);
+		RSG::texture_storage->decal_instance_set_transform(decal->instance, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
 	} else if (p_instance->base_type == RS::INSTANCE_LIGHTMAP) {
 		InstanceLightmapData *lightmap = static_cast<InstanceLightmapData *>(p_instance->base_data);
 		InstanceLightmapData *lightmap = static_cast<InstanceLightmapData *>(p_instance->base_data);
 
 
-		RSG::light_storage->lightmap_instance_set_transform(lightmap->instance, p_instance->transform);
+		RSG::light_storage->lightmap_instance_set_transform(lightmap->instance, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_VOXEL_GI) {
 	} else if (p_instance->base_type == RS::INSTANCE_VOXEL_GI) {
 		InstanceVoxelGIData *voxel_gi = static_cast<InstanceVoxelGIData *>(p_instance->base_data);
 		InstanceVoxelGIData *voxel_gi = static_cast<InstanceVoxelGIData *>(p_instance->base_data);
 
 
-		scene_render->voxel_gi_instance_set_transform_to_data(voxel_gi->probe_instance, p_instance->transform);
+		scene_render->voxel_gi_instance_set_transform_to_data(voxel_gi->probe_instance, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_PARTICLES) {
 	} else if (p_instance->base_type == RS::INSTANCE_PARTICLES) {
-		RSG::particles_storage->particles_set_emission_transform(p_instance->base, p_instance->transform);
+		RSG::particles_storage->particles_set_emission_transform(p_instance->base, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) {
 	} else if (p_instance->base_type == RS::INSTANCE_PARTICLES_COLLISION) {
 		InstanceParticlesCollisionData *collision = static_cast<InstanceParticlesCollisionData *>(p_instance->base_data);
 		InstanceParticlesCollisionData *collision = static_cast<InstanceParticlesCollisionData *>(p_instance->base_data);
 
 
@@ -1628,13 +1775,13 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
 		if (RSG::particles_storage->particles_collision_is_heightfield(p_instance->base)) {
 		if (RSG::particles_storage->particles_collision_is_heightfield(p_instance->base)) {
 			heightfield_particle_colliders_update_list.insert(p_instance);
 			heightfield_particle_colliders_update_list.insert(p_instance);
 		}
 		}
-		RSG::particles_storage->particles_collision_instance_set_transform(collision->instance, p_instance->transform);
+		RSG::particles_storage->particles_collision_instance_set_transform(collision->instance, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_FOG_VOLUME) {
 	} else if (p_instance->base_type == RS::INSTANCE_FOG_VOLUME) {
 		InstanceFogVolumeData *volume = static_cast<InstanceFogVolumeData *>(p_instance->base_data);
 		InstanceFogVolumeData *volume = static_cast<InstanceFogVolumeData *>(p_instance->base_data);
-		scene_render->fog_volume_instance_set_transform(volume->instance, p_instance->transform);
+		scene_render->fog_volume_instance_set_transform(volume->instance, *instance_xform);
 	} else if (p_instance->base_type == RS::INSTANCE_OCCLUDER) {
 	} else if (p_instance->base_type == RS::INSTANCE_OCCLUDER) {
 		if (p_instance->scenario) {
 		if (p_instance->scenario) {
-			RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, p_instance->transform, p_instance->visible);
+			RendererSceneOcclusionCull::get_singleton()->scenario_set_instance(p_instance->scenario->self, p_instance->self, p_instance->base, *instance_xform, p_instance->visible);
 		}
 		}
 	}
 	}
 
 
@@ -1654,7 +1801,7 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
 	}
 	}
 
 
 	AABB new_aabb;
 	AABB new_aabb;
-	new_aabb = p_instance->transform.xform(p_instance->aabb);
+	new_aabb = instance_xform->xform(p_instance->aabb);
 	p_instance->transformed_aabb = new_aabb;
 	p_instance->transformed_aabb = new_aabb;
 
 
 	if ((1 << p_instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) {
 	if ((1 << p_instance->base_type) & RS::INSTANCE_GEOMETRY_MASK) {
@@ -1681,11 +1828,11 @@ void RendererSceneCull::_update_instance(Instance *p_instance) {
 		}
 		}
 
 
 		ERR_FAIL_NULL(geom->geometry_instance);
 		ERR_FAIL_NULL(geom->geometry_instance);
-		geom->geometry_instance->set_transform(p_instance->transform, p_instance->aabb, p_instance->transformed_aabb);
+		geom->geometry_instance->set_transform(*instance_xform, p_instance->aabb, p_instance->transformed_aabb);
 	}
 	}
 
 
 	// note: we had to remove is equal approx check here, it meant that det == 0.000004 won't work, which is the case for some of our scenes.
 	// note: we had to remove is equal approx check here, it meant that det == 0.000004 won't work, which is the case for some of our scenes.
-	if (p_instance->scenario == nullptr || !p_instance->visible || p_instance->transform.basis.determinant() == 0) {
+	if (p_instance->scenario == nullptr || !p_instance->visible || instance_xform->basis.determinant() == 0) {
 		p_instance->prev_transformed_aabb = p_instance->transformed_aabb;
 		p_instance->prev_transformed_aabb = p_instance->transformed_aabb;
 		return;
 		return;
 	}
 	}
@@ -4180,6 +4327,8 @@ bool RendererSceneCull::free(RID p_rid) {
 
 
 		Instance *instance = instance_owner.get_or_null(p_rid);
 		Instance *instance = instance_owner.get_or_null(p_rid);
 
 
+		_interpolation_data.notify_free_instance(p_rid, *instance);
+
 		instance_geometry_set_lightmap(p_rid, RID(), Rect2(), 0);
 		instance_geometry_set_lightmap(p_rid, RID(), Rect2(), 0);
 		instance_set_scenario(p_rid, RID());
 		instance_set_scenario(p_rid, RID());
 		instance_set_base(p_rid, RID());
 		instance_set_base(p_rid, RID());
@@ -4240,6 +4389,106 @@ void RendererSceneCull::set_scene_render(RendererSceneRender *p_scene_render) {
 	geometry_instance_pair_mask = scene_render->geometry_instance_get_pair_mask();
 	geometry_instance_pair_mask = scene_render->geometry_instance_get_pair_mask();
 }
 }
 
 
+/* INTERPOLATION API */
+
+void RendererSceneCull::update_interpolation_tick(bool p_process) {
+	// TODO (MultiMesh): Update interpolation in storage.
+
+	// INSTANCES
+
+	// Detect any that were on the previous transform list that are no longer active;
+	// we should remove them from the interpolate list.
+
+	for (const RID &rid : *_interpolation_data.instance_transform_update_list_prev) {
+		Instance *instance = instance_owner.get_or_null(rid);
+
+		bool active = true;
+
+		// No longer active? (Either the instance deleted or no longer being transformed.)
+		if (instance && !instance->on_interpolate_transform_list) {
+			active = false;
+			instance->on_interpolate_list = false;
+
+			// Make sure the most recent transform is set...
+			instance->transform = instance->transform_curr;
+
+			// ... and that both prev and current are the same, just in case of any interpolations.
+			instance->transform_prev = instance->transform_curr;
+
+			// Make sure instances are updated one more time to ensure the AABBs are correct.
+			_instance_queue_update(instance, true);
+		}
+
+		if (!instance) {
+			active = false;
+		}
+
+		if (!active) {
+			_interpolation_data.instance_interpolate_update_list.erase(rid);
+		}
+	}
+
+	// Now for any in the transform list (being actively interpolated), keep the previous transform
+	// value up to date, ready for the next tick.
+	if (p_process) {
+		for (const RID &rid : *_interpolation_data.instance_transform_update_list_curr) {
+			Instance *instance = instance_owner.get_or_null(rid);
+			if (instance) {
+				instance->transform_prev = instance->transform_curr;
+				instance->transform_checksum_prev = instance->transform_checksum_curr;
+				instance->on_interpolate_transform_list = false;
+			}
+		}
+	}
+
+	// We maintain a mirror list for the transform updates, so we can detect when an instance
+	// is no longer being transformed, and remove it from the interpolate list.
+	SWAP(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev);
+
+	// Prepare for the next iteration.
+	_interpolation_data.instance_transform_update_list_curr->clear();
+}
+
+void RendererSceneCull::update_interpolation_frame(bool p_process) {
+	// TODO (MultiMesh): Update interpolation in storage.
+
+	if (p_process) {
+		real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
+
+		for (const RID &rid : _interpolation_data.instance_interpolate_update_list) {
+			Instance *instance = instance_owner.get_or_null(rid);
+			if (instance) {
+				TransformInterpolator::interpolate_transform_3d_via_method(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method);
+
+#ifdef RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+				print_line("\t\tinterpolated: " + rtos(instance->transform.origin.x) + "\t( prev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
+#endif
+
+				// Make sure AABBs are constantly up to date through the interpolation.
+				_instance_queue_update(instance, true);
+			}
+		}
+	}
+}
+
+void RendererSceneCull::set_physics_interpolation_enabled(bool p_enabled) {
+	_interpolation_data.interpolation_enabled = p_enabled;
+}
+
+void RendererSceneCull::InterpolationData::notify_free_instance(RID p_rid, Instance &r_instance) {
+	r_instance.on_interpolate_list = false;
+	r_instance.on_interpolate_transform_list = false;
+
+	if (!interpolation_enabled) {
+		return;
+	}
+
+	// If the instance was on any of the lists, remove.
+	instance_interpolate_update_list.erase_multiple_unordered(p_rid);
+	instance_transform_update_list_curr->erase_multiple_unordered(p_rid);
+	instance_transform_update_list_prev->erase_multiple_unordered(p_rid);
+}
+
 RendererSceneCull::RendererSceneCull() {
 RendererSceneCull::RendererSceneCull() {
 	render_pass = 1;
 	render_pass = 1;
 	singleton = this;
 	singleton = this;

+ 57 - 7
servers/rendering/renderer_scene_cull.h

@@ -32,6 +32,7 @@
 #define RENDERER_SCENE_CULL_H
 #define RENDERER_SCENE_CULL_H
 
 
 #include "core/math/dynamic_bvh.h"
 #include "core/math/dynamic_bvh.h"
+#include "core/math/transform_interpolator.h"
 #include "core/templates/bin_sorted_array.h"
 #include "core/templates/bin_sorted_array.h"
 #include "core/templates/local_vector.h"
 #include "core/templates/local_vector.h"
 #include "core/templates/paged_allocator.h"
 #include "core/templates/paged_allocator.h"
@@ -66,6 +67,11 @@ public:
 
 
 	static RendererSceneCull *singleton;
 	static RendererSceneCull *singleton;
 
 
+	/* EVENT QUEUING */
+
+	void tick();
+	void pre_draw(bool p_will_draw);
+
 	/* CAMERA API */
 	/* CAMERA API */
 
 
 	struct Camera {
 	struct Camera {
@@ -406,8 +412,16 @@ public:
 
 
 		RID mesh_instance; //only used for meshes and when skeleton/blendshapes exist
 		RID mesh_instance; //only used for meshes and when skeleton/blendshapes exist
 
 
+		// This is the main transform to be drawn with ...
+		// This will either be the interpolated transform (when using fixed timestep interpolation)
+		// or the ONLY transform (when not using FTI).
 		Transform3D transform;
 		Transform3D transform;
 
 
+		// For interpolation we store the current transform (this physics tick)
+		// and the transform in the previous tick.
+		Transform3D transform_curr;
+		Transform3D transform_prev;
+
 		float lod_bias;
 		float lod_bias;
 
 
 		bool ignore_occlusion_culling;
 		bool ignore_occlusion_culling;
@@ -418,13 +432,23 @@ public:
 		RS::ShadowCastingSetting cast_shadows;
 		RS::ShadowCastingSetting cast_shadows;
 
 
 		uint32_t layer_mask;
 		uint32_t layer_mask;
-		//fit in 32 bits
-		bool mirror : 8;
-		bool receive_shadows : 8;
-		bool visible : 8;
-		bool baked_light : 2; //this flag is only to know if it actually did use baked light
-		bool dynamic_gi : 2; //same above for dynamic objects
-		bool redraw_if_visible : 4;
+		// Fit in 32 bits.
+		bool mirror : 1;
+		bool receive_shadows : 1;
+		bool visible : 1;
+		bool baked_light : 1; // This flag is only to know if it actually did use baked light.
+		bool dynamic_gi : 1; // Same as above for dynamic objects.
+		bool redraw_if_visible : 1;
+
+		bool on_interpolate_list : 1;
+		bool on_interpolate_transform_list : 1;
+		bool interpolated : 1;
+		TransformInterpolator::Method interpolation_method : 3;
+
+		// For fixed timestep interpolation.
+		// Note 32 bits is plenty for checksum, no need for real_t
+		float transform_checksum_curr;
+		float transform_checksum_prev;
 
 
 		Instance *lightmap = nullptr;
 		Instance *lightmap = nullptr;
 		Rect2 lightmap_uv_scale;
 		Rect2 lightmap_uv_scale;
@@ -574,6 +598,14 @@ public:
 			baked_light = true;
 			baked_light = true;
 			dynamic_gi = false;
 			dynamic_gi = false;
 			redraw_if_visible = false;
 			redraw_if_visible = false;
+
+			on_interpolate_list = false;
+			on_interpolate_transform_list = false;
+			interpolated = true;
+			interpolation_method = TransformInterpolator::INTERP_LERP;
+			transform_checksum_curr = 0.0;
+			transform_checksum_prev = 0.0;
+
 			lightmap_slice_index = 0;
 			lightmap_slice_index = 0;
 			lightmap = nullptr;
 			lightmap = nullptr;
 			lightmap_cull_index = 0;
 			lightmap_cull_index = 0;
@@ -1027,6 +1059,8 @@ public:
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask);
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask);
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center);
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center);
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform);
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform);
+	virtual void instance_set_interpolated(RID p_instance, bool p_interpolated);
+	virtual void instance_reset_physics_interpolation(RID p_instance);
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id);
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id);
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material);
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material);
@@ -1393,6 +1427,22 @@ public:
 
 
 	virtual void update_visibility_notifiers();
 	virtual void update_visibility_notifiers();
 
 
+	/* INTERPOLATION */
+
+	void update_interpolation_tick(bool p_process = true);
+	void update_interpolation_frame(bool p_process = true);
+	virtual void set_physics_interpolation_enabled(bool p_enabled);
+
+	struct InterpolationData {
+		void notify_free_instance(RID p_rid, Instance &r_instance);
+		LocalVector<RID> instance_interpolate_update_list;
+		LocalVector<RID> instance_transform_update_lists[2];
+		LocalVector<RID> *instance_transform_update_list_curr = &instance_transform_update_lists[0];
+		LocalVector<RID> *instance_transform_update_list_prev = &instance_transform_update_lists[1];
+
+		bool interpolation_enabled = false;
+	} _interpolation_data;
+
 	RendererSceneCull();
 	RendererSceneCull();
 	virtual ~RendererSceneCull();
 	virtual ~RendererSceneCull();
 };
 };

+ 12 - 0
servers/rendering/rendering_method.h

@@ -83,6 +83,8 @@ public:
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
+	virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0;
+	virtual void instance_reset_physics_interpolation(RID p_instance) = 0;
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;
@@ -350,6 +352,16 @@ public:
 
 
 	virtual bool free(RID p_rid) = 0;
 	virtual bool free(RID p_rid) = 0;
 
 
+	/* Physics interpolation */
+
+	virtual void update_interpolation_tick(bool p_process = true) = 0;
+	virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
+
+	/* Event queueing */
+
+	virtual void tick() = 0;
+	virtual void pre_draw(bool p_will_draw) = 0;
+
 	RenderingMethod();
 	RenderingMethod();
 	virtual ~RenderingMethod();
 	virtual ~RenderingMethod();
 };
 };

+ 48 - 0
servers/rendering/rendering_server_constants.h

@@ -0,0 +1,48 @@
+/**************************************************************************/
+/*  rendering_server_constants.h                                          */
+/**************************************************************************/
+/*                         This file is part of:                          */
+/*                             GODOT ENGINE                               */
+/*                        https://godotengine.org                         */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
+/*                                                                        */
+/* Permission is hereby granted, free of charge, to any person obtaining  */
+/* a copy of this software and associated documentation files (the        */
+/* "Software"), to deal in the Software without restriction, including    */
+/* without limitation the rights to use, copy, modify, merge, publish,    */
+/* distribute, sublicense, and/or sell copies of the Software, and to     */
+/* permit persons to whom the Software is furnished to do so, subject to  */
+/* the following conditions:                                              */
+/*                                                                        */
+/* The above copyright notice and this permission notice shall be         */
+/* included in all copies or substantial portions of the Software.        */
+/*                                                                        */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
+/**************************************************************************/
+
+#ifndef RENDERING_SERVER_CONSTANTS_H
+#define RENDERING_SERVER_CONSTANTS_H
+
+// Use for constants etc. that need not be included as often as rendering_server.h
+// to reduce dependencies and prevent slow compilation.
+
+// This is a "cheap" include, and can be used from scene side code as well as servers.
+
+// N.B. ONLY allow these defined in DEV_ENABLED builds, they will slow
+// performance, and are only necessary to use for debugging.
+#ifdef DEV_ENABLED
+
+// Uncomment this define to produce debugging output for physics interpolation.
+//#define RENDERING_SERVER_DEBUG_PHYSICS_INTERPOLATION
+
+#endif // DEV_ENABLED
+
+#endif // RENDERING_SERVER_CONSTANTS_H

+ 10 - 4
servers/rendering/rendering_server_default.cpp

@@ -381,12 +381,9 @@ void RenderingServerDefault::_thread_loop() {
 
 
 /* INTERPOLATION */
 /* INTERPOLATION */
 
 
-void RenderingServerDefault::tick() {
-	RSG::canvas->tick();
-}
-
 void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
 void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
 	RSG::canvas->set_physics_interpolation_enabled(p_enabled);
 	RSG::canvas->set_physics_interpolation_enabled(p_enabled);
+	RSG::scene->set_physics_interpolation_enabled(p_enabled);
 }
 }
 
 
 /* EVENT QUEUING */
 /* EVENT QUEUING */
@@ -411,6 +408,15 @@ void RenderingServerDefault::draw(bool p_swap_buffers, double frame_step) {
 	}
 	}
 }
 }
 
 
+void RenderingServerDefault::tick() {
+	RSG::canvas->tick();
+	RSG::scene->tick();
+}
+
+void RenderingServerDefault::pre_draw(bool p_will_draw) {
+	RSG::scene->pre_draw(p_will_draw);
+}
+
 void RenderingServerDefault::_call_on_render_thread(const Callable &p_callable) {
 void RenderingServerDefault::_call_on_render_thread(const Callable &p_callable) {
 	p_callable.call();
 	p_callable.call();
 }
 }

+ 4 - 1
servers/rendering/rendering_server_default.h

@@ -802,6 +802,8 @@ public:
 	FUNC2(instance_set_layer_mask, RID, uint32_t)
 	FUNC2(instance_set_layer_mask, RID, uint32_t)
 	FUNC3(instance_set_pivot_data, RID, float, bool)
 	FUNC3(instance_set_pivot_data, RID, float, bool)
 	FUNC2(instance_set_transform, RID, const Transform3D &)
 	FUNC2(instance_set_transform, RID, const Transform3D &)
+	FUNC2(instance_set_interpolated, RID, bool)
+	FUNC1(instance_reset_physics_interpolation, RID)
 	FUNC2(instance_attach_object_instance_id, RID, ObjectID)
 	FUNC2(instance_attach_object_instance_id, RID, ObjectID)
 	FUNC3(instance_set_blend_shape_weight, RID, int, float)
 	FUNC3(instance_set_blend_shape_weight, RID, int, float)
 	FUNC3(instance_set_surface_override_material, RID, int, RID)
 	FUNC3(instance_set_surface_override_material, RID, int, RID)
@@ -1048,7 +1050,6 @@ public:
 
 
 	/* INTERPOLATION */
 	/* INTERPOLATION */
 
 
-	virtual void tick() override;
 	virtual void set_physics_interpolation_enabled(bool p_enabled) override;
 	virtual void set_physics_interpolation_enabled(bool p_enabled) override;
 
 
 	/* EVENT QUEUING */
 	/* EVENT QUEUING */
@@ -1060,6 +1061,8 @@ public:
 	virtual bool has_changed() const override;
 	virtual bool has_changed() const override;
 	virtual void init() override;
 	virtual void init() override;
 	virtual void finish() override;
 	virtual void finish() override;
+	virtual void tick() override;
+	virtual void pre_draw(bool p_will_draw) override;
 
 
 	virtual bool is_on_render_thread() override {
 	virtual bool is_on_render_thread() override {
 		return Thread::get_caller_id() == server_thread;
 		return Thread::get_caller_id() == server_thread;

+ 2 - 0
servers/rendering_server.cpp

@@ -3116,6 +3116,8 @@ void RenderingServer::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask);
 	ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask);
 	ClassDB::bind_method(D_METHOD("instance_set_pivot_data", "instance", "sorting_offset", "use_aabb_center"), &RenderingServer::instance_set_pivot_data);
 	ClassDB::bind_method(D_METHOD("instance_set_pivot_data", "instance", "sorting_offset", "use_aabb_center"), &RenderingServer::instance_set_pivot_data);
 	ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform);
 	ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform);
+	ClassDB::bind_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::instance_set_interpolated);
+	ClassDB::bind_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::instance_reset_physics_interpolation);
 	ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id);
 	ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id);
 	ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight);
 	ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight);
 	ClassDB::bind_method(D_METHOD("instance_set_surface_override_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_override_material);
 	ClassDB::bind_method(D_METHOD("instance_set_surface_override_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_override_material);

+ 4 - 1
servers/rendering_server.h

@@ -1344,6 +1344,8 @@ public:
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
 	virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
 	virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
 	virtual void instance_set_transform(RID p_instance, const Transform3D &p_transform) = 0;
+	virtual void instance_set_interpolated(RID p_instance, bool p_interpolated) = 0;
+	virtual void instance_reset_physics_interpolation(RID p_instance) = 0;
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
 	virtual void instance_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
 	virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;
 	virtual void instance_set_surface_override_material(RID p_instance, int p_surface, RID p_material) = 0;
@@ -1657,7 +1659,6 @@ public:
 
 
 	/* INTERPOLATION */
 	/* INTERPOLATION */
 
 
-	virtual void tick() = 0;
 	virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
 	virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
 
 
 	/* EVENT QUEUING */
 	/* EVENT QUEUING */
@@ -1669,6 +1670,8 @@ public:
 	virtual bool has_changed() const = 0;
 	virtual bool has_changed() const = 0;
 	virtual void init();
 	virtual void init();
 	virtual void finish() = 0;
 	virtual void finish() = 0;
+	virtual void tick() = 0;
+	virtual void pre_draw(bool p_will_draw) = 0;
 
 
 	/* STATUS INFORMATION */
 	/* STATUS INFORMATION */