Fixed Timestep Interpolation (3D)

Adds fixed timestep interpolation to the visual server.
Switchable on and off with project setting.

This version does not add new API for set_transform etc, when nodes have the interpolated flag set they will always use interpolation.
This commit is contained in:
lawnjelly 2021-09-15 12:30:45 +01:00
parent 9cb169504a
commit 522bce1159
41 changed files with 2490 additions and 390 deletions

View file

@ -101,6 +101,22 @@ public:
}
}
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]);

View file

@ -171,6 +171,7 @@ public:
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

View file

@ -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;
}

View file

@ -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

View file

@ -48,6 +48,14 @@
Returns the [Transform2D] of a specific instance.
</description>
</method>
<method name="reset_instance_physics_interpolation">
<return type="void" />
<argument index="0" name="instance" type="int" />
<description>
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.
</description>
</method>
<method name="set_as_bulk_array">
<return type="void" />
<argument index="0" name="array" type="PoolRealArray" />
@ -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.
</description>
</method>
<method name="set_as_bulk_array_interpolated">
<return type="void" />
<argument index="0" name="array_current" type="PoolRealArray" />
<argument index="1" name="array_previous" type="PoolRealArray" />
<description>
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.
</description>
</method>
<method name="set_instance_color">
<return type="void" />
<argument index="0" name="instance" type="int" />
@ -104,6 +122,11 @@
<member name="mesh" type="Mesh" setter="set_mesh" getter="get_mesh">
Mesh to be drawn.
</member>
<member name="physics_interpolation_quality" type="int" setter="set_physics_interpolation_quality" getter="get_physics_interpolation_quality" enum="MultiMesh.PhysicsInterpolationQuality" default="0">
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.
</member>
<member name="transform_format" type="int" setter="set_transform_format" getter="get_transform_format" enum="MultiMesh.TransformFormat" default="0">
Format of transform used to transform mesh, either 2D or 3D.
</member>
@ -136,5 +159,11 @@
<constant name="CUSTOM_DATA_FLOAT" value="2" enum="CustomDataFormat">
The [Color] passed into [method set_instance_custom_data] will use 4 floats. Use this for highest precision.
</constant>
<constant name="INTERP_QUALITY_FAST" value="0" enum="PhysicsInterpolationQuality">
Always interpolate using Basis lerping, which can produce warping artifacts in some situations.
</constant>
<constant name="INTERP_QUALITY_HIGH" value="1" enum="PhysicsInterpolationQuality">
Attempt to interpolate using Basis slerping (spherical linear interpolation) where possible, otherwise fall back to lerping.
</constant>
</constants>
</class>

View file

@ -379,6 +379,21 @@
Returns [code]true[/code] if the local system is the master of this node.
</description>
</method>
<method name="is_physics_interpolated" qualifiers="const">
<return type="bool" />
<description>
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].
</description>
</method>
<method name="is_physics_interpolated_and_enabled" qualifiers="const">
<return type="bool" />
<description>
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].
</description>
</method>
<method name="is_physics_processing" qualifiers="const">
<return type="bool" />
<description>
@ -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).
</description>
</method>
<method name="reset_physics_interpolation">
<return type="void" />
<description>
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.
</description>
</method>
<method name="rpc" qualifiers="vararg">
<return type="Variant" />
<argument index="0" name="method" type="String" />
@ -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.
</description>
</method>
<method name="set_physics_interpolated">
<return type="void" />
<argument index="0" name="enable" type="bool" />
<description>
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.
</description>
</method>
<method name="set_physics_process">
<return type="void" />
<argument index="0" name="enable" type="bool" />
@ -809,6 +841,9 @@
<constant name="NOTIFICATION_POST_ENTER_TREE" value="27">
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.
</constant>
<constant name="NOTIFICATION_RESET_PHYSICS_INTERPOLATION" value="28">
Notification received when [method reset_physics_interpolation] is called on the node or parent nodes.
</constant>
<constant name="NOTIFICATION_WM_MOUSE_ENTER" value="1002">
Notification received from the OS when the mouse enters the game window.
Implemented on desktop and web platforms.

View file

@ -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.
</member>
<member name="physics/common/physics_interpolation" type="bool" setter="" getter="" default="false">
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.
</member>
<member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5">
Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be 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].

View file

@ -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.
</member>
<member name="physics_interpolation" type="bool" setter="set_physics_interpolation_enabled" getter="is_physics_interpolation_enabled" default="false">
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.
</member>
<member name="refuse_new_network_connections" type="bool" setter="set_refuse_new_network_connections" getter="is_refusing_new_network_connections" default="false">
If [code]true[/code], the [SceneTree]'s [member network_peer] refuses new incoming connections.
</member>

View file

@ -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.
</description>
</method>
<method name="get_global_transform_interpolated">
<return type="Transform" />
<description>
When using physics interpolation, there will be circumstances in which you want to know the interpolated (displayed) transform of a node rather than the standard transform (which may only be accurate to the most recent physics tick).
This is particularly important for frame-based operations that take place in [method Node._process], rather than [method Node._physics_process]. Examples include [Camera]s focusing on a node, or finding where to fire lasers from on a frame rather than physics tick.
</description>
</method>
<method name="get_parent_spatial" qualifiers="const">
<return type="Spatial" />
<description>

View file

@ -435,30 +435,26 @@ public:
/* MULTIMESH API */
virtual RID multimesh_create() { return RID(); }
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_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<float> &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(); }
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<float> &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 */

View file

@ -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<float> &p_array) {
void RasterizerStorageGLES2::_multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &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();

View file

@ -787,6 +787,8 @@ public:
bool dirty_aabb;
bool dirty_data;
MMInterpolator interpolator;
MultiMesh() :
size(0),
transform_format(VS::MULTIMESH_TRANSFORM_2D),
@ -807,30 +809,31 @@ public:
SelfList<MultiMesh>::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<float> &p_array);
virtual void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &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();

View file

@ -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<float> &p_array) {
void RasterizerStorageGLES3::_multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &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();

View file

@ -811,6 +811,8 @@ public:
bool dirty_aabb;
bool dirty_data;
MMInterpolator interpolator;
MultiMesh() :
size(0),
transform_format(VS::MULTIMESH_TRANSFORM_2D),
@ -834,30 +836,31 @@ public:
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<float> &p_array);
virtual void _multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &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 */

View file

@ -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();
}

View file

@ -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();

View file

@ -94,6 +94,8 @@ protected:
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;

View file

@ -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<Particle>::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<Particle>::Read r = particles.read();
float *ptr = w.ptr();
PoolVector<float>::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<int, SortAxis> 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<float>::Write w = particle_data.write();
PoolVector<Particle>::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);
}
}

View file

@ -84,10 +84,25 @@ public:
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 @@ private:
RID multimesh;
PoolVector<Particle> particles;
LocalVector<ParticleBase> particles_prev;
PoolVector<float> particle_data;
PoolVector<float> particle_data_prev;
PoolVector<int> particle_order;
struct SortLifetime {
@ -144,8 +161,6 @@ private:
int fixed_fps;
bool fractional_delta;
Transform inv_emission_transform;
SafeFlag can_update;
DrawOrder draw_order;
@ -182,15 +197,55 @@ private:
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();

View file

@ -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<MultiMesh> &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<MultiMesh> 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() {
}

View file

@ -39,8 +39,12 @@ class MultiMeshInstance : public GeometryInstance {
Ref<MultiMesh> multimesh;
void _refresh_interpolated();
protected:
virtual void _physics_interpolated_changed();
static void _bind_methods();
void _notification(int p_what);
// bind helpers
public:

View file

@ -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;
}
}

View file

@ -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 @@ private:
mutable SelfList<Node> 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 @@ private:
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<Spatial *> children;
List<Spatial *>::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<SpatialGizmo> gizmo;
bool gizmo_disabled;
bool gizmo_dirty;
bool gizmo_disabled : 1;
bool gizmo_dirty : 1;
#endif
} data;
@ -115,18 +119,12 @@ private:
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 @@ public:
Transform get_transform() const;
Transform get_global_transform() const;
Transform get_global_transform_interpolated();
#ifdef TOOLS_ENABLED
virtual Transform get_global_gizmo_transform() const;

View file

@ -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;
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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;

View file

@ -100,9 +100,6 @@ private:
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 @@ private:
Map<StringName, MultiplayerAPI::RPCMode> rpc_methods;
Map<StringName, MultiplayerAPI::RPCMode> rpc_properties;
// 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_internal;
bool idle_process_internal;
// variables used to properly sort the node when processing, ignored otherwise
//should move all the stuff below to bits
bool physics_process : 1;
bool idle_process : 1;
bool input;
bool unhandled_input;
bool unhandled_key_input;
bool physics_process_internal : 1;
bool idle_process_internal : 1;
bool parent_owned;
bool in_constructor;
bool use_placeholder;
bool input : 1;
bool unhandled_input : 1;
bool unhandled_key_input : 1;
bool display_folded;
bool editable_instance;
// 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;
// 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;
// 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 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 @@ private:
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 @@ protected:
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 @@ protected:
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 @@ public:
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 @@ public:
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();

View file

@ -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<World>(memnew(World)));
}
set_physics_interpolation_enabled(GLOBAL_DEF("physics/common/physics_interpolation", false));
// Initialize network state
multiplayer_poll = true;

View file

@ -121,6 +121,7 @@ private:
bool _quit;
bool initialized;
bool input_handled;
bool _physics_interpolation_enabled;
Size2 last_screen_size;
StringName tree_changed_name;
@ -407,6 +408,9 @@ public:
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();

View file

@ -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<float> &p_array) {
VisualServer::get_singleton()->multimesh_set_as_bulk_array(multimesh, p_array);
}
void MultiMesh::set_as_bulk_array_interpolated(const PoolVector<float> &p_array_curr, const PoolVector<float> &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() {

View file

@ -56,6 +56,11 @@ public:
CUSTOM_DATA_FLOAT,
};
enum PhysicsInterpolationQuality {
INTERP_QUALITY_FAST,
INTERP_QUALITY_HIGH,
};
private:
Ref<Mesh> mesh;
RID multimesh;
@ -64,6 +69,7 @@ private:
CustomDataFormat custom_data_format;
int instance_count;
int visible_instance_count;
PhysicsInterpolationQuality _physics_interpolation_quality;
protected:
static void _bind_methods();
@ -99,6 +105,9 @@ public:
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 @@ public:
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<float> &p_array);
void set_as_bulk_array_interpolated(const PoolVector<float> &p_array_curr, const PoolVector<float> &p_array_prev);
void set_physics_interpolated(bool p_interpolated);
virtual AABB get_aabb() const;
@ -123,5 +137,6 @@ public:
VARIANT_ENUM_CAST(MultiMesh::TransformFormat);
VARIANT_ENUM_CAST(MultiMesh::ColorFormat);
VARIANT_ENUM_CAST(MultiMesh::CustomDataFormat);
VARIANT_ENUM_CAST(MultiMesh::PhysicsInterpolationQuality);
#endif // MULTI_MESH_H

View file

@ -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<float>::Read r_prev = mmi->_data_prev.read();
PoolVector<float>::Read r_curr = mmi->_data_curr.read();
PoolVector<float>::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<float>::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<float>::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<float>::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<float>::Write w = mmi->_data_prev.write();
PoolVector<float>::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<float> &p_array, const PoolVector<float> &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<float> &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);
}

View file

@ -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 @@ public:
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 @@ public:
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 @@ public:
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 @@ public:
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;
virtual RID multimesh_create() = 0;
// in floats
int _stride = 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;
// Vertex format sizes in floats
int _vf_size_xform = 0;
int _vf_size_color = 0;
int _vf_size_data = 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;
// Set by allocate, can be used to prevent indexing out of range.
int _num_instances = 0;
virtual RID multimesh_get_mesh(RID p_multimesh) const = 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;
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;
PoolVector<float> _data_prev;
PoolVector<float> _data_curr;
PoolVector<float> _data_interpolated;
};
virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) = 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<float> &p_array);
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 void multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector<float> &p_array, const PoolVector<float> &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 AABB multimesh_get_aabb(RID p_multimesh) const = 0;
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<float> &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 @@ public:
virtual RID canvas_light_occluder_create() = 0;
virtual void canvas_light_occluder_set_polylines(RID p_occluder, const PoolVector<Vector2> &p_lines) = 0;
/* INTERPOLATION */
struct InterpolationData {
void notify_free_multimesh(RID p_rid);
LocalVector<RID> multimesh_interpolate_update_list;
LocalVector<RID> multimesh_transform_update_lists[2];
LocalVector<RID> *multimesh_transform_update_list_curr = &multimesh_transform_update_lists[0];
LocalVector<RID> *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 @@ public:
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

View file

@ -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");

View file

@ -280,6 +280,11 @@ public:
BIND2(multimesh_set_as_bulk_array, RID, const PoolVector<float> &)
BIND3(multimesh_set_as_bulk_array_interpolated, RID, const PoolVector<float> &, const PoolVector<float> &)
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 @@ public:
/* 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 @@ public:
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 @@ public:
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 @@ public:
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 */

View file

@ -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<InstanceLightData *>(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<InstanceLightData *>(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<InstanceReflectionProbeData *>(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);

View file

@ -58,6 +58,7 @@ public:
static VisualServerScene *singleton;
/* CAMERA API */
struct Scenario;
struct Camera : public RID_Data {
enum Type {
@ -71,12 +72,27 @@ public:
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 @@ public:
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> 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 @@ public:
SelfList<Instance>::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<RID> instance_interpolate_update_list;
LocalVector<RID> instance_transform_update_lists[2];
LocalVector<RID> *instance_transform_update_list_curr = &instance_transform_update_lists[0];
LocalVector<RID> *instance_transform_update_list_prev = &instance_transform_update_lists[1];
LocalVector<RID> instance_teleport_list;
LocalVector<RID> camera_transform_update_lists[2];
LocalVector<RID> *camera_transform_update_list_curr = &camera_transform_update_lists[0];
LocalVector<RID> *camera_transform_update_list_prev = &camera_transform_update_lists[1];
LocalVector<RID> camera_teleport_list;
bool interpolation_enabled;
} _interpolation_data;
Scenario();
~Scenario() { memdelete(sps); }
};
@ -262,6 +305,9 @@ public:
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 @@ public:
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 @@ public:
};
SelfList<Instance>::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 @@ public:
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 @@ public:
void render_camera(Ref<ARVRInterface> &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;

View file

@ -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();

View file

@ -54,6 +54,8 @@ class VisualServerWrapMT : public VisualServer {
SafeNumeric<uint64_t> 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 @@ public:
FUNC2(multimesh_set_as_bulk_array, RID, const PoolVector<float> &)
FUNC3(multimesh_set_as_bulk_array_interpolated, RID, const PoolVector<float> &, const PoolVector<float> &)
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 @@ public:
/* 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 @@ public:
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 @@ public:
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 @@ public:
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 */

View file

@ -381,6 +381,12 @@ public:
virtual void multimesh_set_as_bulk_array(RID p_multimesh, const PoolVector<float> &p_array) = 0;
// Interpolation
virtual void multimesh_set_as_bulk_array_interpolated(RID p_multimesh, const PoolVector<float> &p_array, const PoolVector<float> &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 @@ public:
/* 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 @@ public:
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 @@ public:
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 @@ public:
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 */