diff --git a/core/local_vector.h b/core/local_vector.h index b5fe6999f75f..397bdc317b72 100644 --- a/core/local_vector.h +++ b/core/local_vector.h @@ -101,6 +101,22 @@ class LocalVector { } } + U erase_multiple_unordered(const T &p_val) { + U from = 0; + U count = 0; + while (true) { + int64_t idx = find(p_val, from); + + if (idx == -1) { + break; + } + remove_unordered(idx); + from = idx; + count++; + } + return count; + } + void invert() { for (U i = 0; i < count / 2; i++) { SWAP(data[i], data[count - i - 1]); diff --git a/core/math/basis.h b/core/math/basis.h index 020c759f8985..434e0827fe39 100644 --- a/core/math/basis.h +++ b/core/math/basis.h @@ -171,6 +171,7 @@ class _NO_DISCARD_CLASS_ Basis { bool is_rotation() const; Basis slerp(const Basis &p_to, const real_t &p_weight) const; + _FORCE_INLINE_ Basis lerp(const Basis &p_to, const real_t &p_weight) const; operator String() const; @@ -340,4 +341,13 @@ real_t Basis::determinant() const { elements[1][0] * (elements[0][1] * elements[2][2] - elements[2][1] * elements[0][2]) + elements[2][0] * (elements[0][1] * elements[1][2] - elements[1][1] * elements[0][2]); } + +Basis Basis::lerp(const Basis &p_to, const real_t &p_weight) const { + Basis b; + b.elements[0] = elements[0].linear_interpolate(p_to.elements[0], p_weight); + b.elements[1] = elements[1].linear_interpolate(p_to.elements[1], p_weight); + b.elements[2] = elements[2].linear_interpolate(p_to.elements[2], p_weight); + + return b; +} #endif // BASIS_H diff --git a/core/math/transform_interpolator.cpp b/core/math/transform_interpolator.cpp new file mode 100644 index 000000000000..9d48cb65a0f2 --- /dev/null +++ b/core/math/transform_interpolator.cpp @@ -0,0 +1,369 @@ +/*************************************************************************/ +/* transform_interpolator.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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. */ +/*************************************************************************/ + +#include "transform_interpolator.h" + +void TransformInterpolator::interpolate_transform(const Transform &p_prev, const Transform &p_curr, Transform &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_via_method(const Transform &p_prev, const Transform &p_curr, Transform &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; + } +} + +Quat TransformInterpolator::_basis_to_quat_unchecked(const Basis &p_basis) { + Basis m = p_basis; + real_t trace = m.elements[0][0] + m.elements[1][1] + m.elements[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.elements[2][1] - m.elements[1][2]) * s); + temp[1] = ((m.elements[0][2] - m.elements[2][0]) * s); + temp[2] = ((m.elements[1][0] - m.elements[0][1]) * s); + } else { + int i = m.elements[0][0] < m.elements[1][1] + ? (m.elements[1][1] < m.elements[2][2] ? 2 : 1) + : (m.elements[0][0] < m.elements[2][2] ? 2 : 0); + int j = (i + 1) % 3; + int k = (i + 2) % 3; + + real_t s = Math::sqrt(m.elements[i][i] - m.elements[j][j] - m.elements[k][k] + 1.0f); + temp[i] = s * 0.5f; + s = 0.5f / s; + + temp[3] = (m.elements[k][j] - m.elements[j][k]) * s; + temp[j] = (m.elements[j][i] + m.elements[i][j]) * s; + temp[k] = (m.elements[k][i] + m.elements[i][k]) * s; + } + + return Quat(temp[0], temp[1], temp[2], temp[3]); +} + +Quat TransformInterpolator::_quat_slerp_unchecked(const Quat &p_from, const Quat &p_to, real_t p_fraction) { + Quat to1; + real_t omega, cosom, sinom, scale0, scale1; + + // calc 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 Quat( + 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) { + Quat from = _basis_to_quat_unchecked(p_from); + Quat 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; + } + } +} + +real_t TransformInterpolator::checksum_transform(const Transform &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.elements[0]); + sum += vec3_sum(p_transform.basis.elements[1]); + sum -= vec3_sum(p_transform.basis.elements[2]); + return sum; +} + +// return 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_axis(0); + Vector3 y = r_basis.get_axis(1); + Vector3 z = r_basis.get_axis(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_axis(0, x); + r_basis.set_axis(1, y); + r_basis.set_axis(2, z); + + return lengths; +} + +TransformInterpolator::Method TransformInterpolator::_test_basis(Basis p_basis, bool r_needed_normalize, Quat &r_quat) { + // axis lengths + Vector3 al = Vector3(p_basis.get_axis(0).length_squared(), + p_basis.get_axis(1).length_squared(), + p_basis.get_axis(2).length_squared()); + + // non unit scale? + if (r_needed_normalize || !al.is_equal_approx(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_axis(0, norm_basis.get_axis(0) / al.x); + norm_basis.set_axis(1, norm_basis.get_axis(1) / al.y); + norm_basis.set_axis(2, norm_basis.get_axis(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 basis, 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; + } + + // 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_axis(0).cross(p_basis.get_axis(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_axis(2)); + if (dot < epsilon) { + return false; + } + + cross = p_basis.get_axis(1).cross(p_basis.get_axis(2)); + l = _vec3_normalize(cross); + // too small numbers, revert to lerp + if (l < 0.001f) { + return false; + } + + dot = cross.dot(p_basis.get_axis(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 (!m[0].is_equal_approx(identity[0], p_epsilon) || !m[1].is_equal_approx(identity[1], p_epsilon) || !m[2].is_equal_approx(identity[2], p_epsilon)) { + return false; + } + return true; +} + +TransformInterpolator::Method TransformInterpolator::find_method(const Basis &p_a, const Basis &p_b) { + bool needed_normalize = false; + + Quat q0; + Method method = _test_basis(p_a, needed_normalize, q0); + if (method == INTERP_LERP) { + return method; + } + + Quat 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. + // Calc cosine + real_t cosom = Math::abs(q0.dot(q1)); + if ((1.0f - cosom) <= (real_t)CMP_EPSILON) { + return INTERP_LERP; + } + + return method; +} diff --git a/core/math/transform_interpolator.h b/core/math/transform_interpolator.h new file mode 100644 index 000000000000..cbe1c6b56030 --- /dev/null +++ b/core/math/transform_interpolator.h @@ -0,0 +1,87 @@ +/*************************************************************************/ +/* transform_interpolator.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* 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 TRANSFORM_INTERPOLATOR_H +#define TRANSFORM_INTERPOLATOR_H + +#include "core/math/math_defs.h" +#include "core/math/quat.h" +#include "core/math/transform.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. + +class Transform; + +class TransformInterpolator { +public: + enum Method { + INTERP_LERP, + INTERP_SLERP, + INTERP_SCALED_SLERP, + }; + +private: + static real_t _vec3_normalize(Vector3 &p_vec); + static Vector3 _basis_orthonormalize(Basis &r_basis); + static real_t vec3_sum(const Vector3 &p_pt) { return p_pt.x + p_pt.y + p_pt.z; } + static Method _test_basis(Basis p_basis, bool r_needed_normalize, Quat &r_quat); + static Basis _basis_slerp_unchecked(Basis p_from, Basis p_to, real_t p_fraction); + static Quat _quat_slerp_unchecked(const Quat &p_from, const Quat &p_to, real_t p_fraction); + static Quat _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: + // 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(const Transform &p_prev, const Transform &p_curr, Transform &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_via_method(const Transform &p_prev, const Transform &p_curr, Transform &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(const Transform &p_transform); + static Method find_method(const Basis &p_a, const Basis &p_b); +}; + +#endif // TRANSFORM_INTERPOLATOR_H diff --git a/doc/classes/MultiMesh.xml b/doc/classes/MultiMesh.xml index 875d5c777ec2..e6dde4288d07 100644 --- a/doc/classes/MultiMesh.xml +++ b/doc/classes/MultiMesh.xml @@ -48,6 +48,14 @@ Returns the [Transform2D] of a specific instance. + + + + + When using [i]physics interpolation[/i], this function allows you to prevent interpolation on an instance in the current physics tick. + This allows you to move instances instantaneously, and should usually be used when initially placing an instance such as a bullet to prevent graphical glitches. + + @@ -57,6 +65,16 @@ [Transform] is stored as 12 floats, [Transform2D] is stored as 8 floats, [code]COLOR_8BIT[/code] / [code]CUSTOM_DATA_8BIT[/code] is stored as 1 float (4 bytes as is) and [code]COLOR_FLOAT[/code] / [code]CUSTOM_DATA_FLOAT[/code] is stored as 4 floats. + + + + + + An alternative version of [method MultiMesh.set_as_bulk_array] which can be used with [i]physics interpolation[/i]. This method takes two arrays, and can set the data for the current and previous tick in one go. The renderer will automatically interpolate the data at each frame. + This is useful for situations where the order of instances may change from physics tick to tick, such as particle systems. + When the order of instances is coherent, the simpler [method MultiMesh.set_as_bulk_array] can still be used with interpolation. + + @@ -104,6 +122,11 @@ Mesh to be drawn. + + Choose whether to use an interpolation method that favors speed or quality. + When using low physics tick rates (typically below 20) or high rates of object rotation, you may get better results from the high quality setting. + [b]Note:[/b] Fast quality does not equate to low quality. Except in the special cases mentioned above, the quality should be comparable to high quality. + Format of transform used to transform mesh, either 2D or 3D. @@ -136,5 +159,11 @@ The [Color] passed into [method set_instance_custom_data] will use 4 floats. Use this for highest precision. + + Always interpolate using Basis lerping, which can produce warping artifacts in some situations. + + + Attempt to interpolate using Basis slerping (spherical linear interpolation) where possible, otherwise fall back to lerping. + diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml index c4a81b054c90..6f9566f2e9c0 100644 --- a/doc/classes/Node.xml +++ b/doc/classes/Node.xml @@ -379,6 +379,21 @@ Returns [code]true[/code] if the local system is the master of this node. + + + + Returns [code]true[/code] if the physics interpolated flag is set for this Node (see [method set_physics_interpolated]). + [b]Note:[/b] Interpolation will only be active is both the flag is set [b]and[/b] physics interpolation is enabled within the [SceneTree]. This can be tested using [method is_physics_interpolated_and_enabled]. + + + + + + Returns [code]true[/code] if physics interpolation is enabled (see [method set_physics_interpolated]) [b]and[/b] enabled in the [SceneTree]. + This is a convenience version of [method is_physics_interpolated] that also checks whether physics interpolation is enabled globally. + See [member SceneTree.physics_interpolation] and [member ProjectSettings.physics/common/physics_interpolation]. + + @@ -531,6 +546,15 @@ Requests that [code]_ready[/code] be called again. Note that the method won't be called immediately, but is scheduled for when the node is added to the scene tree again (see [method _ready]). [code]_ready[/code] is called only for the node which requested it, which means that you need to request ready for each child if you want them to call [code]_ready[/code] too (in which case, [code]_ready[/code] will be called in the same order as it would normally). + + + + When physics interpolation is active, moving a node to a radically different transform (such as placement within a level) can result in a visible glitch as the object is rendered moving from the old to new position over the physics tick. + This glitch can be prevented by calling [code]reset_physics_interpolation[/code], which temporarily turns off interpolation until the physics tick is complete. + [constant NOTIFICATION_RESET_PHYSICS_INTERPOLATION] will be received by the node and all children recursively. + [b]Note:[/b] This function should be called [b]after[/b] moving the node, rather than before. + + @@ -627,6 +651,14 @@ Sets the node's network master to the peer with the given peer ID. The network master is the peer that has authority over the node on the network. Useful in conjunction with the [code]master[/code] and [code]puppet[/code] keywords. Inherited from the parent node by default, which ultimately defaults to peer ID 1 (the server). If [code]recursive[/code], the given peer is recursively set as the master for all children of this node. + + + + + Enables or disables physics interpolation per node, offering a finer grain of control than turning physics interpolation on and off globally. + [b]Note:[/b] This can be especially useful for [Camera]s, where custom interpolation can sometimes give superior results. + + @@ -809,6 +841,9 @@ Notification received when the node is ready, just before [constant NOTIFICATION_READY] is received. Unlike the latter, it's sent every time the node enters tree, instead of only once. + + Notification received when [method reset_physics_interpolation] is called on the node or parent nodes. + Notification received from the OS when the mouse enters the game window. Implemented on desktop and web platforms. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index fcf408f553c7..0b64a6f24c3b 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1152,6 +1152,10 @@ [b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.iterations_per_second] instead. [b]Note:[/b] Only 8 physics ticks may be simulated per rendered frame at most. If more than 8 physics ticks have to be simulated per rendered frame to keep up with rendering, the game will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended not to increase [member physics/common/physics_fps] above 240. Otherwise, the game will slow down when the rendering framerate goes below 30 FPS. + + If [code]true[/code], the renderer will interpolate the transforms of physics objects between the last two transforms, such that smooth motion is seen when physics ticks do not coincide with rendered frames. + [b]Note:[/b] When moving objects to new positions (rather than the usual physics motion) you may want to temporarily turn off interpolation to prevent a visible glitch. You can do this using the [method Node.reset_physics_interpolation] function. + 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 fine 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] For best results, 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[/code]. diff --git a/doc/classes/SceneTree.xml b/doc/classes/SceneTree.xml index d7b7205e02fa..0e1e9effbc21 100644 --- a/doc/classes/SceneTree.xml +++ b/doc/classes/SceneTree.xml @@ -254,6 +254,9 @@ - 2D and 3D physics will be stopped. This includes signals and collision detection. - [method Node._process], [method Node._physics_process] and [method Node._input] will not be called anymore in nodes. + + Although physics interpolation would normally be globally turned on and off using [member ProjectSettings.physics/common/physics_interpolation], this property allows control over interpolation at runtime. + If [code]true[/code], the [SceneTree]'s [member network_peer] refuses new incoming connections. diff --git a/doc/classes/Spatial.xml b/doc/classes/Spatial.xml index 6bd6bc73be2a..e8eabd2cd6a6 100644 --- a/doc/classes/Spatial.xml +++ b/doc/classes/Spatial.xml @@ -19,6 +19,13 @@ Forces the transform to update. Transform changes in physics are not instant for performance reasons. Transforms are accumulated and then set. Use this if you need an up-to-date transform when doing physics operations. + + + + 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 [Camera]s focusing on a node, or finding where to fire lasers from on a frame rather than physics tick. + + diff --git a/drivers/dummy/rasterizer_dummy.h b/drivers/dummy/rasterizer_dummy.h index a458062d5268..0373bbcc0ef2 100644 --- a/drivers/dummy/rasterizer_dummy.h +++ b/drivers/dummy/rasterizer_dummy.h @@ -435,30 +435,26 @@ class RasterizerStorageDummy : public RasterizerStorage { /* MULTIMESH API */ - virtual RID multimesh_create() { return RID(); } - - void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) {} - int multimesh_get_instance_count(RID p_multimesh) const { return 0; } - - void multimesh_set_mesh(RID p_multimesh, RID p_mesh) {} - void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) {} - void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {} - void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {} - void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {} - - RID multimesh_get_mesh(RID p_multimesh) const { return RID(); } - - Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const { return Transform(); } - Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { return Transform2D(); } - Color multimesh_instance_get_color(RID p_multimesh, int p_index) const { return Color(); } - Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { return Color(); } - - void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) {} - - void multimesh_set_visible_instances(RID p_multimesh, int p_visible) {} - int multimesh_get_visible_instances(RID p_multimesh) const { return 0; } - - AABB multimesh_get_aabb(RID p_multimesh) const { return AABB(); } + virtual RID _multimesh_create() { return RID(); } + + void _multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) {} + int _multimesh_get_instance_count(RID p_multimesh) const { return 0; } + void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) {} + void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) {} + void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) {} + void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) {} + void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) {} + RID _multimesh_get_mesh(RID p_multimesh) const { return RID(); } + Transform _multimesh_instance_get_transform(RID p_multimesh, int p_index) const { return Transform(); } + Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { return Transform2D(); } + Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const { return Color(); } + Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { return Color(); } + void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) {} + void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) {} + int _multimesh_get_visible_instances(RID p_multimesh) const { return 0; } + AABB _multimesh_get_aabb(RID p_multimesh) const { return AABB(); } + + MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const { return nullptr; } /* IMMEDIATE API */ diff --git a/drivers/gles2/rasterizer_storage_gles2.cpp b/drivers/gles2/rasterizer_storage_gles2.cpp index 80a6bf882301..1fa52a50af06 100644 --- a/drivers/gles2/rasterizer_storage_gles2.cpp +++ b/drivers/gles2/rasterizer_storage_gles2.cpp @@ -2923,12 +2923,12 @@ void RasterizerStorageGLES2::mesh_clear(RID p_mesh) { /* MULTIMESH API */ -RID RasterizerStorageGLES2::multimesh_create() { +RID RasterizerStorageGLES2::_multimesh_create() { MultiMesh *multimesh = memnew(MultiMesh); return multimesh_owner.make_rid(multimesh); } -void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data) { +void RasterizerStorageGLES2::_multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); @@ -3045,14 +3045,14 @@ void RasterizerStorageGLES2::multimesh_allocate(RID p_multimesh, int p_instances } } -int RasterizerStorageGLES2::multimesh_get_instance_count(RID p_multimesh) const { +int RasterizerStorageGLES2::_multimesh_get_instance_count(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, 0); return multimesh->size; } -void RasterizerStorageGLES2::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { +void RasterizerStorageGLES2::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); @@ -3079,7 +3079,7 @@ void RasterizerStorageGLES2::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { } } -void RasterizerStorageGLES2::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) { +void RasterizerStorageGLES2::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -3110,7 +3110,7 @@ void RasterizerStorageGLES2::multimesh_instance_set_transform(RID p_multimesh, i } } -void RasterizerStorageGLES2::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) { +void RasterizerStorageGLES2::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -3136,7 +3136,7 @@ void RasterizerStorageGLES2::multimesh_instance_set_transform_2d(RID p_multimesh } } -void RasterizerStorageGLES2::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) { +void RasterizerStorageGLES2::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -3168,7 +3168,7 @@ void RasterizerStorageGLES2::multimesh_instance_set_color(RID p_multimesh, int p } } -void RasterizerStorageGLES2::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data) { +void RasterizerStorageGLES2::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -3200,14 +3200,14 @@ void RasterizerStorageGLES2::multimesh_instance_set_custom_data(RID p_multimesh, } } -RID RasterizerStorageGLES2::multimesh_get_mesh(RID p_multimesh) const { +RID RasterizerStorageGLES2::_multimesh_get_mesh(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, RID()); return multimesh->mesh; } -Transform RasterizerStorageGLES2::multimesh_instance_get_transform(RID p_multimesh, int p_index) const { +Transform RasterizerStorageGLES2::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Transform()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform()); @@ -3234,7 +3234,7 @@ Transform RasterizerStorageGLES2::multimesh_instance_get_transform(RID p_multime return xform; } -Transform2D RasterizerStorageGLES2::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { +Transform2D RasterizerStorageGLES2::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Transform2D()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform2D()); @@ -3255,7 +3255,7 @@ Transform2D RasterizerStorageGLES2::multimesh_instance_get_transform_2d(RID p_mu return xform; } -Color RasterizerStorageGLES2::multimesh_instance_get_color(RID p_multimesh, int p_index) const { +Color RasterizerStorageGLES2::_multimesh_instance_get_color(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); @@ -3288,7 +3288,7 @@ Color RasterizerStorageGLES2::multimesh_instance_get_color(RID p_multimesh, int return Color(); } -Color RasterizerStorageGLES2::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { +Color RasterizerStorageGLES2::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); @@ -3321,7 +3321,7 @@ Color RasterizerStorageGLES2::multimesh_instance_get_custom_data(RID p_multimesh return Color(); } -void RasterizerStorageGLES2::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) { +void RasterizerStorageGLES2::_multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_COND(!multimesh->data.ptr()); @@ -3342,21 +3342,21 @@ void RasterizerStorageGLES2::multimesh_set_as_bulk_array(RID p_multimesh, const } } -void RasterizerStorageGLES2::multimesh_set_visible_instances(RID p_multimesh, int p_visible) { +void RasterizerStorageGLES2::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); multimesh->visible_instances = p_visible; } -int RasterizerStorageGLES2::multimesh_get_visible_instances(RID p_multimesh) const { +int RasterizerStorageGLES2::_multimesh_get_visible_instances(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, -1); return multimesh->visible_instances; } -AABB RasterizerStorageGLES2::multimesh_get_aabb(RID p_multimesh) const { +AABB RasterizerStorageGLES2::_multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, AABB()); @@ -3365,6 +3365,13 @@ AABB RasterizerStorageGLES2::multimesh_get_aabb(RID p_multimesh) const { return multimesh->aabb; } +RasterizerStorage::MMInterpolator *RasterizerStorageGLES2::_multimesh_get_interpolator(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); + ERR_FAIL_COND_V(!multimesh, nullptr); + + return &multimesh->interpolator; +} + void RasterizerStorageGLES2::update_dirty_multimeshes() { while (multimesh_update_list.first()) { MultiMesh *multimesh = multimesh_update_list.first()->self(); @@ -5961,6 +5968,9 @@ bool RasterizerStorageGLES2::free(RID p_rid) { return true; } else if (multimesh_owner.owns(p_rid)) { + // remove from interpolator + _interpolation_data.notify_free_multimesh(p_rid); + MultiMesh *multimesh = multimesh_owner.get(p_rid); multimesh->instance_remove_deps(); diff --git a/drivers/gles2/rasterizer_storage_gles2.h b/drivers/gles2/rasterizer_storage_gles2.h index 7dbf56501bf9..58c3f7b02ca5 100644 --- a/drivers/gles2/rasterizer_storage_gles2.h +++ b/drivers/gles2/rasterizer_storage_gles2.h @@ -787,6 +787,8 @@ class RasterizerStorageGLES2 : public RasterizerStorage { bool dirty_aabb; bool dirty_data; + MMInterpolator interpolator; + MultiMesh() : size(0), transform_format(VS::MULTIMESH_TRANSFORM_2D), @@ -807,30 +809,31 @@ class RasterizerStorageGLES2 : public RasterizerStorage { SelfList::List multimesh_update_list; - virtual RID multimesh_create(); + virtual RID _multimesh_create(); - virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE); - virtual int multimesh_get_instance_count(RID p_multimesh) const; + virtual void _multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE); + virtual int _multimesh_get_instance_count(RID p_multimesh) const; - virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh); - virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform); - virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform); - virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color); - virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data); + virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh); + virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform); + virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform); + virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color); + virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data); - virtual RID multimesh_get_mesh(RID p_multimesh) const; + virtual RID _multimesh_get_mesh(RID p_multimesh) const; - virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const; - virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const; - virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const; - virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const; + virtual Transform _multimesh_instance_get_transform(RID p_multimesh, int p_index) const; + virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const; + virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const; + virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const; - virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array); + virtual void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array); - virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible); - virtual int multimesh_get_visible_instances(RID p_multimesh) const; + virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible); + virtual int _multimesh_get_visible_instances(RID p_multimesh) const; - virtual AABB multimesh_get_aabb(RID p_multimesh) const; + virtual AABB _multimesh_get_aabb(RID p_multimesh) const; + virtual MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const; void update_dirty_multimeshes(); diff --git a/drivers/gles3/rasterizer_storage_gles3.cpp b/drivers/gles3/rasterizer_storage_gles3.cpp index ff206834eb91..131ef8ffb1d6 100644 --- a/drivers/gles3/rasterizer_storage_gles3.cpp +++ b/drivers/gles3/rasterizer_storage_gles3.cpp @@ -4459,12 +4459,12 @@ void RasterizerStorageGLES3::mesh_render_blend_shapes(Surface *s, const float *p /* MULTIMESH API */ -RID RasterizerStorageGLES3::multimesh_create() { +RID RasterizerStorageGLES3::_multimesh_create() { MultiMesh *multimesh = memnew(MultiMesh); return multimesh_owner.make_rid(multimesh); } -void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format) { +void RasterizerStorageGLES3::_multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); @@ -4597,14 +4597,14 @@ void RasterizerStorageGLES3::multimesh_allocate(RID p_multimesh, int p_instances } } -int RasterizerStorageGLES3::multimesh_get_instance_count(RID p_multimesh) const { +int RasterizerStorageGLES3::_multimesh_get_instance_count(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, 0); return multimesh->size; } -void RasterizerStorageGLES3::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { +void RasterizerStorageGLES3::_multimesh_set_mesh(RID p_multimesh, RID p_mesh) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); @@ -4631,7 +4631,7 @@ void RasterizerStorageGLES3::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { } } -void RasterizerStorageGLES3::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) { +void RasterizerStorageGLES3::_multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -4661,7 +4661,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_transform(RID p_multimesh, i } } -void RasterizerStorageGLES3::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) { +void RasterizerStorageGLES3::_multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -4686,7 +4686,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_transform_2d(RID p_multimesh multimesh_update_list.add(&multimesh->update_list); } } -void RasterizerStorageGLES3::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) { +void RasterizerStorageGLES3::_multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -4718,7 +4718,7 @@ void RasterizerStorageGLES3::multimesh_instance_set_color(RID p_multimesh, int p } } -void RasterizerStorageGLES3::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data) { +void RasterizerStorageGLES3::_multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_INDEX(p_index, multimesh->size); @@ -4749,14 +4749,14 @@ void RasterizerStorageGLES3::multimesh_instance_set_custom_data(RID p_multimesh, multimesh_update_list.add(&multimesh->update_list); } } -RID RasterizerStorageGLES3::multimesh_get_mesh(RID p_multimesh) const { +RID RasterizerStorageGLES3::_multimesh_get_mesh(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, RID()); return multimesh->mesh; } -Transform RasterizerStorageGLES3::multimesh_instance_get_transform(RID p_multimesh, int p_index) const { +Transform RasterizerStorageGLES3::_multimesh_instance_get_transform(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Transform()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform()); @@ -4782,7 +4782,7 @@ Transform RasterizerStorageGLES3::multimesh_instance_get_transform(RID p_multime return xform; } -Transform2D RasterizerStorageGLES3::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { +Transform2D RasterizerStorageGLES3::_multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Transform2D()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Transform2D()); @@ -4803,7 +4803,7 @@ Transform2D RasterizerStorageGLES3::multimesh_instance_get_transform_2d(RID p_mu return xform; } -Color RasterizerStorageGLES3::multimesh_instance_get_color(RID p_multimesh, int p_index) const { +Color RasterizerStorageGLES3::_multimesh_instance_get_color(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); @@ -4836,7 +4836,7 @@ Color RasterizerStorageGLES3::multimesh_instance_get_color(RID p_multimesh, int return Color(); } -Color RasterizerStorageGLES3::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { +Color RasterizerStorageGLES3::_multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, Color()); ERR_FAIL_INDEX_V(p_index, multimesh->size, Color()); @@ -4869,7 +4869,7 @@ Color RasterizerStorageGLES3::multimesh_instance_get_custom_data(RID p_multimesh return Color(); } -void RasterizerStorageGLES3::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) { +void RasterizerStorageGLES3::_multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); ERR_FAIL_COND(!multimesh->data.ptr()); @@ -4889,20 +4889,20 @@ void RasterizerStorageGLES3::multimesh_set_as_bulk_array(RID p_multimesh, const } } -void RasterizerStorageGLES3::multimesh_set_visible_instances(RID p_multimesh, int p_visible) { +void RasterizerStorageGLES3::_multimesh_set_visible_instances(RID p_multimesh, int p_visible) { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND(!multimesh); multimesh->visible_instances = p_visible; } -int RasterizerStorageGLES3::multimesh_get_visible_instances(RID p_multimesh) const { +int RasterizerStorageGLES3::_multimesh_get_visible_instances(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, -1); return multimesh->visible_instances; } -AABB RasterizerStorageGLES3::multimesh_get_aabb(RID p_multimesh) const { +AABB RasterizerStorageGLES3::_multimesh_get_aabb(RID p_multimesh) const { MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); ERR_FAIL_COND_V(!multimesh, AABB()); @@ -4911,6 +4911,13 @@ AABB RasterizerStorageGLES3::multimesh_get_aabb(RID p_multimesh) const { return multimesh->aabb; } +RasterizerStorage::MMInterpolator *RasterizerStorageGLES3::_multimesh_get_interpolator(RID p_multimesh) const { + MultiMesh *multimesh = multimesh_owner.getornull(p_multimesh); + ERR_FAIL_COND_V_MSG(!multimesh, nullptr, "Multimesh not found: " + itos(p_multimesh.get_id())); + + return &multimesh->interpolator; +} + void RasterizerStorageGLES3::update_dirty_multimeshes() { while (multimesh_update_list.first()) { MultiMesh *multimesh = multimesh_update_list.first()->self(); @@ -7856,6 +7863,9 @@ bool RasterizerStorageGLES3::free(RID p_rid) { memdelete(mesh); } else if (multimesh_owner.owns(p_rid)) { + // remove from interpolator + _interpolation_data.notify_free_multimesh(p_rid); + // delete the texture MultiMesh *multimesh = multimesh_owner.get(p_rid); multimesh->instance_remove_deps(); diff --git a/drivers/gles3/rasterizer_storage_gles3.h b/drivers/gles3/rasterizer_storage_gles3.h index 5b63a05b0e33..cea3502939fa 100644 --- a/drivers/gles3/rasterizer_storage_gles3.h +++ b/drivers/gles3/rasterizer_storage_gles3.h @@ -811,6 +811,8 @@ class RasterizerStorageGLES3 : public RasterizerStorage { bool dirty_aabb; bool dirty_data; + MMInterpolator interpolator; + MultiMesh() : size(0), transform_format(VS::MULTIMESH_TRANSFORM_2D), @@ -834,30 +836,31 @@ class RasterizerStorageGLES3 : public RasterizerStorage { void update_dirty_multimeshes(); - virtual RID multimesh_create(); + virtual RID _multimesh_create(); - virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format = VS::MULTIMESH_CUSTOM_DATA_NONE); - virtual int multimesh_get_instance_count(RID p_multimesh) const; + virtual void _multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data_format = VS::MULTIMESH_CUSTOM_DATA_NONE); + virtual int _multimesh_get_instance_count(RID p_multimesh) const; - virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh); - virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform); - virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform); - virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color); - virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data); + virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh); + virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform); + virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform); + virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color); + virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_custom_data); - virtual RID multimesh_get_mesh(RID p_multimesh) const; + virtual RID _multimesh_get_mesh(RID p_multimesh) const; - virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const; - virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const; - virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const; - virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const; + virtual Transform _multimesh_instance_get_transform(RID p_multimesh, int p_index) const; + virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const; + virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const; + virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const; - virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array); + virtual void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array); - virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible); - virtual int multimesh_get_visible_instances(RID p_multimesh) const; + virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible); + virtual int _multimesh_get_visible_instances(RID p_multimesh) const; - virtual AABB multimesh_get_aabb(RID p_multimesh) const; + virtual AABB _multimesh_get_aabb(RID p_multimesh) const; + virtual MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const; /* IMMEDIATE API */ diff --git a/main/main_timer_sync.cpp b/main/main_timer_sync.cpp index 0c1499e9abba..a4cd78e6dcb4 100644 --- a/main/main_timer_sync.cpp +++ b/main/main_timer_sync.cpp @@ -291,6 +291,17 @@ 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 float MainTimerSync::get_physics_jitter_fix() { + // Turn off jitter fix when using fixed timestep interpolation + // Note this shouldn't be on UNTIL 2d interpolation is implemented, + // otherwise we will get people making 2d 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(); } diff --git a/scene/3d/camera.cpp b/scene/3d/camera.cpp index c025502763a8..a20e1f85aa3d 100644 --- a/scene/3d/camera.cpp +++ b/scene/3d/camera.cpp @@ -98,6 +98,10 @@ void Camera::_update_camera() { } } +void Camera::_physics_interpolated_changed() { + VisualServer::get_singleton()->camera_set_interpolated(camera, is_physics_interpolated()); +} + void Camera::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -112,6 +116,9 @@ void Camera::_notification(int p_what) { viewport->_camera_set(this); } + ERR_FAIL_COND(get_world().is_null()); + VisualServer::get_singleton()->camera_set_scenario(camera, get_world()->get_scenario()); + } break; case NOTIFICATION_TRANSFORM_CHANGED: { _request_camera_update(); @@ -119,7 +126,14 @@ void Camera::_notification(int p_what) { velocity_tracker->update_position(get_global_transform().origin); } } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (is_physics_interpolated()) { + VisualServer::get_singleton()->camera_reset_physics_interpolation(camera); + } + } break; case NOTIFICATION_EXIT_WORLD: { + VisualServer::get_singleton()->camera_set_scenario(camera, RID()); + if (!get_tree()->is_node_being_edited(this)) { if (is_current()) { clear_current(); diff --git a/scene/3d/camera.h b/scene/3d/camera.h index bbc23d0ad8a5..090f1c91ddc1 100644 --- a/scene/3d/camera.h +++ b/scene/3d/camera.h @@ -94,6 +94,8 @@ class Camera : public Spatial { virtual void _request_camera_update(); void _update_camera_mode(); + virtual void _physics_interpolated_changed(); + void _notification(int p_what); virtual void _validate_property(PropertyInfo &p_property) const; diff --git a/scene/3d/cpu_particles.cpp b/scene/3d/cpu_particles.cpp index dadcba1a85a1..e91f41ec246f 100644 --- a/scene/3d/cpu_particles.cpp +++ b/scene/3d/cpu_particles.cpp @@ -53,8 +53,8 @@ void CPUParticles::set_emitting(bool p_emitting) { set_process_internal(true); // first update before rendering to avoid one frame delay after emitting starts - if (time == 0) { - _update_internal(); + if ((time == 0) && !_interpolated) { + _update_internal(false); } } } @@ -63,16 +63,20 @@ void CPUParticles::set_amount(int p_amount) { ERR_FAIL_COND_MSG(p_amount < 1, "Amount of particles must be greater than 0."); particles.resize(p_amount); + particles_prev.resize(p_amount); { PoolVector::Write w = particles.write(); for (int i = 0; i < p_amount; i++) { w[i].active = false; w[i].custom[3] = 0.0; // Make sure w component isn't garbage data + + particles_prev[i].blank(); } } particle_data.resize((12 + 4 + 1) * p_amount); + particle_data_prev.resize(particle_data.size()); VS::get_singleton()->multimesh_allocate(multimesh, p_amount, VS::MULTIMESH_TRANSFORM_3D, VS::MULTIMESH_COLOR_8BIT, VS::MULTIMESH_CUSTOM_DATA_FLOAT); particle_order.resize(p_amount); @@ -100,6 +104,9 @@ void CPUParticles::set_lifetime_randomness(float p_random) { } void CPUParticles::set_use_local_coordinates(bool p_enable) { local_coords = p_enable; + + // prevent sending instance transforms when using global coords + set_instance_use_identity_transform(!p_enable); } void CPUParticles::set_speed_scale(float p_scale) { speed_scale = p_scale; @@ -505,13 +512,23 @@ static float rand_from_seed(uint32_t &seed) { return float(seed % uint32_t(65536)) / 65535.0; } -void CPUParticles::_update_internal() { +void CPUParticles::_update_internal(bool p_on_physics_tick) { if (particles.size() == 0 || !is_visible_in_tree()) { _set_redraw(false); return; } - float delta = get_process_delta_time(); + // change update mode? + _refresh_interpolation_state(); + + float delta = 0.0f; + + // Is this update occurring on a physics tick (i.e. interpolated), or a frame tick? + if (p_on_physics_tick) { + delta = get_physics_process_delta_time(); + } else { + delta = get_process_delta_time(); + } if (emitting) { inactive_time = 0; } else { @@ -577,6 +594,12 @@ void CPUParticles::_update_internal() { if (processed) { _update_particle_data_buffer(); } + + // If we are interpolating, we send the data to the VisualServer + // right away on a physics tick instead of waiting until a render frame. + if (p_on_physics_tick && redraw) { + _update_render_thread(); + } } void CPUParticles::_particles_process(float p_delta) { @@ -606,6 +629,13 @@ void CPUParticles::_particles_process(float p_delta) { } float system_phase = time / lifetime; + real_t physics_tick_delta = 1.0 / Engine::get_singleton()->get_iterations_per_second(); + + // Streaky particles can "prime" started particles by placing them back in time + // from the current physics tick, to place them in the position they would have reached + // had they been created in an infinite timestream (rather than at fixed iteration times). + bool streaky = _streaky && _interpolated && fractional_delta; + real_t streak_fraction = 1.0f; for (int i = 0; i < pcount; i++) { Particle &p = parray[i]; @@ -614,6 +644,11 @@ void CPUParticles::_particles_process(float p_delta) { continue; } + // For interpolation we need to keep a record of previous particles + if (_interpolated) { + p.copy_to(particles_prev[i]); + } + float local_delta = p_delta; // The phase is a ratio between 0 (birth) and 1 (end of life) for each particle. @@ -661,8 +696,22 @@ void CPUParticles::_particles_process(float p_delta) { } } + // Normal condition for a starting particle, allow priming. + // Possibly test emitting flag here too, if profiling shows it helps. + if (streaky && restart) { + streak_fraction = local_delta / physics_tick_delta; + streak_fraction = CLAMP(streak_fraction, 0.0f, 1.0f); + } + if (p.time * (1.0 - explosiveness_ratio) > p.lifetime) { restart = true; + + // Not absolutely sure on this, may be able to streak this case, + // but turning off in case this is expected to be a similar timed + // explosion. + if (streaky) { + streak_fraction = 1.0f; + } } float tv = 0.0; @@ -812,8 +861,23 @@ void CPUParticles::_particles_process(float p_delta) { } } + // We could possibly attempt streaking with local_coords as well, but NYI if (!local_coords) { - p.velocity = velocity_xform.xform(p.velocity); + // Apply streaking interpolation of start positions between ticks + if (streaky) { + emission_xform = _get_global_transform_interpolated(streak_fraction); + velocity_xform = emission_xform.basis; + + p.velocity = velocity_xform.xform(p.velocity); + + // prime the particle by moving "backward" in time + real_t adjusted_delta = (1.0f - streak_fraction) * physics_tick_delta; + _particle_process(p, emission_xform, adjusted_delta, tv); + + } else { + p.velocity = velocity_xform.xform(p.velocity); + } + p.transform = emission_xform * p.transform; } @@ -822,122 +886,20 @@ void CPUParticles::_particles_process(float p_delta) { p.transform.origin.z = 0.0; } + // Teleport if starting a new particle, so + // we don't get a streak from the old position + // to this new start. + if (_interpolated) { + p.copy_to(particles_prev[i]); + } + } else if (!p.active) { continue; } else if (p.time > p.lifetime) { p.active = false; tv = 1.0; } else { - uint32_t alt_seed = p.seed; - - p.time += local_delta; - p.custom[1] = p.time / lifetime; - tv = p.time / p.lifetime; - - float tex_linear_velocity = 0.0; - if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(tv); - } - - float tex_orbit_velocity = 0.0; - if (flags[FLAG_DISABLE_Z]) { - if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { - tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(tv); - } - } - - float tex_angular_velocity = 0.0; - if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { - tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(tv); - } - - float tex_linear_accel = 0.0; - if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { - tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(tv); - } - - float tex_tangential_accel = 0.0; - if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { - tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(tv); - } - - float tex_radial_accel = 0.0; - if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { - tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(tv); - } - - float tex_damping = 0.0; - if (curve_parameters[PARAM_DAMPING].is_valid()) { - tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(tv); - } - - float tex_angle = 0.0; - if (curve_parameters[PARAM_ANGLE].is_valid()) { - tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(tv); - } - float tex_anim_speed = 0.0; - if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { - tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(tv); - } - - float tex_anim_offset = 0.0; - if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { - tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(tv); - } - - Vector3 force = gravity; - Vector3 position = p.transform.origin; - if (flags[FLAG_DISABLE_Z]) { - position.z = 0.0; - } - //apply linear acceleration - force += p.velocity.length() > 0.0 ? p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); - //apply radial acceleration - Vector3 org = emission_xform.origin; - Vector3 diff = position - org; - force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); - //apply tangential acceleration; - if (flags[FLAG_DISABLE_Z]) { - Vector2 yx = Vector2(diff.y, diff.x); - Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); - force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); - - } else { - Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); - force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); - } - //apply attractor forces - p.velocity += force * local_delta; - //orbit velocity - if (flags[FLAG_DISABLE_Z]) { - float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); - if (orbit_amount != 0.0) { - float ang = orbit_amount * local_delta * Math_PI * 2.0; - // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, - // but we use -ang here to reproduce its behavior. - Transform2D rot = Transform2D(-ang, Vector2()); - Vector2 rotv = rot.basis_xform(Vector2(diff.x, diff.y)); - p.transform.origin -= Vector3(diff.x, diff.y, 0); - p.transform.origin += Vector3(rotv.x, rotv.y, 0); - } - } - if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { - p.velocity = p.velocity.normalized() * tex_linear_velocity; - } - if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { - float v = p.velocity.length(); - float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); - v -= damp * local_delta; - if (v < 0.0) { - p.velocity = Vector3(); - } else { - p.velocity = p.velocity.normalized() * v; - } - } - float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, p.angle_rand, randomness[PARAM_ANGLE]); - base_angle += p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); - p.custom[0] = Math::deg2rad(base_angle); //angle - p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + tv * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle + _particle_process(p, emission_xform, local_delta, tv); } //apply color //apply hue rotation @@ -1039,6 +1001,119 @@ void CPUParticles::_particles_process(float p_delta) { } } +void CPUParticles::_particle_process(Particle &r_p, const Transform &p_emission_xform, float p_local_delta, float &r_tv) { + uint32_t alt_seed = r_p.seed; + + r_p.time += p_local_delta; + r_p.custom[1] = r_p.time / lifetime; + r_tv = r_p.time / r_p.lifetime; + + float tex_linear_velocity = 0.0; + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + tex_linear_velocity = curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY]->interpolate(r_tv); + } + + float tex_orbit_velocity = 0.0; + if (flags[FLAG_DISABLE_Z]) { + if (curve_parameters[PARAM_ORBIT_VELOCITY].is_valid()) { + tex_orbit_velocity = curve_parameters[PARAM_ORBIT_VELOCITY]->interpolate(r_tv); + } + } + + float tex_angular_velocity = 0.0; + if (curve_parameters[PARAM_ANGULAR_VELOCITY].is_valid()) { + tex_angular_velocity = curve_parameters[PARAM_ANGULAR_VELOCITY]->interpolate(r_tv); + } + + float tex_linear_accel = 0.0; + if (curve_parameters[PARAM_LINEAR_ACCEL].is_valid()) { + tex_linear_accel = curve_parameters[PARAM_LINEAR_ACCEL]->interpolate(r_tv); + } + + float tex_tangential_accel = 0.0; + if (curve_parameters[PARAM_TANGENTIAL_ACCEL].is_valid()) { + tex_tangential_accel = curve_parameters[PARAM_TANGENTIAL_ACCEL]->interpolate(r_tv); + } + + float tex_radial_accel = 0.0; + if (curve_parameters[PARAM_RADIAL_ACCEL].is_valid()) { + tex_radial_accel = curve_parameters[PARAM_RADIAL_ACCEL]->interpolate(r_tv); + } + + float tex_damping = 0.0; + if (curve_parameters[PARAM_DAMPING].is_valid()) { + tex_damping = curve_parameters[PARAM_DAMPING]->interpolate(r_tv); + } + + float tex_angle = 0.0; + if (curve_parameters[PARAM_ANGLE].is_valid()) { + tex_angle = curve_parameters[PARAM_ANGLE]->interpolate(r_tv); + } + float tex_anim_speed = 0.0; + if (curve_parameters[PARAM_ANIM_SPEED].is_valid()) { + tex_anim_speed = curve_parameters[PARAM_ANIM_SPEED]->interpolate(r_tv); + } + + float tex_anim_offset = 0.0; + if (curve_parameters[PARAM_ANIM_OFFSET].is_valid()) { + tex_anim_offset = curve_parameters[PARAM_ANIM_OFFSET]->interpolate(r_tv); + } + + Vector3 force = gravity; + Vector3 position = r_p.transform.origin; + if (flags[FLAG_DISABLE_Z]) { + position.z = 0.0; + } + //apply linear acceleration + force += r_p.velocity.length() > 0.0 ? r_p.velocity.normalized() * (parameters[PARAM_LINEAR_ACCEL] + tex_linear_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_LINEAR_ACCEL]) : Vector3(); + //apply radial acceleration + Vector3 org = p_emission_xform.origin; + Vector3 diff = position - org; + force += diff.length() > 0.0 ? diff.normalized() * (parameters[PARAM_RADIAL_ACCEL] + tex_radial_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_RADIAL_ACCEL]) : Vector3(); + //apply tangential acceleration; + if (flags[FLAG_DISABLE_Z]) { + Vector2 yx = Vector2(diff.y, diff.x); + Vector2 yx2 = (yx * Vector2(-1.0, 1.0)).normalized(); + force += yx.length() > 0.0 ? Vector3(yx2.x, yx2.y, 0.0) * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + + } else { + Vector3 crossDiff = diff.normalized().cross(gravity.normalized()); + force += crossDiff.length() > 0.0 ? crossDiff.normalized() * ((parameters[PARAM_TANGENTIAL_ACCEL] + tex_tangential_accel) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_TANGENTIAL_ACCEL])) : Vector3(); + } + //apply attractor forces + r_p.velocity += force * p_local_delta; + //orbit velocity + if (flags[FLAG_DISABLE_Z]) { + float orbit_amount = (parameters[PARAM_ORBIT_VELOCITY] + tex_orbit_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ORBIT_VELOCITY]); + if (orbit_amount != 0.0) { + float ang = orbit_amount * p_local_delta * Math_PI * 2.0; + // Not sure why the ParticlesMaterial code uses a clockwise rotation matrix, + // but we use -ang here to reproduce its behavior. + Transform2D rot = Transform2D(-ang, Vector2()); + Vector2 rotv = rot.basis_xform(Vector2(diff.x, diff.y)); + r_p.transform.origin -= Vector3(diff.x, diff.y, 0); + r_p.transform.origin += Vector3(rotv.x, rotv.y, 0); + } + } + if (curve_parameters[PARAM_INITIAL_LINEAR_VELOCITY].is_valid()) { + r_p.velocity = r_p.velocity.normalized() * tex_linear_velocity; + } + if (parameters[PARAM_DAMPING] + tex_damping > 0.0) { + float v = r_p.velocity.length(); + float damp = (parameters[PARAM_DAMPING] + tex_damping) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_DAMPING]); + v -= damp * p_local_delta; + if (v < 0.0) { + r_p.velocity = Vector3(); + } else { + r_p.velocity = r_p.velocity.normalized() * v; + } + } + float base_angle = (parameters[PARAM_ANGLE] + tex_angle) * Math::lerp(1.0f, r_p.angle_rand, randomness[PARAM_ANGLE]); + base_angle += r_p.custom[1] * lifetime * (parameters[PARAM_ANGULAR_VELOCITY] + tex_angular_velocity) * Math::lerp(1.0f, rand_from_seed(alt_seed) * 2.0f - 1.0f, randomness[PARAM_ANGULAR_VELOCITY]); + r_p.custom[0] = Math::deg2rad(base_angle); //angle + r_p.custom[2] = (parameters[PARAM_ANIM_OFFSET] + tex_anim_offset) * Math::lerp(1.0f, r_p.anim_offset_rand, randomness[PARAM_ANIM_OFFSET]) + r_tv * (parameters[PARAM_ANIM_SPEED] + tex_anim_speed) * Math::lerp(1.0f, rand_from_seed(alt_seed), randomness[PARAM_ANIM_SPEED]); //angle +} + void CPUParticles::_update_particle_data_buffer() { update_mutex.lock(); @@ -1052,6 +1127,14 @@ void CPUParticles::_update_particle_data_buffer() { PoolVector::Read r = particles.read(); float *ptr = w.ptr(); + PoolVector::Write w_prev; + float *ptr_prev = nullptr; + + if (_interpolated) { + w_prev = particle_data_prev.write(); + ptr_prev = w_prev.ptr(); + } + if (draw_order != DRAW_ORDER_INDEX) { ow = particle_order.write(); order = ow.ptr(); @@ -1069,13 +1152,12 @@ void CPUParticles::_update_particle_data_buffer() { if (c) { Vector3 dir = c->get_global_transform().basis.get_axis(2); //far away to close - if (local_coords) { - // will look different from Particles in editor as this is based on the camera in the scenetree - // and not the editor camera - dir = inv_emission_transform.xform(dir).normalized(); - } else { - dir = dir.normalized(); - } + // now if local_coords is not set, the particles are in global coords + // so should be sorted according to the camera direction + + // will look different from Particles in editor as this is based on the camera in the scenetree + // and not the editor camera + dir = dir.normalized(); SortArray sorter; sorter.compare.particles = r.ptr(); @@ -1085,45 +1167,20 @@ void CPUParticles::_update_particle_data_buffer() { } } - for (int i = 0; i < pc; i++) { - int idx = order ? order[i] : i; - - Transform t = r[idx].transform; - - if (!local_coords) { - t = inv_emission_transform * t; + if (_interpolated) { + for (int i = 0; i < pc; i++) { + int idx = order ? order[i] : i; + _fill_particle_data(r[idx], ptr, r[idx].active); + ptr += 17; + _fill_particle_data(particles_prev[idx], ptr_prev, r[idx].active); + ptr_prev += 17; } - - if (r[idx].active) { - ptr[0] = t.basis.elements[0][0]; - ptr[1] = t.basis.elements[0][1]; - ptr[2] = t.basis.elements[0][2]; - ptr[3] = t.origin.x; - ptr[4] = t.basis.elements[1][0]; - ptr[5] = t.basis.elements[1][1]; - ptr[6] = t.basis.elements[1][2]; - ptr[7] = t.origin.y; - ptr[8] = t.basis.elements[2][0]; - ptr[9] = t.basis.elements[2][1]; - ptr[10] = t.basis.elements[2][2]; - ptr[11] = t.origin.z; - } else { - memset(ptr, 0, sizeof(float) * 12); + } else { + for (int i = 0; i < pc; i++) { + int idx = order ? order[i] : i; + _fill_particle_data(r[idx], ptr, r[idx].active); + ptr += 17; } - - Color c = r[idx].color; - uint8_t *data8 = (uint8_t *)&ptr[12]; - data8[0] = CLAMP(c.r * 255.0, 0, 255); - data8[1] = CLAMP(c.g * 255.0, 0, 255); - data8[2] = CLAMP(c.b * 255.0, 0, 255); - data8[3] = CLAMP(c.a * 255.0, 0, 255); - - ptr[13] = r[idx].custom[0]; - ptr[14] = r[idx].custom[1]; - ptr[15] = r[idx].custom[2]; - ptr[16] = r[idx].custom[3]; - - ptr += 17; } can_update.set(); @@ -1132,20 +1189,51 @@ void CPUParticles::_update_particle_data_buffer() { update_mutex.unlock(); } +void CPUParticles::_refresh_interpolation_state() { + if (!is_inside_tree()) { + return; + } + bool interpolated = is_physics_interpolated_and_enabled(); + + if (_interpolated == interpolated) { + return; + } + + bool curr_redraw = redraw; + + // Remove all connections + // This isn't super efficient, but should only happen rarely. + _set_redraw(false); + + _interpolated = interpolated; + set_process_internal(!_interpolated); + set_physics_process_internal(_interpolated); + + // re-establish all connections + _set_redraw(curr_redraw); +} + void CPUParticles::_set_redraw(bool p_redraw) { if (redraw == p_redraw) { return; } redraw = p_redraw; update_mutex.lock(); + + if (!_interpolated) { + if (redraw) { + VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); + } else { + if (VS::get_singleton()->is_connected("frame_pre_draw", this, "_update_render_thread")) { + VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); + } + } + } + if (redraw) { - VS::get_singleton()->connect("frame_pre_draw", this, "_update_render_thread"); VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, true); VS::get_singleton()->multimesh_set_visible_instances(multimesh, -1); } else { - if (VS::get_singleton()->is_connected("frame_pre_draw", this, "_update_render_thread")) { - VS::get_singleton()->disconnect("frame_pre_draw", this, "_update_render_thread"); - } VS::get_singleton()->instance_geometry_set_flag(get_instance(), VS::INSTANCE_FLAG_DRAW_NEXT_FRAME_IF_VISIBLE, false); VS::get_singleton()->multimesh_set_visible_instances(multimesh, 0); } @@ -1157,7 +1245,11 @@ void CPUParticles::_update_render_thread() { update_mutex.lock(); if (can_update.is_set()) { - VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); + if (_interpolated) { + VS::get_singleton()->multimesh_set_as_bulk_array_interpolated(multimesh, particle_data, particle_data_prev); + } else { + VS::get_singleton()->multimesh_set_as_bulk_array(multimesh, particle_data); + } can_update.clear(); //wait for next time } @@ -1170,8 +1262,8 @@ void CPUParticles::_notification(int p_what) { set_process_internal(emitting); // first update before rendering to avoid one frame delay after emitting starts - if (emitting && (time == 0)) { - _update_internal(); + if (emitting && (time == 0) && !_interpolated) { + _update_internal(false); } } @@ -1181,50 +1273,17 @@ void CPUParticles::_notification(int p_what) { if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { // first update before rendering to avoid one frame delay after emitting starts - if (emitting && (time == 0)) { - _update_internal(); + if (emitting && (time == 0) && !_interpolated) { + _update_internal(false); } } if (p_what == NOTIFICATION_INTERNAL_PROCESS) { - _update_internal(); + _update_internal(false); } - if (p_what == NOTIFICATION_TRANSFORM_CHANGED) { - inv_emission_transform = get_global_transform().affine_inverse(); - - if (!local_coords) { - int pc = particles.size(); - - PoolVector::Write w = particle_data.write(); - PoolVector::Read r = particles.read(); - float *ptr = w.ptr(); - - for (int i = 0; i < pc; i++) { - Transform t = inv_emission_transform * r[i].transform; - - if (r[i].active) { - ptr[0] = t.basis.elements[0][0]; - ptr[1] = t.basis.elements[0][1]; - ptr[2] = t.basis.elements[0][2]; - ptr[3] = t.origin.x; - ptr[4] = t.basis.elements[1][0]; - ptr[5] = t.basis.elements[1][1]; - ptr[6] = t.basis.elements[1][2]; - ptr[7] = t.origin.y; - ptr[8] = t.basis.elements[2][0]; - ptr[9] = t.basis.elements[2][1]; - ptr[10] = t.basis.elements[2][2]; - ptr[11] = t.origin.z; - } else { - memset(ptr, 0, sizeof(float) * 12); - } - - ptr += 17; - } - - can_update.set(); - } + if (p_what == NOTIFICATION_INTERNAL_PHYSICS_PROCESS) { + _update_internal(true); } } diff --git a/scene/3d/cpu_particles.h b/scene/3d/cpu_particles.h index dd39a00ba27a..5a871d1219ef 100644 --- a/scene/3d/cpu_particles.h +++ b/scene/3d/cpu_particles.h @@ -84,10 +84,25 @@ class CPUParticles : public GeometryInstance { private: bool emitting; - struct Particle { + // Previous minimal data for the particle, + // for interpolation. + struct ParticleBase { + void blank() { + for (int n = 0; n < 4; n++) { + custom[n] = 0.0; + } + } Transform transform; Color color; float custom[4]; + }; + + struct Particle : public ParticleBase { + void copy_to(ParticleBase &r_o) { + r_o.transform = transform; + r_o.color = color; + memcpy(r_o.custom, custom, sizeof(custom)); + } Vector3 velocity; bool active; float angle_rand; @@ -111,7 +126,9 @@ class CPUParticles : public GeometryInstance { RID multimesh; PoolVector particles; + LocalVector particles_prev; PoolVector particle_data; + PoolVector particle_data_prev; PoolVector particle_order; struct SortLifetime { @@ -144,8 +161,6 @@ class CPUParticles : public GeometryInstance { int fixed_fps; bool fractional_delta; - Transform inv_emission_transform; - SafeFlag can_update; DrawOrder draw_order; @@ -182,15 +197,55 @@ class CPUParticles : public GeometryInstance { Vector3 gravity; - void _update_internal(); + void _update_internal(bool p_on_physics_tick); void _particles_process(float p_delta); + void _particle_process(Particle &r_p, const Transform &p_emission_xform, float p_local_delta, float &r_tv); void _update_particle_data_buffer(); Mutex update_mutex; + bool _interpolated = false; + + // Hard coded to true for now, if we decide after testing to always enable this + // when using interpolation we can remove the variable, else we can expose to the UI. + bool _streaky = true; void _update_render_thread(); void _set_redraw(bool p_redraw); + void _refresh_interpolation_state(); + + void _fill_particle_data(const ParticleBase &p_source, float *r_dest, bool p_active) const { + const Transform &t = p_source.transform; + + if (p_active) { + r_dest[0] = t.basis.elements[0][0]; + r_dest[1] = t.basis.elements[0][1]; + r_dest[2] = t.basis.elements[0][2]; + r_dest[3] = t.origin.x; + r_dest[4] = t.basis.elements[1][0]; + r_dest[5] = t.basis.elements[1][1]; + r_dest[6] = t.basis.elements[1][2]; + r_dest[7] = t.origin.y; + r_dest[8] = t.basis.elements[2][0]; + r_dest[9] = t.basis.elements[2][1]; + r_dest[10] = t.basis.elements[2][2]; + r_dest[11] = t.origin.z; + } else { + memset(r_dest, 0, sizeof(float) * 12); + } + + Color c = p_source.color; + uint8_t *data8 = (uint8_t *)&r_dest[12]; + data8[0] = CLAMP(c.r * 255.0, 0, 255); + data8[1] = CLAMP(c.g * 255.0, 0, 255); + data8[2] = CLAMP(c.b * 255.0, 0, 255); + data8[3] = CLAMP(c.a * 255.0, 0, 255); + + r_dest[13] = p_source.custom[0]; + r_dest[14] = p_source.custom[1]; + r_dest[15] = p_source.custom[2]; + r_dest[16] = p_source.custom[3]; + } protected: static void _bind_methods(); diff --git a/scene/3d/multimesh_instance.cpp b/scene/3d/multimesh_instance.cpp index d6354e499fd4..96fa7f0bc51c 100644 --- a/scene/3d/multimesh_instance.cpp +++ b/scene/3d/multimesh_instance.cpp @@ -36,15 +36,29 @@ void MultiMeshInstance::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multimesh", PROPERTY_HINT_RESOURCE_TYPE, "MultiMesh"), "set_multimesh", "get_multimesh"); } +void MultiMeshInstance::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE) { + _refresh_interpolated(); + } +} + void MultiMeshInstance::set_multimesh(const Ref &p_multimesh) { multimesh = p_multimesh; if (multimesh.is_valid()) { set_base(multimesh->get_rid()); + _refresh_interpolated(); } else { set_base(RID()); } } +void MultiMeshInstance::_refresh_interpolated() { + if (is_inside_tree() && multimesh.is_valid()) { + bool interpolated = is_physics_interpolated_and_enabled(); + multimesh->set_physics_interpolated(interpolated); + } +} + Ref MultiMeshInstance::get_multimesh() const { return multimesh; } @@ -61,6 +75,11 @@ AABB MultiMeshInstance::get_aabb() const { } } +void MultiMeshInstance::_physics_interpolated_changed() { + VisualInstance::_physics_interpolated_changed(); + _refresh_interpolated(); +} + MultiMeshInstance::MultiMeshInstance() { } diff --git a/scene/3d/multimesh_instance.h b/scene/3d/multimesh_instance.h index 11e7877f4a10..bd38d876cb13 100644 --- a/scene/3d/multimesh_instance.h +++ b/scene/3d/multimesh_instance.h @@ -39,8 +39,12 @@ class MultiMeshInstance : public GeometryInstance { Ref multimesh; + void _refresh_interpolated(); + protected: + virtual void _physics_interpolated_changed(); static void _bind_methods(); + void _notification(int p_what); // bind helpers public: diff --git a/scene/3d/spatial.cpp b/scene/3d/spatial.cpp index 120c7bab5532..a48819f63191 100644 --- a/scene/3d/spatial.cpp +++ b/scene/3d/spatial.cpp @@ -31,6 +31,7 @@ #include "spatial.h" #include "core/engine.h" +#include "core/math/transform_interpolator.h" #include "core/message_queue.h" #include "scene/main/scene_tree.h" #include "scene/main/viewport.h" @@ -236,6 +237,11 @@ void Spatial::_notification(int p_what) { } #endif } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (data.physics_interpolation_data) { + data.physics_interpolation_data->global_xform_prev = data.physics_interpolation_data->global_xform_curr; + } + } break; default: { } @@ -268,6 +274,60 @@ Transform Spatial::get_transform() const { return data.local_transform; } + +void Spatial::_update_physics_interpolation_data() { + if (!_is_physics_interpolated_client_side()) { + return; + } + ERR_FAIL_NULL(data.physics_interpolation_data); + PhysicsInterpolationData &pid = *data.physics_interpolation_data; + + uint64_t tick = Engine::get_singleton()->get_physics_frames(); + if (tick != pid.current_physics_tick) { + pid.global_xform_prev = pid.global_xform_curr; + pid.current_physics_tick = tick; + } + + pid.global_xform_curr = get_global_transform(); +} + +Transform Spatial::_get_global_transform_interpolated(real_t p_interpolation_fraction) { + ERR_FAIL_NULL_V(is_inside_tree(), Transform()); + + // 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.physics_interpolation_data, Transform()); + data.physics_interpolation_data = memnew(PhysicsInterpolationData); + data.physics_interpolation_data->global_xform_curr = get_global_transform(); + data.physics_interpolation_data->global_xform_prev = data.physics_interpolation_data->global_xform_curr; + data.physics_interpolation_data->current_physics_tick = Engine::get_singleton()->get_physics_frames(); + } + + // make sure data is up to date + _update_physics_interpolation_data(); + + // interpolate the current data + const Transform &xform_curr = data.physics_interpolation_data->global_xform_curr; + const Transform &xform_prev = data.physics_interpolation_data->global_xform_prev; + + Transform res; + TransformInterpolator::interpolate_transform(xform_prev, xform_curr, res, p_interpolation_fraction); + return res; +} + +Transform Spatial::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(); + } + + return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction()); +} + Transform Spatial::get_global_transform() const { ERR_FAIL_COND_V(!is_inside_tree(), Transform()); @@ -312,6 +372,10 @@ Spatial *Spatial::get_parent_spatial() const { return data.parent; } +void Spatial::_set_vi_visible(bool p_visible) { + data.vi_visible = p_visible; +} + Transform Spatial::get_relative_transform(const Node *p_parent) const { if (p_parent == this) { return Transform(); @@ -733,6 +797,7 @@ void Spatial::_bind_methods() { ClassDB::bind_method(D_METHOD("get_scale"), &Spatial::get_scale); ClassDB::bind_method(D_METHOD("set_global_transform", "global"), &Spatial::set_global_transform); ClassDB::bind_method(D_METHOD("get_global_transform"), &Spatial::get_global_transform); + ClassDB::bind_method(D_METHOD("get_global_transform_interpolated"), &Spatial::get_global_transform_interpolated); ClassDB::bind_method(D_METHOD("get_parent_spatial"), &Spatial::get_parent_spatial); ClassDB::bind_method(D_METHOD("set_ignore_transform_notification", "enabled"), &Spatial::set_ignore_transform_notification); ClassDB::bind_method(D_METHOD("set_as_toplevel", "enable"), &Spatial::set_as_toplevel); @@ -819,8 +884,9 @@ Spatial::Spatial() : data.inside_world = false; data.visible = true; data.disable_scale = false; + data.vi_visible = true; - data.spatial_flags = SPATIAL_FLAG_VI_VISIBLE; + data.physics_interpolation_data = nullptr; #ifdef TOOLS_ENABLED data.gizmo_disabled = false; @@ -833,4 +899,8 @@ Spatial::Spatial() : } Spatial::~Spatial() { + if (data.physics_interpolation_data) { + memdelete(data.physics_interpolation_data); + data.physics_interpolation_data = nullptr; + } } diff --git a/scene/3d/spatial.h b/scene/3d/spatial.h index ff0cf645a6af..d7a28ef2ee4d 100644 --- a/scene/3d/spatial.h +++ b/scene/3d/spatial.h @@ -52,15 +52,15 @@ class Spatial : public Node { GDCLASS(Spatial, Node); OBJ_CATEGORY("3D"); -public: - enum SpatialFlags { - // 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 - SPATIAL_FLAG_VI_VISIBLE = 1 << 0, + // optionally stored if we need to do interpolation + // client side (i.e. not in VisualServer) so interpolated transforms + // can be read back with get_global_transform_interpolated() + struct PhysicsInterpolationData { + Transform global_xform_curr; + Transform global_xform_prev; + uint64_t current_physics_tick = 0; }; -private: enum TransformDirty { DIRTY_NONE = 0, DIRTY_VECTORS = 1, @@ -71,9 +71,6 @@ class Spatial : public Node { mutable SelfList xform_change; struct Data { - // defined in Spatial::SpatialFlags - uint32_t spatial_flags; - mutable Transform global_transform; mutable Transform local_transform; mutable Vector3 rotation; @@ -83,26 +80,33 @@ class Spatial : public Node { Viewport *viewport; - bool toplevel_active; - bool toplevel; - bool inside_world; + bool toplevel_active : 1; + bool toplevel : 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; int children_lock; Spatial *parent; List children; List::Element *C; - bool ignore_notification; - bool notify_local_transform; - bool notify_transform; - - bool visible; - bool disable_scale; + PhysicsInterpolationData *physics_interpolation_data; #ifdef TOOLS_ENABLED Ref gizmo; - bool gizmo_disabled; - bool gizmo_dirty; + bool gizmo_disabled : 1; + bool gizmo_dirty : 1; #endif } data; @@ -115,18 +119,12 @@ class Spatial : public Node { protected: _FORCE_INLINE_ void set_ignore_transform_notification(bool p_ignore) { data.ignore_notification = p_ignore; } - _FORCE_INLINE_ void _update_local_transform() const; - uint32_t _get_spatial_flags() const { return data.spatial_flags; } - void _replace_spatial_flags(uint32_t p_flags) { data.spatial_flags = p_flags; } - void _set_spatial_flag(uint32_t p_flag, bool p_set) { - if (p_set) { - data.spatial_flags |= p_flag; - } else { - data.spatial_flags &= ~p_flag; - } - } + void _update_physics_interpolation_data(); + void _set_vi_visible(bool p_visible); + bool _is_vi_visible() const { return data.vi_visible; } + Transform _get_global_transform_interpolated(real_t p_interpolation_fraction); void _notification(int p_what); static void _bind_methods(); @@ -163,6 +161,7 @@ class Spatial : public Node { Transform get_transform() const; Transform get_global_transform() const; + Transform get_global_transform_interpolated(); #ifdef TOOLS_ENABLED virtual Transform get_global_gizmo_transform() const; diff --git a/scene/3d/visual_instance.cpp b/scene/3d/visual_instance.cpp index 704e754835e8..539cd5ddb483 100644 --- a/scene/3d/visual_instance.cpp +++ b/scene/3d/visual_instance.cpp @@ -52,19 +52,36 @@ void VisualInstance::_update_visibility() { // keep a quick flag available in each node. // no need to call is_visible_in_tree all over the place, // providing it is propagated with a notification. - bool already_visible = (_get_spatial_flags() & SPATIAL_FLAG_VI_VISIBLE) != 0; - _set_spatial_flag(SPATIAL_FLAG_VI_VISIBLE, visible); + bool already_visible = _is_vi_visible(); + _set_vi_visible(visible); // if making visible, make sure the visual server is up to date with the transform if (visible && (!already_visible)) { - Transform gt = get_global_transform(); - VisualServer::get_singleton()->instance_set_transform(instance, gt); + if (!_is_using_identity_transform()) { + Transform gt = get_global_transform(); + VisualServer::get_singleton()->instance_set_transform(instance, gt); + } } _change_notify("visible"); VS::get_singleton()->instance_set_visible(get_instance(), visible); } +void VisualInstance::set_instance_use_identity_transform(bool p_enable) { + // prevent sending instance transforms when using global coords + _set_use_identity_transform(p_enable); + + if (is_inside_tree()) { + if (p_enable) { + // want to make sure instance is using identity transform + VisualServer::get_singleton()->instance_set_transform(instance, get_global_transform()); + } else { + // want to make sure instance is up to date + VisualServer::get_singleton()->instance_set_transform(instance, Transform()); + } + } +} + void VisualInstance::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_WORLD: { @@ -80,9 +97,18 @@ void VisualInstance::_notification(int p_what) { } break; case NOTIFICATION_TRANSFORM_CHANGED: { - if (_get_spatial_flags() & SPATIAL_FLAG_VI_VISIBLE) { - Transform gt = get_global_transform(); - VisualServer::get_singleton()->instance_set_transform(instance, gt); + if (_is_vi_visible()) { + if (!_is_using_identity_transform()) { + Transform gt = get_global_transform(); + VisualServer::get_singleton()->instance_set_transform(instance, gt); + } + } + } break; + case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { + if (_is_vi_visible()) { + if (is_physics_interpolated()) { + VisualServer::get_singleton()->instance_reset_physics_interpolation(instance); + } } } break; case NOTIFICATION_EXIT_WORLD: { @@ -93,7 +119,7 @@ void VisualInstance::_notification(int p_what) { // the vi visible flag is always set to invisible when outside the tree, // so it can detect re-entering the tree and becoming visible, and send // the transform to the visual server - _set_spatial_flag(SPATIAL_FLAG_VI_VISIBLE, false); + _set_vi_visible(false); } break; case NOTIFICATION_VISIBILITY_CHANGED: { _update_visibility(); @@ -101,6 +127,10 @@ void VisualInstance::_notification(int p_what) { } } +void VisualInstance::_physics_interpolated_changed() { + VisualServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated()); +} + RID VisualInstance::get_instance() const { return instance; } diff --git a/scene/3d/visual_instance.h b/scene/3d/visual_instance.h index 63ba02b0d0bf..0643aa17b679 100644 --- a/scene/3d/visual_instance.h +++ b/scene/3d/visual_instance.h @@ -49,6 +49,8 @@ class VisualInstance : public CullInstance { protected: void _update_visibility(); virtual void _refresh_portal_mode(); + virtual void _physics_interpolated_changed(); + void set_instance_use_identity_transform(bool p_enable); void _notification(int p_what); static void _bind_methods(); diff --git a/scene/animation/skeleton_ik.cpp b/scene/animation/skeleton_ik.cpp index c0c34b877b06..6f3f55ed2548 100644 --- a/scene/animation/skeleton_ik.cpp +++ b/scene/animation/skeleton_ik.cpp @@ -550,7 +550,10 @@ Transform SkeletonIK::_get_target_transform() { } 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. This will pass through + // to get_global_transform() when physics interpolation is off, and 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; } diff --git a/scene/main/node.cpp b/scene/main/node.cpp index b798a9d95398..fcf54bb1db91 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -181,6 +181,19 @@ void Node::_propagate_ready() { } } +void Node::_propagate_physics_interpolated(bool p_interpolated) { + data.physics_interpolated = p_interpolated; + + // allow a call to the VisualServer etc in derived classes + _physics_interpolated_changed(); + + data.blocked++; + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_physics_interpolated(p_interpolated); + } + data.blocked--; +} + void Node::_propagate_enter_tree() { // this needs to happen to all children before any enter_tree @@ -401,6 +414,8 @@ void Node::move_child_notify(Node *p_child) { // to be used when not wanted } +void Node::_physics_interpolated_changed() {} + void Node::set_physics_process(bool p_process) { if (data.physics_process == p_process) { return; @@ -779,6 +794,22 @@ bool Node::can_process() const { return true; } +void Node::set_physics_interpolated(bool p_interpolated) { + // if swapping from interpolated to non-interpolated, use this as + // an extra means to cause a reset + if (is_physics_interpolated() && !p_interpolated) { + reset_physics_interpolation(); + } + + _propagate_physics_interpolated(p_interpolated); +} + +void Node::reset_physics_interpolation() { + if (is_physics_interpolated_and_enabled()) { + propagate_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); + } +} + float Node::get_physics_process_delta_time() const { if (data.tree) { return data.tree->get_physics_process_time(); @@ -925,6 +956,14 @@ bool Node::is_processing_unhandled_key_input() const { return data.unhandled_key_input; } +void Node::_set_physics_interpolated_client_side(bool p_enable) { + data.physics_interpolated_client_side = p_enable; +} + +void Node::_set_use_identity_transform(bool p_enable) { + data.use_identity_transform = p_enable; +} + StringName Node::get_name() const { return data.name; } @@ -2828,6 +2867,11 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_process_internal", "enable"), &Node::set_physics_process_internal); ClassDB::bind_method(D_METHOD("is_physics_processing_internal"), &Node::is_physics_processing_internal); + ClassDB::bind_method(D_METHOD("set_physics_interpolated", "enable"), &Node::set_physics_interpolated); + ClassDB::bind_method(D_METHOD("is_physics_interpolated"), &Node::is_physics_interpolated); + ClassDB::bind_method(D_METHOD("is_physics_interpolated_and_enabled"), &Node::is_physics_interpolated_and_enabled); + ClassDB::bind_method(D_METHOD("reset_physics_interpolation"), &Node::reset_physics_interpolation); + ClassDB::bind_method(D_METHOD("get_tree"), &Node::get_tree); ClassDB::bind_method(D_METHOD("duplicate", "flags"), &Node::duplicate, DEFVAL(DUPLICATE_USE_INSTANCING | DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS)); @@ -2907,6 +2951,7 @@ void Node::_bind_methods() { BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS); BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS); BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE); + BIND_CONSTANT(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); BIND_CONSTANT(NOTIFICATION_WM_MOUSE_ENTER); BIND_CONSTANT(NOTIFICATION_WM_MOUSE_EXIT); @@ -2954,6 +2999,9 @@ void Node::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_custom_multiplayer", "get_custom_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_priority"), "set_process_priority", "get_process_priority"); + // Disabled for now + // ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolated"), "set_physics_interpolated", "is_physics_interpolated"); + BIND_VMETHOD(MethodInfo("_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_physics_process", PropertyInfo(Variant::REAL, "delta"))); BIND_VMETHOD(MethodInfo("_enter_tree")); @@ -2992,6 +3040,9 @@ Node::Node() { data.idle_process_internal = false; data.inside_tree = false; data.ready_notified = false; + data.physics_interpolated = true; + data.physics_interpolated_client_side = false; + data.use_identity_transform = false; data.owner = nullptr; data.OW = nullptr; diff --git a/scene/main/node.h b/scene/main/node.h index d8766fbcd475..71635116e521 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -100,9 +100,6 @@ class Node : public Object { int blocked; // safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed. StringName name; SceneTree *tree; - bool inside_tree; - bool ready_notified; //this is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification - bool ready_first; #ifdef TOOLS_ENABLED NodePath import_path; //path used when imported, used by scene editors to keep tracking #endif @@ -120,25 +117,47 @@ class Node : public Object { Map rpc_methods; Map rpc_properties; + int process_priority; + // variables used to properly sort the node when processing, ignored otherwise //should move all the stuff below to bits - bool physics_process; - bool idle_process; - int process_priority; + bool physics_process : 1; + bool idle_process : 1; + + bool physics_process_internal : 1; + bool idle_process_internal : 1; + + bool input : 1; + bool unhandled_input : 1; + bool unhandled_key_input : 1; - bool physics_process_internal; - bool idle_process_internal; + // Physics interpolation can be turned on and off on a per node basis. + // This only takes effect when the SceneTree (or project setting) physics interpolation + // is switched on. + bool physics_interpolated : 1; - bool input; - bool unhandled_input; - bool unhandled_key_input; + // Most nodes need not be interpolated in the scene tree, physics interpolation + // is normally only needed in the VisualServer. 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 VisualServer + // by reading back. + bool physics_interpolated_client_side : 1; - bool parent_owned; - bool in_constructor; - bool use_placeholder; + // For certain nodes (e.g. CPU Particles in global mode) + // It can be useful to not send the instance transform to the + // VisualServer, and specify the mesh in world space. + bool use_identity_transform : 1; - bool display_folded; - bool editable_instance; + bool parent_owned : 1; + bool in_constructor : 1; + bool use_placeholder : 1; + + bool display_folded : 1; + bool editable_instance : 1; + + bool inside_tree : 1; + bool ready_notified : 1; //this is a small hack, so if a node is added during _ready() to the tree, it correctly gets the _ready() notification + bool ready_first : 1; mutable NodePath *path_cache; @@ -162,6 +181,7 @@ class Node : public Object { void _propagate_ready(); void _propagate_exit_tree(); void _propagate_after_exit_tree(); + void _propagate_physics_interpolated(bool p_interpolated); void _print_stray_nodes(); void _propagate_pause_owner(Node *p_owner); Array _get_node_and_resource(const NodePath &p_path); @@ -192,6 +212,8 @@ class Node : public Object { virtual void remove_child_notify(Node *p_child); virtual void move_child_notify(Node *p_child); + virtual void _physics_interpolated_changed(); + void _propagate_replace_owner(Node *p_owner, Node *p_by_owner); static void _bind_methods(); @@ -202,6 +224,10 @@ class Node : public Object { void _add_child_nocheck(Node *p_child, const StringName &p_name); void _set_owner_nocheck(Node *p_owner); void _set_name_nocheck(const StringName &p_name); + void _set_physics_interpolated_client_side(bool p_enable); + bool _is_physics_interpolated_client_side() const { return data.physics_interpolated_client_side; } + void _set_use_identity_transform(bool p_enable); + bool _is_using_identity_transform() const { return data.use_identity_transform; } public: enum { @@ -225,6 +251,7 @@ class Node : public Object { NOTIFICATION_INTERNAL_PROCESS = 25, NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26, NOTIFICATION_POST_ENTER_TREE = 27, + NOTIFICATION_RESET_PHYSICS_INTERPOLATION = 28, //keep these linked to node NOTIFICATION_WM_MOUSE_ENTER = MainLoop::NOTIFICATION_WM_MOUSE_ENTER, NOTIFICATION_WM_MOUSE_EXIT = MainLoop::NOTIFICATION_WM_MOUSE_EXIT, @@ -385,6 +412,11 @@ class Node : public Object { bool can_process() const; bool can_process_notification(int p_what) const; + void set_physics_interpolated(bool p_interpolated); + _FORCE_INLINE_ bool is_physics_interpolated() const { return data.physics_interpolated; } + _FORCE_INLINE_ bool is_physics_interpolated_and_enabled() const { return is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && is_physics_interpolated(); } + void reset_physics_interpolation(); + void request_ready(); static void print_stray_nodes(); diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index 51504d620b6b..1f047cfafa21 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -474,11 +474,42 @@ void SceneTree::init() { MainLoop::init(); } +void SceneTree::set_physics_interpolation_enabled(bool p_enabled) { + // disallow interpolation in editor + if (Engine::get_singleton()->is_editor_hint()) { + p_enabled = false; + } + + if (p_enabled == _physics_interpolation_enabled) { + return; + } + + _physics_interpolation_enabled = p_enabled; + + if (root->get_world().is_valid()) { + RID scenario = root->get_world()->get_scenario(); + if (scenario.is_valid()) { + VisualServer::get_singleton()->scenario_set_physics_interpolation_enabled(scenario, p_enabled); + } + } +} + +bool SceneTree::is_physics_interpolation_enabled() const { + return _physics_interpolation_enabled; +} + bool SceneTree::iteration(float p_time) { root_lock++; current_frame++; + if (root->get_world().is_valid()) { + RID scenario = root->get_world()->get_scenario(); + if (scenario.is_valid()) { + VisualServer::get_singleton()->scenario_tick(scenario); + } + } + flush_transform_notifications(); MainLoop::iteration(p_time); @@ -618,6 +649,13 @@ bool SceneTree::idle(float p_time) { #endif + if (root->get_world().is_valid()) { + RID scenario = root->get_world()->get_scenario(); + if (scenario.is_valid()) { + VisualServer::get_singleton()->scenario_pre_draw(scenario, true); + } + } + return _quit; } @@ -1840,6 +1878,9 @@ void SceneTree::_bind_methods() { ClassDB::bind_method(D_METHOD("set_screen_stretch", "mode", "aspect", "minsize", "scale"), &SceneTree::set_screen_stretch, DEFVAL(1)); + ClassDB::bind_method(D_METHOD("set_physics_interpolation_enabled", "enabled"), &SceneTree::set_physics_interpolation_enabled); + ClassDB::bind_method(D_METHOD("is_physics_interpolation_enabled"), &SceneTree::is_physics_interpolation_enabled); + ClassDB::bind_method(D_METHOD("queue_delete", "obj"), &SceneTree::queue_delete); MethodInfo mi; @@ -1909,6 +1950,7 @@ void SceneTree::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root", PROPERTY_HINT_RESOURCE_TYPE, "Node", 0), "", "get_root"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", 0), "set_multiplayer", "get_multiplayer"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "multiplayer_poll"), "set_multiplayer_poll_enabled", "is_multiplayer_poll_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_interpolation"), "set_physics_interpolation_enabled", "is_physics_interpolation_enabled"); ADD_SIGNAL(MethodInfo("tree_changed")); ADD_SIGNAL(MethodInfo("node_added", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); @@ -2045,6 +2087,7 @@ SceneTree::SceneTree() { call_lock = 0; root_lock = 0; node_count = 0; + _physics_interpolation_enabled = false; //create with mainloop @@ -2054,6 +2097,7 @@ SceneTree::SceneTree() { if (!root->get_world().is_valid()) { root->set_world(Ref(memnew(World))); } + set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false)); // Initialize network state multiplayer_poll = true; diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h index 6b85f942ff52..8338fddae835 100644 --- a/scene/main/scene_tree.h +++ b/scene/main/scene_tree.h @@ -121,6 +121,7 @@ class SceneTree : public MainLoop { bool _quit; bool initialized; bool input_handled; + bool _physics_interpolation_enabled; Size2 last_screen_size; StringName tree_changed_name; @@ -407,6 +408,9 @@ class SceneTree : public MainLoop { void set_refuse_new_network_connections(bool p_refuse); bool is_refusing_new_network_connections() const; + void set_physics_interpolation_enabled(bool p_enabled); + bool is_physics_interpolation_enabled() const; + static void add_idle_callback(IdleCallback p_callback); SceneTree(); ~SceneTree(); diff --git a/scene/resources/multimesh.cpp b/scene/resources/multimesh.cpp index 3d693d0b3767..9bd55274bd10 100644 --- a/scene/resources/multimesh.cpp +++ b/scene/resources/multimesh.cpp @@ -216,10 +216,20 @@ void MultiMesh::set_visible_instance_count(int p_count) { VisualServer::get_singleton()->multimesh_set_visible_instances(multimesh, p_count); visible_instance_count = p_count; } + +void MultiMesh::set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality) { + _physics_interpolation_quality = p_quality; + VisualServer::get_singleton()->multimesh_set_physics_interpolation_quality(multimesh, (int)p_quality); +} + int MultiMesh::get_visible_instance_count() const { return visible_instance_count; } +void MultiMesh::reset_instance_physics_interpolation(int p_instance) { + VisualServer::get_singleton()->multimesh_instance_reset_physics_interpolation(multimesh, p_instance); +} + void MultiMesh::set_instance_transform(int p_instance, const Transform &p_transform) { VisualServer::get_singleton()->multimesh_instance_set_transform(multimesh, p_instance, p_transform); } @@ -255,6 +265,14 @@ void MultiMesh::set_as_bulk_array(const PoolVector &p_array) { VisualServer::get_singleton()->multimesh_set_as_bulk_array(multimesh, p_array); } +void MultiMesh::set_as_bulk_array_interpolated(const PoolVector &p_array_curr, const PoolVector &p_array_prev) { + VisualServer::get_singleton()->multimesh_set_as_bulk_array_interpolated(multimesh, p_array_curr, p_array_prev); +} + +void MultiMesh::set_physics_interpolated(bool p_interpolated) { + VisualServer::get_singleton()->multimesh_set_physics_interpolated(multimesh, p_interpolated); +} + AABB MultiMesh::get_aabb() const { return VisualServer::get_singleton()->multimesh_get_aabb(multimesh); } @@ -303,6 +321,8 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_count"), &MultiMesh::get_instance_count); ClassDB::bind_method(D_METHOD("set_visible_instance_count", "count"), &MultiMesh::set_visible_instance_count); ClassDB::bind_method(D_METHOD("get_visible_instance_count"), &MultiMesh::get_visible_instance_count); + ClassDB::bind_method(D_METHOD("set_physics_interpolation_quality", "quality"), &MultiMesh::set_physics_interpolation_quality); + ClassDB::bind_method(D_METHOD("get_physics_interpolation_quality"), &MultiMesh::get_physics_interpolation_quality); ClassDB::bind_method(D_METHOD("set_instance_transform", "instance", "transform"), &MultiMesh::set_instance_transform); ClassDB::bind_method(D_METHOD("set_instance_transform_2d", "instance", "transform"), &MultiMesh::set_instance_transform_2d); ClassDB::bind_method(D_METHOD("get_instance_transform", "instance"), &MultiMesh::get_instance_transform); @@ -311,7 +331,9 @@ void MultiMesh::_bind_methods() { ClassDB::bind_method(D_METHOD("get_instance_color", "instance"), &MultiMesh::get_instance_color); ClassDB::bind_method(D_METHOD("set_instance_custom_data", "instance", "custom_data"), &MultiMesh::set_instance_custom_data); ClassDB::bind_method(D_METHOD("get_instance_custom_data", "instance"), &MultiMesh::get_instance_custom_data); + ClassDB::bind_method(D_METHOD("reset_instance_physics_interpolation", "instance"), &MultiMesh::reset_instance_physics_interpolation); ClassDB::bind_method(D_METHOD("set_as_bulk_array", "array"), &MultiMesh::set_as_bulk_array); + ClassDB::bind_method(D_METHOD("set_as_bulk_array_interpolated", "array_current", "array_previous"), &MultiMesh::set_as_bulk_array_interpolated); ClassDB::bind_method(D_METHOD("get_aabb"), &MultiMesh::get_aabb); ClassDB::bind_method(D_METHOD("_set_transform_array"), &MultiMesh::_set_transform_array); @@ -334,6 +356,9 @@ void MultiMesh::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "color_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_color_array", "_get_color_array"); ADD_PROPERTY(PropertyInfo(Variant::POOL_COLOR_ARRAY, "custom_data_array", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_custom_data_array", "_get_custom_data_array"); + ADD_GROUP("Physics Interpolation", "physics_interpolation"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_interpolation_quality", PROPERTY_HINT_ENUM, "Fast,High"), "set_physics_interpolation_quality", "get_physics_interpolation_quality"); + BIND_ENUM_CONSTANT(TRANSFORM_2D); BIND_ENUM_CONSTANT(TRANSFORM_3D); @@ -344,6 +369,9 @@ void MultiMesh::_bind_methods() { BIND_ENUM_CONSTANT(CUSTOM_DATA_NONE); BIND_ENUM_CONSTANT(CUSTOM_DATA_8BIT); BIND_ENUM_CONSTANT(CUSTOM_DATA_FLOAT); + + BIND_ENUM_CONSTANT(INTERP_QUALITY_FAST); + BIND_ENUM_CONSTANT(INTERP_QUALITY_HIGH); } MultiMesh::MultiMesh() { @@ -353,6 +381,7 @@ MultiMesh::MultiMesh() { transform_format = TRANSFORM_2D; visible_instance_count = -1; instance_count = 0; + _physics_interpolation_quality = INTERP_QUALITY_FAST; } MultiMesh::~MultiMesh() { diff --git a/scene/resources/multimesh.h b/scene/resources/multimesh.h index 0ddbb72dc3f8..7698c8826d17 100644 --- a/scene/resources/multimesh.h +++ b/scene/resources/multimesh.h @@ -56,6 +56,11 @@ class MultiMesh : public Resource { CUSTOM_DATA_FLOAT, }; + enum PhysicsInterpolationQuality { + INTERP_QUALITY_FAST, + INTERP_QUALITY_HIGH, + }; + private: Ref mesh; RID multimesh; @@ -64,6 +69,7 @@ class MultiMesh : public Resource { CustomDataFormat custom_data_format; int instance_count; int visible_instance_count; + PhysicsInterpolationQuality _physics_interpolation_quality; protected: static void _bind_methods(); @@ -99,6 +105,9 @@ class MultiMesh : public Resource { void set_visible_instance_count(int p_count); int get_visible_instance_count() const; + void set_physics_interpolation_quality(PhysicsInterpolationQuality p_quality); + PhysicsInterpolationQuality get_physics_interpolation_quality() const { return _physics_interpolation_quality; } + void set_instance_transform(int p_instance, const Transform &p_transform); void set_instance_transform_2d(int p_instance, const Transform2D &p_transform); Transform get_instance_transform(int p_instance) const; @@ -110,7 +119,12 @@ class MultiMesh : public Resource { void set_instance_custom_data(int p_instance, const Color &p_custom_data); Color get_instance_custom_data(int p_instance) const; + void reset_instance_physics_interpolation(int p_instance); + void set_as_bulk_array(const PoolVector &p_array); + void set_as_bulk_array_interpolated(const PoolVector &p_array_curr, const PoolVector &p_array_prev); + + void set_physics_interpolated(bool p_interpolated); virtual AABB get_aabb() const; @@ -123,5 +137,6 @@ class MultiMesh : public Resource { VARIANT_ENUM_CAST(MultiMesh::TransformFormat); VARIANT_ENUM_CAST(MultiMesh::ColorFormat); VARIANT_ENUM_CAST(MultiMesh::CustomDataFormat); +VARIANT_ENUM_CAST(MultiMesh::PhysicsInterpolationQuality); #endif // MULTI_MESH_H diff --git a/servers/visual/rasterizer.cpp b/servers/visual/rasterizer.cpp index 5f3cc55397bf..c96a29b53259 100644 --- a/servers/visual/rasterizer.cpp +++ b/servers/visual/rasterizer.cpp @@ -52,3 +52,475 @@ bool RasterizerStorage::material_uses_tangents(RID p_material) { bool RasterizerStorage::material_uses_ensure_correct_normals(RID p_material) { return false; } + +void RasterizerStorage::InterpolationData::notify_free_multimesh(RID p_rid) { + // print_line("free multimesh " + itos(p_rid.get_id())); + + // if the instance was on any of the lists, remove + multimesh_interpolate_update_list.erase_multiple_unordered(p_rid); + multimesh_transform_update_lists[0].erase_multiple_unordered(p_rid); + multimesh_transform_update_lists[1].erase_multiple_unordered(p_rid); +} + +void RasterizerStorage::update_interpolation_tick(bool p_process) { + // detect any that were on the previous transform list that are no longer active, + // we should remove them from the interpolate list + + for (unsigned int n = 0; n < _interpolation_data.multimesh_transform_update_list_prev->size(); n++) { + const RID &rid = (*_interpolation_data.multimesh_transform_update_list_prev)[n]; + + bool active = true; + + // no longer active? (either the instance deleted or no longer being transformed) + + MMInterpolator *mmi = _multimesh_get_interpolator(rid); + if (mmi && !mmi->on_transform_update_list) { + active = false; + mmi->on_interpolate_update_list = false; + + // make sure the most recent transform is set + // copy data rather than use Pool = function? + mmi->_data_interpolated = mmi->_data_curr; + + // and that both prev and current are the same, just in case of any interpolations + mmi->_data_prev = mmi->_data_curr; + + // make sure are updated one more time to ensure the AABBs are correct + //_instance_queue_update(instance, true); + } + + if (!mmi) { + active = false; + } + + if (!active) { + _interpolation_data.multimesh_interpolate_update_list.erase(rid); + } + } + + if (p_process) { + for (unsigned int i = 0; i < _interpolation_data.multimesh_transform_update_list_curr->size(); i++) { + const RID &rid = (*_interpolation_data.multimesh_transform_update_list_curr)[i]; + + MMInterpolator *mmi = _multimesh_get_interpolator(rid); + if (mmi) { + // reset for next tick + mmi->on_transform_update_list = false; + mmi->_data_prev = mmi->_data_curr; + } + } // for n + } + + // if any have left the transform list, remove from the interpolate list + + // 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.multimesh_transform_update_list_curr, _interpolation_data.multimesh_transform_update_list_prev); + + // prepare for the next iteration + _interpolation_data.multimesh_transform_update_list_curr->clear(); +} + +void RasterizerStorage::update_interpolation_frame(bool p_process) { + if (p_process) { + // Only need 32 bit for interpolation, don't use real_t + float f = Engine::get_singleton()->get_physics_interpolation_fraction(); + + for (unsigned int c = 0; c < _interpolation_data.multimesh_interpolate_update_list.size(); c++) { + const RID &rid = _interpolation_data.multimesh_interpolate_update_list[c]; + + // We could use the TransformInterpolator here to slerp transforms, but that might be too expensive, + // so just using a Basis lerp for now. + MMInterpolator *mmi = _multimesh_get_interpolator(rid); + if (mmi) { + // make sure arrays are correct size + DEV_ASSERT(mmi->_data_prev.size() == mmi->_data_curr.size()); + + if (mmi->_data_interpolated.size() < mmi->_data_curr.size()) { + mmi->_data_interpolated.resize(mmi->_data_curr.size()); + } + DEV_ASSERT(mmi->_data_interpolated.size() >= mmi->_data_curr.size()); + + DEV_ASSERT((mmi->_data_curr.size() % mmi->_stride) == 0); + int num = mmi->_data_curr.size() / mmi->_stride; + + PoolVector::Read r_prev = mmi->_data_prev.read(); + PoolVector::Read r_curr = mmi->_data_curr.read(); + PoolVector::Write w = mmi->_data_interpolated.write(); + + const float *pf_prev = r_prev.ptr(); + const float *pf_curr = r_curr.ptr(); + float *pf_int = w.ptr(); + + bool use_lerp = mmi->quality == 0; + + // temporary transform (needed for swizzling) + // (transform prev, curr and result) + Transform tp, tc, tr; + + // Test for cache friendliness versus doing branchless + for (int n = 0; n < num; n++) { + // Transform + if (use_lerp) { + for (int i = 0; i < mmi->_vf_size_xform; i++) { + float a = pf_prev[i]; + float b = pf_curr[i]; + pf_int[i] = (a + ((b - a) * f)); + } + } else { + // Silly swizzling, this will slow things down. no idea why it is using this format + // .. maybe due to the shader. + tp.basis.elements[0][0] = pf_prev[0]; + tp.basis.elements[0][1] = pf_prev[1]; + tp.basis.elements[0][2] = pf_prev[2]; + tp.basis.elements[1][0] = pf_prev[4]; + tp.basis.elements[1][1] = pf_prev[5]; + tp.basis.elements[1][2] = pf_prev[6]; + tp.basis.elements[2][0] = pf_prev[8]; + tp.basis.elements[2][1] = pf_prev[9]; + tp.basis.elements[2][2] = pf_prev[10]; + tp.origin.x = pf_prev[3]; + tp.origin.y = pf_prev[7]; + tp.origin.z = pf_prev[11]; + + tc.basis.elements[0][0] = pf_curr[0]; + tc.basis.elements[0][1] = pf_curr[1]; + tc.basis.elements[0][2] = pf_curr[2]; + tc.basis.elements[1][0] = pf_curr[4]; + tc.basis.elements[1][1] = pf_curr[5]; + tc.basis.elements[1][2] = pf_curr[6]; + tc.basis.elements[2][0] = pf_curr[8]; + tc.basis.elements[2][1] = pf_curr[9]; + tc.basis.elements[2][2] = pf_curr[10]; + tc.origin.x = pf_curr[3]; + tc.origin.y = pf_curr[7]; + tc.origin.z = pf_curr[11]; + + TransformInterpolator::interpolate_transform(tp, tc, tr, f); + + pf_int[0] = tr.basis.elements[0][0]; + pf_int[1] = tr.basis.elements[0][1]; + pf_int[2] = tr.basis.elements[0][2]; + pf_int[4] = tr.basis.elements[1][0]; + pf_int[5] = tr.basis.elements[1][1]; + pf_int[6] = tr.basis.elements[1][2]; + pf_int[8] = tr.basis.elements[2][0]; + pf_int[9] = tr.basis.elements[2][1]; + pf_int[10] = tr.basis.elements[2][2]; + pf_int[3] = tr.origin.x; + pf_int[7] = tr.origin.y; + pf_int[11] = tr.origin.z; + } + + pf_prev += mmi->_vf_size_xform; + pf_curr += mmi->_vf_size_xform; + pf_int += mmi->_vf_size_xform; + + // Color + if (mmi->_vf_size_color == 1) { + const uint8_t *p8_prev = (const uint8_t *)pf_prev; + const uint8_t *p8_curr = (const uint8_t *)pf_curr; + uint8_t *p8_int = (uint8_t *)pf_int; + _interpolate_RGBA8(p8_prev, p8_curr, p8_int, f); + + pf_prev += 1; + pf_curr += 1; + pf_int += 1; + } else if (mmi->_vf_size_color == 4) { + for (int i = 0; i < 4; i++) { + pf_int[i] = pf_prev[i] + ((pf_curr[i] - pf_prev[i]) * f); + } + + pf_prev += 4; + pf_curr += 4; + pf_int += 4; + } + + // Custom Data + if (mmi->_vf_size_data == 1) { + const uint8_t *p8_prev = (const uint8_t *)pf_prev; + const uint8_t *p8_curr = (const uint8_t *)pf_curr; + uint8_t *p8_int = (uint8_t *)pf_int; + _interpolate_RGBA8(p8_prev, p8_curr, p8_int, f); + + pf_prev += 1; + pf_curr += 1; + pf_int += 1; + } else if (mmi->_vf_size_data == 4) { + for (int i = 0; i < 4; i++) { + pf_int[i] = pf_prev[i] + ((pf_curr[i] - pf_prev[i]) * f); + } + + pf_prev += 4; + pf_curr += 4; + pf_int += 4; + } + } + + _multimesh_set_as_bulk_array(rid, mmi->_data_interpolated); + + // make sure AABBs are constantly up to date through the interpolation? + // NYI + } + } // for n + } +} + +RID RasterizerStorage::multimesh_create() { + return _multimesh_create(); +} + +void RasterizerStorage::multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + mmi->_transform_format = p_transform_format; + mmi->_color_format = p_color_format; + mmi->_data_format = p_data; + mmi->_num_instances = p_instances; + + mmi->_vf_size_xform = p_transform_format == VS::MULTIMESH_TRANSFORM_3D ? 12 : 8; + switch (p_color_format) { + default: { + mmi->_vf_size_color = 0; + } break; + case VS::MULTIMESH_COLOR_8BIT: { + mmi->_vf_size_color = 1; + } break; + case VS::MULTIMESH_COLOR_FLOAT: { + mmi->_vf_size_color = 4; + } break; + } + + switch (p_data) { + default: { + mmi->_vf_size_data = 0; + } break; + case VS::MULTIMESH_CUSTOM_DATA_8BIT: { + mmi->_vf_size_data = 1; + } break; + case VS::MULTIMESH_CUSTOM_DATA_FLOAT: { + mmi->_vf_size_data = 4; + } break; + } + + mmi->_stride = mmi->_vf_size_xform + mmi->_vf_size_color + mmi->_vf_size_data; + + int size_in_floats = p_instances * mmi->_stride; + mmi->_data_curr.resize(size_in_floats); + mmi->_data_prev.resize(size_in_floats); + mmi->_data_interpolated.resize(size_in_floats); + } + + return _multimesh_allocate(p_multimesh, p_instances, p_transform_format, p_color_format, p_data); +} + +int RasterizerStorage::multimesh_get_instance_count(RID p_multimesh) const { + return _multimesh_get_instance_count(p_multimesh); +} + +void RasterizerStorage::multimesh_set_mesh(RID p_multimesh, RID p_mesh) { + _multimesh_set_mesh(p_multimesh, p_mesh); +} + +void RasterizerStorage::multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + if (mmi->interpolated) { + ERR_FAIL_COND(p_index >= mmi->_num_instances); + ERR_FAIL_COND(mmi->_vf_size_xform != 12); + + PoolVector::Write w = mmi->_data_curr.write(); + int start = p_index * mmi->_stride; + + float *ptr = w.ptr(); + ptr += start; + + const Transform &t = p_transform; + ptr[0] = t.basis.elements[0][0]; + ptr[1] = t.basis.elements[0][1]; + ptr[2] = t.basis.elements[0][2]; + ptr[3] = t.origin.x; + ptr[4] = t.basis.elements[1][0]; + ptr[5] = t.basis.elements[1][1]; + ptr[6] = t.basis.elements[1][2]; + ptr[7] = t.origin.y; + ptr[8] = t.basis.elements[2][0]; + ptr[9] = t.basis.elements[2][1]; + ptr[10] = t.basis.elements[2][2]; + ptr[11] = t.origin.z; + + _multimesh_add_to_interpolation_lists(p_multimesh, *mmi); + return; + } + } + _multimesh_instance_set_transform(p_multimesh, p_index, p_transform); +} + +void RasterizerStorage::multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) { + _multimesh_instance_set_transform_2d(p_multimesh, p_index, p_transform); +} + +void RasterizerStorage::multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + if (mmi->interpolated) { + ERR_FAIL_COND(p_index >= mmi->_num_instances); + ERR_FAIL_COND(mmi->_vf_size_color == 0); + + PoolVector::Write w = mmi->_data_curr.write(); + int start = (p_index * mmi->_stride) + mmi->_vf_size_xform; + + float *ptr = w.ptr(); + ptr += start; + + if (mmi->_vf_size_color == 4) { + for (int n = 0; n < 4; n++) { + ptr[n] = p_color.components[n]; + } + } else { +#ifdef DEV_ENABLED + // The options are currently 4, 1, or zero, but just in case this changes in future... + ERR_FAIL_COND(mmi->_vf_size_color != 1); +#endif + uint32_t *pui = (uint32_t *)ptr; + *pui = p_color.to_rgba32(); + } + _multimesh_add_to_interpolation_lists(p_multimesh, *mmi); + return; + } + } + + _multimesh_instance_set_color(p_multimesh, p_index, p_color); +} +void RasterizerStorage::multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + if (mmi->interpolated) { + ERR_FAIL_COND(p_index >= mmi->_num_instances); + ERR_FAIL_COND(mmi->_vf_size_data == 0); + + PoolVector::Write w = mmi->_data_curr.write(); + int start = (p_index * mmi->_stride) + mmi->_vf_size_xform + mmi->_vf_size_color; + + float *ptr = w.ptr(); + ptr += start; + + if (mmi->_vf_size_data == 4) { + for (int n = 0; n < 4; n++) { + ptr[n] = p_color.components[n]; + } + } else { +#ifdef DEV_ENABLED + // The options are currently 4, 1, or zero, but just in case this changes in future... + ERR_FAIL_COND(mmi->_vf_size_data != 1); +#endif + uint32_t *pui = (uint32_t *)ptr; + *pui = p_color.to_rgba32(); + } + _multimesh_add_to_interpolation_lists(p_multimesh, *mmi); + return; + } + } + + _multimesh_instance_set_custom_data(p_multimesh, p_index, p_color); +} + +RID RasterizerStorage::multimesh_get_mesh(RID p_multimesh) const { + return _multimesh_get_mesh(p_multimesh); +} + +Transform RasterizerStorage::multimesh_instance_get_transform(RID p_multimesh, int p_index) const { + return _multimesh_instance_get_transform(p_multimesh, p_index); +} + +Transform2D RasterizerStorage::multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const { + return _multimesh_instance_get_transform_2d(p_multimesh, p_index); +} + +Color RasterizerStorage::multimesh_instance_get_color(RID p_multimesh, int p_index) const { + return _multimesh_instance_get_color(p_multimesh, p_index); +} + +Color RasterizerStorage::multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const { + return _multimesh_instance_get_custom_data(p_multimesh, p_index); +} + +void RasterizerStorage::multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + mmi->interpolated = p_interpolated; + } +} + +void RasterizerStorage::multimesh_set_physics_interpolation_quality(RID p_multimesh, int p_quality) { + ERR_FAIL_COND((p_quality < 0) || (p_quality > 1)); + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + mmi->quality = p_quality; + } +} + +void RasterizerStorage::multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + ERR_FAIL_COND(p_index >= mmi->_num_instances); + + PoolVector::Write w = mmi->_data_prev.write(); + PoolVector::Read r = mmi->_data_curr.read(); + + int start = p_index * mmi->_stride; + + for (int n = 0; n < mmi->_stride; n++) { + w[start + n] = r[start + n]; + } + } +} + +void RasterizerStorage::_multimesh_add_to_interpolation_lists(RID p_multimesh, MMInterpolator &r_mmi) { + if (!r_mmi.on_interpolate_update_list) { + r_mmi.on_interpolate_update_list = true; + _interpolation_data.multimesh_interpolate_update_list.push_back(p_multimesh); + } + + if (!r_mmi.on_transform_update_list) { + r_mmi.on_transform_update_list = true; + _interpolation_data.multimesh_transform_update_list_curr->push_back(p_multimesh); + } +} + +void RasterizerStorage::multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector &p_array, const PoolVector &p_array_prev) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + // We are assuming that mmi->interpolated is the case, + // (can possibly assert this?) + // even if this flag hasn't been set - just calling this function suggests + // interpolation is desired. + mmi->_data_prev = p_array_prev; + mmi->_data_curr = p_array; + _multimesh_add_to_interpolation_lists(p_multimesh, *mmi); + } +} + +void RasterizerStorage::multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) { + MMInterpolator *mmi = _multimesh_get_interpolator(p_multimesh); + if (mmi) { + if (mmi->interpolated) { + mmi->_data_curr = p_array; + _multimesh_add_to_interpolation_lists(p_multimesh, *mmi); + return; + } + } + _multimesh_set_as_bulk_array(p_multimesh, p_array); +} + +void RasterizerStorage::multimesh_set_visible_instances(RID p_multimesh, int p_visible) { + _multimesh_set_visible_instances(p_multimesh, p_visible); +} + +int RasterizerStorage::multimesh_get_visible_instances(RID p_multimesh) const { + return _multimesh_get_visible_instances(p_multimesh); +} + +AABB RasterizerStorage::multimesh_get_aabb(RID p_multimesh) const { + return _multimesh_get_aabb(p_multimesh); +} diff --git a/servers/visual/rasterizer.h b/servers/visual/rasterizer.h index a9b0142a4c3d..71ff30ad6d37 100644 --- a/servers/visual/rasterizer.h +++ b/servers/visual/rasterizer.h @@ -32,6 +32,7 @@ #define RASTERIZER_H #include "core/math/camera_matrix.h" +#include "core/math/transform_interpolator.h" #include "servers/visual_server.h" #include "core/self_list.h" @@ -90,8 +91,16 @@ class RasterizerScene { RID material_override; RID material_overlay; + // 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). Transform transform; + // for interpolation we store the current transform (this physics tick) + // and the transform in the previous tick + Transform transform_curr; + Transform transform_prev; + int depth_layer; uint32_t layer_mask; @@ -107,11 +116,21 @@ class RasterizerScene { VS::ShadowCastingSetting cast_shadows; //fit in 32 bits - bool mirror : 8; - bool receive_shadows : 8; - bool visible : 8; - bool baked_light : 4; //this flag is only to know if it actually did use baked light - bool redraw_if_visible : 4; + 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 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; float depth; //used for sorting @@ -138,6 +157,12 @@ class RasterizerScene { lightmap_capture = nullptr; lightmap_slice = -1; lightmap_uv_rect = Rect2(0, 0, 1, 1); + 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; } }; @@ -320,32 +345,83 @@ class RasterizerStorage { virtual void mesh_clear(RID p_mesh) = 0; /* MULTIMESH API */ + struct MMInterpolator { + VS::MultimeshTransformFormat _transform_format = VS::MULTIMESH_TRANSFORM_3D; + VS::MultimeshColorFormat _color_format = VS::MULTIMESH_COLOR_NONE; + VS::MultimeshCustomDataFormat _data_format = VS::MULTIMESH_CUSTOM_DATA_NONE; + + // in floats + int _stride = 0; + + // Vertex format sizes in floats + int _vf_size_xform = 0; + int _vf_size_color = 0; + int _vf_size_data = 0; + + // Set by allocate, can be used to prevent indexing out of range. + int _num_instances = 0; + + // Quality determines whether to use lerp or slerp etc. + int quality = 0; + bool interpolated = false; + bool on_interpolate_update_list = false; + bool on_transform_update_list = false; + + PoolVector _data_prev; + PoolVector _data_curr; + PoolVector _data_interpolated; + }; - virtual RID multimesh_create() = 0; - - virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) = 0; - virtual int multimesh_get_instance_count(RID p_multimesh) const = 0; - - virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0; - virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) = 0; - virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0; - virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0; - virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0; - - virtual RID multimesh_get_mesh(RID p_multimesh) const = 0; - - virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; - virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0; - virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0; - virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0; - - virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) = 0; - - virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0; - virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0; - - virtual AABB multimesh_get_aabb(RID p_multimesh) const = 0; + virtual RID multimesh_create(); + virtual void multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE); + virtual int multimesh_get_instance_count(RID p_multimesh) const; + virtual void multimesh_set_mesh(RID p_multimesh, RID p_mesh); + virtual void multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform); + virtual void multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform); + virtual void multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color); + virtual void multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color); + virtual RID multimesh_get_mesh(RID p_multimesh) const; + virtual Transform multimesh_instance_get_transform(RID p_multimesh, int p_index) const; + virtual Transform2D multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const; + virtual Color multimesh_instance_get_color(RID p_multimesh, int p_index) const; + virtual Color multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const; + virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array); + + virtual void multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector &p_array, const PoolVector &p_array_prev); + virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated); + virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, int p_quality); + virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index); + + virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible); + virtual int multimesh_get_visible_instances(RID p_multimesh) const; + virtual AABB multimesh_get_aabb(RID p_multimesh) const; + + virtual RID _multimesh_create() = 0; + virtual void _multimesh_allocate(RID p_multimesh, int p_instances, VS::MultimeshTransformFormat p_transform_format, VS::MultimeshColorFormat p_color_format, VS::MultimeshCustomDataFormat p_data = VS::MULTIMESH_CUSTOM_DATA_NONE) = 0; + virtual int _multimesh_get_instance_count(RID p_multimesh) const = 0; + virtual void _multimesh_set_mesh(RID p_multimesh, RID p_mesh) = 0; + virtual void _multimesh_instance_set_transform(RID p_multimesh, int p_index, const Transform &p_transform) = 0; + virtual void _multimesh_instance_set_transform_2d(RID p_multimesh, int p_index, const Transform2D &p_transform) = 0; + virtual void _multimesh_instance_set_color(RID p_multimesh, int p_index, const Color &p_color) = 0; + virtual void _multimesh_instance_set_custom_data(RID p_multimesh, int p_index, const Color &p_color) = 0; + virtual RID _multimesh_get_mesh(RID p_multimesh) const = 0; + virtual Transform _multimesh_instance_get_transform(RID p_multimesh, int p_index) const = 0; + virtual Transform2D _multimesh_instance_get_transform_2d(RID p_multimesh, int p_index) const = 0; + virtual Color _multimesh_instance_get_color(RID p_multimesh, int p_index) const = 0; + virtual Color _multimesh_instance_get_custom_data(RID p_multimesh, int p_index) const = 0; + virtual void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) = 0; + virtual void _multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0; + virtual int _multimesh_get_visible_instances(RID p_multimesh) const = 0; + virtual AABB _multimesh_get_aabb(RID p_multimesh) const = 0; + + // Multimesh is responsible for allocating / destroying an MMInterpolator object. + // This allows shared functionality for interpolation across backends. + virtual MMInterpolator *_multimesh_get_interpolator(RID p_multimesh) const = 0; + +private: + void _multimesh_add_to_interpolation_lists(RID p_multimesh, MMInterpolator &r_mmi); +public: /* IMMEDIATE API */ virtual RID immediate_create() = 0; @@ -596,6 +672,22 @@ class RasterizerStorage { virtual RID canvas_light_occluder_create() = 0; virtual void canvas_light_occluder_set_polylines(RID p_occluder, const PoolVector &p_lines) = 0; + /* INTERPOLATION */ + struct InterpolationData { + void notify_free_multimesh(RID p_rid); + LocalVector multimesh_interpolate_update_list; + LocalVector multimesh_transform_update_lists[2]; + LocalVector *multimesh_transform_update_list_curr = &multimesh_transform_update_lists[0]; + LocalVector *multimesh_transform_update_list_prev = &multimesh_transform_update_lists[1]; + } _interpolation_data; + + void update_interpolation_tick(bool p_process = true); + void update_interpolation_frame(bool p_process = true); + +private: + _FORCE_INLINE_ void _interpolate_RGBA8(const uint8_t *p_a, const uint8_t *p_b, uint8_t *r_dest, float p_f) const; + +public: virtual VS::InstanceType get_base_type(RID p_rid) const = 0; virtual bool free(RID p_rid) = 0; @@ -1169,4 +1261,28 @@ class Rasterizer { virtual ~Rasterizer() {} }; +// Use float rather than real_t as cheaper and no need for 64 bit. +_FORCE_INLINE_ void RasterizerStorage::_interpolate_RGBA8(const uint8_t *p_a, const uint8_t *p_b, uint8_t *r_dest, float p_f) const { + // Todo, jiggle these values and test for correctness. + // Integer interpolation is finicky.. :) + p_f *= 256.0f; + int32_t mult = CLAMP(int32_t(p_f), 0, 255); + + for (int n = 0; n < 4; n++) { + int32_t a = p_a[n]; + int32_t b = p_b[n]; + + int32_t diff = b - a; + + diff *= mult; + diff /= 255; + + int32_t res = a + diff; + + // may not be needed + res = CLAMP(res, 0, 255); + r_dest[n] = res; + } +} + #endif // RASTERIZER_H diff --git a/servers/visual/visual_server_raster.cpp b/servers/visual/visual_server_raster.cpp index d0e7b76bb610..1dc1263b9958 100644 --- a/servers/visual/visual_server_raster.cpp +++ b/servers/visual/visual_server_raster.cpp @@ -94,6 +94,14 @@ void VisualServerRaster::request_frame_drawn_callback(Object *p_where, const Str frame_drawn_callbacks.push_back(fdc); } +void VisualServerRaster::scenario_tick(RID p_scenario) { + VSG::scene->_scenario_tick(p_scenario); +} + +void VisualServerRaster::scenario_pre_draw(RID p_scenario, bool p_will_draw) { + VSG::scene->_scenario_pre_draw(p_scenario, p_will_draw); +} + void VisualServerRaster::draw(bool p_swap_buffers, double frame_step) { //needs to be done before changes is reset to 0, to not force the editor to redraw VS::get_singleton()->emit_signal("frame_pre_draw"); diff --git a/servers/visual/visual_server_raster.h b/servers/visual/visual_server_raster.h index 63d92ce1811a..d0fc27489ce4 100644 --- a/servers/visual/visual_server_raster.h +++ b/servers/visual/visual_server_raster.h @@ -280,6 +280,11 @@ class VisualServerRaster : public VisualServer { BIND2(multimesh_set_as_bulk_array, RID, const PoolVector &) + BIND3(multimesh_set_as_bulk_array_interpolated, RID, const PoolVector &, const PoolVector &) + BIND2(multimesh_set_physics_interpolated, RID, bool) + BIND2(multimesh_set_physics_interpolation_quality, RID, int) + BIND2(multimesh_instance_reset_physics_interpolation, RID, int) + BIND2(multimesh_set_visible_instances, RID, int) BIND1RC(int, multimesh_get_visible_instances, RID) @@ -446,10 +451,13 @@ class VisualServerRaster : public VisualServer { /* CAMERA API */ BIND0R(RID, camera_create) + BIND2(camera_set_scenario, RID, RID) BIND4(camera_set_perspective, RID, float, float, float) BIND4(camera_set_orthogonal, RID, float, float, float) BIND5(camera_set_frustum, RID, float, Vector2, float, float) BIND2(camera_set_transform, RID, const Transform &) + BIND2(camera_set_interpolated, RID, bool) + BIND1(camera_reset_physics_interpolation, RID) BIND2(camera_set_cull_mask, RID, uint32_t) BIND2(camera_set_environment, RID, RID) BIND2(camera_set_use_vertical_aspect, RID, bool) @@ -551,6 +559,7 @@ class VisualServerRaster : public VisualServer { BIND2(scenario_set_environment, RID, RID) BIND3(scenario_set_reflection_atlas_size, RID, int, int) BIND2(scenario_set_fallback_environment, RID, RID) + BIND2(scenario_set_physics_interpolation_enabled, RID, bool) /* INSTANCING API */ BIND0R(RID, instance_create) @@ -559,6 +568,8 @@ class VisualServerRaster : public VisualServer { BIND2(instance_set_scenario, RID, RID) BIND2(instance_set_layer_mask, RID, uint32_t) BIND2(instance_set_transform, RID, const Transform &) + BIND2(instance_set_interpolated, RID, bool) + BIND1(instance_reset_physics_interpolation, RID) BIND2(instance_attach_object_instance_id, RID, ObjectID) BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_surface_material, RID, int, RID) @@ -749,6 +760,8 @@ class VisualServerRaster : public VisualServer { virtual bool has_changed(ChangedPriority p_priority = CHANGED_PRIORITY_ANY) const; virtual void init(); virtual void finish(); + virtual void scenario_tick(RID p_scenario); + virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw); /* STATUS INFORMATION */ diff --git a/servers/visual/visual_server_scene.cpp b/servers/visual/visual_server_scene.cpp index d2b5b078feea..709b60e1832d 100644 --- a/servers/visual/visual_server_scene.cpp +++ b/servers/visual/visual_server_scene.cpp @@ -30,6 +30,7 @@ #include "visual_server_scene.h" +#include "core/math/transform_interpolator.h" #include "core/os/os.h" #include "visual_server_globals.h" #include "visual_server_raster.h" @@ -38,11 +39,40 @@ /* CAMERA API */ +Transform VisualServerScene::Camera::get_transform() const { + if (!is_currently_interpolated()) { + return transform; + } + + Transform final; + TransformInterpolator::interpolate_transform_via_method(transform_prev, transform, final, Engine::get_singleton()->get_physics_interpolation_fraction(), interpolation_method); + return final; +} + RID VisualServerScene::camera_create() { Camera *camera = memnew(Camera); return camera_owner.make_rid(camera); } +void VisualServerScene::camera_set_scenario(RID p_camera, RID p_scenario) { + Camera *camera = camera_owner.get(p_camera); + ERR_FAIL_COND(!camera); + + Scenario *old_scenario = camera->scenario; + + if (p_scenario.is_valid()) { + camera->scenario = scenario_owner.get(p_scenario); + ERR_FAIL_COND(!camera->scenario); + } else { + camera->scenario = nullptr; + } + + if (old_scenario && (old_scenario != camera->scenario)) { + // remove any interpolation data associated with the camera in this scenario + old_scenario->_interpolation_data.notify_free_camera(p_camera, *camera); + } +} + void VisualServerScene::camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) { Camera *camera = camera_owner.get(p_camera); ERR_FAIL_COND(!camera); @@ -71,10 +101,36 @@ void VisualServerScene::camera_set_frustum(RID p_camera, float p_size, Vector2 p camera->zfar = p_z_far; } +void VisualServerScene::camera_reset_physics_interpolation(RID p_camera) { + Camera *camera = camera_owner.get(p_camera); + ERR_FAIL_COND(!camera); + + if (camera->is_currently_interpolated()) { + camera->scenario->_interpolation_data.camera_teleport_list.push_back(p_camera); + } +} + +void VisualServerScene::camera_set_interpolated(RID p_camera, bool p_interpolated) { + Camera *camera = camera_owner.get(p_camera); + ERR_FAIL_COND(!camera); + camera->interpolated = p_interpolated; +} + void VisualServerScene::camera_set_transform(RID p_camera, const Transform &p_transform) { Camera *camera = camera_owner.get(p_camera); ERR_FAIL_COND(!camera); + camera->transform = p_transform.orthonormalized(); + + if (camera->is_currently_interpolated()) { + if (!camera->on_interpolate_transform_list) { + camera->scenario->_interpolation_data.camera_transform_update_list_curr->push_back(p_camera); + camera->on_interpolate_transform_list = true; + } + + // decide on the interpolation method .. slerp if possible + camera->interpolation_method = TransformInterpolator::find_method(camera->transform_prev.basis, camera->transform.basis); + } } void VisualServerScene::camera_set_cull_mask(RID p_camera, uint32_t p_layers) { @@ -246,6 +302,7 @@ void VisualServerScene::SpatialPartitioningScene_Octree::set_balance(float p_bal VisualServerScene::Scenario::Scenario() { debug = VS::SCENARIO_DEBUG_DISABLED; + _interpolation_data.interpolation_enabled = false; bool use_bvh_or_octree = GLOBAL_GET("rendering/quality/spatial_partitioning/use_bvh"); @@ -413,6 +470,33 @@ RID VisualServerScene::scenario_create() { return scenario_rid; } +void VisualServerScene::scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled) { + Scenario *scenario = scenario_owner.get(p_scenario); + ERR_FAIL_COND(!scenario); + scenario->_interpolation_data.interpolation_enabled = p_enabled; +} + +void VisualServerScene::_scenario_tick(RID p_scenario) { + Scenario *scenario = scenario_owner.get(p_scenario); + ERR_FAIL_COND(!scenario); + + if (scenario->is_physics_interpolation_enabled()) { + update_interpolation_tick(scenario->_interpolation_data, true); + } +} + +void VisualServerScene::_scenario_pre_draw(RID p_scenario, bool p_will_draw) { + Scenario *scenario = scenario_owner.get(p_scenario); + ERR_FAIL_COND(!scenario); + + // even when running and not drawing scenes, we still need to clear intermediate per frame + // interpolation data .. hence the p_will_draw flag (so we can reduce the processing if the frame + // will not be drawn) + if (scenario->is_physics_interpolation_enabled()) { + update_interpolation_frame(scenario->_interpolation_data, p_will_draw); + } +} + void VisualServerScene::scenario_set_debug(RID p_scenario, VS::ScenarioDebugMode p_debug_mode) { Scenario *scenario = scenario_owner.get(p_scenario); ERR_FAIL_COND(!scenario); @@ -645,6 +729,9 @@ void VisualServerScene::instance_set_scenario(RID p_instance, RID p_scenario) { _instance_destroy_occlusion_rep(instance); } + // remove any interpolation data associated with the instance in this scenario + instance->scenario->_interpolation_data.notify_free_instance(p_instance, *instance); + switch (instance->base_type) { case VS::INSTANCE_LIGHT: { InstanceLightData *light = static_cast(instance->base_data); @@ -709,12 +796,68 @@ void VisualServerScene::instance_set_layer_mask(RID p_instance, uint32_t p_mask) instance->layer_mask = p_mask; } + +void VisualServerScene::instance_reset_physics_interpolation(RID p_instance) { + Instance *instance = instance_owner.get(p_instance); + ERR_FAIL_COND(!instance); + + if (instance->is_currently_interpolated()) { + instance->scenario->_interpolation_data.instance_teleport_list.push_back(p_instance); + } +} + +void VisualServerScene::instance_set_interpolated(RID p_instance, bool p_interpolated) { + Instance *instance = instance_owner.get(p_instance); + ERR_FAIL_COND(!instance); + instance->interpolated = p_interpolated; +} + void VisualServerScene::instance_set_transform(RID p_instance, const Transform &p_transform) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); - if (instance->transform == p_transform) { - return; //must be checked to avoid worst evil + if (!instance->is_currently_interpolated() || !instance->scenario) { + if (instance->transform == p_transform) { + return; //must be checked to avoid worst evil + } + +#ifdef DEV_ENABLED + // If we are interpolated but without a scenario, unsure whether + // this should be supported... + if (instance->is_currently_interpolated()) { + WARN_PRINT_ONCE("Instance interpolated without a scenario."); + } +#endif + +#ifdef DEBUG_ENABLED + + for (int i = 0; i < 4; i++) { + const Vector3 &v = i < 3 ? p_transform.basis.elements[i] : p_transform.origin; + ERR_FAIL_COND(Math::is_inf(v.x)); + ERR_FAIL_COND(Math::is_nan(v.x)); + ERR_FAIL_COND(Math::is_inf(v.y)); + ERR_FAIL_COND(Math::is_nan(v.y)); + ERR_FAIL_COND(Math::is_inf(v.z)); + ERR_FAIL_COND(Math::is_nan(v.z)); + } + +#endif + instance->transform = p_transform; + _instance_queue_update(instance, true); + return; + } + + float new_checksum = TransformInterpolator::checksum_transform(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 @@ -730,9 +873,191 @@ void VisualServerScene::instance_set_transform(RID p_instance, const Transform & } #endif - instance->transform = p_transform; + + instance->transform_curr = p_transform; + + // decide on the interpolation method .. slerp if possible + instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis); + + // keep checksums up to date + instance->transform_checksum_curr = new_checksum; + + if (!instance->on_interpolate_transform_list) { + instance->scenario->_interpolation_data.instance_transform_update_list_curr->push_back(p_instance); + instance->on_interpolate_transform_list = true; + } else { + DEV_ASSERT(instance->scenario->_interpolation_data.instance_transform_update_list_curr->size()); + } + + if (!instance->on_interpolate_list) { + instance->scenario->_interpolation_data.instance_interpolate_update_list.push_back(p_instance); + instance->on_interpolate_list = true; + } else { + DEV_ASSERT(instance->scenario->_interpolation_data.instance_interpolate_update_list.size()); + } + _instance_queue_update(instance, true); } + +void VisualServerScene::Scenario::InterpolationData::notify_free_camera(RID p_rid, Camera &r_camera) { + r_camera.on_interpolate_transform_list = false; + + if (!interpolation_enabled) { + return; + } + + // if the camera was on any of the lists, remove + camera_transform_update_list_curr->erase_multiple_unordered(p_rid); + camera_transform_update_list_prev->erase_multiple_unordered(p_rid); + camera_teleport_list.erase_multiple_unordered(p_rid); +} + +void VisualServerScene::Scenario::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); + instance_teleport_list.erase_multiple_unordered(p_rid); +} + +void VisualServerScene::update_interpolation_tick(Scenario::InterpolationData &r_interpolation_data, bool p_process) { + // update interpolation in storage + VSG::storage->update_interpolation_tick(p_process); + + // detect any that were on the previous transform list that are no longer active, + // we should remove them from the interpolate list + + for (unsigned int n = 0; n < r_interpolation_data.instance_transform_update_list_prev->size(); n++) { + const RID &rid = (*r_interpolation_data.instance_transform_update_list_prev)[n]; + Instance *instance = instance_owner.getornull(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 are updated one more time to ensure the AABBs are correct + _instance_queue_update(instance, true); + } + + if (!instance) { + active = false; + } + + if (!active) { + r_interpolation_data.instance_interpolate_update_list.erase(rid); + } + } + + // and 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 (unsigned int n = 0; n < r_interpolation_data.instance_transform_update_list_curr->size(); n++) { + const RID &rid = (*r_interpolation_data.instance_transform_update_list_curr)[n]; + Instance *instance = instance_owner.getornull(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(r_interpolation_data.instance_transform_update_list_curr, r_interpolation_data.instance_transform_update_list_prev); + + // prepare for the next iteration + r_interpolation_data.instance_transform_update_list_curr->clear(); + + // CAMERAS + // detect any that were on the previous transform list that are no longer active, + for (unsigned int n = 0; n < r_interpolation_data.camera_transform_update_list_prev->size(); n++) { + const RID &rid = (*r_interpolation_data.camera_transform_update_list_prev)[n]; + Camera *camera = camera_owner.getornull(rid); + + // no longer active? (either the instance deleted or no longer being transformed) + if (camera && !camera->on_interpolate_transform_list) { + camera->transform = camera->transform_prev; + } + } + + // cameras , swap any current with previous + for (unsigned int n = 0; n < r_interpolation_data.camera_transform_update_list_curr->size(); n++) { + const RID &rid = (*r_interpolation_data.camera_transform_update_list_curr)[n]; + Camera *camera = camera_owner.getornull(rid); + if (camera) { + camera->transform_prev = camera->transform; + camera->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(r_interpolation_data.camera_transform_update_list_curr, r_interpolation_data.camera_transform_update_list_prev); + + // prepare for the next iteration + r_interpolation_data.camera_transform_update_list_curr->clear(); +} + +void VisualServerScene::update_interpolation_frame(Scenario::InterpolationData &r_interpolation_data, bool p_process) { + // update interpolation in storage + VSG::storage->update_interpolation_frame(p_process); + + // teleported instances + for (unsigned int n = 0; n < r_interpolation_data.instance_teleport_list.size(); n++) { + const RID &rid = r_interpolation_data.instance_teleport_list[n]; + Instance *instance = instance_owner.getornull(rid); + if (instance) { + instance->transform_prev = instance->transform_curr; + instance->transform_checksum_prev = instance->transform_checksum_curr; + } + } + + r_interpolation_data.instance_teleport_list.clear(); + + // camera teleports + for (unsigned int n = 0; n < r_interpolation_data.camera_teleport_list.size(); n++) { + const RID &rid = r_interpolation_data.camera_teleport_list[n]; + Camera *camera = camera_owner.getornull(rid); + if (camera) { + camera->transform_prev = camera->transform; + } + } + + r_interpolation_data.camera_teleport_list.clear(); + + if (p_process) { + real_t f = Engine::get_singleton()->get_physics_interpolation_fraction(); + + for (unsigned int i = 0; i < r_interpolation_data.instance_interpolate_update_list.size(); i++) { + const RID &rid = r_interpolation_data.instance_interpolate_update_list[i]; + Instance *instance = instance_owner.getornull(rid); + if (instance) { + TransformInterpolator::interpolate_transform_via_method(instance->transform_prev, instance->transform_curr, instance->transform, f, instance->interpolation_method); + + // make sure AABBs are constantly up to date through the interpolation + _instance_queue_update(instance, true); + } + } // for n + } +} + void VisualServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) { Instance *instance = instance_owner.get(p_instance); ERR_FAIL_COND(!instance); @@ -1595,22 +1920,33 @@ void VisualServerScene::instance_geometry_set_as_instance_lod(RID p_instance, RI void VisualServerScene::_update_instance(Instance *p_instance) { p_instance->version++; + // when not using interpolation the transform is used straight + const Transform *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 (p_instance->is_currently_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 == VS::INSTANCE_LIGHT) { InstanceLightData *light = static_cast(p_instance->base_data); - VSG::scene_render->light_instance_set_transform(light->instance, p_instance->transform); + VSG::scene_render->light_instance_set_transform(light->instance, *instance_xform); light->shadow_dirty = true; } if (p_instance->base_type == VS::INSTANCE_REFLECTION_PROBE) { InstanceReflectionProbeData *reflection_probe = static_cast(p_instance->base_data); - VSG::scene_render->reflection_probe_instance_set_transform(reflection_probe->instance, p_instance->transform); + VSG::scene_render->reflection_probe_instance_set_transform(reflection_probe->instance, *instance_xform); reflection_probe->reflection_dirty = true; } if (p_instance->base_type == VS::INSTANCE_PARTICLES) { - VSG::storage->particles_set_emission_transform(p_instance->base, p_instance->transform); + VSG::storage->particles_set_emission_transform(p_instance->base, *instance_xform); } if (p_instance->base_type == VS::INSTANCE_LIGHTMAP_CAPTURE) { @@ -1645,11 +1981,11 @@ void VisualServerScene::_update_instance(Instance *p_instance) { } } - p_instance->mirror = p_instance->transform.basis.determinant() < 0.0; + p_instance->mirror = instance_xform->basis.determinant() < 0.0; 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; @@ -2444,8 +2780,11 @@ void VisualServerScene::render_camera(RID p_camera, RID p_scenario, Size2 p_view } break; } - _prepare_scene(camera->transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint); - _render_scene(camera->transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); + // This getter allows optional fixed timestep interpolation for the camera. + Transform camera_transform = camera->get_transform(); + + _prepare_scene(camera_transform, camera_matrix, ortho, camera->env, camera->visible_layers, p_scenario, p_shadow_atlas, RID(), camera->previous_room_id_hint); + _render_scene(camera_transform, camera_matrix, 0, ortho, camera->env, p_scenario, p_shadow_atlas, RID(), -1); #endif } @@ -4105,9 +4444,12 @@ bool VisualServerScene::free(RID p_rid) { if (camera_owner.owns(p_rid)) { Camera *camera = camera_owner.get(p_rid); + if (camera->scenario) { + camera->scenario->_interpolation_data.notify_free_camera(p_rid, *camera); + } + camera_owner.free(p_rid); memdelete(camera); - } else if (scenario_owner.owns(p_rid)) { Scenario *scenario = scenario_owner.get(p_rid); @@ -4126,6 +4468,15 @@ bool VisualServerScene::free(RID p_rid) { Instance *instance = instance_owner.get(p_rid); + if (instance->scenario) { + instance->scenario->_interpolation_data.notify_free_instance(p_rid, *instance); + } else { + if (instance->on_interpolate_list || instance->on_interpolate_transform_list) { + // These flags should be set to false when removing the scenario. + WARN_PRINT_ONCE("Instance delete without scenario and on interpolate lists."); + } + } + instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1)); instance_set_scenario(p_rid, RID()); instance_set_base(p_rid, RID()); @@ -4137,6 +4488,7 @@ bool VisualServerScene::free(RID p_rid) { instance_owner.free(p_rid); memdelete(instance); + } else if (room_owner.owns(p_rid)) { Room *room = room_owner.get(p_rid); room_owner.free(p_rid); diff --git a/servers/visual/visual_server_scene.h b/servers/visual/visual_server_scene.h index d8c1b52d0f50..9c374ac9e1a8 100644 --- a/servers/visual/visual_server_scene.h +++ b/servers/visual/visual_server_scene.h @@ -58,6 +58,7 @@ class VisualServerScene { static VisualServerScene *singleton; /* CAMERA API */ + struct Scenario; struct Camera : public RID_Data { enum Type { @@ -71,12 +72,27 @@ class VisualServerScene { float size; Vector2 offset; uint32_t visible_layers; - bool vaspect; RID env; + // transform_prev is only used when using fixed timestep interpolation Transform transform; + Transform transform_prev; + + Scenario *scenario; + + bool interpolated : 1; + bool on_interpolate_transform_list : 1; + + bool vaspect : 1; + TransformInterpolator::Method interpolation_method : 3; + int32_t previous_room_id_hint; + // call get transform to get either the transform straight, + // or the interpolated transform if using fixed timestep interpolation + Transform get_transform() const; + bool is_currently_interpolated() const { return scenario && scenario->is_physics_interpolation_enabled() && interpolated; } + Camera() { visible_layers = 0xFFFFFFFF; fov = 70; @@ -86,17 +102,24 @@ class VisualServerScene { size = 1.0; offset = Vector2(); vaspect = false; + scenario = nullptr; previous_room_id_hint = -1; + interpolated = true; + on_interpolate_transform_list = false; + interpolation_method = TransformInterpolator::INTERP_LERP; } }; mutable RID_Owner camera_owner; virtual RID camera_create(); + virtual void camera_set_scenario(RID p_camera, RID p_scenario); virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far); virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far); virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far); virtual void camera_set_transform(RID p_camera, const Transform &p_transform); + virtual void camera_set_interpolated(RID p_camera, bool p_interpolated); + virtual void camera_reset_physics_interpolation(RID p_camera); virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers); virtual void camera_set_environment(RID p_camera, RID p_env); virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable); @@ -247,6 +270,26 @@ class VisualServerScene { SelfList::List instances; + bool is_physics_interpolation_enabled() const { return _interpolation_data.interpolation_enabled; } + + // fixed timestep interpolation + struct InterpolationData { + void notify_free_camera(RID p_rid, Camera &r_camera); + void notify_free_instance(RID p_rid, Instance &r_instance); + LocalVector instance_interpolate_update_list; + LocalVector instance_transform_update_lists[2]; + LocalVector *instance_transform_update_list_curr = &instance_transform_update_lists[0]; + LocalVector *instance_transform_update_list_prev = &instance_transform_update_lists[1]; + LocalVector instance_teleport_list; + + LocalVector camera_transform_update_lists[2]; + LocalVector *camera_transform_update_list_curr = &camera_transform_update_lists[0]; + LocalVector *camera_transform_update_list_prev = &camera_transform_update_lists[1]; + LocalVector camera_teleport_list; + + bool interpolation_enabled; + } _interpolation_data; + Scenario(); ~Scenario() { memdelete(sps); } }; @@ -262,6 +305,9 @@ class VisualServerScene { virtual void scenario_set_environment(RID p_scenario, RID p_environment); virtual void scenario_set_fallback_environment(RID p_scenario, RID p_environment); virtual void scenario_set_reflection_atlas_size(RID p_scenario, int p_size, int p_subdiv); + virtual void scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled); + void _scenario_tick(RID p_scenario); + void _scenario_pre_draw(RID p_scenario, bool p_will_draw); /* INSTANCING API */ @@ -319,6 +365,8 @@ class VisualServerScene { singleton->_instance_queue_update(this, p_aabb, p_materials); } + bool is_currently_interpolated() const { return scenario && scenario->is_physics_interpolation_enabled() && interpolated; } + Instance() : scenario_item(this), update_item(this) { @@ -363,6 +411,7 @@ class VisualServerScene { }; SelfList::List _instance_update_list; + void _instance_queue_update(Instance *p_instance, bool p_update_aabb, bool p_update_materials = false); struct InstanceGeometryData : public InstanceBaseData { @@ -573,6 +622,8 @@ class VisualServerScene { virtual void instance_set_scenario(RID p_instance, RID p_scenario); virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask); virtual void instance_set_transform(RID p_instance, const Transform &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_material(RID p_instance, int p_surface, RID p_material); @@ -770,6 +821,10 @@ class VisualServerScene { void render_camera(Ref &p_interface, ARVRInterface::Eyes p_eye, RID p_camera, RID p_scenario, Size2 p_viewport_size, RID p_shadow_atlas); void update_dirty_instances(); + // interpolation + void update_interpolation_tick(Scenario::InterpolationData &r_interpolation_data, bool p_process = true); + void update_interpolation_frame(Scenario::InterpolationData &r_interpolation_data, bool p_process = true); + //probes struct GIProbeDataHeader { uint32_t version; diff --git a/servers/visual/visual_server_wrap_mt.cpp b/servers/visual/visual_server_wrap_mt.cpp index 574d5b6569ea..2608ddcf263c 100644 --- a/servers/visual/visual_server_wrap_mt.cpp +++ b/servers/visual/visual_server_wrap_mt.cpp @@ -36,6 +36,18 @@ void VisualServerWrapMT::thread_exit() { exit.set(); } +void VisualServerWrapMT::thread_scenario_tick(RID p_scenario) { + if (!draw_pending.decrement()) { + visual_server->scenario_tick(p_scenario); + } +} + +void VisualServerWrapMT::thread_scenario_pre_draw(RID p_scenario, bool p_will_draw) { + if (!draw_pending.decrement()) { + visual_server->scenario_pre_draw(p_scenario, p_will_draw); + } +} + void VisualServerWrapMT::thread_draw(bool p_swap_buffers, double frame_step) { if (!draw_pending.decrement()) { visual_server->draw(p_swap_buffers, frame_step); @@ -82,6 +94,24 @@ void VisualServerWrapMT::sync() { } } +void VisualServerWrapMT::scenario_tick(RID p_scenario) { + if (create_thread) { + draw_pending.increment(); + command_queue.push(this, &VisualServerWrapMT::thread_scenario_tick, p_scenario); + } else { + visual_server->scenario_tick(p_scenario); + } +} + +void VisualServerWrapMT::scenario_pre_draw(RID p_scenario, bool p_will_draw) { + if (create_thread) { + draw_pending.increment(); + command_queue.push(this, &VisualServerWrapMT::thread_scenario_pre_draw, p_scenario, p_will_draw); + } else { + visual_server->scenario_pre_draw(p_scenario, p_will_draw); + } +} + void VisualServerWrapMT::draw(bool p_swap_buffers, double frame_step) { if (create_thread) { draw_pending.increment(); diff --git a/servers/visual/visual_server_wrap_mt.h b/servers/visual/visual_server_wrap_mt.h index 68e157e5f0ba..56328ce59160 100644 --- a/servers/visual/visual_server_wrap_mt.h +++ b/servers/visual/visual_server_wrap_mt.h @@ -54,6 +54,8 @@ class VisualServerWrapMT : public VisualServer { SafeNumeric draw_pending; void thread_draw(bool p_swap_buffers, double frame_step); void thread_flush(); + void thread_scenario_tick(RID p_scenario); + void thread_scenario_pre_draw(RID p_scenario, bool p_will_draw); void thread_exit(); @@ -208,6 +210,11 @@ class VisualServerWrapMT : public VisualServer { FUNC2(multimesh_set_as_bulk_array, RID, const PoolVector &) + FUNC3(multimesh_set_as_bulk_array_interpolated, RID, const PoolVector &, const PoolVector &) + FUNC2(multimesh_set_physics_interpolated, RID, bool) + FUNC2(multimesh_set_physics_interpolation_quality, RID, int) + FUNC2(multimesh_instance_reset_physics_interpolation, RID, int) + FUNC2(multimesh_set_visible_instances, RID, int) FUNC1RC(int, multimesh_get_visible_instances, RID) @@ -367,10 +374,13 @@ class VisualServerWrapMT : public VisualServer { /* CAMERA API */ FUNCRID(camera) + FUNC2(camera_set_scenario, RID, RID) FUNC4(camera_set_perspective, RID, float, float, float) FUNC4(camera_set_orthogonal, RID, float, float, float) FUNC5(camera_set_frustum, RID, float, Vector2, float, float) FUNC2(camera_set_transform, RID, const Transform &) + FUNC2(camera_set_interpolated, RID, bool) + FUNC1(camera_reset_physics_interpolation, RID) FUNC2(camera_set_cull_mask, RID, uint32_t) FUNC2(camera_set_environment, RID, RID) FUNC2(camera_set_use_vertical_aspect, RID, bool) @@ -464,6 +474,7 @@ class VisualServerWrapMT : public VisualServer { FUNC2(scenario_set_environment, RID, RID) FUNC3(scenario_set_reflection_atlas_size, RID, int, int) FUNC2(scenario_set_fallback_environment, RID, RID) + FUNC2(scenario_set_physics_interpolation_enabled, RID, bool) /* INSTANCING API */ FUNCRID(instance) @@ -472,6 +483,8 @@ class VisualServerWrapMT : public VisualServer { FUNC2(instance_set_scenario, RID, RID) FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC2(instance_set_transform, RID, const Transform &) + 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_material, RID, int, RID) @@ -657,6 +670,8 @@ class VisualServerWrapMT : public VisualServer { virtual void finish(); virtual void draw(bool p_swap_buffers, double frame_step); virtual void sync(); + virtual void scenario_tick(RID p_scenario); + virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw); FUNC1RC(bool, has_changed, ChangedPriority) /* RENDER INFO */ diff --git a/servers/visual_server.h b/servers/visual_server.h index bdf2308e759d..35008a743ea5 100644 --- a/servers/visual_server.h +++ b/servers/visual_server.h @@ -381,6 +381,12 @@ class VisualServer : public Object { virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector &p_array) = 0; + // Interpolation + virtual void multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector &p_array, const PoolVector &p_array_prev) = 0; + virtual void multimesh_set_physics_interpolated(RID p_multimesh, bool p_interpolated) = 0; + virtual void multimesh_set_physics_interpolation_quality(RID p_multimesh, int p_quality) = 0; + virtual void multimesh_instance_reset_physics_interpolation(RID p_multimesh, int p_index) = 0; + virtual void multimesh_set_visible_instances(RID p_multimesh, int p_visible) = 0; virtual int multimesh_get_visible_instances(RID p_multimesh) const = 0; @@ -613,10 +619,13 @@ class VisualServer : public Object { /* CAMERA API */ virtual RID camera_create() = 0; + virtual void camera_set_scenario(RID p_camera, RID p_scenario) = 0; virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) = 0; virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far) = 0; virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far) = 0; virtual void camera_set_transform(RID p_camera, const Transform &p_transform) = 0; + virtual void camera_set_interpolated(RID p_camera, bool p_interpolated) = 0; + virtual void camera_reset_physics_interpolation(RID p_camera) = 0; virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers) = 0; virtual void camera_set_environment(RID p_camera, RID p_env) = 0; virtual void camera_set_use_vertical_aspect(RID p_camera, bool p_enable) = 0; @@ -828,6 +837,7 @@ class VisualServer : public Object { virtual void scenario_set_environment(RID p_scenario, RID p_environment) = 0; virtual void scenario_set_reflection_atlas_size(RID p_scenario, int p_size, int p_subdiv) = 0; virtual void scenario_set_fallback_environment(RID p_scenario, RID p_environment) = 0; + virtual void scenario_set_physics_interpolation_enabled(RID p_scenario, bool p_enabled) = 0; /* INSTANCING API */ @@ -855,6 +865,8 @@ class VisualServer : public Object { virtual void instance_set_scenario(RID p_instance, RID p_scenario) = 0; virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_transform(RID p_instance, const Transform &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_material(RID p_instance, int p_surface, RID p_material) = 0; @@ -1114,6 +1126,8 @@ class VisualServer : public Object { virtual bool has_changed(ChangedPriority p_priority = CHANGED_PRIORITY_ANY) const = 0; virtual void init() = 0; virtual void finish() = 0; + virtual void scenario_tick(RID p_scenario) = 0; + virtual void scenario_pre_draw(RID p_scenario, bool p_will_draw) = 0; /* STATUS INFORMATION */