Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RigidBody2D does not clear forces at end of integration, RigidBody does #38646

Closed
Tracked by #45334
latencyhiding opened this issue May 10, 2020 · 10 comments · Fixed by #55736
Closed
Tracked by #45334

RigidBody2D does not clear forces at end of integration, RigidBody does #38646

latencyhiding opened this issue May 10, 2020 · 10 comments · Fixed by #55736
Assignees
Milestone

Comments

@latencyhiding
Copy link

latencyhiding commented May 10, 2020

Godot version:

3.2.1

OS/device including version:

Windows 10

Issue description:

RigidBody2D does not appear to clear accumulated forces at the end of integration in the same way RigidBody appears to do.

This results in calls to add_force in scripts to accumulate forces over multiple physics steps, which I feel might be unintuitive for new users since:

  • The behavior is inconsistent between 3D and 2D
  • In Box2D the forces are cleared at the end of the step by default (appears to be the case in Unity as well), users coming from frameworks like this may be confused
  • There's no option to enable or disable this behavior, or any documentation to expose it.

Not actually sure if this is a bug or if it's intended this way. If it was intended, it would be good to document it.

Steps to reproduce:

Call add_force on any RigidBody2D in a script in _physics_process.

Minimal reproduction project:

PhysicsTest.zip

@aaronfranke
Copy link
Member

In the attached reproduction project, nothing happens when I run the 2D scene. I'm not sure which behavior is desired, but in addition to an inconsistency between 2D and 3D, this is inconsistent between Bullet and Godot physics in 3D.

@latencyhiding
Copy link
Author

latencyhiding commented May 11, 2020

@aaronfranke sorry, you move one of the nodes (Player) with wasd and the other (Magnet) has a force applied to it to follow if it is certain distance away from Player. Same with the 3D scene.

If you replace the apply_central_impulse call in the Magnet script with add_central_force, Magnet flies off the screen because the forces are accumulating on top of the forces from the previous frames.

My opinion is that the behavior should be consistent within Godot, whether it's 2D or 3D, or to make it configurable with the same defaults.

Also fwiw I think clearing forces at the end of integration is probably the most intuitive for common use case.

@madmiraal
Copy link
Contributor

add_force(), add_central_force() and add_torque() are accumulative and are not cleared both in 2D and 3D. From the documentation: They are used to add a "constant directional force (i.e. acceleration)". Instead linear_damp and angular_damp are used to slowly decrease these values over time.
apply_impulse(), apply_central_impulse() and apply_torque_impulse() in both 2D and 3D are used to simulate one-off impacts.
Your minimal reproduction project doesn't show the difference between 2D and 3D because the 3D project uses add_central_force() and the 2D project uses apply_central_impulse(); so the different behaviour is to be expected.

@madmiraal
Copy link
Contributor

madmiraal commented May 11, 2020

Regarding the difference between Godot and Bullet physics 3D: The issue is to do with the RigidBody pushing the KinematicBody in Godot physics, which it shouldn't be able to do. Without looking into it, I suspect it is related to the issues with KinematicBody's test_body_motion() called by move_and_collide() that I've discussed here.

@latencyhiding
Copy link
Author

latencyhiding commented May 11, 2020

add_force(), add_central_force() and add_torque() are accumulative and are not cleared both in 2D and 3D. From the documentation: They are used to add a "constant directional force (i.e. acceleration)". Instead linear_damp and angular_damp are used to slowly decrease these values over time.

In 3D the applied_force is cleared at the end of integration. The equivalent line of code is not there for 2D, unless I'm missing something.

In both 2D and 3D, the damping values only seem to decrease velocity over time (3D, 2D), not applied_force, unless again I'm missing something?

In the phrase "constant directional force" I take constant to mean that the magnitude of the force is constant, not that it will be applied persistently.

Fwiw analogous calls in other frameworks don't exhibit this behavior by default, so it feels like a reasonable assumption it wouldn't here too, especially since it doesn't in 3D RigidBodies.

Your minimal reproduction project doesn't show the difference between 2D and 3D because the 3D project uses add_central_force() and the 2D project uses apply_central_impulse(); so the different behaviour is to be expected.

I'll upload another test project today.

@madmiraal
Copy link
Contributor

In 3D the applied_force is cleared at the end of integration. The equivalent line of code is not there for 2D, unless I'm missing something.

You're right. I had not noticed that before.

In the phrase "constant directional force" I take constant to mean that the magnitude of the force is constant, not that it will be applied persistently.

The question then is, what is the difference between add_force() and apply_impulse() (other than the units). In other words, is the problem that RigidBody2D is not clearing the applied_force or that RigidBody3D is?

I'll upload another test project today.

I've created the following minimal project to demonstrate the difference:
38646.zip

Both implement the following functions (except under 2D the vector is Vector2(10,0)):

var previous_velocity
func _ready():
	previous_velocity = linear_velocity
	add_central_force(Vector3(1,0,0))
func _process(delta):
	#add_central_force(Vector3(1,0,0))
	#apply_central_impulse(Vector3(1,0,0)*delta)
	print("Acceleration = " + str((linear_velocity - previous_velocity)/delta))
	previous_velocity = linear_velocity

Under 2D using add_central_force() is sufficient to continually accelerate the RigidBody. Under 3D it has not effect (the body stays still). Under both 2D and 3D using apply_central_impulse() continually in _process() (or _physics_process()) has the same effect: it provides a constant force on the RigidBody. Under 3D using add_central_force() continually in _process() has the same effect as apply_central_impulse(). Under 2D it continually increases the acceleration.

@mini-glitch
Copy link

The issue I see is that in the 2D physics applied_force is added in its entirety during the integration step, but is never reduced or cleared, so it will continue to accumulate indefinitely. The purpose of apply_impulse is to cause an immediate, large change to the velocity, where add_force applies a continuous change over time by being called during each _physics_process(). In the documentation it's explicitly stated that apply_impulse should not be used each frame to apply a continuous force, since its change is immediate and framerate dependent, where applied_force is modified by the step delta during integration.

Another way to look at it is that add_force should be used each physics step where it is affected by the physics delta in much the same way as moving a Node2D over time by multiplying a velocity vector by delta time. The current implementation, however, does not do this, as calling add_force each physics step will cause the rigidbody's velocity to increase exponentially.

This is how other physics engines like Box2D handle this, and how Godot's 3D physics handles this, so I believe the issue is Godot's 2D physics not clearing applied_force after each integration. Making this change would be considered breaking, but as long as it's documented I think 4.0 is the perfect time to make such a change.

@name-here
Copy link

The docs could probably also use another sentence or two on add_force() clarifying that it should/shouldn't be called every frame, since it's pretty ambiguous as it is now. Should I open an issue on godot-docs referencing this one?

@Calinou
Copy link
Member

Calinou commented May 15, 2020

@name-here Better, you could contribute to the class reference directly 🙂

@lukaszbulzacki
Copy link

lukaszbulzacki commented Mar 20, 2021

I have different question which my clear/help with this discussion.

If RigidBody2D.apply_* methods are "constant" what is proper way to update force every frame (in _process/_physics_process) for RigidBody2D?

I always done it in the past as follows (and it worked)

func _physics_process(delta):
   _rigid_body2d.add_central_force( _force_vec2 * delta)

Also there are ambiguities/inconsistencies in docs between add_force & add_central_force, apply_impulse & apply_central_impulse. But I always assumed add_ & add_central_ are internally the same and of course apply_ & apply_central_ are also internally the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment