Update to use new transform propagation systems

This commit is contained in:
Aevyrie Roessler 2023-03-29 00:55:02 -07:00
parent f86da2d35d
commit 023170b718
6 changed files with 241 additions and 187 deletions

View File

@ -1,3 +1,5 @@
#![allow(clippy::type_complexity)]
use bevy::prelude::*; use bevy::prelude::*;
use big_space::{FloatingOrigin, GridCell}; 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) }; |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 p0 = pos(t_0);
let p1 = pos(t_1); let p1 = pos(t_1);
let dp = p1 - p0; p1 - p0
dp
}; };
q.p0().single_mut().translation += delta_translation(20.0); q.p0().single_mut().translation += delta_translation(20.0);

View File

@ -58,7 +58,7 @@ fn setup(
let mut translation = Vec3::ZERO; let mut translation = Vec3::ZERO;
for i in 1..=37_i128 { 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; translation.x += j;
commands.spawn(( commands.spawn((
PbrBundle { PbrBundle {

View File

@ -54,15 +54,13 @@ fn toggle_plugin(
} else { } else {
"Floating Origin Disabled" "Floating Origin Disabled"
} }
} else { } else if cell_max >= cell.x + i {
if cell_max >= cell.x + i {
cell.x = i64::min(cell_max, cell.x + i); cell.x = i64::min(cell_max, cell.x + i);
cell.y = i64::min(cell_max, cell.y + i); cell.y = i64::min(cell_max, cell.y + i);
cell.z = i64::min(cell_max, cell.z + i); cell.z = i64::min(cell_max, cell.z + i);
"Enabling..." "Enabling..."
} else { } else {
"Floating Origin Enabled" "Floating Origin Enabled"
}
}; };
let dist = (cell_max - cell.x) * 10_000; let dist = (cell_max - cell.x) * 10_000;
@ -92,7 +90,7 @@ fn setup_ui(mut commands: Commands, asset_server: Res<AssetServer>) {
sections: vec![TextSection { sections: vec![TextSection {
value: "hello: ".to_string(), value: "hello: ".to_string(),
style: TextStyle { style: TextStyle {
font: font.clone(), font,
font_size: 30.0, font_size: 30.0,
color: Color::WHITE, color: Color::WHITE,
}, },

View File

@ -131,8 +131,7 @@ impl CameraInput {
self.roll * dt, self.roll * dt,
); );
let translation = let translation = DVec3::new(self.right, self.up, self.forward) * speed * dt;
DVec3::new(self.right as f64, self.up as f64, self.forward as f64) * speed * dt as f64;
(translation, rotation) (translation, rotation)
} }

View File

@ -53,13 +53,13 @@
//! //!
//! Instead of: //! Instead of:
//! //!
//! ```no_run //! ```ignore
//! transform.translation = a_huge_imprecise_position; //! transform.translation = a_huge_imprecise_position;
//! ``` //! ```
//! //!
//! do: //! do:
//! //!
//! ```no_run //! ```ignore
//! let delta = new_pos - old_pos; //! let delta = new_pos - old_pos;
//! transform.translation += delta; //! transform.translation += delta;
//! ``` //! ```
@ -88,40 +88,51 @@
#![deny(missing_docs)] #![deny(missing_docs)]
use bevy::{math::DVec3, prelude::*, transform::TransformSystem}; use bevy::{math::DVec3, prelude::*, transform::TransformSystem};
use propagation::propagate_transforms;
use std::marker::PhantomData; use std::marker::PhantomData;
pub mod camera; pub mod camera;
pub mod precision; pub mod precision;
pub mod propagation;
#[cfg(feature = "debug")] #[cfg(feature = "debug")]
pub mod debug; pub mod debug;
use precision::*; use precision::*;
/// Add this plugin to your [`App`] to for floating origin functionality. /// Add this plugin to your [`App`] for floating origin functionality.
#[derive(Default)]
pub struct FloatingOriginPlugin<P: GridPrecision> { pub struct FloatingOriginPlugin<P: GridPrecision> {
/// Initial floating origin settings. /// The edge length of a single cell.
pub settings: FloatingOriginSettings, 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<P>, phantom: PhantomData<P>,
} }
impl<P: GridPrecision> Default for FloatingOriginPlugin<P> {
fn default() -> Self {
Self::new(10_000f32, 100f32)
}
}
impl<P: GridPrecision> FloatingOriginPlugin<P> { impl<P: GridPrecision> FloatingOriginPlugin<P> {
/// # `switching_threshold`: /// Construct a new plugin with the following settings.
///
/// 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 fn new(grid_edge_length: f32, switching_threshold: f32) -> Self { pub fn new(grid_edge_length: f32, switching_threshold: f32) -> Self {
FloatingOriginPlugin { FloatingOriginPlugin {
settings: FloatingOriginSettings::new(grid_edge_length, switching_threshold), grid_edge_length,
..Default::default() switching_threshold,
phantom: PhantomData::default(),
} }
} }
} }
impl<P: GridPrecision> Plugin for FloatingOriginPlugin<P> { impl<P: GridPrecision> Plugin for FloatingOriginPlugin<P> {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(self.settings.clone()) app.insert_resource(FloatingOriginSettings::new(
self.grid_edge_length,
self.switching_threshold,
))
.register_type::<Transform>() .register_type::<Transform>()
.register_type::<GlobalTransform>() .register_type::<GlobalTransform>()
.register_type::<GridCell<P>>() .register_type::<GridCell<P>>()
@ -136,19 +147,27 @@ impl<P: GridPrecision> Plugin for FloatingOriginPlugin<P> {
.add_startup_systems( .add_startup_systems(
( (
recenter_transform_on_grid::<P>, recenter_transform_on_grid::<P>,
update_global_from_grid::<P>, sync_simple_transforms::<P>
transform_propagate_system::<P>, .after(recenter_transform_on_grid::<P>)
.before(propagate_transforms::<P>),
update_global_from_grid::<P>
.after(recenter_transform_on_grid::<P>)
.before(propagate_transforms::<P>),
propagate_transforms::<P>,
) )
.chain()
.in_set(TransformSystem::TransformPropagate), .in_set(TransformSystem::TransformPropagate),
) )
.add_systems( .add_systems(
( (
recenter_transform_on_grid::<P>, recenter_transform_on_grid::<P>,
update_global_from_grid::<P>, sync_simple_transforms::<P>
transform_propagate_system::<P>, .after(recenter_transform_on_grid::<P>)
.before(propagate_transforms::<P>),
update_global_from_grid::<P>
.after(recenter_transform_on_grid::<P>)
.before(propagate_transforms::<P>),
propagate_transforms::<P>,
) )
.chain()
.in_set(TransformSystem::TransformPropagate), .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. /// Minimal bundle needed to position an entity in floating origin space.
/// ///
/// This is the floating origin equivalent of the [`SpatialBundle`]. /// This is the floating origin equivalent of the [`SpatialBundle`].
@ -282,7 +295,7 @@ pub struct FloatingSpatialBundle<P: GridPrecision> {
/// define a type alias! /// define a type alias!
/// ///
/// ``` /// ```
/// # use crate::GridCell; /// # use big_space::GridCell;
/// type GalacticGrid = GridCell<i64>; /// type GalacticGrid = GridCell<i64>;
/// ``` /// ```
/// ///
@ -380,16 +393,16 @@ pub fn recenter_transform_on_grid<P: GridPrecision>(
{ {
let (grid_cell_delta, translation) = let (grid_cell_delta, translation) =
settings.imprecise_translation_to_grid(transform.as_ref().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; 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<P: GridPrecision>( pub fn update_global_from_grid<P: GridPrecision>(
settings: Res<FloatingOriginSettings>, settings: Res<FloatingOriginSettings>,
origin: Query<(&GridCell<P>, Changed<GridCell<P>>), With<FloatingOrigin>>, origin: Query<Ref<GridCell<P>>, With<FloatingOrigin>>,
mut entities: ParamSet<( mut entities: ParamSet<(
Query< Query<
(&Transform, &mut GlobalTransform, &GridCell<P>), (&Transform, &mut GlobalTransform, &GridCell<P>),
@ -398,21 +411,21 @@ pub fn update_global_from_grid<P: GridPrecision>(
Query<(&Transform, &mut GlobalTransform, &GridCell<P>)>, Query<(&Transform, &mut GlobalTransform, &GridCell<P>)>,
)>, )>,
) { ) {
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(); let mut all_entities = entities.p1();
all_entities all_entities
.par_iter_mut() .par_iter_mut()
.for_each_mut(|(local, global, entity_cell)| { .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 { } else {
let mut moved_cell_entities = entities.p0(); let mut moved_cell_entities = entities.p0();
moved_cell_entities moved_cell_entities
.par_iter_mut() .par_iter_mut()
.for_each_mut(|(local, global, entity_cell)| { .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<P: GridPrecision>(
.into(); .into();
} }
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and /// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
/// [`Transform`] component. ///
pub fn transform_propagate_system<P: GridPrecision>( /// Third party plugins should ensure that this is used in concert with [`propagate_transforms`].
origin_moved: Query<(), (Changed<GridCell<P>>, With<FloatingOrigin>)>, pub fn sync_simple_transforms<P: GridPrecision>(
mut root_query_no_grid: Query< mut query: Query<
(&Transform, &mut GlobalTransform),
( (
Option<(&Children, Changed<Children>)>,
&Transform,
Changed<Transform>, Changed<Transform>,
&mut GlobalTransform, Without<Parent>,
Entity, Without<Children>,
Without<GridCell<P>>,
), ),
(Without<GridCell<P>>, Without<Parent>),
>, >,
mut root_query_grid: Query<
(
Option<(&Children, Changed<Children>)>,
Changed<Transform>,
Changed<GridCell<P>>,
&GlobalTransform,
Entity,
),
(With<GridCell<P>>, Without<Parent>),
>,
mut transform_query: Query<(
&Transform,
Changed<Transform>,
&mut GlobalTransform,
&Parent,
)>,
children_query: Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
) { ) {
let origin_cell_changed = !origin_moved.is_empty(); query
.par_iter_mut()
for (children, transform, transform_changed, mut global_transform, entity) in .for_each_mut(|(transform, mut global_transform)| {
root_query_no_grid.iter_mut()
{
let mut changed = transform_changed || origin_cell_changed;
if transform_changed {
*global_transform = GlobalTransform::from(*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<Transform>,
&mut GlobalTransform,
&Parent,
)>,
children_query: &Query<(&Children, Changed<Children>), (With<Parent>, With<GlobalTransform>)>,
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(())
} }

148
src/propagation.rs Normal file
View File

@ -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<P: GridPrecision>(
origin_moved: Query<(), (Changed<GridCell<P>>, With<FloatingOrigin>)>,
mut root_query: Query<
(
Entity,
&Children,
Ref<Transform>,
&mut GlobalTransform,
Option<Ref<GridCell<P>>>,
),
Without<Parent>,
>,
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
parent_query: Query<(Entity, Ref<Parent>)>,
) {
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<Transform>, &mut GlobalTransform, Option<&Children>),
With<Parent>,
>,
parent_query: &Query<(Entity, Ref<Parent>)>,
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(),
);
}
}
}