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
parent 766b02c9cd
commit 66a2efe491
25 changed files with 599 additions and 424 deletions

View File

@@ -673,9 +673,6 @@ 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
_interpolation_data.notify_free_instance(p_instance, *instance);
switch (instance->base_type) {
case VS::INSTANCE_LIGHT: {
InstanceLightData *light = static_cast<InstanceLightData *>(instance->base_data);
@@ -765,27 +762,6 @@ void VisualServerScene::instance_set_pivot_data(RID p_instance, float p_sorting_
instance->use_aabb_center = p_use_aabb_center;
}
void VisualServerScene::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 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);
@@ -794,47 +770,8 @@ void VisualServerScene::instance_set_transform(RID p_instance, const Transform &
print_line("instance_set_transform " + rtos(p_transform.origin.x) + " .. tick " + itos(Engine::get_singleton()->get_physics_frames()));
#endif
if (!(_interpolation_data.interpolation_enabled && instance->interpolated) || !instance->scenario) {
if (instance->transform == p_transform) {
return; //must be checked to avoid worst evil
}
#ifdef DEBUG_ENABLED
for (int i = 0; i < 4; i++) {
const Vector3 &v = i < 3 ? p_transform.basis.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);
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
if ((_interpolation_data.interpolation_enabled && !instance->interpolated) && (Engine::get_singleton()->is_in_physics_frame())) {
PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Non-interpolated triggered from physics process");
}
#endif
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;
if (instance->transform == p_transform) {
return; //must be checked to avoid worst evil
}
#ifdef DEBUG_ENABLED
@@ -850,62 +787,8 @@ void VisualServerScene::instance_set_transform(RID p_instance, const Transform &
}
#endif
instance->transform_curr = p_transform;
#ifdef VISUAL_SERVER_DEBUG_PHYSICS_INTERPOLATION
print_line("\tprev " + rtos(instance->transform_prev.origin.x) + ", curr " + rtos(instance->transform_curr.origin.x));
#endif
// keep checksums up to date
instance->transform_checksum_curr = new_checksum;
if (!instance->on_interpolate_transform_list) {
_interpolation_data.instance_transform_update_list_curr->push_back(p_instance);
instance->on_interpolate_transform_list = true;
} else {
DEV_ASSERT(_interpolation_data.instance_transform_update_list_curr->size());
}
// If the instance is invisible, then we are simply updating the data flow, there is no need to calculate the interpolated
// transform or anything else.
// Ideally we would not even call the VisualServer::set_transform() when invisible but that would entail having logic
// to keep track of the previous transform on the SceneTree side. The "early out" below is less efficient but a lot cleaner codewise.
if (!instance->visible) {
return;
}
// decide on the interpolation method .. slerp if possible
instance->interpolation_method = TransformInterpolator::find_method(instance->transform_prev.basis, instance->transform_curr.basis);
if (!instance->on_interpolate_list) {
_interpolation_data.instance_interpolate_update_list.push_back(p_instance);
instance->on_interpolate_list = true;
} else {
DEV_ASSERT(_interpolation_data.instance_interpolate_update_list.size());
}
instance->transform = p_transform;
_instance_queue_update(instance, true);
#if defined(DEBUG_ENABLED) && defined(TOOLS_ENABLED)
if (!Engine::get_singleton()->is_in_physics_frame()) {
PHYSICS_INTERPOLATION_NODE_WARNING(instance->object_id, "Interpolated triggered from outside physics process");
}
#endif
}
void VisualServerScene::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 VisualServerScene::update_interpolation_tick(bool p_process) {
@@ -915,84 +798,11 @@ void VisualServerScene::update_interpolation_tick(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 < _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 VisualServerScene::update_interpolation_frame(bool p_process) {
// update interpolation in storage
VSG::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 VisualServerScene::instance_attach_object_instance_id(RID p_instance, ObjectID p_id) {
@@ -1046,25 +856,6 @@ void VisualServerScene::instance_set_visible(RID p_instance, bool 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
// for efficiency (supported in BVH but not octree)
@@ -4496,7 +4287,6 @@ bool VisualServerScene::free(RID p_rid) {
update_dirty_instances();
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_scenario(p_rid, RID());