Physics Interpolation - Move 3D FTI to SceneTree

Moves 3D interpolation from `VisualServer` to the client code (`SceneTree`).
Complete rework of 3D physics interpolation, but using the same user API.
This commit is contained in:
lawnjelly 2025-03-04 10:32:07 +00:00 committed by Relintai
parent dedeec9492
commit 8a735a3c02
25 changed files with 601 additions and 400 deletions

View File

@ -105,6 +105,15 @@ public:
return false; return false;
} }
bool erase_unordered(const T &p_val) {
int64_t idx = find(p_val);
if (idx >= 0) {
remove_unordered(idx);
return true;
}
return false;
}
U erase_multiple_unordered(const T &p_val) { U erase_multiple_unordered(const T &p_val) {
U from = 0; U from = 0;
U count = 0; U count = 0;

View File

@ -1476,14 +1476,6 @@
Sets a material that will override the material for all surfaces on the mesh associated with this instance. Equivalent to [member GeometryInstance.material_override]. Sets a material that will override the material for all surfaces on the mesh associated with this instance. Equivalent to [member GeometryInstance.material_override].
</description> </description>
</method> </method>
<method name="instance_reset_physics_interpolation">
<return type="void" />
<argument index="0" name="instance" type="RID" />
<description>
Prevents physics interpolation for the current physics tick.
This is useful when moving an instance to a new location, to give an instantaneous change rather than interpolation from the previous location.
</description>
</method>
<method name="instance_set_base"> <method name="instance_set_base">
<return type="void" /> <return type="void" />
<argument index="0" name="instance" type="RID" /> <argument index="0" name="instance" type="RID" />
@ -1525,14 +1517,6 @@
Sets a margin to increase the size of the AABB when culling objects from the view frustum. This allows you to avoid culling objects that fall outside the view frustum. Equivalent to [member GeometryInstance.extra_cull_margin]. Sets a margin to increase the size of the AABB when culling objects from the view frustum. This allows you to avoid culling objects that fall outside the view frustum. Equivalent to [member GeometryInstance.extra_cull_margin].
</description> </description>
</method> </method>
<method name="instance_set_interpolated">
<return type="void" />
<argument index="0" name="instance" type="RID" />
<argument index="1" name="interpolated" type="bool" />
<description>
Turns on and off physics interpolation for the instance.
</description>
</method>
<method name="instance_set_layer_mask"> <method name="instance_set_layer_mask">
<return type="void" /> <return type="void" />
<argument index="0" name="instance" type="RID" /> <argument index="0" name="instance" type="RID" />

View File

@ -52,6 +52,7 @@
[b]Note:[/b] The simulation does not take the effect of gears into account, you will need to add logic for this if you wish to simulate gears. [b]Note:[/b] The simulation does not take the effect of gears into account, you will need to add logic for this if you wish to simulate gears.
A negative value will result in the wheel reversing. A negative value will result in the wheel reversing.
</member> </member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="1" />
<member name="steering" type="float" setter="set_steering" getter="get_steering" default="0.0"> <member name="steering" type="float" setter="set_steering" getter="get_steering" default="0.0">
The steering angle for the wheel. Setting this to a non-zero value will result in the vehicle turning when it's moving. The steering angle for the wheel. Setting this to a non-zero value will result in the vehicle turning when it's moving.
</member> </member>

View File

@ -39,3 +39,4 @@ MainLoop *test();
} }
#endif // TEST_EXPRESSION_H #endif // TEST_EXPRESSION_H

View File

@ -77,6 +77,7 @@
<member name="bone_name" type="String" setter="set_bone_name" getter="get_bone_name" default="&quot;&quot;"> <member name="bone_name" type="String" setter="set_bone_name" getter="get_bone_name" default="&quot;&quot;">
The name of the attached bone. The name of the attached bone.
</member> </member>
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="1" />
</members> </members>
<constants> <constants>
</constants> </constants>

View File

@ -187,6 +187,7 @@ String BoneAttachment::get_configuration_warning() const {
} }
BoneAttachment::BoneAttachment() { BoneAttachment::BoneAttachment() {
set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF);
bound = false; bound = false;
bone_idx = -1; bone_idx = -1;
override_pose = false; override_pose = false;

View File

@ -279,7 +279,7 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove
p_task->skeleton->set_bone_global_pose_override(p_task->chain.chain_root.bone, Transform(), 0.0, false); p_task->skeleton->set_bone_global_pose_override(p_task->chain.chain_root.bone, Transform(), 0.0, false);
Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin; Vector3 origin_pos = p_task->skeleton->get_bone_global_pose(p_task->chain.chain_root.bone).origin;
make_goal(p_task, p_task->skeleton->get_global_transform().affine_inverse(), blending_delta); make_goal(p_task, p_task->skeleton->get_global_transform_interpolated().affine_inverse(), blending_delta);
if (p_use_magnet && p_task->chain.middle_chain_item) { if (p_use_magnet && p_task->chain.middle_chain_item) {
p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.linear_interpolate(p_magnet_position, blending_delta); p_task->chain.magnet_position = p_task->chain.middle_chain_item->initial_transform.origin.linear_interpolate(p_magnet_position, blending_delta);

View File

@ -50,6 +50,13 @@ void Camera::_request_camera_update() {
_update_camera(); _update_camera();
} }
void Camera::fti_update_servers() {
if (camera.is_valid()) {
Transform tr = _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
VisualServer::get_singleton()->camera_set_transform(camera, tr);
}
}
void Camera::_update_camera_mode() { void Camera::_update_camera_mode() {
force_change = true; force_change = true;
switch (mode) { switch (mode) {
@ -90,12 +97,8 @@ void Camera::_update_camera() {
if (!is_physics_interpolated_and_enabled()) { if (!is_physics_interpolated_and_enabled()) {
RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform()); RenderingServer::get_singleton()->camera_set_transform(camera, get_camera_transform());
} else { } else {
// Ideally we shouldn't be moving a physics interpolated camera within a frame, // Force a refresh next frame.
// because it will break smooth interpolation, but it may occur on e.g. level load. fti_notify_node_changed();
if (!Engine::get_singleton()->is_in_physics_frame() && camera.is_valid()) {
_physics_interpolation_ensure_transform_calculated(true);
RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
}
} }
// here goes listener stuff // here goes listener stuff
@ -119,40 +122,6 @@ void Camera::_physics_interpolated_changed() {
_update_process_mode(); _update_process_mode();
} }
void Camera::_physics_interpolation_ensure_data_flipped() {
// The curr -> previous update can either occur
// on the INTERNAL_PHYSICS_PROCESS OR
// on NOTIFICATION_TRANSFORM_CHANGED,
// if NOTIFICATION_TRANSFORM_CHANGED takes place
// earlier than INTERNAL_PHYSICS_PROCESS on a tick.
// This is to ensure that the data keeps flowing, but the new data
// doesn't overwrite before prev has been set.
// Keep the data flowing.
uint64_t tick = Engine::get_singleton()->get_physics_frames();
if (_interpolation_data.last_update_physics_tick != tick) {
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
_interpolation_data.last_update_physics_tick = tick;
physics_interpolation_flip_data();
}
}
void Camera::_physics_interpolation_ensure_transform_calculated(bool p_force) const {
DEV_CHECK_ONCE(!Engine::get_singleton()->is_in_physics_frame());
InterpolationData &id = _interpolation_data;
uint64_t frame = Engine::get_singleton()->get_frames_drawn();
if (id.last_update_frame != frame || p_force) {
id.last_update_frame = frame;
TransformInterpolator::interpolate_transform(id.xform_prev, id.xform_curr, id.xform_interpolated, Engine::get_singleton()->get_physics_interpolation_fraction());
Transform &tr = id.camera_xform_interpolated;
tr = _get_adjusted_camera_transform(id.xform_interpolated);
}
}
void Camera::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) { void Camera::set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal) {
_desired_process_internal = p_process_internal; _desired_process_internal = p_process_internal;
_desired_physics_process_internal = p_physics_process_internal; _desired_physics_process_internal = p_physics_process_internal;
@ -160,17 +129,8 @@ void Camera::set_desired_process_modes(bool p_process_internal, bool p_physics_p
} }
void Camera::_update_process_mode() { void Camera::_update_process_mode() {
bool process = _desired_process_internal; set_process_internal(_desired_process_internal);
bool physics_process = _desired_physics_process_internal; set_physics_process_internal(_desired_physics_process_internal);
if (is_physics_interpolated_and_enabled()) {
if (is_current()) {
process = true;
physics_process = true;
}
}
set_process_internal(process);
set_physics_process_internal(physics_process);
} }
void Camera::_notification(int p_what) { void Camera::_notification(int p_what) {
@ -187,50 +147,21 @@ void Camera::_notification(int p_what) {
world->_camera_set(this); world->_camera_set(this);
} }
} break; } break;
case NOTIFICATION_INTERNAL_PROCESS: {
if (is_physics_interpolated_and_enabled() && camera.is_valid()) {
_physics_interpolation_ensure_transform_calculated();
#ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION
print_line("\t\tinterpolated Camera: " + rtos(_interpolation_data.xform_interpolated.origin.x) + "\t( prev " + rtos(_interpolation_data.xform_prev.origin.x) + ", curr " + rtos(_interpolation_data.xform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
#endif
RenderingServer::get_singleton()->camera_set_transform(camera, _interpolation_data.camera_xform_interpolated);
}
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (is_physics_interpolated_and_enabled()) {
_physics_interpolation_ensure_data_flipped();
_interpolation_data.xform_curr = get_global_transform();
}
} break;
case NOTIFICATION_TRANSFORM_CHANGED: { case NOTIFICATION_TRANSFORM_CHANGED: {
if (is_physics_interpolated_and_enabled()) {
_physics_interpolation_ensure_data_flipped();
_interpolation_data.xform_curr = get_global_transform();
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED) #if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
if (is_physics_interpolated_and_enabled()) {
if (!Engine::get_singleton()->is_in_physics_frame()) { if (!Engine::get_singleton()->is_in_physics_frame()) {
PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera triggered from outside physics process"); PHYSICS_INTERPOLATION_NODE_WARNING(get_instance_id(), "Interpolated Camera triggered from outside physics process");
} }
#endif
} }
#endif
_request_camera_update(); _request_camera_update();
if (doppler_tracking != DOPPLER_TRACKING_DISABLED) { if (doppler_tracking != DOPPLER_TRACKING_DISABLED) {
velocity_tracker->update_position(get_global_transform().origin); velocity_tracker->update_position(get_global_transform().origin);
} }
// Allow auto-reset when first adding to the tree, as a convenience.
if (_is_physics_interpolation_reset_requested() && is_inside_tree()) {
_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
_set_physics_interpolation_reset_requested(false);
}
} break; } break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: { case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (is_inside_tree()) { _update_process_mode();
_interpolation_data.xform_curr = get_global_transform();
_interpolation_data.xform_prev = _interpolation_data.xform_curr;
_update_process_mode();
}
} break; } break;
case NOTIFICATION_EXIT_WORLD: { case NOTIFICATION_EXIT_WORLD: {
if (!get_tree()->is_node_being_edited(this)) { if (!get_tree()->is_node_being_edited(this)) {
@ -273,8 +204,7 @@ Transform Camera::_get_adjusted_camera_transform(const Transform &p_xform) const
Transform Camera::get_camera_transform() const { Transform Camera::get_camera_transform() const {
if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) { if (is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame()) {
_physics_interpolation_ensure_transform_calculated(); return _get_adjusted_camera_transform(_get_cached_global_transform_interpolated());
return _interpolation_data.camera_xform_interpolated;
} }
return _get_adjusted_camera_transform(get_global_transform()); return _get_adjusted_camera_transform(get_global_transform());
@ -864,9 +794,16 @@ float ClippedCamera::get_margin() const {
return margin; return margin;
} }
void ClippedCamera::set_process_mode(ProcessMode p_mode) { void ClippedCamera::set_process_mode(ProcessMode p_mode) {
if (is_physics_interpolated_and_enabled() && p_mode == CLIP_PROCESS_IDLE) { if (is_physics_interpolated_and_enabled()) {
p_mode = CLIP_PROCESS_PHYSICS; if (p_mode == CLIP_PROCESS_IDLE) {
WARN_PRINT_ONCE("[Physics interpolation] Forcing ClippedCamera to PROCESS_PHYSICS mode."); p_mode = CLIP_PROCESS_PHYSICS;
WARN_PRINT_ONCE("[Physics interpolation] Forcing ClippedCamera to PROCESS_PHYSICS mode.");
}
process_mode = p_mode;
set_desired_process_modes(false, true);
return;
} }
if (process_mode == p_mode) { if (process_mode == p_mode) {
@ -880,8 +817,11 @@ ClippedCamera::ProcessMode ClippedCamera::get_process_mode() const {
return process_mode; return process_mode;
} }
void ClippedCamera::physics_interpolation_flip_data() { void ClippedCamera::fti_pump() {
_interpolation_data.clip_offset_prev = _interpolation_data.clip_offset_curr; _interpolation_data.clip_offset_prev = _interpolation_data.clip_offset_curr;
// Must call the base class.
Spatial::fti_pump();
} }
void ClippedCamera::_physics_interpolated_changed() { void ClippedCamera::_physics_interpolated_changed() {
@ -898,6 +838,11 @@ Transform ClippedCamera::_get_adjusted_camera_transform(const Transform &p_xform
return t; return t;
} }
void ClippedCamera::fti_update_servers() {
clip_offset = ((_interpolation_data.clip_offset_curr - _interpolation_data.clip_offset_prev) * Engine::get_singleton()->get_physics_interpolation_fraction()) + _interpolation_data.clip_offset_prev;
Camera::fti_update_servers();
}
void ClippedCamera::_notification(int p_what) { void ClippedCamera::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) { if (p_what == NOTIFICATION_ENTER_TREE) {
// Switch process mode to physics if we are turning on interpolation. // Switch process mode to physics if we are turning on interpolation.
@ -965,10 +910,6 @@ void ClippedCamera::_notification(int p_what) {
_update_camera(); _update_camera();
} }
if (is_physics_interpolated_and_enabled() && (p_what == NOTIFICATION_INTERNAL_PROCESS)) {
clip_offset = ((_interpolation_data.clip_offset_curr - _interpolation_data.clip_offset_prev) * Engine::get_singleton()->get_physics_interpolation_fraction()) + _interpolation_data.clip_offset_prev;
}
if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) { if (p_what == NOTIFICATION_LOCAL_TRANSFORM_CHANGED) {
update_gizmos(); update_gizmos();
} }

View File

@ -96,26 +96,10 @@ private:
Ref<SpatialVelocityTracker> velocity_tracker; Ref<SpatialVelocityTracker> velocity_tracker;
bool affect_lod = true; bool affect_lod = true;
/////////////////////////////////////////////////////// // These can be set by derived Cameras.
// INTERPOLATION FUNCTIONS
void _physics_interpolation_ensure_transform_calculated(bool p_force = false) const;
void _physics_interpolation_ensure_data_flipped();
// These can be set by derived Cameras,
// if they wish to do processing (while still
// allowing physics interpolation to function).
bool _desired_process_internal = false; bool _desired_process_internal = false;
bool _desired_physics_process_internal = false; bool _desired_physics_process_internal = false;
mutable struct InterpolationData {
Transform xform_curr;
Transform xform_prev;
Transform xform_interpolated;
Transform camera_xform_interpolated; // After modification according to camera type.
uint32_t last_update_physics_tick = 0;
uint32_t last_update_frame = UINT32_MAX;
} _interpolation_data;
void _update_process_mode(); void _update_process_mode();
protected: protected:
@ -123,9 +107,6 @@ protected:
// This is because physics interpolation may need to request process modes additionally. // This is because physics interpolation may need to request process modes additionally.
void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal); void set_desired_process_modes(bool p_process_internal, bool p_physics_process_internal);
// Opportunity for derived classes to interpolate extra attributes.
virtual void physics_interpolation_flip_data() {}
virtual void _physics_interpolated_changed(); virtual void _physics_interpolated_changed();
virtual Transform _get_adjusted_camera_transform(const Transform &p_xform) const; virtual Transform _get_adjusted_camera_transform(const Transform &p_xform) const;
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
@ -133,6 +114,7 @@ protected:
void _update_camera(); void _update_camera();
virtual void _request_camera_update(); virtual void _request_camera_update();
void _update_camera_mode(); void _update_camera_mode();
virtual void fti_update_servers();
void _notification(int p_what); void _notification(int p_what);
virtual void _validate_property(PropertyInfo &p_property) const; virtual void _validate_property(PropertyInfo &p_property) const;
@ -253,8 +235,9 @@ private:
protected: protected:
virtual Transform _get_adjusted_camera_transform(const Transform &p_xform) const; virtual Transform _get_adjusted_camera_transform(const Transform &p_xform) const;
virtual void physics_interpolation_flip_data(); virtual void fti_pump();
virtual void _physics_interpolated_changed(); virtual void _physics_interpolated_changed();
virtual void fti_update_servers();
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
void _notification(int p_what); void _notification(int p_what);

View File

@ -47,7 +47,7 @@ public:
real_t getDiagonal() const { return m_Adiag; } real_t getDiagonal() const { return m_Adiag; }
btVehicleJacobianEntry() {}; btVehicleJacobianEntry() {}
//constraint between two different rigidbodies //constraint between two different rigidbodies
btVehicleJacobianEntry( btVehicleJacobianEntry(
const Basis &world2A, const Basis &world2A,
@ -369,6 +369,8 @@ VehicleWheel::VehicleWheel() {
m_raycastInfo.m_suspensionLength = 0.0; m_raycastInfo.m_suspensionLength = 0.0;
body = nullptr; body = nullptr;
set_physics_interpolation_mode(PHYSICS_INTERPOLATION_MODE_OFF);
} }
void VehicleBody::_update_wheel_transform(VehicleWheel &wheel, PhysicsDirectBodyState *s) { void VehicleBody::_update_wheel_transform(VehicleWheel &wheel, PhysicsDirectBodyState *s) {

View File

@ -85,6 +85,12 @@ void VisualInstance::set_instance_use_identity_transform(bool p_enable) {
} }
} }
void VisualInstance::fti_update_servers() {
if (!_is_using_identity_transform()) {
VisualServer::get_singleton()->instance_set_transform(get_instance(), _get_cached_global_transform_interpolated());
}
}
void VisualInstance::_notification(int p_what) { void VisualInstance::_notification(int p_what) {
switch (p_what) { switch (p_what) {
case NOTIFICATION_ENTER_WORLD: { case NOTIFICATION_ENTER_WORLD: {
@ -100,33 +106,26 @@ void VisualInstance::_notification(int p_what) {
} break; } break;
case NOTIFICATION_TRANSFORM_CHANGED: { case NOTIFICATION_TRANSFORM_CHANGED: {
if (_is_vi_visible() || is_physics_interpolated_and_enabled()) { if (_is_vi_visible()) {
if (!_is_using_identity_transform()) { if (!_is_using_identity_transform()) {
RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform()); // Physics interpolated VIs don't need to send their transform immediately after setting,
// indeed it is counterproductive, because the interpolated transform will be sent
// For instance when first adding to the tree, when the previous transform is // to the VisualServer immediately prior to rendering.
// unset, to prevent streaking from the origin. if (!is_physics_interpolated_and_enabled()) {
if (_is_physics_interpolation_reset_requested() && is_physics_interpolated_and_enabled() && is_inside_tree()) { RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
if (_is_vi_visible()) { } else {
_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION); // For instance when first adding to the tree, when the previous transform is
// unset, to prevent streaking from the origin.
if (_is_physics_interpolation_reset_requested() && is_inside_tree()) {
if (_is_vi_visible()) {
_notification(NOTIFICATION_RESET_PHYSICS_INTERPOLATION);
}
_set_physics_interpolation_reset_requested(false);
} }
_set_physics_interpolation_reset_requested(false);
} }
} }
} }
} break; } break;
case NOTIFICATION_RESET_PHYSICS_INTERPOLATION: {
if (_is_vi_visible() && is_physics_interpolated() && is_inside_tree()) {
// We must ensure the VisualServer transform is up to date before resetting.
// This is because NOTIFICATION_TRANSFORM_CHANGED is deferred,
// and cannot be relied to be called in order before NOTIFICATION_RESET_PHYSICS_INTERPOLATION.
if (!_is_using_identity_transform()) {
RenderingServer::get_singleton()->instance_set_transform(instance, get_global_transform());
}
RenderingServer::get_singleton()->instance_reset_physics_interpolation(instance);
}
} break;
case NOTIFICATION_EXIT_WORLD: { case NOTIFICATION_EXIT_WORLD: {
RenderingServer::get_singleton()->instance_set_scenario(instance, RID()); RenderingServer::get_singleton()->instance_set_scenario(instance, RID());
RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID()); RenderingServer::get_singleton()->instance_attach_skeleton(instance, RID());
@ -142,10 +141,6 @@ void VisualInstance::_notification(int p_what) {
} }
} }
void VisualInstance::_physics_interpolated_changed() {
RenderingServer::get_singleton()->instance_set_interpolated(instance, is_physics_interpolated());
}
RID VisualInstance::get_instance() const { RID VisualInstance::get_instance() const {
return instance; return instance;
} }

View File

@ -55,8 +55,8 @@ class VisualInstance : public CullInstance {
protected: protected:
void _update_visibility(); void _update_visibility();
virtual void _refresh_portal_mode(); virtual void _refresh_portal_mode();
virtual void _physics_interpolated_changed();
void set_instance_use_identity_transform(bool p_enable); void set_instance_use_identity_transform(bool p_enable);
virtual void fti_update_servers();
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();

View File

@ -592,6 +592,8 @@ void SceneTree::set_physics_interpolation_enabled(bool p_enabled) {
RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled); RenderingServer::get_singleton()->set_physics_interpolation_enabled(p_enabled);
get_scene_tree_fti().set_enabled(get_root(), p_enabled);
// Perform an auto reset on the root node for convenience for the user. // Perform an auto reset on the root node for convenience for the user.
if (root) { if (root) {
root->reset_physics_interpolation(); root->reset_physics_interpolation();
@ -617,6 +619,7 @@ void SceneTree::iteration_prepare() {
// Make sure any pending transforms from the last tick / frame // Make sure any pending transforms from the last tick / frame
// are flushed before pumping the interpolation prev and currents. // are flushed before pumping the interpolation prev and currents.
flush_transform_notifications(); flush_transform_notifications();
get_scene_tree_fti().tick_update();
RenderingServer::get_singleton()->tick(); RenderingServer::get_singleton()->tick();
} }
} }
@ -689,6 +692,17 @@ bool SceneTree::idle(float p_time) {
//print_line("node count: "+itos(get_node_count())); //print_line("node count: "+itos(get_node_count()));
//print_line("TEXTURE RAM: "+itos(RS::get_singleton()->get_render_info(RS::INFO_TEXTURE_MEM_USED))); //print_line("TEXTURE RAM: "+itos(RS::get_singleton()->get_render_info(RS::INFO_TEXTURE_MEM_USED)));
// First pass of scene tree fixed timestep interpolation.
if (get_scene_tree_fti().is_enabled()) {
// Special, we need to ensure RenderingServer is up to date
// with *all* the pending xforms *before* updating it during
// the FTI update.
// If this is not done, we can end up with a deferred `set_transform()`
// overwriting the interpolated xform in the server.
flush_transform_notifications();
get_scene_tree_fti().frame_update(get_root(), true);
}
root_lock++; root_lock++;
if (MainLoop::idle(p_time)) { if (MainLoop::idle(p_time)) {
@ -805,6 +819,11 @@ bool SceneTree::idle(float p_time) {
#endif #endif
// Second pass of scene tree fixed timestep interpolation.
// ToDo: Possibly needs another flush_transform_notifications here
// depending on whether there are side effects to _call_idle_callbacks().
get_scene_tree_fti().frame_update(get_root(), false);
RenderingServer::get_singleton()->pre_draw(true); RenderingServer::get_singleton()->pre_draw(true);
return _quit; return _quit;

View File

@ -36,6 +36,7 @@
#include "core/io/multiplayer_api.h" #include "core/io/multiplayer_api.h"
#include "core/os/main_loop.h" #include "core/os/main_loop.h"
#include "core/os/thread_safe.h" #include "core/os/thread_safe.h"
#include "scene/main/scene_tree_fti.h"
class PackedScene; class PackedScene;
class Node; class Node;
@ -160,6 +161,7 @@ private:
StretchAspect stretch_aspect; StretchAspect stretch_aspect;
Size2i stretch_min; Size2i stretch_min;
real_t stretch_scale; real_t stretch_scale;
SceneTreeFTI scene_tree_fti;
void _update_font_oversampling(float p_ratio); void _update_font_oversampling(float p_ratio);
void _update_root_rect(); void _update_root_rect();
@ -468,6 +470,8 @@ public:
void client_physics_interpolation_add_spatial(SelfList<Spatial> *p_elem); void client_physics_interpolation_add_spatial(SelfList<Spatial> *p_elem);
void client_physics_interpolation_remove_spatial(SelfList<Spatial> *p_elem); void client_physics_interpolation_remove_spatial(SelfList<Spatial> *p_elem);
SceneTreeFTI &get_scene_tree_fti() { return scene_tree_fti; }
static void add_idle_callback(IdleCallback p_callback); static void add_idle_callback(IdleCallback p_callback);
SceneTree(); SceneTree();
~SceneTree(); ~SceneTree();

View File

@ -0,0 +1,288 @@
/**************************************************************************/
/* scene_tree_fti.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef _3D_DISABLED
#include "scene_tree_fti.h"
#include "core/config/engine.h"
#include "core/error/error_macros.h"
#include "core/math/transform_interpolator.h"
#include "core/os/os.h"
#include "scene/main/spatial.h"
#include "scene/3d/visual_instance.h"
void SceneTreeFTI::_reset_flags(Node *p_node) {
Spatial *s = Object::cast_to<Spatial>(p_node);
if (s) {
s->data.fti_on_frame_list = false;
s->data.fti_on_tick_list = false;
// In most cases the later NOTIFICATION_RESET_PHYSICS_INTERPOLATION
// will reset this, but this should help cover hidden nodes.
s->data.local_transform_prev = s->data.local_transform;
}
for (int n = 0; n < p_node->get_child_count(); n++) {
_reset_flags(p_node->get_child(n));
}
}
void SceneTreeFTI::set_enabled(Node *p_root, bool p_enabled) {
if (data.enabled == p_enabled) {
return;
}
data.spatial_tick_list[0].clear();
data.spatial_tick_list[1].clear();
// Spatial flags must be reset.
if (p_root) {
_reset_flags(p_root);
}
data.enabled = p_enabled;
}
void SceneTreeFTI::tick_update() {
if (!data.enabled) {
return;
}
uint32_t curr_mirror = data.mirror;
uint32_t prev_mirror = curr_mirror ? 0 : 1;
LocalVector<Spatial *> &curr = data.spatial_tick_list[curr_mirror];
LocalVector<Spatial *> &prev = data.spatial_tick_list[prev_mirror];
// First detect on the previous list but not on this tick list.
for (uint32_t n = 0; n < prev.size(); n++) {
Spatial *s = prev[n];
if (!s->data.fti_on_tick_list) {
// Needs a reset so jittering will stop.
s->fti_pump();
// This may not get updated so set it to the same as global xform.
// TODO: double check this is the best value.
s->data.global_transform_interpolated = s->get_global_transform();
// Remove from interpolation list.
if (s->data.fti_on_frame_list) {
s->data.fti_on_frame_list = false;
}
}
}
// Now pump all on the current list.
for (uint32_t n = 0; n < curr.size(); n++) {
Spatial *s = curr[n];
// Reset, needs to be marked each tick.
s->data.fti_on_tick_list = false;
// Pump.
s->fti_pump();
}
// Clear previous list and flip.
prev.clear();
data.mirror = prev_mirror;
}
void SceneTreeFTI::_spatial_notify_set_transform(Spatial &r_spatial) {
// This may be checked by the calling routine already,
// but needs to be double checked for custom SceneTrees.
if (!data.enabled || !r_spatial.is_physics_interpolated()) {
return;
}
if (!r_spatial.data.fti_on_tick_list) {
r_spatial.data.fti_on_tick_list = true;
data.spatial_tick_list[data.mirror].push_back(&r_spatial);
}
if (!r_spatial.data.fti_on_frame_list) {
r_spatial.data.fti_on_frame_list = true;
}
}
void SceneTreeFTI::spatial_notify_delete(Spatial *p_spatial) {
if (!data.enabled) {
return;
}
if (p_spatial->data.fti_on_frame_list) {
p_spatial->data.fti_on_frame_list = false;
}
// This can potentially be optimized for large scenes with large churn,
// as it will be doing a linear search through the lists.
data.spatial_tick_list[0].erase_unordered(p_spatial);
data.spatial_tick_list[1].erase_unordered(p_spatial);
}
void SceneTreeFTI::_update_dirty_spatials(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform *p_parent_global_xform, int p_depth) {
Spatial *s = Object::cast_to<Spatial>(p_node);
// Don't recurse into hidden branches.
if (s && !s->is_visible()) {
// NOTE : If we change from recursing entire tree, we should do an is_visible_in_tree()
// check for the first of the branch.
return;
}
// Not a Spatial.
// Could be e.g. a viewport or something
// so we should still recurse to children.
if (!s) {
for (int n = 0; n < p_node->get_child_count(); n++) {
_update_dirty_spatials(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, nullptr, p_depth + 1);
}
return;
}
// Start the active interpolation chain from here onwards
// as we recurse further into the SceneTree.
// Once we hit an active (interpolated) node, we have to fully
// process all ancestors because their xform will also change.
// Anything not moving (inactive) higher in the tree need not be processed.
if (!p_active) {
if (data.frame_start) {
// On the frame start, activate whenever we hit something that requests interpolation.
if (s->data.fti_on_frame_list) {
p_active = true;
}
} else {
// On the frame end, we want to re-interpolate *anything* that has moved
// since the frame start.
if (s->data.dirty & Spatial::DIRTY_GLOBAL_INTERPOLATED) {
p_active = true;
}
}
}
if (data.frame_start) {
// Mark on the Spatial whether we have set global_transform_interp.
// This can later be used when calling `get_global_transform_interpolated()`
// to know which xform to return.
s->data.fti_global_xform_interp_set = p_active;
}
if (p_active) {
#if 0
bool dirty = s->data.dirty & Spatial::DIRTY_GLOBAL_INTERP;
if (data.debug) {
String sz;
for (int n = 0; n < p_depth; n++) {
sz += "\t";
}
print_line(sz + p_node->get_name() + (dirty ? " DIRTY" : ""));
}
#endif
// First calculate our local xform.
// This will either use interpolation, or just use the current local if not interpolated.
Transform local_interp;
if (s->is_physics_interpolated()) {
TransformInterpolator::interpolate_transform(s->data.local_transform_prev, s->data.local_transform, local_interp, p_interpolation_fraction);
} else {
local_interp = s->data.local_transform;
}
// Concatenate parent xform.
if (!s->is_set_as_toplevel()) {
if (p_parent_global_xform) {
s->data.global_transform_interpolated = (*p_parent_global_xform) * local_interp;
} else {
const Spatial *parent = s->get_parent_spatial();
if (parent) {
const Transform &parent_glob = parent->data.fti_global_xform_interp_set ? parent->data.global_transform_interpolated : parent->data.global_transform;
s->data.global_transform_interpolated = parent_glob * local_interp;
} else {
s->data.global_transform_interpolated = local_interp;
}
}
} else {
s->data.global_transform_interpolated = local_interp;
}
// Upload to VisualServer the interpolated global xform.
s->fti_update_servers();
} // if active.
// Remove the dirty interp flag from EVERYTHING as we go.
s->data.dirty &= ~Spatial::DIRTY_GLOBAL_INTERPOLATED;
// Recurse to children.
for (int n = 0; n < p_node->get_child_count(); n++) {
_update_dirty_spatials(p_node->get_child(n), p_current_frame, p_interpolation_fraction, p_active, s->data.fti_global_xform_interp_set ? &s->data.global_transform_interpolated : &s->data.global_transform, p_depth + 1);
}
}
void SceneTreeFTI::frame_update(Node *p_root, bool p_frame_start) {
if (!data.enabled || !p_root) {
return;
}
data.frame_start = p_frame_start;
float f = Engine::get_singleton()->get_physics_interpolation_fraction();
uint32_t frame = Engine::get_singleton()->get_frames_drawn();
// #define SCENE_TREE_FTI_TAKE_TIMINGS
#ifdef SCENE_TREE_FTI_TAKE_TIMINGS
uint64_t before = OS::get_singleton()->get_ticks_usec();
#endif
if (data.debug) {
print_line(String("\nScene: ") + (data.frame_start ? "start" : "end") + "\n");
}
// Probably not the most optimal approach as we traverse the entire SceneTree
// but simple and foolproof.
// Can be optimized later.
_update_dirty_spatials(p_root, frame, f, false);
if (!p_frame_start && data.debug) {
data.debug = false;
}
#ifdef SCENE_TREE_FTI_TAKE_TIMINGS
uint64_t after = OS::get_singleton()->get_ticks_usec();
if ((Engine::get_singleton()->get_frames_drawn() % 60) == 0) {
print_line("Took " + itos(after - before) + " usec " + (data.frame_start ? "start" : "end"));
}
#endif
}
#endif // ndef _3D_DISABLED

112
scene/main/scene_tree_fti.h Normal file
View File

@ -0,0 +1,112 @@
/**************************************************************************/
/* scene_tree_fti.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef SCENE_TREE_FTI_H
#define SCENE_TREE_FTI_H
#include "core/containers/local_vector.h"
#include "core/os/mutex.h"
class Spatial;
class Node;
class Transform;
#ifdef _3D_DISABLED
// Stubs
class SceneTreeFTI {
public:
void frame_update(Node *p_root, bool p_frame_start) {}
void tick_update() {}
void set_enabled(Node *p_root, bool p_enabled) {}
bool is_enabled() const { return false; }
void spatial_notify_set_transform(Spatial &r_spatial) {}
void spatial_notify_delete(Spatial *p_spatial) {}
};
#else
// Important.
// This class uses raw pointers, so it is essential that on deletion, this class is notified
// so that any references can be cleared up to prevent dangling pointer access.
// This class can be used from a custom SceneTree.
// Note we could potentially make SceneTreeFTI static / global to avoid the lookup through scene tree,
// but this covers the custom case of multiple scene trees.
// This class is not thread safe, but can be made thread safe easily with a mutex as in the 4.x version.
class SceneTreeFTI {
struct Data {
// Prev / Curr lists of spatials having local xforms pumped.
LocalVector<Spatial *> spatial_tick_list[2];
uint32_t mirror = 0;
bool enabled = false;
// Whether we are in physics ticks, or in a frame.
bool in_frame = false;
// Updating at the start of the frame, or the end on second pass.
bool frame_start = true;
bool debug = false;
} data;
void _update_dirty_spatials(Node *p_node, uint32_t p_current_frame, float p_interpolation_fraction, bool p_active, const Transform *p_parent_global_xform = nullptr, int p_depth = 0);
void _reset_flags(Node *p_node);
void _spatial_notify_set_transform(Spatial &r_spatial);
public:
// Hottest function, allow inlining the data.enabled check.
void spatial_notify_set_transform(Spatial &r_spatial) {
if (!data.enabled) {
return;
}
_spatial_notify_set_transform(r_spatial);
}
void spatial_notify_delete(Spatial *p_spatial);
// Calculate interpolated xforms, send to visual server.
void frame_update(Node *p_root, bool p_frame_start);
// Update local xform pumps.
void tick_update();
void set_enabled(Node *p_root, bool p_enabled);
bool is_enabled() const { return data.enabled; }
void set_debug_next_frame() { data.debug = true; }
};
#endif // ndef _3D_DISABLED
#endif // SCENE_TREE_FTI_H

View File

@ -118,7 +118,7 @@ void Spatial::_propagate_transform_changed(Spatial *p_origin) {
#endif #endif
get_tree()->xform_change_list.add(&xform_change); get_tree()->xform_change_list.add(&xform_change);
} }
data.dirty |= DIRTY_GLOBAL; data.dirty |= DIRTY_GLOBAL | DIRTY_GLOBAL_INTERPOLATED;
data.children_lock--; data.children_lock--;
} }
@ -174,13 +174,27 @@ void Spatial::_notification(int p_what) {
_propagate_merging_allowed(merging_allowed); _propagate_merging_allowed(merging_allowed);
} }
data.dirty |= DIRTY_GLOBAL; //global is always dirty upon entering a scene data.dirty |= DIRTY_GLOBAL | DIRTY_GLOBAL_INTERPOLATED; //global is always dirty upon entering a scene
_notify_dirty(); _notify_dirty();
notification(NOTIFICATION_ENTER_WORLD); notification(NOTIFICATION_ENTER_WORLD);
if (is_physics_interpolated_and_enabled()) {
// Always reset FTI when entering tree.
fti_pump();
// No need to interpolate as we are doing a reset.
data.global_transform_interpolated = get_global_transform();
// Make sure servers are up to date.
fti_update_servers();
}
} break; } break;
case NOTIFICATION_EXIT_TREE: { case NOTIFICATION_EXIT_TREE: {
if (is_inside_tree()) {
get_tree()->get_scene_tree_fti().spatial_notify_delete(this);
}
notification(NOTIFICATION_EXIT_WORLD, true); notification(NOTIFICATION_EXIT_WORLD, true);
if (xform_change.in_list()) { if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change); get_tree()->xform_change_list.remove(&xform_change);
@ -241,6 +255,13 @@ void Spatial::_notification(int p_what) {
if (data.client_physics_interpolation_data) { if (data.client_physics_interpolation_data) {
data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr; data.client_physics_interpolation_data->global_xform_prev = data.client_physics_interpolation_data->global_xform_curr;
} }
data.local_transform_prev = data.local_transform;
} break;
case NOTIFICATION_PAUSED: {
if (is_physics_interpolated_and_enabled()) {
data.local_transform_prev = data.local_transform;
}
} break; } break;
default: { default: {
@ -270,7 +291,22 @@ void Spatial::set_global_rotation(const Vector3 &p_euler_rad) {
set_global_transform(transform); set_global_transform(transform);
} }
void Spatial::fti_pump() {
if (data.dirty & DIRTY_LOCAL) {
_update_local_transform();
}
data.local_transform_prev = data.local_transform;
}
void Spatial::fti_notify_node_changed() {
if (is_inside_tree()) {
get_tree()->get_scene_tree_fti().spatial_notify_set_transform(*this);
}
}
void Spatial::set_transform(const Transform &p_transform) { void Spatial::set_transform(const Transform &p_transform) {
fti_notify_node_changed();
data.local_transform = p_transform; data.local_transform = p_transform;
data.dirty |= DIRTY_VECTORS; data.dirty |= DIRTY_VECTORS;
data.dirty &= ~DIRTY_LOCAL; data.dirty &= ~DIRTY_LOCAL;
@ -393,14 +429,19 @@ Transform Spatial::_get_global_transform_interpolated(real_t p_interpolation_fra
} }
Transform Spatial::get_global_transform_interpolated() { Transform Spatial::get_global_transform_interpolated() {
#if 1
// Pass through if physics interpolation is switched off. // Pass through if physics interpolation is switched off.
// This is a convenience, as it allows you to easy turn off interpolation // This is a convenience, as it allows you to easy turn off interpolation
// without changing any code. // without changing any code.
if (!is_physics_interpolated_and_enabled()) { if (data.fti_global_xform_interp_set && is_physics_interpolated_and_enabled() && !Engine::get_singleton()->is_in_physics_frame() && is_visible_in_tree()) {
return get_global_transform(); return data.global_transform_interpolated;
} }
// If we are in the physics frame, the interpolated global transform is meaningless. return get_global_transform();
#else
// OLD METHOD - deprecated since moving to SceneTreeFTI,
// but leaving for reference and comparison for debugging.
// However, there is an exception, we may want to use this as a means of starting off the client // However, there is an exception, we may want to use this as a means of starting off the client
// interpolation pump if not already started (when _is_physics_interpolated_client_side() is false). // interpolation pump if not already started (when _is_physics_interpolated_client_side() is false).
if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) { if (Engine::get_singleton()->is_in_physics_frame() && _is_physics_interpolated_client_side()) {
@ -408,6 +449,7 @@ Transform Spatial::get_global_transform_interpolated() {
} }
return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction()); return _get_global_transform_interpolated(Engine::get_singleton()->get_physics_interpolation_fraction());
#endif
} }
Transform Spatial::get_global_transform() const { Transform Spatial::get_global_transform() const {
@ -473,6 +515,7 @@ Transform Spatial::get_relative_transform(const Node *p_parent) const {
} }
void Spatial::set_translation(const Vector3 &p_translation) { void Spatial::set_translation(const Vector3 &p_translation) {
fti_notify_node_changed();
data.local_transform.origin = p_translation; data.local_transform.origin = p_translation;
_change_notify("transform"); _change_notify("transform");
_propagate_transform_changed(this); _propagate_transform_changed(this);
@ -482,6 +525,7 @@ void Spatial::set_translation(const Vector3 &p_translation) {
} }
void Spatial::set_rotation(const Vector3 &p_euler_rad) { void Spatial::set_rotation(const Vector3 &p_euler_rad) {
fti_notify_node_changed();
if (data.dirty & DIRTY_VECTORS) { if (data.dirty & DIRTY_VECTORS) {
data.scale = data.local_transform.basis.get_scale(); data.scale = data.local_transform.basis.get_scale();
data.dirty &= ~DIRTY_VECTORS; data.dirty &= ~DIRTY_VECTORS;
@ -501,6 +545,7 @@ void Spatial::set_rotation_degrees(const Vector3 &p_euler_deg) {
} }
void Spatial::set_scale(const Vector3 &p_scale) { void Spatial::set_scale(const Vector3 &p_scale) {
fti_notify_node_changed();
if (data.dirty & DIRTY_VECTORS) { if (data.dirty & DIRTY_VECTORS) {
data.rotation = data.local_transform.basis.get_rotation(); data.rotation = data.local_transform.basis.get_rotation();
data.dirty &= ~DIRTY_VECTORS; data.dirty &= ~DIRTY_VECTORS;
@ -1106,6 +1151,11 @@ Spatial::Spatial() :
data.disable_scale = false; data.disable_scale = false;
data.vi_visible = true; data.vi_visible = true;
data.merging_allowed = true; data.merging_allowed = true;
data.fti_on_frame_list = false;
data.fti_on_tick_list = false;
data.fti_global_xform_interp_set = false;
data.merging_mode = MERGING_MODE_INHERIT; data.merging_mode = MERGING_MODE_INHERIT;
data.client_physics_interpolation_data = nullptr; data.client_physics_interpolation_data = nullptr;
@ -1123,4 +1173,8 @@ Spatial::Spatial() :
Spatial::~Spatial() { Spatial::~Spatial() {
_disable_client_physics_interpolation(); _disable_client_physics_interpolation();
if (is_inside_tree()) {
get_tree()->get_scene_tree_fti().spatial_notify_delete(this);
}
} }

View File

@ -55,6 +55,8 @@ class Spatial : public Node {
GDCLASS(Spatial, Node); GDCLASS(Spatial, Node);
OBJ_CATEGORY("3D"); OBJ_CATEGORY("3D");
friend class SceneTreeFTI;
public: public:
enum MergingMode : unsigned int { enum MergingMode : unsigned int {
MERGING_MODE_INHERIT, MERGING_MODE_INHERIT,
@ -77,15 +79,27 @@ private:
DIRTY_NONE = 0, DIRTY_NONE = 0,
DIRTY_VECTORS = 1, DIRTY_VECTORS = 1,
DIRTY_LOCAL = 2, DIRTY_LOCAL = 2,
DIRTY_GLOBAL = 4 DIRTY_GLOBAL = 4,
DIRTY_GLOBAL_INTERPOLATED = 8,
}; };
mutable SelfList<Node> xform_change; mutable SelfList<Node> xform_change;
SelfList<Spatial> _client_physics_interpolation_spatials_list; SelfList<Spatial> _client_physics_interpolation_spatials_list;
struct Data { struct Data {
// Interpolated global transform - correct on the frame only.
// Only used with FTI.
Transform global_transform_interpolated;
// Current xforms are either
// * Used for everything (when not using FTI)
// * Correct on the physics tick (when using FTI)
mutable Transform global_transform; mutable Transform global_transform;
mutable Transform local_transform; mutable Transform local_transform;
// Only used with FTI.
Transform local_transform_prev;
mutable Vector3 rotation; mutable Vector3 rotation;
mutable Vector3 scale; mutable Vector3 scale;
@ -109,6 +123,11 @@ private:
bool visible : 1; bool visible : 1;
bool disable_scale : 1; bool disable_scale : 1;
// Scene tree interpolation
bool fti_on_frame_list : 1;
bool fti_on_tick_list : 1;
bool fti_global_xform_interp_set : 1;
bool merging_allowed : 1; bool merging_allowed : 1;
int children_lock; int children_lock;
@ -145,9 +164,27 @@ protected:
bool _is_vi_visible() const { bool _is_vi_visible() const {
return data.vi_visible; return data.vi_visible;
} }
Transform _get_global_transform_interpolated(real_t p_interpolation_fraction); Transform _get_global_transform_interpolated(real_t p_interpolation_fraction);
const Transform &_get_cached_global_transform_interpolated() const { return data.global_transform_interpolated; }
void _disable_client_physics_interpolation(); void _disable_client_physics_interpolation();
// Calling this announces to the FTI system that a node has been moved,
// or requires an update in terms of interpolation
// (e.g. changing Camera zoom even if position hasn't changed).
void fti_notify_node_changed();
// Opportunity after FTI to update the servers
// with global_transform_interpolated,
// and any custom interpolated data in derived classes.
// Make sure to call the parent class fti_update_servers(),
// so all data is updated to the servers.
virtual void fti_update_servers() {}
// Pump the FTI data, also gives a chance for inherited classes
// to pump custom data, but they *must* call the base class here too.
virtual void fti_pump();
void _notification(int p_what); void _notification(int p_what);
static void _bind_methods(); static void _bind_methods();

View File

@ -92,16 +92,8 @@ public:
RID material_override; RID material_override;
RID material_overlay; 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; 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; int depth_layer;
uint32_t layer_mask; uint32_t layer_mask;
@ -123,16 +115,6 @@ public:
bool baked_light : 1; //this flag is only to know if it actually did use baked light bool baked_light : 1; //this flag is only to know if it actually did use baked light
bool redraw_if_visible : 1; 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 float depth; //used for sorting
SelfList<InstanceBase> dependency_item; SelfList<InstanceBase> dependency_item;
@ -158,12 +140,6 @@ public:
lightmap_capture = nullptr; lightmap_capture = nullptr;
lightmap_slice = -1; lightmap_slice = -1;
lightmap_uv_rect = Rect2(0, 0, 1, 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;
} }
}; };

View File

@ -569,8 +569,6 @@ public:
BIND2(instance_set_layer_mask, RID, uint32_t) BIND2(instance_set_layer_mask, RID, uint32_t)
BIND3(instance_set_pivot_data, RID, float, bool) BIND3(instance_set_pivot_data, RID, float, bool)
BIND2(instance_set_transform, RID, const Transform &) 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) BIND2(instance_attach_object_instance_id, RID, ObjectID)
BIND3(instance_set_blend_shape_weight, RID, int, float) BIND3(instance_set_blend_shape_weight, RID, int, float)
BIND3(instance_set_surface_material, RID, int, RID) BIND3(instance_set_surface_material, RID, int, RID)

View File

@ -675,9 +675,6 @@ void RenderingServerScene::instance_set_scenario(RID p_instance, RID p_scenario)
_instance_destroy_occlusion_rep(instance); _instance_destroy_occlusion_rep(instance);
} }
// remove any interpolation data associated with the instance in this scenario
_interpolation_data.notify_free_instance(p_instance, *instance);
switch (instance->base_type) { switch (instance->base_type) {
case RS::INSTANCE_LIGHT: { case RS::INSTANCE_LIGHT: {
InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data); InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data);
@ -767,27 +764,6 @@ void RenderingServerScene::instance_set_pivot_data(RID p_instance, float p_sorti
instance->use_aabb_center = p_use_aabb_center; instance->use_aabb_center = p_use_aabb_center;
} }
void RenderingServerScene::instance_reset_physics_interpolation(RID p_instance) {
Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!instance);
if (_interpolation_data.interpolation_enabled && instance->interpolated) {
instance->transform_prev = instance->transform_curr;
instance->transform_checksum_prev = instance->transform_checksum_curr;
#ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION
print_line("instance_reset_physics_interpolation .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
#endif
}
}
void RenderingServerScene::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 RenderingServerScene::instance_set_transform(RID p_instance, const Transform &p_transform) { void RenderingServerScene::instance_set_transform(RID p_instance, const Transform &p_transform) {
Instance *instance = instance_owner.get(p_instance); Instance *instance = instance_owner.get(p_instance);
ERR_FAIL_COND(!instance); ERR_FAIL_COND(!instance);
@ -796,40 +772,8 @@ void RenderingServerScene::instance_set_transform(RID p_instance, const Transfor
print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames())); print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
#endif #endif
if (!(_interpolation_data.interpolation_enabled && instance->interpolated) || !instance->scenario) { if (instance->transform == p_transform) {
if (instance->transform == p_transform) { return; //must be checked to avoid worst evil
return; //must be checked to avoid worst evil
}
#ifdef DEBUG_ENABLED
for (int i = 0; i < 4; i++) {
const Vector3 &v = i < 3 ? p_transform.basis.rows[i] : p_transform.origin;
ERR_FAIL_COND(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 #ifdef DEBUG_ENABLED
@ -846,53 +790,10 @@ void RenderingServerScene::instance_set_transform(RID p_instance, const Transfor
#endif #endif
instance->transform_curr = p_transform; instance->transform = p_transform;
// keep checksums up to date
instance->transform_checksum_curr = new_checksum;
if (!instance->on_interpolate_transform_list) {
_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
instance->on_interpolate_transform_list = true;
} else {
DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size());
}
// If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated
// transform or anything else.
// Ideally we would not even call the RenderingServer::set_transform() when invisible but that would entail having logic
// to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise.
if (!instance->visible) {
return;
}
// decide on the interpolation method .. slerp if possible
instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
if (!instance->on_interpolate_list) {
_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
instance->on_interpolate_list = true;
} else {
DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size());
}
_instance_queue_update(instance, true); _instance_queue_update(instance, true);
} }
void RenderingServerScene::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);
}
void RenderingServerScene::update_interpolation_tick(bool p_process) { void RenderingServerScene::update_interpolation_tick(bool p_process) {
#ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION #ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION
print_line("update_interpolation_tick " + itos(Engine::get_singleton()->get_physics_frames())); print_line("update_interpolation_tick " + itos(Engine::get_singleton()->get_physics_frames()));
@ -900,84 +801,11 @@ void RenderingServerScene::update_interpolation_tick(bool p_process) {
// update interpolation in storage // update interpolation in storage
RSG::storage->update_interpolation_tick(p_process); RSG::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 < _interpolation_data.instance_transform_update_list_prev->size(); n++) {
const RID &rid = (*_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) {
_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 < _interpolation_data.instance_transform_update_list_curr->size(); n++) {
const RID &rid = (*_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(_interpolation_data.instance_transform_update_list_curr, _interpolation_data.instance_transform_update_list_prev);
// prepare for the next iteration
_interpolation_data.instance_transform_update_list_curr->clear();
} }
void RenderingServerScene::update_interpolation_frame(bool p_process) { void RenderingServerScene::update_interpolation_frame(bool p_process) {
// update interpolation in storage // update interpolation in storage
RSG::storage->update_interpolation_frame(p_process); RSG::storage->update_interpolation_frame(p_process);
if (p_process) {
real_t f = Engine::get_singleton()->get_physics_interpolation_fraction();
for (unsigned int i = 0; i < _interpolation_data.instance_interpolate_update_list.size(); i++) {
const RID &rid = _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);
#ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION
print_line("\t\tinterpolated: " + rtos(instance->transform.origin.x) + "\t( prev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x) + " ) on tick " + itos(Engine::get_singleton()->get_physics_frames()));
#endif
// make sure AABBs are constantly up to date through the interpolation
_instance_queue_update(instance, true);
}
} // for n
}
} }
void RenderingServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) { void RenderingServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
@ -1031,25 +859,6 @@ void RenderingServerScene::instance_set_visible(RID p_instance, bool p_visible)
instance->visible = p_visible; instance->visible = p_visible;
// Special case for physics interpolation, we want to ensure the interpolated data is up to date
if (_interpolation_data.interpolation_enabled && p_visible && instance->interpolated && instance->scenario && !instance->on_interpolate_list) {
// Do all the extra work we normally do on instance_set_transform(), because this is optimized out for hidden instances.
// This prevents a glitch of stale interpolation transform data when unhiding before the next physics tick.
instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
instance->on_interpolate_list = true;
_instance_queue_update(instance, true);
// We must also place on the transform update list for a tick, so the system
// can auto-detect if the instance is no longer moving, and remove from the interpolate lists again.
// If this step is ignored, an unmoving instance could remain on the interpolate lists indefinitely
// (or rather until the object is deleted) and cause unnecessary updates and drawcalls.
if (!instance->on_interpolate_transform_list) {
_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
instance->on_interpolate_transform_list = true;
}
}
// give the opportunity for the spatial partitioning scene to use a special implementation of visibility // give the opportunity for the spatial partitioning scene to use a special implementation of visibility
// for efficiency (supported in BVH but not octree) // for efficiency (supported in BVH but not octree)
@ -4398,8 +4207,6 @@ bool RenderingServerScene::free(RID p_rid) {
Instance *instance = instance_owner.get(p_rid); Instance *instance = instance_owner.get(p_rid);
_interpolation_data.notify_free_instance(p_rid, *instance);
instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1)); instance_set_use_lightmap(p_rid, RID(), RID(), -1, Rect2(0, 0, 1, 1));
instance_set_scenario(p_rid, RID()); instance_set_scenario(p_rid, RID());
instance_set_base(p_rid, RID()); instance_set_base(p_rid, RID());

View File

@ -398,12 +398,6 @@ public:
virtual void set_physics_interpolation_enabled(bool p_enabled); virtual void set_physics_interpolation_enabled(bool p_enabled);
struct InterpolationData { struct InterpolationData {
void notify_free_instance(RID p_rid, Instance &r_instance);
LocalVector<RID> instance_interpolate_update_list;
LocalVector<RID> instance_transform_update_lists[2];
LocalVector<RID> *instance_transform_update_list_curr = &instance_transform_update_lists[0];
LocalVector<RID> *instance_transform_update_list_prev = &instance_transform_update_lists[1];
bool interpolation_enabled = false; bool interpolation_enabled = false;
} _interpolation_data; } _interpolation_data;
@ -664,8 +658,6 @@ public:
virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask); virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask);
virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center); virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center);
virtual void instance_set_transform(RID p_instance, const Transform &p_transform); 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_attach_object_instance_id(RID p_instance, ObjectID p_id);
virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight); virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight);
virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material); virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material);

View File

@ -478,8 +478,6 @@ public:
FUNC2(instance_set_layer_mask, RID, uint32_t) FUNC2(instance_set_layer_mask, RID, uint32_t)
FUNC3(instance_set_pivot_data, RID, float, bool) FUNC3(instance_set_pivot_data, RID, float, bool)
FUNC2(instance_set_transform, RID, const Transform &) 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) FUNC2(instance_attach_object_instance_id, RID, ObjectID)
FUNC3(instance_set_blend_shape_weight, RID, int, float) FUNC3(instance_set_blend_shape_weight, RID, int, float)
FUNC3(instance_set_surface_material, RID, int, RID) FUNC3(instance_set_surface_material, RID, int, RID)

View File

@ -2168,8 +2168,6 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("instance_set_scenario", "instance", "scenario"), &RenderingServer::instance_set_scenario); ClassDB::bind_method(D_METHOD("instance_set_scenario", "instance", "scenario"), &RenderingServer::instance_set_scenario);
ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask); ClassDB::bind_method(D_METHOD("instance_set_layer_mask", "instance", "mask"), &RenderingServer::instance_set_layer_mask);
ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform); ClassDB::bind_method(D_METHOD("instance_set_transform", "instance", "transform"), &RenderingServer::instance_set_transform);
ClassDB::bind_method(D_METHOD("instance_set_interpolated", "instance", "interpolated"), &RenderingServer::instance_set_interpolated);
ClassDB::bind_method(D_METHOD("instance_reset_physics_interpolation", "instance"), &RenderingServer::instance_reset_physics_interpolation);
ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id); ClassDB::bind_method(D_METHOD("instance_attach_object_instance_id", "instance", "id"), &RenderingServer::instance_attach_object_instance_id);
ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight); ClassDB::bind_method(D_METHOD("instance_set_blend_shape_weight", "instance", "shape", "weight"), &RenderingServer::instance_set_blend_shape_weight);
ClassDB::bind_method(D_METHOD("instance_set_surface_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_material); ClassDB::bind_method(D_METHOD("instance_set_surface_material", "instance", "surface", "material"), &RenderingServer::instance_set_surface_material);
@ -2187,7 +2185,9 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("instances_cull_aabb", "aabb", "scenario"), &RenderingServer::_instances_cull_aabb_bind, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("instances_cull_aabb", "aabb", "scenario"), &RenderingServer::_instances_cull_aabb_bind, DEFVAL(RID()));
ClassDB::bind_method(D_METHOD("instances_cull_ray", "from", "to", "scenario"), &RenderingServer::_instances_cull_ray_bind, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("instances_cull_ray", "from", "to", "scenario"), &RenderingServer::_instances_cull_ray_bind, DEFVAL(RID()));
ClassDB::bind_method(D_METHOD("instances_cull_convex", "convex", "scenario"), &RenderingServer::_instances_cull_convex_bind, DEFVAL(RID())); ClassDB::bind_method(D_METHOD("instances_cull_convex", "convex", "scenario"), &RenderingServer::_instances_cull_convex_bind, DEFVAL(RID()));
#endif #endif
ClassDB::bind_method(D_METHOD("canvas_create"), &RenderingServer::canvas_create); ClassDB::bind_method(D_METHOD("canvas_create"), &RenderingServer::canvas_create);
ClassDB::bind_method(D_METHOD("canvas_set_item_mirroring", "canvas", "item", "mirroring"), &RenderingServer::canvas_set_item_mirroring); ClassDB::bind_method(D_METHOD("canvas_set_item_mirroring", "canvas", "item", "mirroring"), &RenderingServer::canvas_set_item_mirroring);
ClassDB::bind_method(D_METHOD("canvas_set_modulate", "canvas", "color"), &RenderingServer::canvas_set_modulate); ClassDB::bind_method(D_METHOD("canvas_set_modulate", "canvas", "color"), &RenderingServer::canvas_set_modulate);

View File

@ -876,8 +876,6 @@ public:
virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0; virtual void instance_set_layer_mask(RID p_instance, uint32_t p_mask) = 0;
virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0; virtual void instance_set_pivot_data(RID p_instance, float p_sorting_offset, bool p_use_aabb_center) = 0;
virtual void instance_set_transform(RID p_instance, const Transform &p_transform) = 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_attach_object_instance_id(RID p_instance, ObjectID p_id) = 0;
virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0; virtual void instance_set_blend_shape_weight(RID p_instance, int p_shape, float p_weight) = 0;
virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0; virtual void instance_set_surface_material(RID p_instance, int p_surface, RID p_material) = 0;