Weird apply_force() behavior with local coordinates\

Godot Version

v4.7.stable.official [5b4e0cb0f]

Question

Hello everyone! I stumbled upon an interesting bug and I want to know why this happens, and if there is a better fix.
So normally, when using apply_force() function, the force position must be in local coordinates of the RigidBody3D, that’s nice and easy. However, things became somewhat weird with local coordinates when I made a script for moving RigidBody3Ds with mouse.

The script simply grabs a rigidbody from the position you click on it, then moves it towards the mouse cursor by using apply_force() every physics frame. Here’s the breakdown of how I’m doing it, and where it goes weird.

  1. I cast a ray from the camera in order to get the position.
if event is InputEventMouseButton and event.pressed and event.button_index == 1:
		## Cast a ray from the screen to detect what is being clicked on.
		var from = camera.project_ray_origin(event.position)
		var to = from + camera.project_ray_normal(event.position) * raycast_length
		var raycast_data = Global.cast_ray(camera, from, to, 1)
		## If the clicked object is a RigidBody, set necessary variables.
		if raycast_data.has("collider") and raycast_data["collider"] is RigidBody3D:
			rigid_body = raycast_data["collider"]
			force_position = raycast_data["position"]
			localized_force_position = rigid_body.to_local(force_position)

Raycast returns a global coordinate with ["position’] key, I then convert it to RigidBody3D’s local coordinates and store it so the point you grab the RigidBody doesn’t change even if it moves.

  1. I then convert that local coordinate back to global in order to compare it to the mouse position and apply the necessary force to move the RigidBody towards the mouse.
#### This code runs in _physics_process() ####
if rigid_body:
		var grab_point: Vector3 = rigid_body.to_global(localized_force_position)
        ## This time, instead of casting a ray, I'm just using the camera.project_ray_normal() function
        ## to have a fixed position from camera. It works well, no bugs here.
		var from = camera.project_ray_origin(mouse_position)
		var to = from + camera.project_ray_normal(mouse_position) * pick_distance

		var force: Vector3 = grab_point.direction_to(to) * grab_point.distance_to(to) * spring_stiffness * rigid_body.mass

		## RigidBody3D.apply_force() takes in local vector values, but somehow
		## you got to rotate it too when applying force to a rigidbody from a different node.
		## Weird but this is the current fix I could come up with.
		var rotated_position: Vector3 = localized_force_position.rotated(rigid_body.global_basis.y, rigid_body.rotation.y)
		
var velocity: Vector3 = Global.get_point_velocity(rigid_body, localized_force_position)
		var damping_force: Vector3 = rigid_body.linear_velocity * rigid_body.mass * damping
		
        rigid_body.apply_force(force - damping_force , rotated_position)

Now here is where it gets weird. In order for apply_force() to get the correct position, I got to rotate the local coordinate (var rotated_position). If I dont rotate the localized_position variable based on the RigidBody3Ds current rotation, local coordinate behaves as if the RigidBody3D is always facing the -z axis.

Let me show it with a video.

I’m curious about the reason behind this behaviour. I may be doing something wrong, and I’d like to know the reason. Using apply_force() never caused an issue like this before for me, and I always used it for my custom car physics without any issues.

Docs say otherwise:

position is the offset from the body origin in global coordinates.

Thanks a lot for the reply!

Is that why rotating the local coordinates fixes the force position issue? When I do that, I convert the local coordinate to a global offset from the origin.

Found this post on reddit and it helped me understand it better for anyone coming to this post in the future. https://www.reddit.com/r/godot/comments/1ia7mc0/proper_usage_of_apply_force_in_rigidbody3d/

I’m silly :laughing: Now it works flawlessly. Rather than rotating the local vector, I’m just attaching a Node3D to the position and then do (Node3D.global_position - rigid_body.global_position) for getting the correct position argument.