mirror of
https://github.com/godotengine/godot.git
synced 2024-11-24 05:04:10 +00:00
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 <lawnjelly@gmail.com>
This commit is contained in:
parent
42e5b3ac2d
commit
2f8ab4a654
@ -34,6 +34,12 @@
|
||||
#include "core/os/os.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;
|
||||
|
||||
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() {
|
||||
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
|
||||
}
|
||||
|
@ -31,6 +31,7 @@
|
||||
#ifndef ERROR_MACROS_H
|
||||
#define ERROR_MACROS_H
|
||||
|
||||
#include "core/object/object_id.h"
|
||||
#include "core/typedefs.h"
|
||||
|
||||
#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_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__
|
||||
//#define FUNCTION_STR __PRETTY_FUNCTION__ - too annoying
|
||||
#define FUNCTION_STR __FUNCTION__
|
||||
@ -832,4 +835,14 @@ void _err_flush_stdout();
|
||||
#define DEV_CHECK_ONCE(m_cond)
|
||||
#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
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include "transform_interpolator.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) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -32,15 +32,64 @@
|
||||
#define TRANSFORM_INTERPOLATOR_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 Transform3D;
|
||||
struct Basis;
|
||||
struct Quaternion;
|
||||
|
||||
class TransformInterpolator {
|
||||
public:
|
||||
enum Method {
|
||||
INTERP_LERP,
|
||||
INTERP_SLERP,
|
||||
INTERP_SCALED_SLERP,
|
||||
};
|
||||
|
||||
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:
|
||||
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
|
||||
|
@ -64,6 +64,7 @@ public:
|
||||
virtual void initialize();
|
||||
virtual void iteration_prepare() {}
|
||||
virtual bool physics_process(double p_time);
|
||||
virtual void iteration_end() {}
|
||||
virtual bool process(double p_time);
|
||||
virtual void finalize();
|
||||
|
||||
|
@ -46,6 +46,14 @@
|
||||
Returns all the gizmos attached to this [Node3D].
|
||||
</description>
|
||||
</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">
|
||||
<return type="Node3D" />
|
||||
<description>
|
||||
|
@ -630,6 +630,10 @@
|
||||
<member name="debug/settings/gdscript/max_call_stack" type="int" setter="" getter="" default="1024">
|
||||
Maximum call stack allowed for debugging GDScript.
|
||||
</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">
|
||||
Maximum number of functions per frame allowed when profiling.
|
||||
</member>
|
||||
@ -2325,7 +2329,8 @@
|
||||
</member>
|
||||
<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.
|
||||
[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.
|
||||
</member>
|
||||
<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">
|
||||
|
@ -1855,6 +1855,14 @@
|
||||
Sets the visibility range values for the given geometry instance. Equivalent to [member GeometryInstance3D.visibility_range_begin] and related properties.
|
||||
</description>
|
||||
</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">
|
||||
<return type="void" />
|
||||
<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.
|
||||
</description>
|
||||
</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">
|
||||
<return type="void" />
|
||||
<param index="0" name="instance" type="RID" />
|
||||
|
@ -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">
|
||||
If [code]true[/code], the viewport will use a unique copy of the [World3D] defined in [member world_3d].
|
||||
</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">
|
||||
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.
|
||||
|
@ -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_gpu_profile", 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.
|
||||
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();
|
||||
|
||||
#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
|
||||
// by the physics server, otherwise the current and previous transforms
|
||||
// may be the same, and no interpolation takes place.
|
||||
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()->flush_queries();
|
||||
|
||||
@ -4066,6 +4067,7 @@ bool Main::iteration() {
|
||||
#endif // _3D_DISABLED
|
||||
PhysicsServer2D::get_singleton()->end_sync();
|
||||
|
||||
Engine::get_singleton()->_in_physics = false;
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
@ -4089,6 +4091,8 @@ bool Main::iteration() {
|
||||
|
||||
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_max = MAX(OS::get_singleton()->get_ticks_usec() - physics_begin, physics_process_max);
|
||||
|
||||
|
@ -299,17 +299,6 @@ int64_t MainTimerSync::DeltaSmoother::smooth_delta(int64_t p_delta) {
|
||||
// before advance_core considers changing the physics_steps return from
|
||||
// the typical values as defined by typical_physics_steps
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,9 @@
|
||||
#include "camera_3d.h"
|
||||
|
||||
#include "core/math/projection.h"
|
||||
#include "core/math/transform_interpolator.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "servers/rendering/rendering_server_constants.h"
|
||||
|
||||
void Camera3D::_update_audio_listener_state() {
|
||||
}
|
||||
@ -88,7 +90,16 @@ void Camera3D::_update_camera() {
|
||||
return;
|
||||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
@ -97,6 +108,64 @@ void Camera3D::_update_camera() {
|
||||
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) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_WORLD: {
|
||||
@ -118,11 +187,58 @@ void Camera3D::_notification(int p_what) {
|
||||
#endif
|
||||
} 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: {
|
||||
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();
|
||||
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
|
||||
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;
|
||||
|
||||
case NOTIFICATION_EXIT_WORLD: {
|
||||
@ -151,23 +267,34 @@ void Camera3D::_notification(int p_what) {
|
||||
if (viewport) {
|
||||
viewport->find_world_3d()->_register_camera(this);
|
||||
}
|
||||
_update_process_mode();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_LOST_CURRENT: {
|
||||
if (viewport) {
|
||||
viewport->find_world_3d()->_remove_camera(this);
|
||||
}
|
||||
_update_process_mode();
|
||||
} 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(0) * h_offset;
|
||||
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 {
|
||||
Size2 viewport_size = get_viewport()->get_visible_rect().size;
|
||||
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);
|
||||
|
||||
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;
|
||||
|
||||
Point2 res;
|
||||
|
@ -98,7 +98,39 @@ private:
|
||||
RID pyramid_shape;
|
||||
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:
|
||||
// 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();
|
||||
virtual void _request_camera_update();
|
||||
void _update_camera_mode();
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
#include "node_3d.h"
|
||||
|
||||
#include "core/math/transform_interpolator.h"
|
||||
#include "scene/3d/visual_instance_3d.h"
|
||||
#include "scene/main/viewport.h"
|
||||
#include "scene/property_utils.h"
|
||||
@ -176,6 +177,7 @@ void Node3D::_notification(int p_what) {
|
||||
data.parent = nullptr;
|
||||
data.C = nullptr;
|
||||
_update_visibility_parent(true);
|
||||
_disable_client_physics_interpolation();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_ENTER_WORLD: {
|
||||
@ -226,6 +228,12 @@ void Node3D::_notification(int p_what) {
|
||||
}
|
||||
#endif
|
||||
} 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 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 {
|
||||
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("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("get_global_position"), &Node3D::get_global_position);
|
||||
ClassDB::bind_method(D_METHOD("set_global_basis", "basis"), &Node3D::set_global_basis);
|
||||
@ -1236,4 +1358,27 @@ void Node3D::_bind_methods() {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
@ -85,7 +85,15 @@ private:
|
||||
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;
|
||||
SelfList<Node3D> _client_physics_interpolation_node_3d_list;
|
||||
|
||||
// This Data struct is to avoid namespace pollution in derived classes.
|
||||
|
||||
@ -101,8 +109,19 @@ private:
|
||||
|
||||
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;
|
||||
|
||||
@ -110,18 +129,13 @@ private:
|
||||
List<Node3D *> children;
|
||||
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
|
||||
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
|
||||
|
||||
} data;
|
||||
@ -150,6 +164,11 @@ protected:
|
||||
_FORCE_INLINE_ void _update_local_transform() 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);
|
||||
static void _bind_methods();
|
||||
|
||||
@ -208,6 +227,9 @@ public:
|
||||
Quaternion get_quaternion() const;
|
||||
Transform3D get_global_transform() const;
|
||||
|
||||
Transform3D get_global_transform_interpolated();
|
||||
bool update_client_physics_interpolation_data();
|
||||
|
||||
#ifdef TOOLS_ENABLED
|
||||
virtual Transform3D get_global_gizmo_transform() const;
|
||||
virtual Transform3D get_local_gizmo_transform() const;
|
||||
@ -279,6 +301,7 @@ public:
|
||||
NodePath get_visibility_parent() const;
|
||||
|
||||
Node3D();
|
||||
~Node3D();
|
||||
};
|
||||
|
||||
VARIANT_ENUM_CAST(Node3D::RotationEditMode)
|
||||
|
@ -503,7 +503,11 @@ Transform3D SkeletonIK3D::_get_target_transform() {
|
||||
|
||||
Node3D *target_node_override = cast_to<Node3D>(target_node_override_ref.get_validated_object());
|
||||
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 {
|
||||
return target;
|
||||
}
|
||||
|
@ -30,6 +30,8 @@
|
||||
|
||||
#include "visual_instance_3d.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
|
||||
AABB VisualInstance3D::get_aabb() const {
|
||||
AABB ret;
|
||||
GDVIRTUAL_CALL(_get_aabb, ret);
|
||||
@ -41,7 +43,38 @@ void VisualInstance3D::_update_visibility() {
|
||||
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) {
|
||||
@ -53,13 +86,52 @@ void VisualInstance3D::_notification(int p_what) {
|
||||
} break;
|
||||
|
||||
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;
|
||||
|
||||
case NOTIFICATION_EXIT_WORLD: {
|
||||
RenderingServer::get_singleton()->instance_set_scenario(instance, RID());
|
||||
RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID());
|
||||
_set_vi_visible(false);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
|
@ -45,6 +45,9 @@ class VisualInstance3D : public Node3D {
|
||||
protected:
|
||||
void _update_visibility();
|
||||
|
||||
virtual void _physics_interpolated_changed() override;
|
||||
void set_instance_use_identity_transform(bool p_enable);
|
||||
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
void _validate_property(PropertyInfo &p_property) const;
|
||||
|
@ -138,6 +138,12 @@ void Node::_notification(int p_notification) {
|
||||
|
||||
get_tree()->nodes_in_tree_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;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
@ -437,6 +443,18 @@ void Node::_propagate_physics_interpolated(bool p_interpolated) {
|
||||
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) {
|
||||
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);
|
||||
@ -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 (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);
|
||||
}
|
||||
|
||||
void Node::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 {
|
||||
@ -3825,6 +3851,9 @@ Node::Node() {
|
||||
data.unhandled_key_input = false;
|
||||
|
||||
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.in_constructor = true;
|
||||
|
@ -225,6 +225,21 @@ private:
|
||||
// is switched on.
|
||||
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 in_constructor : 1;
|
||||
bool use_placeholder : 1;
|
||||
@ -263,6 +278,7 @@ private:
|
||||
void _propagate_exit_tree();
|
||||
void _propagate_after_exit_tree();
|
||||
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_groups_dirty();
|
||||
Array _get_node_and_resource(const NodePath &p_path);
|
||||
@ -334,6 +350,15 @@ protected:
|
||||
void _set_owner_nocheck(Node *p_owner);
|
||||
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
|
||||
void _call_input(const Ref<InputEvent> &p_event);
|
||||
void _call_shortcut_input(const Ref<InputEvent> &p_event);
|
||||
|
@ -59,6 +59,7 @@
|
||||
#include "servers/navigation_server_3d.h"
|
||||
#include "servers/physics_server_2d.h"
|
||||
#ifndef _3D_DISABLED
|
||||
#include "scene/3d/node_3d.h"
|
||||
#include "scene/resources/3d/world_3d.h"
|
||||
#include "servers/physics_server_3d.h"
|
||||
#endif // _3D_DISABLED
|
||||
@ -118,6 +119,29 @@ void SceneTreeTimer::release_connections() {
|
||||
|
||||
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() {
|
||||
emit_signal(tree_changed_name);
|
||||
}
|
||||
@ -466,9 +490,31 @@ bool SceneTree::is_physics_interpolation_enabled() const {
|
||||
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() {
|
||||
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();
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
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) {
|
||||
if (MainLoop::process(p_time)) {
|
||||
_quit = true;
|
||||
@ -570,6 +624,10 @@ bool SceneTree::process(double p_time) {
|
||||
#endif // _3D_DISABLED
|
||||
#endif // TOOLS_ENABLED
|
||||
|
||||
if (_physics_interpolation_enabled) {
|
||||
RenderingServer::get_singleton()->pre_draw(true);
|
||||
}
|
||||
|
||||
return _quit;
|
||||
}
|
||||
|
||||
@ -1761,6 +1819,13 @@ SceneTree::SceneTree() {
|
||||
|
||||
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.
|
||||
set_multiplayer(MultiplayerAPI::create_default_interface());
|
||||
|
||||
|
@ -41,6 +41,9 @@
|
||||
|
||||
class PackedScene;
|
||||
class Node;
|
||||
#ifndef _3D_DISABLED
|
||||
class Node3D;
|
||||
#endif
|
||||
class Window;
|
||||
class Material;
|
||||
class Mesh;
|
||||
@ -120,6 +123,13 @@ private:
|
||||
bool changed = false;
|
||||
};
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
struct ClientPhysicsInterpolation {
|
||||
SelfList<Node3D>::List _node_3d_list;
|
||||
void physics_process();
|
||||
} _client_physics_interpolation;
|
||||
#endif
|
||||
|
||||
Window *root = nullptr;
|
||||
|
||||
double physics_process_time = 0.0;
|
||||
@ -315,6 +325,7 @@ public:
|
||||
virtual void iteration_prepare() override;
|
||||
|
||||
virtual bool physics_process(double p_time) override;
|
||||
virtual void iteration_end() override;
|
||||
virtual bool process(double p_time) override;
|
||||
|
||||
virtual void finalize() override;
|
||||
@ -423,6 +434,11 @@ public:
|
||||
void set_physics_interpolation_enabled(bool p_enabled);
|
||||
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();
|
||||
};
|
||||
|
@ -5024,6 +5024,13 @@ Viewport::Viewport() {
|
||||
#endif // _3D_DISABLED
|
||||
|
||||
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() {
|
||||
|
@ -34,10 +34,16 @@
|
||||
#include "core/object/worker_thread_pool.h"
|
||||
#include "core/os/os.h"
|
||||
#include "rendering_light_culler.h"
|
||||
#include "rendering_server_constants.h"
|
||||
#include "rendering_server_default.h"
|
||||
|
||||
#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 */
|
||||
|
||||
#ifndef _3D_DISABLED
|
||||
@ -53,6 +59,20 @@ static float get_halton_value(int p_index, int p_base) {
|
||||
}
|
||||
#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 */
|
||||
|
||||
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) {
|
||||
Camera *camera = camera_owner.get_or_null(p_camera);
|
||||
ERR_FAIL_NULL(camera);
|
||||
|
||||
camera->transform = p_transform.orthonormalized();
|
||||
}
|
||||
|
||||
@ -924,8 +945,13 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
|
||||
Instance *instance = instance_owner.get_or_null(p_instance);
|
||||
ERR_FAIL_NULL(instance);
|
||||
|
||||
#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
|
||||
return; // Must be checked to avoid worst evil.
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
@ -938,6 +964,99 @@ void RendererSceneCull::instance_set_transform(RID p_instance, const Transform3D
|
||||
#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
|
||||
|
||||
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_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);
|
||||
|
||||
#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) {
|
||||
@ -990,6 +1109,23 @@ void RendererSceneCull::instance_set_visible(RID p_instance, bool p_visible) {
|
||||
|
||||
if (p_visible) {
|
||||
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);
|
||||
}
|
||||
} 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) {
|
||||
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) {
|
||||
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();
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
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)) {
|
||||
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) {
|
||||
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) {
|
||||
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;
|
||||
new_aabb = p_instance->transform.xform(p_instance->aabb);
|
||||
new_aabb = instance_xform->xform(p_instance->aabb);
|
||||
p_instance->transformed_aabb = new_aabb;
|
||||
|
||||
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);
|
||||
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.
|
||||
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;
|
||||
return;
|
||||
}
|
||||
@ -4180,6 +4327,8 @@ bool RendererSceneCull::free(RID 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_set_scenario(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();
|
||||
}
|
||||
|
||||
/* 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() {
|
||||
render_pass = 1;
|
||||
singleton = this;
|
||||
|
@ -32,6 +32,7 @@
|
||||
#define RENDERER_SCENE_CULL_H
|
||||
|
||||
#include "core/math/dynamic_bvh.h"
|
||||
#include "core/math/transform_interpolator.h"
|
||||
#include "core/templates/bin_sorted_array.h"
|
||||
#include "core/templates/local_vector.h"
|
||||
#include "core/templates/paged_allocator.h"
|
||||
@ -66,6 +67,11 @@ public:
|
||||
|
||||
static RendererSceneCull *singleton;
|
||||
|
||||
/* EVENT QUEUING */
|
||||
|
||||
void tick();
|
||||
void pre_draw(bool p_will_draw);
|
||||
|
||||
/* CAMERA API */
|
||||
|
||||
struct Camera {
|
||||
@ -406,8 +412,16 @@ public:
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
bool ignore_occlusion_culling;
|
||||
@ -418,13 +432,23 @@ public:
|
||||
RS::ShadowCastingSetting cast_shadows;
|
||||
|
||||
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;
|
||||
Rect2 lightmap_uv_scale;
|
||||
@ -574,6 +598,14 @@ public:
|
||||
baked_light = true;
|
||||
dynamic_gi = 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 = nullptr;
|
||||
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_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_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_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);
|
||||
@ -1393,6 +1427,22 @@ public:
|
||||
|
||||
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();
|
||||
virtual ~RendererSceneCull();
|
||||
};
|
||||
|
@ -83,6 +83,8 @@ public:
|
||||
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_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_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;
|
||||
@ -350,6 +352,16 @@ public:
|
||||
|
||||
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();
|
||||
virtual ~RenderingMethod();
|
||||
};
|
||||
|
48
servers/rendering/rendering_server_constants.h
Normal file
48
servers/rendering/rendering_server_constants.h
Normal file
@ -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
|
@ -381,12 +381,9 @@ void RenderingServerDefault::_thread_loop() {
|
||||
|
||||
/* INTERPOLATION */
|
||||
|
||||
void RenderingServerDefault::tick() {
|
||||
RSG::canvas->tick();
|
||||
}
|
||||
|
||||
void RenderingServerDefault::set_physics_interpolation_enabled(bool p_enabled) {
|
||||
RSG::canvas->set_physics_interpolation_enabled(p_enabled);
|
||||
RSG::scene->set_physics_interpolation_enabled(p_enabled);
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
p_callable.call();
|
||||
}
|
||||
|
@ -802,6 +802,8 @@ public:
|
||||
FUNC2(instance_set_layer_mask, RID, uint32_t)
|
||||
FUNC3(instance_set_pivot_data, RID, float, bool)
|
||||
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)
|
||||
FUNC3(instance_set_blend_shape_weight, RID, int, float)
|
||||
FUNC3(instance_set_surface_override_material, RID, int, RID)
|
||||
@ -1048,7 +1050,6 @@ public:
|
||||
|
||||
/* INTERPOLATION */
|
||||
|
||||
virtual void tick() override;
|
||||
virtual void set_physics_interpolation_enabled(bool p_enabled) override;
|
||||
|
||||
/* EVENT QUEUING */
|
||||
@ -1060,6 +1061,8 @@ public:
|
||||
virtual bool has_changed() const override;
|
||||
virtual void init() 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 {
|
||||
return Thread::get_caller_id() == server_thread;
|
||||
|
@ -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_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_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_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);
|
||||
|
@ -1344,6 +1344,8 @@ public:
|
||||
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_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_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;
|
||||
@ -1657,7 +1659,6 @@ public:
|
||||
|
||||
/* INTERPOLATION */
|
||||
|
||||
virtual void tick() = 0;
|
||||
virtual void set_physics_interpolation_enabled(bool p_enabled) = 0;
|
||||
|
||||
/* EVENT QUEUING */
|
||||
@ -1669,6 +1670,8 @@ public:
|
||||
virtual bool has_changed() const = 0;
|
||||
virtual void init();
|
||||
virtual void finish() = 0;
|
||||
virtual void tick() = 0;
|
||||
virtual void pre_draw(bool p_will_draw) = 0;
|
||||
|
||||
/* STATUS INFORMATION */
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user