Force Rework#770
Merged
Merged
Conversation
…tedForces` component
We use a `ForceHelper` system param again, this time exposing a faster and more capable API that only does one `get_mut` query per entity. Local forces and acceleration are also supported!
…stantLocalTorque`
This was referenced Jul 17, 2025
Merged
Jondolf
added a commit
that referenced
this pull request
Jul 17, 2025
Jondolf
added a commit
that referenced
this pull request
Jul 18, 2025
# Objective Whoops, #770 broke kinematic bodies! Gravity and external forces now affect them 😬 ## Solution Skip kinematic bodies during velocity integration.
Jondolf
added a commit
that referenced
this pull request
Jul 19, 2025
# Objective #770 added a `Forces` helper `QueryData` for applying forces, impulses, and accelerations to dynamic bodies. The intent was that forces would wake up bodies by default, but this was actually not the case, as `Forces` has some components that sleeping bodies don't. However, we should *also* have a way to allow forces not to wake up bodies. This can be important for optimization in some cases. ## Solution Rework `Forces` to wake up bodies by default when applying non-zero forces, impulses, or accelerations. Additionally, add a `ForcesItem::non_waking` method that returns a `NonWakingForcesItem`. The two force types share a `RigidBodyForces` trait and the same API, but the former wakes up bodies while the latter doesn't. The API looks like this: ```rust // This does not wake up the body. forces.non_waking().apply_force(force); // This wakes up the body if it is sleeping. forces.apply_force(force); ``` Many existing physics engines instead take a boolean argument in all force methods to control whether the force should wake the body up. However, I am of the opinion that sleeping should primarily be transparent to the user and just an internal optimization, and that intuitively you would expect a force or impulse to *always* have an effect on a body by default. Disabling waking for a force should be a more advanced use case, so it should not affect the common API. Game engines like Unity and Godot actually seem to always wake up bodies for non-zero forces, with no option to configure this. I took the middle route, defaulting to waking up, but having an additional `non_waking` method to opt in to the non-waking behavior. I considered some component-driven approaches like a `ForceWakingMode` component, but I think this needs to be easily configurable per force and not a persisted per-entity setting.
|
This is awesome! I had tried making a Mario Galaxy-esq platformer before and it was quite tough to get it going, but this seems much easier to use! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Objective
The current force APIs are very limited, confusing, and cumbersome to use.
ExternalForcealso needs to store a torque, just because ofapply_force_at_point, duplicating data.ExternalForce,ExternalTorque,ExternalImpulse, andExternalAngularImpulseare all required components for rigid bodies, wasting memory.It seems like we need to redesign our force APIs from the ground up. Some goals for the new design are:
Rigidbody.AddForcein Unity, or similar methods in other engines.To accomplish these goals efficiently, we also need to rework the solver's integration logic. Thus, a secondary goal of this PR is to rework and optimize velocity and position integration.
Solution
Replace
ExternalForce,ExternalTorque,ExternalImpulse, andExternalAngularImpulsewith two types of APIs:ForceshelperQueryDataConstant Forces
Constant forces and torques that persist across time steps can be applied using the following components:
ConstantForce: Applies a constant force in world space.ConstantTorque: Applies a constant torque in world space.ConstantLinearAcceleration: Applies a constant linear acceleration in world space.ConstantAngularAcceleration: Applies a constant angular acceleration in world space.They also have local space equivalents:
ConstantLocalForce: Applies a constant force in local space.ConstantLocalTorque: Applies a constant torque in local space.ConstantLocalLinearAcceleration: Applies a constant linear acceleration in local space.ConstantLocalAngularAcceleration: Applies a constant angular acceleration in local space.These components are useful for simulating continuously applied forces that are expected to remain the same across time steps, such as per-body gravity or force fields.
The forces are only constant in the sense that they persist across time steps. They can still be modified in systems like normal.
ForcesIt is common to apply many individual forces and impulses to dynamic rigid bodies, and to clear them afterwards. This can be done using the
ForceshelperQueryData.To use
Forces, add it to aQuery(without&or&mut), and use the associated methods to apply forces, impulses, and accelerations to the rigid bodies.The force is applied continuously during the physics step, and cleared automatically after the step is complete.
Forcescan also apply forces and impulses at a specific point in the world. If the point is not aligned with theGlobalCenterOfMass, it will also apply a torque to the body.As an example, you could implement radial gravity that pulls rigid bodies towards the world origin with a system like the following:
Integration Rework
Currently, the solver runs velocity integration at each substep, applying forces, torques, and damping each time. This involves some math, transformations, and branching at each substep.
However, the velocity increment produced by external forces and torque is actually constant across substeps (excluding changes in the 3D inertia tensor for torque). The same is true for the right hand-side of the velocity damping formula. This means that we can actually precompute these values before the substepping loop, and apply the increments and damping with basic addition and multiplication, without any branching. Sweet! (Note that Rapier also does this.)
On the surface, it still seems a bit annoying to store an additional 32 bytes (or 20 bytes in 2D) per
SolverBodyfor this, since the computations are still quite cheap, and integration is far from a bottleneck for us. However, these cached velocity increments can actually serve a secondary objective: we can accumulate forces and acceleration applied viaForcesto the increments! This means that we don't need to repeat a bunch of computations for each type of force, and can instead funnel it all into a single optimized output.Note that we do still need to handle local accelerations separately, because they can change as the body rotates between substeps.
Other Changes
GlobalCenterOfMasscomponent for the global center of mass (used when applying forces at a world point, and in some other places)SolverBodyFlagsTorquetype alias to more generalAngularVectornamePerformance
In a 3D scene with 10k dynamic bodies without colliders, just flying in the air, with 10 substeps, we can observe a small but noticeable performance improvement. Before and after:
However the timers here are very unstable for some reason, and seem to change quite a lot between runs, so take the results with a grain of salt. The main thing is that this at least shouldn't regress performance.
Migration Guide
Force Rework
Avian's force APIs have been overhauled, and
ExternalForce,ExternalTorque,ExternalImpulse, andExternalAngularImpulsehave been removed.ConstantForceandConstantTorquecomponents.ForceshelperQueryData.The new
ForcePluginmust be enabled for forces to function properly. It is included inPhysicsPluginsby default.