diff --git a/src/collision/collider/backend.rs b/src/collision/collider/backend.rs index 58547062..a6cedb39 100644 --- a/src/collision/collider/backend.rs +++ b/src/collision/collider/backend.rs @@ -4,7 +4,7 @@ use std::marker::PhantomData; -use crate::{broad_phase::BroadPhaseSet, prelude::*, prepare::PrepareSet}; +use crate::{broad_phase::BroadPhaseSet, prelude::*, prepare::PrepareSet, sync::SyncConfig}; #[cfg(all(feature = "bevy_scene", feature = "default-collider"))] use bevy::scene::SceneInstance; use bevy::{ @@ -95,9 +95,53 @@ impl Plugin for ColliderBackendPlugin { // Initialize missing components for colliders. hooks.on_add(|mut world, entity, _| { - let entity_ref = world.entity(entity); + let existing_global_transform = world + .entity(entity) + .get::() + .copied() + .unwrap_or_default(); + let global_transform = if existing_global_transform != GlobalTransform::IDENTITY { + // This collider was built deferred, probably via `ColliderConstructor`. + existing_global_transform + } else { + // This collider *may* have been `spawn`ed directly on a new entity. + // As such, its global transform is not yet available. + // You may notice that this will fail if the hierarchy's scale was updated in this + // frame. Remember that `GlobalTransform` is not updated in between fixed updates. + // But this is fine, as `update_collider_scale` will be updated in the next fixed update anyway. + // The reason why we care about initializing this scale here is for those users that opted out of + // `update_collider_scale` in order to do their own interpolation, which implies that they won't touch + // the `Transform` component before the collider is initialized, which in turn means that it will + // always be initialized with the correct `GlobalTransform`. + let parent_global_transform = world + .entity(entity) + .get::() + .and_then(|parent| world.entity(parent.get()).get::().copied()) + .unwrap_or_default(); + let transform = world + .entity(entity) + .get::() + .copied() + .unwrap_or_default(); + parent_global_transform * transform + }; + + let scale = global_transform.compute_transform().scale; + #[cfg(feature = "2d")] + let scale = scale.xy(); + + // Make sure the collider is initialized with the correct scale. + // This overwrites the scale set by the constructor, but that one is + // meant to be only changed after initialization. + world + .entity_mut(entity) + .get_mut::() + .unwrap() + .set_scale(scale.adjust_precision(), 10); + let entity_ref = world.entity(entity); let collider = entity_ref.get::().unwrap(); + let aabb = entity_ref .get::() .copied() @@ -612,20 +656,22 @@ pub fn update_collider_scale( // Child colliders Query<(&ColliderTransform, &mut C), (With, Changed)>, )>, + sync_config: Res, ) { - // Update collider scale for root bodies - for (transform, mut collider) in &mut colliders.p0() { - #[cfg(feature = "2d")] - let scale = transform.scale.truncate().adjust_precision(); - #[cfg(feature = "3d")] - let scale = transform.scale.adjust_precision(); - if scale != collider.scale() { - // TODO: Support configurable subdivision count for shapes that - // can't be represented without approximations after scaling. - collider.set_scale(scale, 10); + if sync_config.transform_to_collider_scale { + // Update collider scale for root bodies + for (transform, mut collider) in &mut colliders.p0() { + #[cfg(feature = "2d")] + let scale = transform.scale.truncate().adjust_precision(); + #[cfg(feature = "3d")] + let scale = transform.scale.adjust_precision(); + if scale != collider.scale() { + // TODO: Support configurable subdivision count for shapes that + // can't be represented without approximations after scaling. + collider.set_scale(scale, 10); + } } } - // Update collider scale for child colliders for (collider_transform, mut collider) in &mut colliders.p1() { if collider_transform.scale != collider.scale() { diff --git a/src/prepare.rs b/src/prepare.rs index ade1863c..3ef976e0 100644 --- a/src/prepare.rs +++ b/src/prepare.rs @@ -4,7 +4,7 @@ #![allow(clippy::type_complexity)] -use crate::prelude::*; +use crate::{prelude::*, sync::SyncConfig}; use bevy::{ ecs::{ intern::Interned, @@ -77,6 +77,8 @@ pub enum PrepareSet { impl Plugin for PreparePlugin { fn build(&self, app: &mut App) { + app.init_resource::() + .register_type::(); app.configure_sets( self.schedule, ( diff --git a/src/sync/mod.rs b/src/sync/mod.rs index ca5579e2..31331131 100644 --- a/src/sync/mod.rs +++ b/src/sync/mod.rs @@ -143,15 +143,24 @@ impl Plugin for SyncPlugin { } } -/// Configures what physics data is synchronized by the [`SyncPlugin`] and how. +/// Configures what physics data is synchronized by the [`SyncPlugin`] and [`PreparePlugin`] and how. #[derive(Resource, Reflect, Clone, Debug, PartialEq, Eq)] #[reflect(Resource)] pub struct SyncConfig { /// Updates transforms based on [`Position`] and [`Rotation`] changes. Defaults to true. + /// + /// This operation is run in [`SyncSet::PositionToTransform`]. pub position_to_transform: bool, /// Updates [`Position`] and [`Rotation`] based on transform changes, - /// allowing you to move bodies using `Transform`. Defaults to true. + /// allowing you to move bodies using [`Transform`]. Defaults to true. + /// + /// This operation is run in [`SyncSet::TransformToPosition`]. pub transform_to_position: bool, + /// Updates [`Collider::scale()`] based on transform changes, + /// allowing you to scale colliders using [`Transform`]. Defaults to true. + /// + /// This operation is run in [`PrepareSet::Finalize`] + pub transform_to_collider_scale: bool, } impl Default for SyncConfig { @@ -159,6 +168,7 @@ impl Default for SyncConfig { SyncConfig { position_to_transform: true, transform_to_position: true, + transform_to_collider_scale: true, } } }