From 023170b718907fff178d85899026d5e615f5a952 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Wed, 29 Mar 2023 00:55:02 -0700 Subject: [PATCH] Update to use new transform propagation systems --- examples/debug.rs | 5 +- examples/demo.rs | 2 +- examples/error.rs | 16 ++- src/camera.rs | 3 +- src/lib.rs | 254 +++++++++++++++------------------------------ src/propagation.rs | 148 ++++++++++++++++++++++++++ 6 files changed, 241 insertions(+), 187 deletions(-) create mode 100644 src/propagation.rs diff --git a/examples/debug.rs b/examples/debug.rs index 00f714f..866d019 100644 --- a/examples/debug.rs +++ b/examples/debug.rs @@ -1,3 +1,5 @@ +#![allow(clippy::type_complexity)] + use bevy::prelude::*; use big_space::{FloatingOrigin, GridCell}; @@ -32,8 +34,7 @@ fn movement( |t: f32| -> Vec3 { Vec3::new(t.cos() * 2.0, t.sin() * 2.0, (t * 1.3).sin() * 2.0) }; let p0 = pos(t_0); let p1 = pos(t_1); - let dp = p1 - p0; - dp + p1 - p0 }; q.p0().single_mut().translation += delta_translation(20.0); diff --git a/examples/demo.rs b/examples/demo.rs index 6c703ab..8391a89 100644 --- a/examples/demo.rs +++ b/examples/demo.rs @@ -58,7 +58,7 @@ fn setup( let mut translation = Vec3::ZERO; for i in 1..=37_i128 { - let j = 10_f32.powf(i as f32 - 10.0) as f32; + let j = 10_f32.powf(i as f32 - 10.0); translation.x += j; commands.spawn(( PbrBundle { diff --git a/examples/error.rs b/examples/error.rs index faf8f28..3eca887 100644 --- a/examples/error.rs +++ b/examples/error.rs @@ -54,15 +54,13 @@ fn toggle_plugin( } else { "Floating Origin Disabled" } + } else if cell_max >= cell.x + i { + cell.x = i64::min(cell_max, cell.x + i); + cell.y = i64::min(cell_max, cell.y + i); + cell.z = i64::min(cell_max, cell.z + i); + "Enabling..." } else { - if cell_max >= cell.x + i { - cell.x = i64::min(cell_max, cell.x + i); - cell.y = i64::min(cell_max, cell.y + i); - cell.z = i64::min(cell_max, cell.z + i); - "Enabling..." - } else { - "Floating Origin Enabled" - } + "Floating Origin Enabled" }; let dist = (cell_max - cell.x) * 10_000; @@ -92,7 +90,7 @@ fn setup_ui(mut commands: Commands, asset_server: Res) { sections: vec![TextSection { value: "hello: ".to_string(), style: TextStyle { - font: font.clone(), + font, font_size: 30.0, color: Color::WHITE, }, diff --git a/src/camera.rs b/src/camera.rs index 7a52a41..6100e19 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -131,8 +131,7 @@ impl CameraInput { self.roll * dt, ); - let translation = - DVec3::new(self.right as f64, self.up as f64, self.forward as f64) * speed * dt as f64; + let translation = DVec3::new(self.right, self.up, self.forward) * speed * dt; (translation, rotation) } diff --git a/src/lib.rs b/src/lib.rs index 7793748..7ee2a4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -53,13 +53,13 @@ //! //! Instead of: //! -//! ```no_run +//! ```ignore //! transform.translation = a_huge_imprecise_position; //! ``` //! //! do: //! -//! ```no_run +//! ```ignore //! let delta = new_pos - old_pos; //! transform.translation += delta; //! ``` @@ -88,69 +88,88 @@ #![deny(missing_docs)] use bevy::{math::DVec3, prelude::*, transform::TransformSystem}; +use propagation::propagate_transforms; use std::marker::PhantomData; pub mod camera; pub mod precision; +pub mod propagation; #[cfg(feature = "debug")] pub mod debug; use precision::*; -/// Add this plugin to your [`App`] to for floating origin functionality. -#[derive(Default)] +/// Add this plugin to your [`App`] for floating origin functionality. pub struct FloatingOriginPlugin { - /// Initial floating origin settings. - pub settings: FloatingOriginSettings, + /// The edge length of a single cell. + pub grid_edge_length: f32, + /// How far past the extents of a cell an entity must travel before a grid recentering occurs. + /// This prevents entities from rapidly switching between cells when moving along a boundary. + pub switching_threshold: f32, phantom: PhantomData

, } +impl Default for FloatingOriginPlugin

{ + fn default() -> Self { + Self::new(10_000f32, 100f32) + } +} + impl FloatingOriginPlugin

{ - /// # `switching_threshold`: - /// - /// How far past the extents of a cell an entity must travel before a grid recentering occurs. - /// This prevents entities from rapidly switching between cells when moving along a boundary. + /// Construct a new plugin with the following settings. pub fn new(grid_edge_length: f32, switching_threshold: f32) -> Self { FloatingOriginPlugin { - settings: FloatingOriginSettings::new(grid_edge_length, switching_threshold), - ..Default::default() + grid_edge_length, + switching_threshold, + phantom: PhantomData::default(), } } } impl Plugin for FloatingOriginPlugin

{ fn build(&self, app: &mut App) { - app.insert_resource(self.settings.clone()) - .register_type::() - .register_type::() - .register_type::>() - .add_plugin(ValidParentCheckPlugin::::default()) - .configure_set(TransformSystem::TransformPropagate.in_base_set(CoreSet::PostUpdate)) - .edit_schedule(CoreSchedule::Startup, |schedule| { - schedule.configure_set( - TransformSystem::TransformPropagate.in_base_set(StartupSet::PostStartup), - ); - }) - // add transform systems to startup so the first update is "correct" - .add_startup_systems( - ( - recenter_transform_on_grid::

, - update_global_from_grid::

, - transform_propagate_system::

, - ) - .chain() - .in_set(TransformSystem::TransformPropagate), - ) - .add_systems( - ( - recenter_transform_on_grid::

, - update_global_from_grid::

, - transform_propagate_system::

, - ) - .chain() - .in_set(TransformSystem::TransformPropagate), + app.insert_resource(FloatingOriginSettings::new( + self.grid_edge_length, + self.switching_threshold, + )) + .register_type::() + .register_type::() + .register_type::>() + .add_plugin(ValidParentCheckPlugin::::default()) + .configure_set(TransformSystem::TransformPropagate.in_base_set(CoreSet::PostUpdate)) + .edit_schedule(CoreSchedule::Startup, |schedule| { + schedule.configure_set( + TransformSystem::TransformPropagate.in_base_set(StartupSet::PostStartup), ); + }) + // add transform systems to startup so the first update is "correct" + .add_startup_systems( + ( + recenter_transform_on_grid::

, + sync_simple_transforms::

+ .after(recenter_transform_on_grid::

) + .before(propagate_transforms::

), + update_global_from_grid::

+ .after(recenter_transform_on_grid::

) + .before(propagate_transforms::

), + propagate_transforms::

, + ) + .in_set(TransformSystem::TransformPropagate), + ) + .add_systems( + ( + recenter_transform_on_grid::

, + sync_simple_transforms::

+ .after(recenter_transform_on_grid::

) + .before(propagate_transforms::

), + update_global_from_grid::

+ .after(recenter_transform_on_grid::

) + .before(propagate_transforms::

), + propagate_transforms::

, + ) + .in_set(TransformSystem::TransformPropagate), + ); } } @@ -236,12 +255,6 @@ impl FloatingOriginSettings { } } -impl Default for FloatingOriginSettings { - fn default() -> Self { - Self::new(10_000f32, 100f32) - } -} - /// Minimal bundle needed to position an entity in floating origin space. /// /// This is the floating origin equivalent of the [`SpatialBundle`]. @@ -282,7 +295,7 @@ pub struct FloatingSpatialBundle { /// define a type alias! /// /// ``` -/// # use crate::GridCell; +/// # use big_space::GridCell; /// type GalacticGrid = GridCell; /// ``` /// @@ -380,16 +393,16 @@ pub fn recenter_transform_on_grid( { let (grid_cell_delta, translation) = settings.imprecise_translation_to_grid(transform.as_ref().translation); - *grid_pos = *grid_pos + grid_cell_delta; + *grid_pos += grid_cell_delta; transform.translation = translation; } }); } -/// Compute the `GlobalTransform` relative to the floating origin. +/// Compute the `GlobalTransform` relative to the floating origin's cell. pub fn update_global_from_grid( settings: Res, - origin: Query<(&GridCell

, Changed>), With>, + origin: Query>, With>, mut entities: ParamSet<( Query< (&Transform, &mut GlobalTransform, &GridCell

), @@ -398,21 +411,21 @@ pub fn update_global_from_grid( Query<(&Transform, &mut GlobalTransform, &GridCell

)>, )>, ) { - let (origin_cell, origin_grid_pos_changed) = origin.single(); + let origin_cell = origin.single(); - if origin_grid_pos_changed { + if origin_cell.is_changed() { let mut all_entities = entities.p1(); all_entities .par_iter_mut() .for_each_mut(|(local, global, entity_cell)| { - update_global_from_cell_local(&settings, entity_cell, origin_cell, local, global); + update_global_from_cell_local(&settings, entity_cell, &origin_cell, local, global); }); } else { let mut moved_cell_entities = entities.p0(); moved_cell_entities .par_iter_mut() .for_each_mut(|(local, global, entity_cell)| { - update_global_from_cell_local(&settings, entity_cell, origin_cell, local, global); + update_global_from_cell_local(&settings, entity_cell, &origin_cell, local, global); }); } } @@ -430,128 +443,23 @@ fn update_global_from_cell_local( .into(); } -/// Update [`GlobalTransform`] component of entities based on entity hierarchy and -/// [`Transform`] component. -pub fn transform_propagate_system( - origin_moved: Query<(), (Changed>, With)>, - mut root_query_no_grid: Query< +/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy +/// +/// Third party plugins should ensure that this is used in concert with [`propagate_transforms`]. +pub fn sync_simple_transforms( + mut query: Query< + (&Transform, &mut GlobalTransform), ( - Option<(&Children, Changed)>, - &Transform, Changed, - &mut GlobalTransform, - Entity, + Without, + Without, + Without>, ), - (Without>, Without), >, - mut root_query_grid: Query< - ( - Option<(&Children, Changed)>, - Changed, - Changed>, - &GlobalTransform, - Entity, - ), - (With>, Without), - >, - mut transform_query: Query<( - &Transform, - Changed, - &mut GlobalTransform, - &Parent, - )>, - children_query: Query<(&Children, Changed), (With, With)>, ) { - let origin_cell_changed = !origin_moved.is_empty(); - - for (children, transform, transform_changed, mut global_transform, entity) in - root_query_no_grid.iter_mut() - { - let mut changed = transform_changed || origin_cell_changed; - - if transform_changed { + query + .par_iter_mut() + .for_each_mut(|(transform, mut global_transform)| { *global_transform = GlobalTransform::from(*transform); - } - - if let Some((children, changed_children)) = children { - // If our `Children` has changed, we need to recalculate everything below us - changed |= changed_children; - for child in children { - let _ = propagate_recursive( - &global_transform, - &mut transform_query, - &children_query, - *child, - entity, - changed, - ); - } - } - } - - for (children, cell_changed, transform_changed, global_transform, entity) in - root_query_grid.iter_mut() - { - let mut changed = transform_changed || cell_changed || origin_cell_changed; - - if let Some((children, changed_children)) = children { - // If our `Children` has changed, we need to recalculate everything below us - changed |= changed_children; - for child in children { - let _ = propagate_recursive( - global_transform, - &mut transform_query, - &children_query, - *child, - entity, - changed, - ); - } - } - } -} - -fn propagate_recursive( - parent: &GlobalTransform, - transform_query: &mut Query<( - &Transform, - Changed, - &mut GlobalTransform, - &Parent, - )>, - children_query: &Query<(&Children, Changed), (With, With)>, - entity: Entity, - expected_parent: Entity, - mut changed: bool, - // We use a result here to use the `?` operator. Ideally we'd use a try block instead -) -> Result<(), ()> { - let global_matrix = { - let (transform, transform_changed, mut global_transform, child_parent) = - transform_query.get_mut(entity).map_err(drop)?; - // Note that for parallelising, this check cannot occur here, since there is an `&mut GlobalTransform` (in global_transform) - assert_eq!( - child_parent.get(), expected_parent, - "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" - ); - changed |= transform_changed; - if changed { - *global_transform = parent.mul_transform(*transform); - } - *global_transform - }; - - let (children, changed_children) = children_query.get(entity).map_err(drop)?; - // If our `Children` has changed, we need to recalculate everything below us - changed |= changed_children; - for child in children { - let _ = propagate_recursive( - &global_matrix, - transform_query, - children_query, - *child, - entity, - changed, - ); - } - Ok(()) + }); } diff --git a/src/propagation.rs b/src/propagation.rs new file mode 100644 index 0000000..c9c9ca9 --- /dev/null +++ b/src/propagation.rs @@ -0,0 +1,148 @@ +//! Propagates transforms through the entity hierarchy. +//! +//! This is a slightly modified version of Bevy's own transform propagation system. + +use crate::{precision::GridPrecision, FloatingOrigin, GridCell}; +use bevy::prelude::*; + +/// Update [`GlobalTransform`] component of entities based on entity hierarchy and +/// [`Transform`] component. +pub fn propagate_transforms( + origin_moved: Query<(), (Changed>, With)>, + mut root_query: Query< + ( + Entity, + &Children, + Ref, + &mut GlobalTransform, + Option>>, + ), + Without, + >, + transform_query: Query<(Ref, &mut GlobalTransform, Option<&Children>), With>, + parent_query: Query<(Entity, Ref)>, +) { + let origin_cell_changed = !origin_moved.is_empty(); + + for (entity, children, transform, mut global_transform, cell) in root_query.iter_mut() { + let cell_changed = cell.as_ref().filter(|cell| cell.is_changed()).is_some(); + let transform_changed = transform.is_changed(); + + if transform_changed && cell.is_none() { + *global_transform = GlobalTransform::from(*transform); + } + + let changed = transform_changed || cell_changed || origin_cell_changed; + + for (child, actual_parent) in parent_query.iter_many(children) { + assert_eq!( + actual_parent.get(), entity, + "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" + ); + // SAFETY: + // - `child` must have consistent parentage, or the above assertion would panic. + // Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent. + // - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before + // continuing to propagate if it encounters an entity with inconsistent parentage. + // - Since each root entity is unique and the hierarchy is consistent and forest-like, + // other root entities' `propagate_recursive` calls will not conflict with this one. + // - Since this is the only place where `transform_query` gets used, there will be no conflicting fetches elsewhere. + unsafe { + propagate_recursive( + &global_transform, + &transform_query, + &parent_query, + child, + changed || actual_parent.is_changed(), + ); + } + } + } +} + +/// COPIED EXACTLY FROM BEVY +/// +/// Recursively propagates the transforms for `entity` and all of its descendants. +/// +/// # Panics +/// +/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating +/// the transforms of any malformed entities and their descendants. +/// +/// # Safety +/// +/// - While this function is running, `transform_query` must not have any fetches for `entity`, +/// nor any of its descendants. +/// - The caller must ensure that the hierarchy leading to `entity` +/// is well-formed and must remain as a tree or a forest. Each entity must have at most one parent. +unsafe fn propagate_recursive( + parent: &GlobalTransform, + transform_query: &Query< + (Ref, &mut GlobalTransform, Option<&Children>), + With, + >, + parent_query: &Query<(Entity, Ref)>, + entity: Entity, + mut changed: bool, +) { + let (global_matrix, children) = { + let Ok((transform, mut global_transform, children)) = + // SAFETY: This call cannot create aliased mutable references. + // - The top level iteration parallelizes on the roots of the hierarchy. + // - The caller ensures that each child has one and only one unique parent throughout the entire + // hierarchy. + // + // For example, consider the following malformed hierarchy: + // + // A + // / \ + // B C + // \ / + // D + // + // D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B, + // the above check will panic as the origin parent does match the recorded parent. + // + // Also consider the following case, where A and B are roots: + // + // A B + // \ / + // C D + // \ / + // E + // + // Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting + // to mutably access E. + (unsafe { transform_query.get_unchecked(entity) }) else { + return; + }; + + changed |= transform.is_changed(); + if changed { + *global_transform = parent.mul_transform(*transform); + } + (*global_transform, children) + }; + + let Some(children) = children else { return }; + for (child, actual_parent) in parent_query.iter_many(children) { + assert_eq!( + actual_parent.get(), entity, + "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" + ); + // SAFETY: The caller guarantees that `transform_query` will not be fetched + // for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child. + // + // The above assertion ensures that each child has one and only one unique parent throughout the + // entire hierarchy. + unsafe { + propagate_recursive( + &global_matrix, + transform_query, + parent_query, + child, + changed || actual_parent.is_changed(), + ); + } + } +}