Skip to content

Commit 081d2de

Browse files
authored
Fix init_physics_transform running twice and breaking scene hierarchies (#873)
# Objective The `init_physics_transform` hook can fail for scenes, because hierarchies may not be finalized in hooks (see bevyengine/bevy#18671). This normally doesn't actually cause problems, but in a very specific case where an entity has both a `RigidBody` and `Collider` (causing `init_physics_transform` to be called twice) in a hierarchy, and the insertion of one of the components is deferred such that the hierarchy *is* finalized for that one but not the other, the computed global transforms will end up being wrong. Yeah. ## Solution For now, just avoid calling `init_physics_transform` twice by checking if the entity has a `RigidBody` for colliders. Additionally, only run `transform_to_position` logic for `Position` and `Rotation` if they were not just added, to avoid using the invalid values. Propagation will fix things up later. ## Testing The [`physics_avian`](https://github.com/Noxmore/bevy_trenchbroom/tree/main/example/physics_avian) example from bevy_trenchbroom, adding a `RigidBody` for the brushes.
1 parent 0571869 commit 081d2de

File tree

2 files changed

+58
-50
lines changed

2 files changed

+58
-50
lines changed

src/collision/collider/backend.rs

Lines changed: 46 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -108,47 +108,53 @@ impl<C: ScalableCollider> Plugin for ColliderBackendPlugin<C> {
108108
let hooks = app.world_mut().register_component_hooks::<C>();
109109

110110
// Initialize missing components for colliders.
111-
hooks.on_add(|mut world, ctx| {
112-
// Initialize the global physics transform for the collider.
113-
init_physics_transform(&mut world, &ctx);
114-
115-
let scale = world
116-
.entity(ctx.entity)
117-
.get::<GlobalTransform>()
118-
.map(|gt| gt.scale())
119-
.unwrap_or_default();
120-
#[cfg(feature = "2d")]
121-
let scale = scale.xy();
122-
123-
let mut entity_mut = world.entity_mut(ctx.entity);
124-
125-
// Make sure the collider is initialized with the correct scale.
126-
// This overwrites the scale set by the constructor, but that one is
127-
// meant to be only changed after initialization.
128-
entity_mut
129-
.get_mut::<C>()
130-
.unwrap()
131-
.set_scale(scale.adjust_precision(), 10);
132-
133-
let collider = entity_mut.get::<C>().unwrap();
134-
135-
let density = entity_mut
136-
.get::<ColliderDensity>()
137-
.copied()
138-
.unwrap_or_default();
139-
140-
let mass_properties = if entity_mut.get::<Sensor>().is_some() {
141-
MassProperties::ZERO
142-
} else {
143-
collider.mass_properties(density.0)
144-
};
111+
hooks
112+
.on_add(|mut world, ctx| {
113+
// Initialize the global physics transform for the collider.
114+
// Avoid doing this twice for rigid bodies added at the same time.
115+
// TODO: The special case for rigid bodies is a bit of a hack here.
116+
if !world.entity(ctx.entity).contains::<RigidBody>() {
117+
init_physics_transform(&mut world, &ctx);
118+
}
119+
})
120+
.on_insert(|mut world, ctx| {
121+
let scale = world
122+
.entity(ctx.entity)
123+
.get::<GlobalTransform>()
124+
.map(|gt| gt.scale())
125+
.unwrap_or_default();
126+
#[cfg(feature = "2d")]
127+
let scale = scale.xy();
128+
129+
let mut entity_mut = world.entity_mut(ctx.entity);
130+
131+
// Make sure the collider is initialized with the correct scale.
132+
// This overwrites the scale set by the constructor, but that one is
133+
// meant to be only changed after initialization.
134+
entity_mut
135+
.get_mut::<C>()
136+
.unwrap()
137+
.set_scale(scale.adjust_precision(), 10);
138+
139+
let collider = entity_mut.get::<C>().unwrap();
140+
141+
let density = entity_mut
142+
.get::<ColliderDensity>()
143+
.copied()
144+
.unwrap_or_default();
145+
146+
let mass_properties = if entity_mut.get::<Sensor>().is_some() {
147+
MassProperties::ZERO
148+
} else {
149+
collider.mass_properties(density.0)
150+
};
145151

146-
if let Some(mut collider_mass_properties) =
147-
entity_mut.get_mut::<ColliderMassProperties>()
148-
{
149-
*collider_mass_properties = ColliderMassProperties::from(mass_properties);
150-
}
151-
});
152+
if let Some(mut collider_mass_properties) =
153+
entity_mut.get_mut::<ColliderMassProperties>()
154+
{
155+
*collider_mass_properties = ColliderMassProperties::from(mass_properties);
156+
}
157+
});
152158

153159
// Register a component hook that removes `ColliderMarker` components
154160
// and updates rigid bodies when their collider is removed.

src/physics_transform/mod.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -207,20 +207,22 @@ pub fn transform_to_position(
207207
let transform_translation = global_transform.translation.adjust_precision();
208208
let transform_rotation = Rotation::from(global_transform.rotation.adjust_precision());
209209

210-
let position_changed = is_changed_after_tick(
211-
Ref::from(position.reborrow()),
212-
last_physics_tick.0,
213-
this_run,
214-
);
210+
let position_changed = !position.is_added()
211+
&& is_changed_after_tick(
212+
Ref::from(position.reborrow()),
213+
last_physics_tick.0,
214+
this_run,
215+
);
215216
if !position_changed && position.abs_diff_ne(&transform_translation, distance_tolerance) {
216217
position.0 = transform_translation;
217218
}
218219

219-
let rotation_changed = is_changed_after_tick(
220-
Ref::from(rotation.reborrow()),
221-
last_physics_tick.0,
222-
this_run,
223-
);
220+
let rotation_changed = !rotation.is_added()
221+
&& is_changed_after_tick(
222+
Ref::from(rotation.reborrow()),
223+
last_physics_tick.0,
224+
this_run,
225+
);
224226
if !rotation_changed
225227
&& rotation.angle_between(transform_rotation).abs() > rotation_tolerance
226228
{

0 commit comments

Comments
 (0)