Skip to content

[3.x] Physics Interpolation - Add 3D helper for using servers directly.#104518

Closed
lawnjelly wants to merge 1 commit intogodotengine:3.xfrom
lawnjelly:fti_instance_helper
Closed

[3.x] Physics Interpolation - Add 3D helper for using servers directly.#104518
lawnjelly wants to merge 1 commit intogodotengine:3.xfrom
lawnjelly:fti_instance_helper

Conversation

@lawnjelly
Copy link
Copy Markdown
Member

@lawnjelly lawnjelly commented Mar 23, 2025

#103685 by design removes server side physics interpolation, which has the side effect of removing in-built physics interpolation for users who create 3D nodes directly using servers, rather than via SceneTree nodes, and ideally we don't want these users to feel they are "losing out".

This use case is far simpler than SceneTree interpolation, because only global xforms need be supported.

There are several options to deal with this area, including:

  1. Provide no engine support and leave it to users (as was the case before physics interpolation PRs)
  2. Provide support via an addon
  3. Provide a simple helper in engine code to make this task much easier

This PR provides a simple solution for (3) for instances on the server, via a wrapper object called fti_instance.
It should be fairly simple to use, although admittedly there are a couple more lines to type than the old system.

The fti_instance is created in VisualServer like an instance, but must be linked to an instance via the prepare() call.
Then the user should call fti_set_transform() on the fti_instance instead of instance_set_transform(), and can call fti_instance_reset() to reset physics interpolation.

Everything else should work automagically. Nearly everything in 3D works via instances so this should cover most cases. Cameras are not covered and would have to be interpolated manually, but as far as I remember this was also the case before #103685.

Model at 5 ticks per second with FTI

2025-04-03.09-27-22.mp4

Usage Example

# VisualServer expects references to be kept around.
var _mesh
var _instance
var _fti_instance
var _time = 0

func _enter_tree():
	# Create a visual instance (for 3D).
	_instance = VisualServer.instance_create()
	
	# Set the scenario from the world, this ensures it
	# appears with the same objects as the scene.
	var scenario = get_world().scenario
	VisualServer.instance_set_scenario(_instance, scenario)
	
	# Add a mesh to it.
	# Remember, keep the reference.
	_mesh = load("res://Model/scarab.obj")
	VisualServer.instance_set_base(_instance, _mesh)
	
	# Create an FTI instance and link it to the actual instance,
	# so we can use the interpolation functionality.
	_fti_instance = VisualServer.fti_instance_create()
	VisualServer.fti_instance_prepare(_fti_instance, _instance)
	
func _exit_tree():
	# Make sure to free rids when we are finished,
	# to prevent memory leaks.
	VisualServer.free_rid(_fti_instance)
	_fti_instance = RID()
	
	VisualServer.free_rid(_instance)
	_instance = RID()

func _physics_process(delta):
	_time += delta

	# Move the model around.
	VisualServer.fti_instance_set_transform(_fti_instance, Transform(Basis(), Vector3(sin(_time) * 4, 0, 0)))
	
	# Test resetting.
	if Input.is_action_just_pressed("ui_accept"):
		VisualServer.fti_instance_reset(_fti_instance)

Notes

  • This also applies in 4.x so a port should also be made for Physics Interpolation - Move 3D FTI to SceneTree #104269 if we merge the main PRs.
  • I did test making the multithreaded versions of the functions just act single threaded and push the wrapped commands directly on the queue, but I don't think this should be necessary as the queue can add commands to the tail while being processed, and I don't see any obvious reason the ordering should matter. If there are any problems in the wild though it's fairly easy to change (and it won't cause anything catastrophic, worst case some jitter in multithread mode).
  • There are admittedly several ways of doing this, and I'm happy to look at alternative approaches (especially if we can reduce boiler plate further for users), this is just the first that popped into my head.

@rburing
Copy link
Copy Markdown
Member

rburing commented Aug 16, 2025

Here's a naive idea I had to reduce boilerplate for users: use the same RID for the visual instance and the FTI instance, and refer to both as an "instance with FTI". The RID will refer to the visual instance, so the existing instance_ methods will work, and extra instance_with_fti_ methods access the extra FTI data that is stored separately. You would have:

  • instance_with_fti_create
  • instance_with_fti_set_transform
  • instance_with_fti_reset

This sounds like a pretty nice API to me but the implementation wouldn't work the same as the current one with id(), so maybe it would be less efficient. Also maybe it would add overhead to free_rid for instances, but this could be worked around by adding an extra method like instance_with_fti_clear that would be required to call before free_rid.

Do you think there's a way to make something like this work?

@lawnjelly
Copy link
Copy Markdown
Member Author

lawnjelly commented Aug 16, 2025

Do you think there's a way to make something like this work?

I did initially look at this but there are a couple of restrictions:

  • There are already quite a few existing instance API functions, and we want to avoid duplicating these
  • Ideally we don't want to introduce any new code in the existing instance, both in terms of slowing down, and also in terms of making the code more complex for instances, when we are fixing up a rare use case
  • Minimal maintenance is good

I'll try and have another look at this.

@lawnjelly
Copy link
Copy Markdown
Member Author

lawnjelly commented Oct 24, 2025

Ok I've thought of a alternative which may be better from a user perspective (not sure) .. so opinions welcome.

The issue is the lifetime of both the fti_instance and the instance need to be managed. In the first go (above) they are managed independently by the user.

An alternative may be when creating an fti_instance it automatically creates an instance in the background for you, and deletes it when you delete fti_instance.

There are two gotchas as a result:

  • you would need to call a function specifically to retrieve the instance RID from the fti_instance (e.g. fti_instance_get_instance .. which in itself is rather confusing 😀 ), so you could then perform regular instance calls...
  • you would have to be sure not to free the instance that is returned above, otherwise you would get a double delete.

Let me know if this is preferred. Perhaps I can create this as a separate PR to make it easier to see.

@lawnjelly
Copy link
Copy Markdown
Member Author

Closing for now in favour of #112119.

This can be re-opened if desired, the new PR mentioned handles more cases (especially with FTI off) and should be easier in terms of use.

@lawnjelly lawnjelly closed this Oct 28, 2025
@AThousandShips AThousandShips removed this from the 3.7 milestone Oct 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants