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 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);

View File

@ -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 {

View File

@ -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<AssetServer>) {
sections: vec![TextSection {
value: "hello: ".to_string(),
style: TextStyle {
font: font.clone(),
font,
font_size: 30.0,
color: Color::WHITE,
},

View File

@ -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)
}

View File

@ -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<P: GridPrecision> {
/// 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<P>,
}
impl<P: GridPrecision> Default for FloatingOriginPlugin<P> {
fn default() -> Self {
Self::new(10_000f32, 100f32)
}
}
impl<P: GridPrecision> FloatingOriginPlugin<P> {
/// # `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<P: GridPrecision> Plugin for FloatingOriginPlugin<P> {
fn build(&self, app: &mut App) {
app.insert_resource(self.settings.clone())
.register_type::<Transform>()
.register_type::<GlobalTransform>()
.register_type::<GridCell<P>>()
.add_plugin(ValidParentCheckPlugin::<GlobalTransform>::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::<P>,
update_global_from_grid::<P>,
transform_propagate_system::<P>,
)
.chain()
.in_set(TransformSystem::TransformPropagate),
)
.add_systems(
(
recenter_transform_on_grid::<P>,
update_global_from_grid::<P>,
transform_propagate_system::<P>,
)
.chain()
.in_set(TransformSystem::TransformPropagate),
app.insert_resource(FloatingOriginSettings::new(
self.grid_edge_length,
self.switching_threshold,
))
.register_type::<Transform>()
.register_type::<GlobalTransform>()
.register_type::<GridCell<P>>()
.add_plugin(ValidParentCheckPlugin::<GlobalTransform>::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::<P>,
sync_simple_transforms::<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>,
)
.in_set(TransformSystem::TransformPropagate),
)
.add_systems(
(
recenter_transform_on_grid::<P>,
sync_simple_transforms::<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>,
)
.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<P: GridPrecision> {
/// define a type alias!
///
/// ```
/// # use crate::GridCell;
/// # use big_space::GridCell;
/// type GalacticGrid = GridCell<i64>;
/// ```
///
@ -380,16 +393,16 @@ pub fn recenter_transform_on_grid<P: GridPrecision>(
{
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<P: GridPrecision>(
settings: Res<FloatingOriginSettings>,
origin: Query<(&GridCell<P>, Changed<GridCell<P>>), With<FloatingOrigin>>,
origin: Query<Ref<GridCell<P>>, With<FloatingOrigin>>,
mut entities: ParamSet<(
Query<
(&Transform, &mut GlobalTransform, &GridCell<P>),
@ -398,21 +411,21 @@ pub fn update_global_from_grid<P: GridPrecision>(
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();
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<P: GridPrecision>(
.into();
}
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
/// [`Transform`] component.
pub fn transform_propagate_system<P: GridPrecision>(
origin_moved: Query<(), (Changed<GridCell<P>>, With<FloatingOrigin>)>,
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<P: GridPrecision>(
mut query: Query<
(&Transform, &mut GlobalTransform),
(
Option<(&Children, Changed<Children>)>,
&Transform,
Changed<Transform>,
&mut GlobalTransform,
Entity,
Without<Parent>,
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();
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<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(),
);
}
}
}