mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-10 23:58:28 +00:00
Update to use new transform propagation systems
This commit is contained in:
parent
f86da2d35d
commit
023170b718
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
},
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
254
src/lib.rs
254
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<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
148
src/propagation.rs
Normal 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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user