mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-11 18:38:28 +00:00
Reference Frames (#16)
Adds the concept of reference frames, allowing hierarchies of high precision objects, e.g. objects in the reference frame of a planet, which is itself rotating, and orbiting about a star. --------- Co-authored-by: Oliver Scherer <github@oli-obk.de>
This commit is contained in:
parent
0dafa2b83c
commit
2e69f80b8d
13
Cargo.toml
13
Cargo.toml
@ -23,6 +23,7 @@ bevy = { version = "0.13", default-features = false, features = [
|
|||||||
"tonemapping_luts",
|
"tonemapping_luts",
|
||||||
] }
|
] }
|
||||||
bevy_framepace = { version = "0.15", default-features = false }
|
bevy_framepace = { version = "0.15", default-features = false }
|
||||||
|
rand = "0.8.5"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["debug", "camera", "bevy_render"]
|
default = ["debug", "camera", "bevy_render"]
|
||||||
@ -47,3 +48,15 @@ name = "error"
|
|||||||
path = "examples/error.rs"
|
path = "examples/error.rs"
|
||||||
required-features = ["default"]
|
required-features = ["default"]
|
||||||
doc-scrape-examples = true
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "error_child"
|
||||||
|
path = "examples/error_child.rs"
|
||||||
|
required-features = ["default"]
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "planets"
|
||||||
|
path = "examples/planets.rs"
|
||||||
|
required-features = ["default"]
|
||||||
|
doc-scrape-examples = true
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use big_space::{FloatingOrigin, GridCell};
|
use big_space::{reference_frame::ReferenceFrame, FloatingOrigin, GridCell};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
@ -25,22 +25,24 @@ fn movement(
|
|||||||
Query<&mut Transform, With<Mover<1>>>,
|
Query<&mut Transform, With<Mover<1>>>,
|
||||||
Query<&mut Transform, With<Mover<2>>>,
|
Query<&mut Transform, With<Mover<2>>>,
|
||||||
Query<&mut Transform, With<Mover<3>>>,
|
Query<&mut Transform, With<Mover<3>>>,
|
||||||
|
Query<&mut Transform, With<Mover<4>>>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
let delta_translation = |offset: f32| -> Vec3 {
|
let delta_translation = |offset: f32, scale: f32| -> Vec3 {
|
||||||
let t_1 = time.elapsed_seconds() + offset;
|
let t_1 = time.elapsed_seconds() * 0.1 + offset;
|
||||||
let dt = time.delta_seconds();
|
let dt = time.delta_seconds() * 0.1;
|
||||||
let t_0 = t_1 - dt;
|
let t_0 = t_1 - dt;
|
||||||
let pos =
|
let pos =
|
||||||
|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) * scale;
|
||||||
let p1 = pos(t_1);
|
let p1 = pos(t_1) * scale;
|
||||||
p1 - p0
|
p1 - p0
|
||||||
};
|
};
|
||||||
|
|
||||||
q.p0().single_mut().translation += delta_translation(20.0);
|
q.p0().single_mut().translation += delta_translation(20.0, 1.0);
|
||||||
q.p1().single_mut().translation += delta_translation(251.0);
|
q.p1().single_mut().translation += delta_translation(251.0, 1.0);
|
||||||
q.p2().single_mut().translation += delta_translation(812.0);
|
q.p2().single_mut().translation += delta_translation(812.0, 1.0);
|
||||||
|
q.p3().single_mut().translation += delta_translation(863.0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
@ -48,7 +50,7 @@ struct Rotator;
|
|||||||
|
|
||||||
fn rotation(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
|
fn rotation(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
|
||||||
for mut transform in &mut query {
|
for mut transform in &mut query {
|
||||||
transform.rotate_x(3.0 * time.delta_seconds());
|
transform.rotate_z(3.0 * time.delta_seconds() * 0.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,16 +94,21 @@ fn setup(
|
|||||||
..default()
|
..default()
|
||||||
},
|
},
|
||||||
GridCell::<i64>::default(),
|
GridCell::<i64>::default(),
|
||||||
|
ReferenceFrame::<i64>::new(0.2, 0.01),
|
||||||
Rotator,
|
Rotator,
|
||||||
Mover::<3>,
|
Mover::<3>,
|
||||||
))
|
))
|
||||||
.with_children(|parent| {
|
.with_children(|parent| {
|
||||||
parent.spawn(PbrBundle {
|
parent.spawn((
|
||||||
mesh: mesh_handle,
|
PbrBundle {
|
||||||
material: matl_handle,
|
mesh: mesh_handle,
|
||||||
transform: Transform::from_xyz(0.0, 0.0, 1.0),
|
material: matl_handle,
|
||||||
..default()
|
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||||
});
|
..default()
|
||||||
|
},
|
||||||
|
GridCell::<i64>::default(),
|
||||||
|
Mover::<4>,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
// light
|
// light
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
use bevy::{
|
use bevy::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
transform::TransformSystem,
|
transform::TransformSystem,
|
||||||
window::{CursorGrabMode, PrimaryWindow, WindowMode},
|
window::{CursorGrabMode, PrimaryWindow},
|
||||||
};
|
};
|
||||||
use big_space::{
|
use big_space::{
|
||||||
camera::{CameraController, CameraInput},
|
camera::{CameraController, CameraInput},
|
||||||
|
propagation::IgnoreFloatingOrigin,
|
||||||
world_query::GridTransformReadOnly,
|
world_query::GridTransformReadOnly,
|
||||||
FloatingOrigin, GridCell,
|
FloatingOrigin, GridCell,
|
||||||
};
|
};
|
||||||
@ -63,7 +64,8 @@ fn setup(
|
|||||||
let mut translation = Vec3::ZERO;
|
let mut translation = Vec3::ZERO;
|
||||||
for i in -16..=27 {
|
for i in -16..=27 {
|
||||||
let j = 10_f32.powf(i as f32);
|
let j = 10_f32.powf(i as f32);
|
||||||
translation.x += j;
|
let k = 10_f32.powf((i - 1) as f32);
|
||||||
|
translation.x += j / 2.0 + k;
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
PbrBundle {
|
PbrBundle {
|
||||||
mesh: mesh_handle.clone(),
|
mesh: mesh_handle.clone(),
|
||||||
@ -109,6 +111,7 @@ fn ui_setup(mut commands: Commands) {
|
|||||||
..default()
|
..default()
|
||||||
}),
|
}),
|
||||||
BigSpaceDebugText,
|
BigSpaceDebugText,
|
||||||
|
IgnoreFloatingOrigin,
|
||||||
));
|
));
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
@ -129,6 +132,7 @@ fn ui_setup(mut commands: Commands) {
|
|||||||
})
|
})
|
||||||
.with_text_justify(JustifyText::Center),
|
.with_text_justify(JustifyText::Center),
|
||||||
FunFactText,
|
FunFactText,
|
||||||
|
IgnoreFloatingOrigin,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +154,12 @@ fn highlight_nearest_sphere(
|
|||||||
.circle_segments(128);
|
.circle_segments(128);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
fn ui_text_system(
|
fn ui_text_system(
|
||||||
mut debug_text: Query<&mut Text, (With<BigSpaceDebugText>, Without<FunFactText>)>,
|
mut debug_text: Query<
|
||||||
|
(&mut Text, &GlobalTransform),
|
||||||
|
(With<BigSpaceDebugText>, Without<FunFactText>),
|
||||||
|
>,
|
||||||
mut fun_text: Query<&mut Text, (With<FunFactText>, Without<BigSpaceDebugText>)>,
|
mut fun_text: Query<&mut Text, (With<FunFactText>, Without<BigSpaceDebugText>)>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
origin: Query<GridTransformReadOnly<i128>, With<FloatingOrigin>>,
|
origin: Query<GridTransformReadOnly<i128>, With<FloatingOrigin>>,
|
||||||
@ -194,7 +202,9 @@ fn ui_text_system(
|
|||||||
("".into(), "".into())
|
("".into(), "".into())
|
||||||
};
|
};
|
||||||
|
|
||||||
debug_text.single_mut().sections[0].value =
|
let mut debug_text = debug_text.single_mut();
|
||||||
|
|
||||||
|
debug_text.0.sections[0].value =
|
||||||
format!("{grid_text}\n{translation_text}\n{camera_text}\n{nearest_text}");
|
format!("{grid_text}\n{translation_text}\n{camera_text}\n{nearest_text}");
|
||||||
|
|
||||||
fun_text.single_mut().sections[0].value = fact_text
|
fun_text.single_mut().sections[0].value = fact_text
|
||||||
@ -255,14 +265,14 @@ fn cursor_grab_system(
|
|||||||
if btn.just_pressed(MouseButton::Left) {
|
if btn.just_pressed(MouseButton::Left) {
|
||||||
window.cursor.grab_mode = CursorGrabMode::Locked;
|
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||||
window.cursor.visible = false;
|
window.cursor.visible = false;
|
||||||
window.mode = WindowMode::BorderlessFullscreen;
|
// window.mode = WindowMode::BorderlessFullscreen;
|
||||||
cam.defaults_disabled = false;
|
cam.defaults_disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if key.just_pressed(KeyCode::Escape) {
|
if key.just_pressed(KeyCode::Escape) {
|
||||||
window.cursor.grab_mode = CursorGrabMode::None;
|
window.cursor.grab_mode = CursorGrabMode::None;
|
||||||
window.cursor.visible = true;
|
window.cursor.visible = true;
|
||||||
window.mode = WindowMode::Windowed;
|
// window.mode = WindowMode::Windowed;
|
||||||
cam.defaults_disabled = true;
|
cam.defaults_disabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,9 @@
|
|||||||
//! origin when not using this plugin.
|
//! origin when not using this plugin.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use big_space::{FloatingOrigin, FloatingOriginSettings, GridCell};
|
use big_space::{
|
||||||
|
reference_frame::RootReferenceFrame, FloatingOrigin, GridCell, IgnoreFloatingOrigin,
|
||||||
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
App::new()
|
App::new()
|
||||||
@ -33,7 +35,7 @@ const DISTANCE: i128 = 21_000_000;
|
|||||||
/// this issue.
|
/// this issue.
|
||||||
fn toggle_plugin(
|
fn toggle_plugin(
|
||||||
input: Res<ButtonInput<KeyCode>>,
|
input: Res<ButtonInput<KeyCode>>,
|
||||||
settings: Res<big_space::FloatingOriginSettings>,
|
settings: Res<RootReferenceFrame<i128>>,
|
||||||
mut text: Query<&mut Text>,
|
mut text: Query<&mut Text>,
|
||||||
mut disabled: Local<bool>,
|
mut disabled: Local<bool>,
|
||||||
mut floating_origin: Query<&mut GridCell<i128>, With<FloatingOrigin>>,
|
mut floating_origin: Query<&mut GridCell<i128>, With<FloatingOrigin>>,
|
||||||
@ -43,7 +45,7 @@ fn toggle_plugin(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut origin_cell = floating_origin.single_mut();
|
let mut origin_cell = floating_origin.single_mut();
|
||||||
let index_max = DISTANCE / settings.grid_edge_length() as i128;
|
let index_max = DISTANCE / settings.cell_edge_length() as i128;
|
||||||
let increment = index_max / 100;
|
let increment = index_max / 100;
|
||||||
|
|
||||||
let msg = if *disabled {
|
let msg = if *disabled {
|
||||||
@ -60,7 +62,7 @@ fn toggle_plugin(
|
|||||||
"Floating Origin Enabled"
|
"Floating Origin Enabled"
|
||||||
};
|
};
|
||||||
|
|
||||||
let dist = index_max.saturating_sub(origin_cell.x) * settings.grid_edge_length() as i128;
|
let dist = index_max.saturating_sub(origin_cell.x) * settings.cell_edge_length() as i128;
|
||||||
|
|
||||||
let thousands = |num: i128| {
|
let thousands = |num: i128| {
|
||||||
num.to_string()
|
num.to_string()
|
||||||
@ -87,32 +89,35 @@ fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn setup_ui(mut commands: Commands) {
|
fn setup_ui(mut commands: Commands) {
|
||||||
commands.spawn(TextBundle {
|
commands.spawn((
|
||||||
style: Style {
|
TextBundle {
|
||||||
align_self: AlignSelf::FlexStart,
|
style: Style {
|
||||||
flex_direction: FlexDirection::Column,
|
align_self: AlignSelf::FlexStart,
|
||||||
|
flex_direction: FlexDirection::Column,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
text: Text {
|
||||||
|
sections: vec![TextSection {
|
||||||
|
value: "hello: ".to_string(),
|
||||||
|
style: TextStyle {
|
||||||
|
font_size: 30.0,
|
||||||
|
color: Color::WHITE,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
text: Text {
|
IgnoreFloatingOrigin,
|
||||||
sections: vec![TextSection {
|
));
|
||||||
value: "hello: ".to_string(),
|
|
||||||
style: TextStyle {
|
|
||||||
font_size: 30.0,
|
|
||||||
color: Color::WHITE,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
}],
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_scene(
|
fn setup_scene(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
settings: Res<FloatingOriginSettings>,
|
reference_frame: Res<RootReferenceFrame<i128>>,
|
||||||
) {
|
) {
|
||||||
let mesh_handle = meshes.add(Sphere::new(1.5).mesh());
|
let mesh_handle = meshes.add(Sphere::new(1.5).mesh());
|
||||||
let matl_handle = materials.add(StandardMaterial {
|
let matl_handle = materials.add(StandardMaterial {
|
||||||
@ -120,7 +125,7 @@ fn setup_scene(
|
|||||||
..default()
|
..default()
|
||||||
});
|
});
|
||||||
|
|
||||||
let d = DISTANCE / settings.grid_edge_length() as i128;
|
let d = DISTANCE / reference_frame.cell_edge_length() as i128;
|
||||||
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
|
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
|
||||||
|
|
||||||
// Normally, we would put the floating origin on the camera. However in this example, we want to
|
// Normally, we would put the floating origin on the camera. However in this example, we want to
|
||||||
|
|||||||
100
examples/error_child.rs
Normal file
100
examples/error_child.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//! This example demonstrates error accumulating from parent to children in nested reference frames.
|
||||||
|
use bevy::{math::DVec3, prelude::*};
|
||||||
|
use big_space::{
|
||||||
|
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||||
|
FloatingOrigin, GridCell,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||||
|
big_space::FloatingOriginPlugin::<i128>::default(),
|
||||||
|
big_space::camera::CameraControllerPlugin::<i128>::default(),
|
||||||
|
big_space::debug::FloatingOriginDebugPlugin::<i128>::default(),
|
||||||
|
))
|
||||||
|
.add_systems(Startup, setup_scene)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The distance being used to test precision. A sphere is placed at this position, and a child is
|
||||||
|
// added in the opposite direction. This should sum to zero if we had infinite precision.
|
||||||
|
const DISTANT: DVec3 = DVec3::new(1e17, 0.0, 0.0);
|
||||||
|
const ORIGIN: DVec3 = DVec3::new(200.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
fn setup_scene(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
root: Res<RootReferenceFrame<i128>>,
|
||||||
|
) {
|
||||||
|
let mesh_handle = meshes.add(Sphere::new(0.5).mesh());
|
||||||
|
let matl_handle = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::rgb(0.8, 0.7, 0.6),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
// A red sphere located at the origin
|
||||||
|
commands.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: mesh_handle.clone(),
|
||||||
|
material: materials.add(Color::RED),
|
||||||
|
transform: Transform::from_translation(ORIGIN.as_vec3()),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
GridCell::<i128>::default(),
|
||||||
|
));
|
||||||
|
|
||||||
|
let parent = root.translation_to_grid(DISTANT);
|
||||||
|
let child = root.translation_to_grid(-DISTANT + ORIGIN);
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
// A sphere very far from the origin
|
||||||
|
PbrBundle {
|
||||||
|
mesh: mesh_handle.clone(),
|
||||||
|
material: matl_handle.clone(),
|
||||||
|
transform: Transform::from_translation(parent.1),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
parent.0,
|
||||||
|
ReferenceFrame::<i128>::default(),
|
||||||
|
))
|
||||||
|
.with_children(|parent| {
|
||||||
|
// A green sphere that is a child of the sphere very far from the origin. This child is
|
||||||
|
// very far from its parent, and should be located exactly at the origin (if there was
|
||||||
|
// no floating point error). The distance from the green sphere to the red sphere is the
|
||||||
|
// error caused by float imprecision. Note that the sphere does not have any rendering
|
||||||
|
// artifacts, its position just has a fixed error.
|
||||||
|
parent.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: mesh_handle,
|
||||||
|
material: materials.add(Color::GREEN),
|
||||||
|
transform: Transform::from_translation(child.1),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
child.0,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
// light
|
||||||
|
commands.spawn((
|
||||||
|
DirectionalLightBundle {
|
||||||
|
transform: Transform::from_xyz(4.0, -10.0, -4.0),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
GridCell::<i128>::default(),
|
||||||
|
));
|
||||||
|
// camera
|
||||||
|
commands.spawn((
|
||||||
|
Camera3dBundle {
|
||||||
|
transform: Transform::from_translation(ORIGIN.as_vec3() + Vec3::new(0.0, 0.0, 8.0))
|
||||||
|
.looking_at(Vec3::ZERO, Vec3::Y),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
GridCell::<i128>::default(),
|
||||||
|
FloatingOrigin,
|
||||||
|
big_space::camera::CameraController::default() // Built-in camera controller
|
||||||
|
.with_speed_bounds([10e-18, 10e35])
|
||||||
|
.with_smoothness(0.9, 0.8)
|
||||||
|
.with_speed(1.0),
|
||||||
|
));
|
||||||
|
}
|
||||||
193
examples/planets.rs
Normal file
193
examples/planets.rs
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
/// Example with spheres at the scale and distance of the earth and moon around the sun, at 1:1
|
||||||
|
/// scale. The earth is rotating on its axis, and the camera is in this reference frame, to
|
||||||
|
/// demonstrate how high precision nested reference frames work at large scales.
|
||||||
|
use bevy::{core_pipeline::bloom::BloomSettings, prelude::*, render::camera::Exposure};
|
||||||
|
use big_space::{
|
||||||
|
camera::CameraController,
|
||||||
|
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||||
|
FloatingOrigin, GridCell,
|
||||||
|
};
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins((
|
||||||
|
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||||
|
big_space::FloatingOriginPlugin::<i64>::default(),
|
||||||
|
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||||
|
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||||
|
bevy_framepace::FramepacePlugin,
|
||||||
|
))
|
||||||
|
.insert_resource(ClearColor(Color::BLACK))
|
||||||
|
.insert_resource(AmbientLight {
|
||||||
|
color: Color::WHITE,
|
||||||
|
brightness: 100.0,
|
||||||
|
})
|
||||||
|
.add_systems(Startup, setup)
|
||||||
|
.add_systems(Update, rotate)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
struct Rotates(f32);
|
||||||
|
|
||||||
|
fn rotate(mut rotate_query: Query<(&mut Transform, &Rotates)>) {
|
||||||
|
for (mut transform, rotates) in rotate_query.iter_mut() {
|
||||||
|
transform.rotate_local_y(rotates.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
space: Res<RootReferenceFrame<i64>>,
|
||||||
|
) {
|
||||||
|
let mut sphere = |radius| meshes.add(Sphere::new(radius).mesh().ico(32).unwrap());
|
||||||
|
|
||||||
|
let star = sphere(1e10);
|
||||||
|
let star_mat = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
emissive: Color::rgb_linear(100000., 100000., 100000.),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
for _ in 0..500 {
|
||||||
|
commands.spawn((
|
||||||
|
GridCell::<i64>::new(
|
||||||
|
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||||
|
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||||
|
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||||
|
),
|
||||||
|
PbrBundle {
|
||||||
|
mesh: star.clone(),
|
||||||
|
material: star_mat.clone(),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sun_mat = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::WHITE,
|
||||||
|
emissive: Color::rgb_linear(10000000., 10000000., 10000000.),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
let sun_radius_m = 695_508_000.0;
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
GridCell::<i64>::ZERO,
|
||||||
|
PointLightBundle {
|
||||||
|
point_light: PointLight {
|
||||||
|
intensity: 35.73e27,
|
||||||
|
range: 1e20,
|
||||||
|
radius: sun_radius_m,
|
||||||
|
shadows_enabled: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.with_children(|builder| {
|
||||||
|
builder.spawn((PbrBundle {
|
||||||
|
mesh: sphere(sun_radius_m),
|
||||||
|
material: sun_mat,
|
||||||
|
..default()
|
||||||
|
},));
|
||||||
|
});
|
||||||
|
|
||||||
|
let earth_orbit_radius_m = 149.60e9;
|
||||||
|
let earth_radius_m = 6.371e6;
|
||||||
|
|
||||||
|
let earth_mat = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::BLUE,
|
||||||
|
perceptual_roughness: 0.8,
|
||||||
|
reflectance: 1.0,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let (earth_cell, earth_pos): (GridCell<i64>, _) =
|
||||||
|
space.imprecise_translation_to_grid(Vec3::Z * earth_orbit_radius_m);
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: sphere(earth_radius_m),
|
||||||
|
material: earth_mat,
|
||||||
|
transform: Transform::from_translation(earth_pos),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
earth_cell,
|
||||||
|
ReferenceFrame::<i64>::default(),
|
||||||
|
Rotates(0.001),
|
||||||
|
))
|
||||||
|
.with_children(|commands| {
|
||||||
|
let moon_orbit_radius_m = 385e6;
|
||||||
|
let moon_radius_m = 1.7375e6;
|
||||||
|
|
||||||
|
let moon_mat = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::GRAY,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
reflectance: 0.0,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let (moon_cell, moon_pos): (GridCell<i64>, _) =
|
||||||
|
space.imprecise_translation_to_grid(Vec3::X * moon_orbit_radius_m);
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: sphere(moon_radius_m),
|
||||||
|
material: moon_mat,
|
||||||
|
transform: Transform::from_translation(moon_pos),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
moon_cell,
|
||||||
|
));
|
||||||
|
|
||||||
|
let (cam_cell, cam_pos): (GridCell<i64>, _) =
|
||||||
|
space.imprecise_translation_to_grid(Vec3::X * (earth_radius_m + 1.0));
|
||||||
|
|
||||||
|
// camera
|
||||||
|
commands.spawn((
|
||||||
|
Camera3dBundle {
|
||||||
|
transform: Transform::from_translation(cam_pos)
|
||||||
|
.looking_to(Vec3::NEG_Z, Vec3::X),
|
||||||
|
camera: Camera {
|
||||||
|
hdr: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
exposure: Exposure::SUNLIGHT,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
BloomSettings::default(),
|
||||||
|
cam_cell,
|
||||||
|
FloatingOrigin, // Important: marks the floating origin entity for rendering.
|
||||||
|
CameraController::default() // Built-in camera controller
|
||||||
|
.with_speed_bounds([10e-18, 10e35])
|
||||||
|
.with_smoothness(0.9, 0.8)
|
||||||
|
.with_speed(1.0),
|
||||||
|
));
|
||||||
|
|
||||||
|
let (ball_cell, ball_pos): (GridCell<i64>, _) = space.imprecise_translation_to_grid(
|
||||||
|
Vec3::X * (earth_radius_m + 1.0) + Vec3::NEG_Z * 5.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ball_mat = materials.add(StandardMaterial {
|
||||||
|
base_color: Color::FUCHSIA,
|
||||||
|
perceptual_roughness: 1.0,
|
||||||
|
reflectance: 0.0,
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
PbrBundle {
|
||||||
|
mesh: sphere(1.0),
|
||||||
|
material: ball_mat,
|
||||||
|
transform: Transform::from_translation(ball_pos),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
ball_cell,
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -12,8 +12,8 @@ use bevy::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
precision::GridPrecision,
|
precision::GridPrecision,
|
||||||
|
reference_frame::{local_origin::ReferenceFrames, RootReferenceFrame},
|
||||||
world_query::{GridTransform, GridTransformReadOnly},
|
world_query::{GridTransform, GridTransformReadOnly},
|
||||||
FloatingOriginSettings,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Adds the `big_space` camera controller
|
/// Adds the `big_space` camera controller
|
||||||
@ -175,12 +175,14 @@ pub fn default_camera_inputs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Find the object nearest the camera
|
/// Find the object nearest the camera
|
||||||
pub fn nearest_objects<T: GridPrecision>(
|
pub fn nearest_objects<P: GridPrecision>(
|
||||||
settings: Res<FloatingOriginSettings>,
|
settings: Res<RootReferenceFrame<P>>,
|
||||||
objects: Query<(Entity, GridTransformReadOnly<T>, &Aabb)>,
|
objects: Query<(Entity, GridTransformReadOnly<P>, &Aabb)>,
|
||||||
mut camera: Query<(&mut CameraController, GridTransformReadOnly<T>)>,
|
mut camera: Query<(&mut CameraController, GridTransformReadOnly<P>)>,
|
||||||
) {
|
) {
|
||||||
let (mut camera, cam_pos) = camera.single_mut();
|
let Ok((mut camera, cam_pos)) = camera.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let nearest_object = objects
|
let nearest_object = objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(entity, obj_pos, aabb)| {
|
.map(|(entity, obj_pos, aabb)| {
|
||||||
@ -201,11 +203,17 @@ pub fn nearest_objects<T: GridPrecision>(
|
|||||||
/// Uses [`CameraInput`] state to update the camera position.
|
/// Uses [`CameraInput`] state to update the camera position.
|
||||||
pub fn camera_controller<P: GridPrecision>(
|
pub fn camera_controller<P: GridPrecision>(
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
settings: Res<FloatingOriginSettings>,
|
frames: ReferenceFrames<P>,
|
||||||
mut input: ResMut<CameraInput>,
|
mut input: ResMut<CameraInput>,
|
||||||
mut camera: Query<(GridTransform<P>, &mut CameraController)>,
|
mut camera: Query<(Entity, GridTransform<P>, &mut CameraController)>,
|
||||||
) {
|
) {
|
||||||
for (mut position, mut controller) in camera.iter_mut() {
|
for (camera, mut position, mut controller) in camera.iter_mut() {
|
||||||
|
let Some(frame) = frames
|
||||||
|
.get_handle(camera)
|
||||||
|
.map(|handle| frames.resolve_handle(handle))
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let speed = match (controller.nearest_object, controller.slow_near_objects) {
|
let speed = match (controller.nearest_object, controller.slow_near_objects) {
|
||||||
(Some(nearest), true) => nearest.1.abs(),
|
(Some(nearest), true) => nearest.1.abs(),
|
||||||
_ => controller.speed,
|
_ => controller.speed,
|
||||||
@ -224,7 +232,7 @@ pub fn camera_controller<P: GridPrecision>(
|
|||||||
let vel_t_next = cam_rot * vel_t_target; // Orients the translation to match the camera
|
let vel_t_next = cam_rot * vel_t_target; // Orients the translation to match the camera
|
||||||
let vel_t_next = vel_t_current.lerp(vel_t_next, lerp_translation);
|
let vel_t_next = vel_t_current.lerp(vel_t_next, lerp_translation);
|
||||||
// Convert the high precision translation to a grid cell and low precision translation
|
// Convert the high precision translation to a grid cell and low precision translation
|
||||||
let (cell_offset, new_translation) = settings.translation_to_grid(vel_t_next);
|
let (cell_offset, new_translation) = frame.translation_to_grid(vel_t_next);
|
||||||
*position.cell += cell_offset;
|
*position.cell += cell_offset;
|
||||||
position.transform.translation += new_translation;
|
position.transform.translation += new_translation;
|
||||||
|
|
||||||
|
|||||||
51
src/debug.rs
51
src/debug.rs
@ -4,7 +4,11 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::{precision::GridPrecision, FloatingOrigin, FloatingOriginSettings, GridCell};
|
use crate::{
|
||||||
|
precision::GridPrecision,
|
||||||
|
reference_frame::{local_origin::ReferenceFrames, ReferenceFrame},
|
||||||
|
FloatingOrigin, GridCell,
|
||||||
|
};
|
||||||
|
|
||||||
/// This plugin will render the bounds of occupied grid cells.
|
/// This plugin will render the bounds of occupied grid cells.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -13,9 +17,9 @@ impl<P: GridPrecision> Plugin for FloatingOriginDebugPlugin<P> {
|
|||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
update_debug_bounds::<P>
|
(update_debug_bounds::<P>, update_reference_frame_axes::<P>)
|
||||||
.after(crate::recenter_transform_on_grid::<P>)
|
.chain()
|
||||||
.before(crate::update_global_from_grid::<P>),
|
.after(bevy::transform::TransformSystem::TransformPropagate),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,20 +27,31 @@ impl<P: GridPrecision> Plugin for FloatingOriginDebugPlugin<P> {
|
|||||||
/// Update the rendered debug bounds to only highlight occupied [`GridCell`]s.
|
/// Update the rendered debug bounds to only highlight occupied [`GridCell`]s.
|
||||||
pub fn update_debug_bounds<P: GridPrecision>(
|
pub fn update_debug_bounds<P: GridPrecision>(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
settings: Res<FloatingOriginSettings>,
|
reference_frames: ReferenceFrames<P>,
|
||||||
occupied_cells: Query<&GridCell<P>, Without<FloatingOrigin>>,
|
occupied_cells: Query<(Entity, &GridCell<P>), Without<FloatingOrigin>>,
|
||||||
origin_cells: Query<&GridCell<P>, With<FloatingOrigin>>,
|
|
||||||
) {
|
) {
|
||||||
let Ok(origin_cell) = origin_cells.get_single() else {
|
for (cell_entity, cell) in occupied_cells.iter() {
|
||||||
return;
|
let Some(frame) = reference_frames.get(cell_entity) else {
|
||||||
};
|
continue;
|
||||||
for cell in occupied_cells.iter() {
|
};
|
||||||
let cell = cell - origin_cell;
|
let transform = frame.global_transform(
|
||||||
let scale = Vec3::splat(settings.grid_edge_length * 0.999);
|
cell,
|
||||||
let translation = settings.grid_position(&cell, &Transform::IDENTITY);
|
&Transform::from_scale(Vec3::splat(frame.cell_edge_length() * 0.999)),
|
||||||
gizmos.cuboid(
|
);
|
||||||
Transform::from_translation(translation).with_scale(scale),
|
gizmos.cuboid(transform, Color::GREEN)
|
||||||
Color::GREEN,
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
|
/// Draw axes for reference frames.
|
||||||
|
pub fn update_reference_frame_axes<P: GridPrecision>(
|
||||||
|
mut gizmos: Gizmos,
|
||||||
|
frames: Query<(&GlobalTransform, &ReferenceFrame<P>)>,
|
||||||
|
) {
|
||||||
|
for (transform, frame) in frames.iter() {
|
||||||
|
let start = transform.translation();
|
||||||
|
let len = frame.cell_edge_length() * 1.0;
|
||||||
|
gizmos.ray(start, transform.right() * len, Color::RED);
|
||||||
|
gizmos.ray(start, transform.up() * len, Color::GREEN);
|
||||||
|
gizmos.ray(start, transform.back() * len, Color::BLUE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
301
src/lib.rs
301
src/lib.rs
@ -1,5 +1,5 @@
|
|||||||
//! This [`bevy`] plugin makes it easy to build high-precision worlds that exceed the size of the
|
//! This [`bevy`] plugin makes it possible to build high-precision worlds that exceed the size of
|
||||||
//! observable universe, with no added dependencies, while remaining largely compatible with the
|
//! the observable universe, with no added dependencies, while remaining largely compatible with the
|
||||||
//! rest of the Bevy ecosystem.
|
//! rest of the Bevy ecosystem.
|
||||||
//!
|
//!
|
||||||
//! ### Problem
|
//! ### Problem
|
||||||
@ -14,15 +14,25 @@
|
|||||||
//!
|
//!
|
||||||
//! ### Solution
|
//! ### Solution
|
||||||
//!
|
//!
|
||||||
//! While using the [`FloatingOriginPlugin`], entities are placed into a [`GridCell`] in a large
|
//! While using the [`FloatingOriginPlugin`], the position of entities is now defined with the
|
||||||
//! fixed precision grid. Inside a `GridCell`, an entity's `Transform` is relative to the center of
|
//! [`ReferenceFrame`], [`GridCell`], and [`Transform`] components. The `ReferenceFrame` is a large
|
||||||
//! that grid cell. If an entity moves into a neighboring cell, its transform will be recomputed
|
//! integer grid of cells; entities are located within this grid using the `GridCell` component.
|
||||||
//! relative to the center of that new cell. This prevents `Transforms` from ever becoming larger
|
//! Finally, the `Transform` is used to position the entity relative to the center of its
|
||||||
//! than a single grid cell, and thus prevents floating point precision artifacts.
|
//! `GridCell`. If an entity moves into a neighboring cell, its transform will be automatically
|
||||||
|
//! recomputed relative to the center of that new cell. This prevents `Transforms` from ever
|
||||||
|
//! becoming larger than a single grid cell, and thus prevents floating point precision artifacts.
|
||||||
//!
|
//!
|
||||||
//! The same thing happens to the entity marked with the [`FloatingOrigin`] component. The only
|
//! `ReferenceFrame`s can also be nested. This allows you to define moving reference frames, which
|
||||||
//! difference is that the `GridCell` of the floating origin is used when computing the
|
//! can make certain use cases much simpler. For example, if you have a planet rotating, and
|
||||||
//! `GlobalTransform` of all other entities. To an outside observer, as the floating origin camera
|
//! orbiting around its star, it would be very annoying if you had to compute this orbit and
|
||||||
|
//! rotation for all object on the surface in high precision. Instead, you can place the planet and
|
||||||
|
//! all objects on its surface in the same reference frame. The motion of the planet will be
|
||||||
|
//! inherited by all children in that reference frame, in high precision. Entities at the root of
|
||||||
|
//! the hierarchy will be implicitly placed in the [`RootReferenceFrame`].
|
||||||
|
//!
|
||||||
|
//! The above steps are also applied to the entity marked with the [`FloatingOrigin`] component. The
|
||||||
|
//! only difference is that the `GridCell` of the floating origin is used when computing the
|
||||||
|
//! [`GlobalTransform`] of all other entities. To an outside observer, as the floating origin camera
|
||||||
//! moves through space and reaches the limits of its `GridCell`, it would appear to teleport to the
|
//! moves through space and reaches the limits of its `GridCell`, it would appear to teleport to the
|
||||||
//! opposite side of the cell, similar to the spaceship in the game *Asteroids*.
|
//! opposite side of the cell, similar to the spaceship in the game *Asteroids*.
|
||||||
//!
|
//!
|
||||||
@ -31,17 +41,20 @@
|
|||||||
//! However, this is always relative to the camera (floating origin), so these artifacts will always
|
//! However, this is always relative to the camera (floating origin), so these artifacts will always
|
||||||
//! be too far away to be seen, no matter where the camera moves. Because this only affects the
|
//! be too far away to be seen, no matter where the camera moves. Because this only affects the
|
||||||
//! `GlobalTransform` and not the `Transform`, this also means that entities will never permanently
|
//! `GlobalTransform` and not the `Transform`, this also means that entities will never permanently
|
||||||
//! lose precision just because they were far from the origin at some point.
|
//! lose precision just because they were far from the origin at some point. The lossy calculation
|
||||||
|
//! only occurs when computing the `GlobalTransform` of entities, the high precision `GridCell` and
|
||||||
|
//! `Transform` are unaffected.
|
||||||
//!
|
//!
|
||||||
//! # Getting Started
|
//! # Getting Started
|
||||||
//!
|
//!
|
||||||
//! All that's needed to start using this plugin:
|
//! To start using this plugin:
|
||||||
//! 1. Disable Bevy's transform plugin: `DefaultPlugins.build().disable::<TransformPlugin>()`
|
//! 1. Disable Bevy's transform plugin: `DefaultPlugins.build().disable::<TransformPlugin>()`
|
||||||
//! 2. Add the [`FloatingOriginPlugin`] to your `App`
|
//! 2. Add the [`FloatingOriginPlugin`] to your `App`
|
||||||
//! 3. Add the [`GridCell`] component to all spatial entities
|
//! 3. Add the [`GridCell`] component to all spatial entities
|
||||||
//! 4. Add the [`FloatingOrigin`] component to the active camera
|
//! 4. Add the [`FloatingOrigin`] component to the active camera
|
||||||
|
//! 5. Add the [`IgnoreFloatingOrigin`] component
|
||||||
//!
|
//!
|
||||||
//! Take a look at [`FloatingOriginSettings`] resource for some useful helper methods.
|
//! Take a look at [`ReferenceFrame`] component for some useful helper methods.
|
||||||
//!
|
//!
|
||||||
//! # Moving Entities
|
//! # Moving Entities
|
||||||
//!
|
//!
|
||||||
@ -78,7 +91,7 @@
|
|||||||
//! However, if you have something that must not accumulate error, like the orbit of a planet, you
|
//! However, if you have something that must not accumulate error, like the orbit of a planet, you
|
||||||
//! can instead do the orbital calculation (position as a function of time) to compute the absolute
|
//! can instead do the orbital calculation (position as a function of time) to compute the absolute
|
||||||
//! position of the planet with high precision, then directly compute the [`GridCell`] and
|
//! position of the planet with high precision, then directly compute the [`GridCell`] and
|
||||||
//! [`Transform`] of that entity using [`FloatingOriginSettings::translation_to_grid`]. If the star
|
//! [`Transform`] of that entity using [`ReferenceFrame::translation_to_grid`]. If the star
|
||||||
//! this planet is orbiting around is also moving through space, note that you can add/subtract grid
|
//! this planet is orbiting around is also moving through space, note that you can add/subtract grid
|
||||||
//! cells. This means you can do each calculation in the reference frame of the moving body, and sum
|
//! cells. This means you can do each calculation in the reference frame of the moving body, and sum
|
||||||
//! up the computed translations and grid cell offsets to get a more precise result.
|
//! up the computed translations and grid cell offsets to get a more precise result.
|
||||||
@ -86,17 +99,20 @@
|
|||||||
#![allow(clippy::type_complexity)]
|
#![allow(clippy::type_complexity)]
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use bevy::{math::DVec3, prelude::*, transform::TransformSystem};
|
use bevy::{prelude::*, transform::TransformSystem};
|
||||||
use propagation::propagate_transforms;
|
use propagation::{propagate_transforms, sync_simple_transforms};
|
||||||
|
use reference_frame::local_origin::ReferenceFrames;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use world_query::{GridTransformReadOnly, GridTransformReadOnlyItem};
|
use world_query::GridTransformReadOnly;
|
||||||
|
|
||||||
pub mod grid_cell;
|
pub mod grid_cell;
|
||||||
pub mod precision;
|
pub mod precision;
|
||||||
pub mod propagation;
|
pub mod propagation;
|
||||||
|
pub mod reference_frame;
|
||||||
pub mod world_query;
|
pub mod world_query;
|
||||||
|
|
||||||
pub use grid_cell::GridCell;
|
pub use grid_cell::GridCell;
|
||||||
|
pub use propagation::IgnoreFloatingOrigin;
|
||||||
|
|
||||||
#[cfg(feature = "debug")]
|
#[cfg(feature = "debug")]
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
@ -106,6 +122,10 @@ pub mod camera;
|
|||||||
|
|
||||||
use precision::*;
|
use precision::*;
|
||||||
|
|
||||||
|
use crate::reference_frame::{
|
||||||
|
local_origin::LocalFloatingOrigin, ReferenceFrame, RootReferenceFrame,
|
||||||
|
};
|
||||||
|
|
||||||
/// Add this plugin to your [`App`] for floating origin functionality.
|
/// Add this plugin to your [`App`] for floating origin functionality.
|
||||||
pub struct FloatingOriginPlugin<P: GridPrecision> {
|
pub struct FloatingOriginPlugin<P: GridPrecision> {
|
||||||
/// The edge length of a single cell.
|
/// The edge length of a single cell.
|
||||||
@ -136,130 +156,44 @@ impl<P: GridPrecision> FloatingOriginPlugin<P> {
|
|||||||
impl<P: GridPrecision + Reflect + FromReflect + TypePath> Plugin for FloatingOriginPlugin<P> {
|
impl<P: GridPrecision + Reflect + FromReflect + TypePath> Plugin for FloatingOriginPlugin<P> {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
struct RootGlobalTransformUpdates;
|
enum FloatingOriginSet {
|
||||||
|
RecenterLargeTransforms,
|
||||||
|
LocalFloatingOrigins,
|
||||||
|
RootGlobalTransforms,
|
||||||
|
PropagateTransforms,
|
||||||
|
}
|
||||||
|
|
||||||
app.insert_resource(FloatingOriginSettings::new(
|
let system_set_config = || {
|
||||||
|
(
|
||||||
|
recenter_transform_on_grid::<P>.in_set(FloatingOriginSet::RecenterLargeTransforms),
|
||||||
|
LocalFloatingOrigin::<P>::update
|
||||||
|
.in_set(FloatingOriginSet::LocalFloatingOrigins)
|
||||||
|
.after(FloatingOriginSet::RecenterLargeTransforms),
|
||||||
|
(
|
||||||
|
sync_simple_transforms::<P>,
|
||||||
|
update_grid_cell_global_transforms::<P>,
|
||||||
|
)
|
||||||
|
.in_set(FloatingOriginSet::RootGlobalTransforms)
|
||||||
|
.after(FloatingOriginSet::LocalFloatingOrigins),
|
||||||
|
propagate_transforms::<P>
|
||||||
|
.in_set(FloatingOriginSet::PropagateTransforms)
|
||||||
|
.after(FloatingOriginSet::RootGlobalTransforms),
|
||||||
|
)
|
||||||
|
.in_set(TransformSystem::TransformPropagate)
|
||||||
|
};
|
||||||
|
|
||||||
|
app.insert_resource(RootReferenceFrame::<P>(ReferenceFrame::new(
|
||||||
self.grid_edge_length,
|
self.grid_edge_length,
|
||||||
self.switching_threshold,
|
self.switching_threshold,
|
||||||
))
|
)))
|
||||||
.register_type::<Transform>()
|
.register_type::<Transform>()
|
||||||
.register_type::<GlobalTransform>()
|
.register_type::<GlobalTransform>()
|
||||||
.register_type::<GridCell<P>>()
|
.register_type::<GridCell<P>>()
|
||||||
|
.register_type::<ReferenceFrame<P>>()
|
||||||
|
.register_type::<RootReferenceFrame<P>>()
|
||||||
.add_plugins(ValidParentCheckPlugin::<GlobalTransform>::default())
|
.add_plugins(ValidParentCheckPlugin::<GlobalTransform>::default())
|
||||||
.add_systems(
|
.add_systems(PostStartup, system_set_config())
|
||||||
PostStartup,
|
.add_systems(PostUpdate, system_set_config());
|
||||||
(
|
|
||||||
recenter_transform_on_grid::<P>.before(RootGlobalTransformUpdates),
|
|
||||||
(sync_simple_transforms::<P>, update_global_from_grid::<P>)
|
|
||||||
.in_set(RootGlobalTransformUpdates),
|
|
||||||
propagate_transforms::<P>.after(RootGlobalTransformUpdates),
|
|
||||||
)
|
|
||||||
.in_set(TransformSystem::TransformPropagate),
|
|
||||||
)
|
|
||||||
.add_systems(
|
|
||||||
PostUpdate,
|
|
||||||
(
|
|
||||||
recenter_transform_on_grid::<P>.before(RootGlobalTransformUpdates),
|
|
||||||
(sync_simple_transforms::<P>, update_global_from_grid::<P>)
|
|
||||||
.in_set(RootGlobalTransformUpdates),
|
|
||||||
propagate_transforms::<P>.after(RootGlobalTransformUpdates),
|
|
||||||
)
|
|
||||||
.in_set(TransformSystem::TransformPropagate),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration settings for the floating origin plugin.
|
|
||||||
#[derive(Reflect, Clone, Resource)]
|
|
||||||
pub struct FloatingOriginSettings {
|
|
||||||
grid_edge_length: f32,
|
|
||||||
maximum_distance_from_origin: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FloatingOriginSettings {
|
|
||||||
/// Construct a new [`FloatingOriginSettings`] struct. This cannot be updated after the plugin
|
|
||||||
/// is built.
|
|
||||||
pub fn new(grid_edge_length: f32, switching_threshold: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
grid_edge_length,
|
|
||||||
maximum_distance_from_origin: grid_edge_length / 2.0 + switching_threshold,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the plugin's `grid_edge_length`.
|
|
||||||
pub fn grid_edge_length(&self) -> f32 {
|
|
||||||
self.grid_edge_length
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the plugin's `maximum_distance_from_origin`.
|
|
||||||
pub fn maximum_distance_from_origin(&self) -> f32 {
|
|
||||||
self.maximum_distance_from_origin
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the double precision position of an entity's [`Transform`] with respect to the given
|
|
||||||
/// [`GridCell`].
|
|
||||||
pub fn grid_position_double<P: GridPrecision>(
|
|
||||||
&self,
|
|
||||||
pos: &GridCell<P>,
|
|
||||||
transform: &Transform,
|
|
||||||
) -> DVec3 {
|
|
||||||
DVec3 {
|
|
||||||
x: pos.x.as_f64() * self.grid_edge_length as f64 + transform.translation.x as f64,
|
|
||||||
y: pos.y.as_f64() * self.grid_edge_length as f64 + transform.translation.y as f64,
|
|
||||||
z: pos.z.as_f64() * self.grid_edge_length as f64 + transform.translation.z as f64,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the single precision position of an entity's [`Transform`] with respect to the given
|
|
||||||
/// [`GridCell`].
|
|
||||||
pub fn grid_position<P: GridPrecision>(
|
|
||||||
&self,
|
|
||||||
pos: &GridCell<P>,
|
|
||||||
transform: &Transform,
|
|
||||||
) -> Vec3 {
|
|
||||||
Vec3 {
|
|
||||||
x: pos.x.as_f64() as f32 * self.grid_edge_length + transform.translation.x,
|
|
||||||
y: pos.y.as_f64() as f32 * self.grid_edge_length + transform.translation.y,
|
|
||||||
z: pos.z.as_f64() as f32 * self.grid_edge_length + transform.translation.z,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a large translation into a small translation relative to a grid cell.
|
|
||||||
pub fn translation_to_grid<P: GridPrecision>(
|
|
||||||
&self,
|
|
||||||
input: impl Into<DVec3>,
|
|
||||||
) -> (GridCell<P>, Vec3) {
|
|
||||||
let l = self.grid_edge_length as f64;
|
|
||||||
let input = input.into();
|
|
||||||
let DVec3 { x, y, z } = input;
|
|
||||||
|
|
||||||
if input.abs().max_element() < self.maximum_distance_from_origin as f64 {
|
|
||||||
return (GridCell::default(), input.as_vec3());
|
|
||||||
}
|
|
||||||
|
|
||||||
let x_r = (x / l).round();
|
|
||||||
let y_r = (y / l).round();
|
|
||||||
let z_r = (z / l).round();
|
|
||||||
let t_x = x - x_r * l;
|
|
||||||
let t_y = y - y_r * l;
|
|
||||||
let t_z = z - z_r * l;
|
|
||||||
|
|
||||||
(
|
|
||||||
GridCell {
|
|
||||||
x: P::from_f32(x_r as f32),
|
|
||||||
y: P::from_f32(y_r as f32),
|
|
||||||
z: P::from_f32(z_r as f32),
|
|
||||||
},
|
|
||||||
Vec3::new(t_x as f32, t_y as f32, t_z as f32),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert a large translation into a small translation relative to a grid cell.
|
|
||||||
pub fn imprecise_translation_to_grid<P: GridPrecision>(
|
|
||||||
&self,
|
|
||||||
input: Vec3,
|
|
||||||
) -> (GridCell<P>, Vec3) {
|
|
||||||
self.translation_to_grid(input.as_dvec3())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,86 +227,57 @@ pub struct FloatingOrigin;
|
|||||||
/// If an entity's transform becomes larger than the specified limit, it is relocated to the nearest
|
/// If an entity's transform becomes larger than the specified limit, it is relocated to the nearest
|
||||||
/// grid cell to reduce the size of the transform.
|
/// grid cell to reduce the size of the transform.
|
||||||
pub fn recenter_transform_on_grid<P: GridPrecision>(
|
pub fn recenter_transform_on_grid<P: GridPrecision>(
|
||||||
settings: Res<FloatingOriginSettings>,
|
reference_frames: ReferenceFrames<P>,
|
||||||
mut query: Query<(&mut GridCell<P>, &mut Transform), (Changed<Transform>, Without<Parent>)>,
|
mut changed_transform: Query<(Entity, &mut GridCell<P>, &mut Transform), Changed<Transform>>,
|
||||||
) {
|
) {
|
||||||
query
|
changed_transform
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each(|(mut grid_pos, mut transform)| {
|
.for_each(|(entity, mut grid_pos, mut transform)| {
|
||||||
|
let Some(frame) = reference_frames
|
||||||
|
.get_handle(entity)
|
||||||
|
.map(|handle| reference_frames.resolve_handle(handle))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
if transform.as_ref().translation.abs().max_element()
|
if transform.as_ref().translation.abs().max_element()
|
||||||
> settings.maximum_distance_from_origin
|
> frame.maximum_distance_from_origin()
|
||||||
{
|
{
|
||||||
let (grid_cell_delta, translation) =
|
let (grid_cell_delta, translation) =
|
||||||
settings.imprecise_translation_to_grid(transform.as_ref().translation);
|
frame.imprecise_translation_to_grid(transform.as_ref().translation);
|
||||||
*grid_pos += grid_cell_delta;
|
*grid_pos += grid_cell_delta;
|
||||||
transform.translation = translation;
|
transform.translation = translation;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the `GlobalTransform` relative to the floating origin's cell.
|
/// Update the `GlobalTransform` of entities with a [`GridCell`], using the [`ReferenceFrame`] the
|
||||||
pub fn update_global_from_grid<P: GridPrecision>(
|
/// entity belongs to.
|
||||||
settings: Res<FloatingOriginSettings>,
|
pub fn update_grid_cell_global_transforms<P: GridPrecision>(
|
||||||
origin: Query<(Ref<GridCell<P>>, Ref<FloatingOrigin>)>,
|
root: Res<RootReferenceFrame<P>>,
|
||||||
|
reference_frames: Query<(&ReferenceFrame<P>, &Children)>,
|
||||||
mut entities: ParamSet<(
|
mut entities: ParamSet<(
|
||||||
Query<
|
Query<(GridTransformReadOnly<P>, &mut GlobalTransform), With<Parent>>, // Node entities
|
||||||
(GridTransformReadOnly<P>, &mut GlobalTransform),
|
Query<(GridTransformReadOnly<P>, &mut GlobalTransform), Without<Parent>>, // Root entities
|
||||||
Or<(Changed<GridCell<P>>, Changed<Transform>)>,
|
|
||||||
>,
|
|
||||||
Query<(GridTransformReadOnly<P>, &mut GlobalTransform)>,
|
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
let Ok((origin_cell, floating_origin)) = origin.get_single() else {
|
// Update the GlobalTransform of GridCell entities at the root of the hierarchy
|
||||||
return;
|
entities
|
||||||
};
|
.p1()
|
||||||
|
|
||||||
if origin_cell.is_changed() || floating_origin.is_changed() {
|
|
||||||
let mut all_entities = entities.p1();
|
|
||||||
all_entities.par_iter_mut().for_each(|(local, global)| {
|
|
||||||
update_global_from_cell_local(&settings, &origin_cell, local, global);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let mut moved_cell_entities = entities.p0();
|
|
||||||
moved_cell_entities
|
|
||||||
.par_iter_mut()
|
|
||||||
.for_each(|(local, global)| {
|
|
||||||
update_global_from_cell_local(&settings, &origin_cell, local, global);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_global_from_cell_local<P: GridPrecision>(
|
|
||||||
settings: &FloatingOriginSettings,
|
|
||||||
origin_cell: &GridCell<P>,
|
|
||||||
local: GridTransformReadOnlyItem<P>,
|
|
||||||
mut global: Mut<GlobalTransform>,
|
|
||||||
) {
|
|
||||||
let grid_cell_delta = *local.cell - *origin_cell;
|
|
||||||
*global = local
|
|
||||||
.transform
|
|
||||||
.with_translation(settings.grid_position(&grid_cell_delta, local.transform))
|
|
||||||
.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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),
|
|
||||||
(
|
|
||||||
Changed<Transform>,
|
|
||||||
Without<Parent>,
|
|
||||||
Without<Children>,
|
|
||||||
Without<GridCell<P>>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
) {
|
|
||||||
query
|
|
||||||
.par_iter_mut()
|
.par_iter_mut()
|
||||||
.for_each(|(transform, mut global_transform)| {
|
.for_each(|(grid_transform, mut global_transform)| {
|
||||||
*global_transform = GlobalTransform::from(*transform);
|
*global_transform =
|
||||||
|
root.global_transform(grid_transform.cell, grid_transform.transform);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update the GlobalTransform of GridCell entities that are children of a ReferenceFrame
|
||||||
|
for (frame, children) in &reference_frames {
|
||||||
|
let mut with_parent_query = entities.p0();
|
||||||
|
let mut frame_children = with_parent_query.iter_many_mut(children);
|
||||||
|
while let Some((grid_transform, mut global_transform)) = frame_children.fetch_next() {
|
||||||
|
*global_transform =
|
||||||
|
frame.global_transform(grid_transform.cell, grid_transform.transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -1,95 +1,169 @@
|
|||||||
//! Propagates transforms through the entity hierarchy.
|
//! Propagates transforms through the entity hierarchy.
|
||||||
//!
|
//!
|
||||||
//! This is a slightly modified version of Bevy's own transform propagation system.
|
//! This is a modified version of Bevy's own transform propagation system.
|
||||||
|
|
||||||
use crate::{precision::GridPrecision, FloatingOrigin, GridCell};
|
use crate::{
|
||||||
|
precision::GridPrecision,
|
||||||
|
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||||
|
GridCell,
|
||||||
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
/// Update [`GlobalTransform`] component of entities based on entity hierarchy and
|
/// Entities with this component will ignore the floating origin, and will instead propagate
|
||||||
/// [`Transform`] component.
|
/// transforms normally.
|
||||||
|
#[derive(Component, Debug, Reflect)]
|
||||||
|
pub struct IgnoreFloatingOrigin;
|
||||||
|
|
||||||
|
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy.
|
||||||
|
pub fn sync_simple_transforms<P: GridPrecision>(
|
||||||
|
root: Res<RootReferenceFrame<P>>,
|
||||||
|
mut query: ParamSet<(
|
||||||
|
Query<
|
||||||
|
(&Transform, &mut GlobalTransform, Has<IgnoreFloatingOrigin>),
|
||||||
|
(
|
||||||
|
Or<(Changed<Transform>, Added<GlobalTransform>)>,
|
||||||
|
Without<Parent>,
|
||||||
|
Without<Children>,
|
||||||
|
Without<GridCell<P>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
Query<
|
||||||
|
(
|
||||||
|
Ref<Transform>,
|
||||||
|
&mut GlobalTransform,
|
||||||
|
Has<IgnoreFloatingOrigin>,
|
||||||
|
),
|
||||||
|
(Without<Parent>, Without<Children>, Without<GridCell<P>>),
|
||||||
|
>,
|
||||||
|
)>,
|
||||||
|
mut orphaned: RemovedComponents<Parent>,
|
||||||
|
) {
|
||||||
|
// Update changed entities.
|
||||||
|
query.p0().par_iter_mut().for_each(
|
||||||
|
|(transform, mut global_transform, ignore_floating_origin)| {
|
||||||
|
if ignore_floating_origin {
|
||||||
|
*global_transform = GlobalTransform::from(*transform);
|
||||||
|
} else {
|
||||||
|
*global_transform = root.global_transform(&GridCell::ZERO, transform);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Update orphaned entities.
|
||||||
|
let mut query = query.p1();
|
||||||
|
let mut iter = query.iter_many_mut(orphaned.read());
|
||||||
|
while let Some((transform, mut global_transform, ignore_floating_origin)) = iter.fetch_next() {
|
||||||
|
if !transform.is_changed() && !global_transform.is_added() {
|
||||||
|
if ignore_floating_origin {
|
||||||
|
*global_transform = GlobalTransform::from(*transform);
|
||||||
|
} else {
|
||||||
|
*global_transform = root.global_transform(&GridCell::ZERO, &transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the [`GlobalTransform`] of entities with a [`Transform`] that are children of a
|
||||||
|
/// [`ReferenceFrame`] and do not have a [`GridCell`] component, or that are children of
|
||||||
|
/// [`GridCell`]s.
|
||||||
pub fn propagate_transforms<P: GridPrecision>(
|
pub fn propagate_transforms<P: GridPrecision>(
|
||||||
origin_moved: Query<
|
frames: Query<&Children, With<ReferenceFrame<P>>>,
|
||||||
(),
|
frame_child_query: Query<(Entity, &Children, &GlobalTransform), With<GridCell<P>>>,
|
||||||
(
|
root_frame_query: Query<
|
||||||
Or<(Changed<GridCell<P>>, Changed<FloatingOrigin>)>,
|
(Entity, &Children, &GlobalTransform),
|
||||||
With<FloatingOrigin>,
|
(With<GridCell<P>>, Without<Parent>),
|
||||||
),
|
|
||||||
>,
|
>,
|
||||||
mut root_query: Query<
|
root_frame: Res<RootReferenceFrame<P>>,
|
||||||
|
mut root_frame_gridless_query: Query<
|
||||||
(
|
(
|
||||||
Entity,
|
Entity,
|
||||||
&Children,
|
&Children,
|
||||||
Ref<Transform>,
|
&Transform,
|
||||||
&mut GlobalTransform,
|
&mut GlobalTransform,
|
||||||
Option<Ref<GridCell<P>>>,
|
Has<IgnoreFloatingOrigin>,
|
||||||
|
),
|
||||||
|
(Without<GridCell<P>>, Without<Parent>),
|
||||||
|
>,
|
||||||
|
transform_query: Query<
|
||||||
|
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||||
|
(
|
||||||
|
With<Parent>,
|
||||||
|
Without<GridCell<P>>,
|
||||||
|
Without<ReferenceFrame<P>>,
|
||||||
),
|
),
|
||||||
Without<Parent>,
|
|
||||||
>,
|
>,
|
||||||
transform_query: Query<(Ref<Transform>, &mut GlobalTransform, Option<&Children>), With<Parent>>,
|
|
||||||
parent_query: Query<(Entity, Ref<Parent>)>,
|
parent_query: Query<(Entity, Ref<Parent>)>,
|
||||||
) {
|
) {
|
||||||
let origin_cell_changed = !origin_moved.is_empty();
|
let update_transforms = |(entity, children, global_transform)| {
|
||||||
|
|
||||||
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) {
|
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual_parent.get(), entity,
|
actual_parent.get(), entity,
|
||||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||||
);
|
);
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - `child` must have consistent parentage, or the above assertion would panic.
|
// - `child` must have consistent parentage, or the above assertion would panic. Since
|
||||||
// Since `child` is parented to a root entity, the entire hierarchy leading to it is consistent.
|
// `child` is parented to a root entity, the entire hierarchy leading to it is
|
||||||
// - We may operate as if all descendants are consistent, since `propagate_recursive` will panic before
|
// consistent.
|
||||||
// continuing to propagate if it encounters an entity with inconsistent parentage.
|
// - 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,
|
// - 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.
|
// 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.
|
// - Since this is the only place where `transform_query` gets used, there will be no
|
||||||
|
// conflicting fetches elsewhere.
|
||||||
unsafe {
|
unsafe {
|
||||||
propagate_recursive(
|
propagate_recursive(&global_transform, &transform_query, &parent_query, child);
|
||||||
&global_transform,
|
|
||||||
&transform_query,
|
|
||||||
&parent_query,
|
|
||||||
child,
|
|
||||||
changed || actual_parent.is_changed(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
frames.par_iter().for_each(|children| {
|
||||||
|
children
|
||||||
|
.iter()
|
||||||
|
.filter_map(|child| frame_child_query.get(*child).ok())
|
||||||
|
.for_each(|(e, c, g)| update_transforms((e, c, *g)))
|
||||||
|
});
|
||||||
|
root_frame_query
|
||||||
|
.par_iter()
|
||||||
|
.for_each(|(e, c, g)| update_transforms((e, c, *g)));
|
||||||
|
root_frame_gridless_query.par_iter_mut().for_each(
|
||||||
|
|(entity, children, local, mut global, ignore_floating_origin)| {
|
||||||
|
if ignore_floating_origin {
|
||||||
|
*global = GlobalTransform::from(*local);
|
||||||
|
} else {
|
||||||
|
*global = root_frame.global_transform(&GridCell::ZERO, local);
|
||||||
|
}
|
||||||
|
update_transforms((entity, children, *global))
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// COPIED EXACTLY FROM BEVY
|
/// COPIED FROM BEVY
|
||||||
///
|
///
|
||||||
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before propagating
|
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
|
||||||
/// the transforms of any malformed entities and their descendants.
|
/// propagating the transforms of any malformed entities and their descendants.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// - While this function is running, `transform_query` must not have any fetches for `entity`,
|
/// - While this function is running, `transform_query` must not have any fetches for `entity`, nor
|
||||||
/// nor any of its descendants.
|
/// any of its descendants.
|
||||||
/// - The caller must ensure that the hierarchy leading to `entity`
|
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must remain
|
||||||
/// is well-formed and must remain as a tree or a forest. Each entity must have at most one parent.
|
/// as a tree or a forest. Each entity must have at most one parent.
|
||||||
unsafe fn propagate_recursive(
|
unsafe fn propagate_recursive<P: GridPrecision>(
|
||||||
parent: &GlobalTransform,
|
parent: &GlobalTransform,
|
||||||
transform_query: &Query<
|
transform_query: &Query<
|
||||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||||
With<Parent>,
|
(
|
||||||
|
With<Parent>,
|
||||||
|
Without<GridCell<P>>,
|
||||||
|
Without<ReferenceFrame<P>>,
|
||||||
|
),
|
||||||
>,
|
>,
|
||||||
parent_query: &Query<(Entity, Ref<Parent>)>,
|
parent_query: &Query<(Entity, Ref<Parent>)>,
|
||||||
entity: Entity,
|
entity: Entity,
|
||||||
mut changed: bool,
|
|
||||||
) {
|
) {
|
||||||
let (global_matrix, children) = {
|
let (global_matrix, children) = {
|
||||||
let Ok((transform, mut global_transform, children)) =
|
let Ok((transform, mut global_transform, children)) =
|
||||||
@ -123,10 +197,8 @@ unsafe fn propagate_recursive(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
changed |= transform.is_changed();
|
*global_transform = parent.mul_transform(*transform);
|
||||||
if changed {
|
|
||||||
*global_transform = parent.mul_transform(*transform);
|
|
||||||
}
|
|
||||||
(*global_transform, children)
|
(*global_transform, children)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -142,13 +214,7 @@ unsafe fn propagate_recursive(
|
|||||||
// The above assertion ensures that each child has one and only one unique parent throughout the
|
// The above assertion ensures that each child has one and only one unique parent throughout the
|
||||||
// entire hierarchy.
|
// entire hierarchy.
|
||||||
unsafe {
|
unsafe {
|
||||||
propagate_recursive(
|
propagate_recursive(&global_matrix, transform_query, parent_query, child);
|
||||||
&global_matrix,
|
|
||||||
transform_query,
|
|
||||||
parent_query,
|
|
||||||
child,
|
|
||||||
changed || actual_parent.is_changed(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
735
src/reference_frame/local_origin.rs
Normal file
735
src/reference_frame/local_origin.rs
Normal file
@ -0,0 +1,735 @@
|
|||||||
|
//! Describes how the floating origin's position is propagated through the hierarchy of reference
|
||||||
|
//! frames, and used to compute the floating origin's position relative to each reference frame.
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
ecs::{
|
||||||
|
prelude::*,
|
||||||
|
system::{
|
||||||
|
lifetimeless::{Read, Write},
|
||||||
|
SystemParam,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hierarchy::prelude::*,
|
||||||
|
log::prelude::*,
|
||||||
|
math::{prelude::*, DAffine3, DQuat},
|
||||||
|
transform::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{ReferenceFrame, RootReferenceFrame};
|
||||||
|
use crate::{FloatingOrigin, GridCell, GridPrecision};
|
||||||
|
|
||||||
|
pub use inner::LocalFloatingOrigin;
|
||||||
|
|
||||||
|
/// A module kept private to enforce use of setters and getters within the parent module.
|
||||||
|
mod inner {
|
||||||
|
use bevy::{
|
||||||
|
math::{prelude::*, DAffine3, DMat3, DQuat},
|
||||||
|
reflect::prelude::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{GridCell, GridPrecision};
|
||||||
|
|
||||||
|
/// An isometry that describes the location of the floating origin's grid cell's origin, in the
|
||||||
|
/// local reference frame.
|
||||||
|
///
|
||||||
|
/// Used to compute the [`GlobalTransform`](bevy::transform::components::GlobalTransform) of
|
||||||
|
/// every entity within a reference frame. Because this tells us where the floating origin cell
|
||||||
|
/// is located in the local frame, we can compute the inverse transform once, then use it to
|
||||||
|
/// transform every entity relative to the floating origin.
|
||||||
|
///
|
||||||
|
/// If the floating origin is in this local reference frame, the `float` fields will be
|
||||||
|
/// identity. The `float` fields` will be non-identity when the floating origin is in a
|
||||||
|
/// different reference frame that does not perfectly align with this one. Different reference
|
||||||
|
/// frames can be rotated and offset from each other - consider the reference frame of a planet,
|
||||||
|
/// spinning about its axis and orbiting about a star, it will not align with the reference
|
||||||
|
/// frame of the star system!
|
||||||
|
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
|
||||||
|
pub struct LocalFloatingOrigin<P: GridPrecision> {
|
||||||
|
/// The local cell that the floating origin's grid cell origin falls into.
|
||||||
|
cell: GridCell<P>,
|
||||||
|
/// The translation of floating origin's grid cell relative to the origin of
|
||||||
|
/// [`LocalFloatingOrigin::cell`].
|
||||||
|
translation: Vec3,
|
||||||
|
/// The rotation of the floating origin's grid cell relative to the origin of
|
||||||
|
/// [`LocalFloatingOrigin::cell`].
|
||||||
|
rotation: DQuat,
|
||||||
|
/// Transform from the local reference frame to the floating origin's grid cell. This is
|
||||||
|
/// used to compute the `GlobalTransform` of all entities in this reference frame.
|
||||||
|
///
|
||||||
|
/// Imagine you have the local reference frame and the floating origin's reference frame
|
||||||
|
/// overlapping in space, misaligned. This transform is the smallest possible that will
|
||||||
|
/// align the two reference frame grids, going from the local frame, to the floating
|
||||||
|
/// origin's frame.
|
||||||
|
///
|
||||||
|
/// This is like a camera's "view transform", but instead of transforming an object into a
|
||||||
|
/// camera's view space, this will transform an object into the floating origin's reference
|
||||||
|
/// frame.
|
||||||
|
/// - That object must be positioned in the same [`super::ReferenceFrame`] that this
|
||||||
|
/// [`LocalFloatingOrigin`] is part of.
|
||||||
|
/// - That object's position must be relative to the same grid cell as defined by
|
||||||
|
/// [`Self::cell`].
|
||||||
|
///
|
||||||
|
/// The above requirements help to ensure this transform has a small magnitude, maximizing
|
||||||
|
/// precision, and minimizing floating point error.
|
||||||
|
reference_frame_transform: DAffine3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||||
|
/// The reference frame transform from the local reference frame, to the floating origin's
|
||||||
|
/// reference frame. See [Self::reference_frame_transform].
|
||||||
|
pub fn reference_frame_transform(&self) -> DAffine3 {
|
||||||
|
self.reference_frame_transform
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets [`Self::cell`].
|
||||||
|
pub fn cell(&self) -> GridCell<P> {
|
||||||
|
self.cell
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets [`Self::translation`].
|
||||||
|
pub fn translation(&self) -> Vec3 {
|
||||||
|
self.translation
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets [`Self::rotation`].
|
||||||
|
pub fn rotation(&self) -> DQuat {
|
||||||
|
self.rotation
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update this local floating origin, and compute the new inverse transform.
|
||||||
|
pub fn set(
|
||||||
|
&mut self,
|
||||||
|
translation_grid: GridCell<P>,
|
||||||
|
translation_float: Vec3,
|
||||||
|
rotation_float: DQuat,
|
||||||
|
) {
|
||||||
|
self.cell = translation_grid;
|
||||||
|
self.translation = translation_float;
|
||||||
|
self.rotation = rotation_float;
|
||||||
|
|
||||||
|
self.reference_frame_transform = DAffine3 {
|
||||||
|
matrix3: DMat3::from_quat(self.rotation),
|
||||||
|
translation: self.translation.as_dvec3(),
|
||||||
|
}
|
||||||
|
.inverse()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`LocalFloatingOrigin`].
|
||||||
|
pub fn new(cell: GridCell<P>, translation: Vec3, rotation: DQuat) -> Self {
|
||||||
|
let reference_frame_transform = DAffine3 {
|
||||||
|
matrix3: DMat3::from_quat(rotation),
|
||||||
|
translation: translation.as_dvec3(),
|
||||||
|
}
|
||||||
|
.inverse();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
cell,
|
||||||
|
translation,
|
||||||
|
rotation,
|
||||||
|
reference_frame_transform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||||
|
/// Use the [`ReferenceFrames`] [`SystemParam`] to do useful things with this handle.
|
||||||
|
///
|
||||||
|
/// A reference frame can either be a node in the entity hierarchy stored as a component, or will be
|
||||||
|
/// the root reference frame, which is tracked with a resource. This handle is used to unify access
|
||||||
|
/// to reference frames with a single lightweight type.
|
||||||
|
pub enum ReferenceFrameHandle {
|
||||||
|
/// The reference frame is a node in the hierarchy, stored in a [`ReferenceFrame`] component.
|
||||||
|
Node(Entity),
|
||||||
|
/// The root reference frame, defined in the [`RootReferenceFrame`] resource.
|
||||||
|
Root,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReferenceFrameHandle {
|
||||||
|
/// Propagate the local origin position from `self` to `child`.
|
||||||
|
///
|
||||||
|
/// This is not a method on `References` to help prevent misuse when accidentally
|
||||||
|
/// swapping the position of arguments.
|
||||||
|
fn propagate_origin_to_child<P: GridPrecision>(
|
||||||
|
self,
|
||||||
|
reference_frames: &mut ReferenceFramesMut<P>,
|
||||||
|
child: ReferenceFrameHandle,
|
||||||
|
) {
|
||||||
|
let (this_frame, _this_cell, _this_transform) = reference_frames.get(self);
|
||||||
|
let (child_frame, child_cell, child_transform) = reference_frames.get(child);
|
||||||
|
|
||||||
|
// compute double precision translation of origin treating child as the origin grid cell. Add this to the origin's float translation in double,
|
||||||
|
let origin_cell_relative_to_child = this_frame.local_floating_origin.cell() - child_cell;
|
||||||
|
let origin_translation = this_frame.grid_position_double(
|
||||||
|
&origin_cell_relative_to_child,
|
||||||
|
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// then combine with rotation to get a double transform from the child's cell origin to the origin.
|
||||||
|
let origin_rotation = this_frame.local_floating_origin.rotation();
|
||||||
|
let origin_transform_child_cell_local =
|
||||||
|
DAffine3::from_rotation_translation(origin_rotation, origin_translation);
|
||||||
|
|
||||||
|
// Take the inverse of the child's transform as double (this is the "view" transform of the child reference frame)
|
||||||
|
let child_view_child_cell_local = DAffine3::from_rotation_translation(
|
||||||
|
child_transform.rotation.as_dquat(),
|
||||||
|
child_transform.translation.as_dvec3(),
|
||||||
|
)
|
||||||
|
.inverse();
|
||||||
|
|
||||||
|
// then multiply this by the double transform we got of the origin. This is now a transform64 of the origin, wrt to the child.
|
||||||
|
let origin_child_affine = child_view_child_cell_local * origin_transform_child_cell_local;
|
||||||
|
|
||||||
|
// We can decompose into translation (high precision) and rotation.
|
||||||
|
let (_, origin_child_rotation, origin_child_translation) =
|
||||||
|
origin_child_affine.to_scale_rotation_translation();
|
||||||
|
let (child_origin_cell, child_origin_translation_float) =
|
||||||
|
child_frame.translation_to_grid(origin_child_translation);
|
||||||
|
|
||||||
|
reference_frames.update(child, |child_frame, _, _| {
|
||||||
|
child_frame.local_floating_origin.set(
|
||||||
|
child_origin_cell,
|
||||||
|
child_origin_translation_float,
|
||||||
|
origin_child_rotation,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn propagate_origin_to_parent<P: GridPrecision>(
|
||||||
|
self,
|
||||||
|
reference_frames: &mut ReferenceFramesMut<P>,
|
||||||
|
parent: ReferenceFrameHandle,
|
||||||
|
) {
|
||||||
|
let (this_frame, this_cell, this_transform) = reference_frames.get(self);
|
||||||
|
let (parent_frame, _parent_cell, _parent_transform) = reference_frames.get(parent);
|
||||||
|
|
||||||
|
// Get this frame's double precision transform, relative to its cell. We ignore the grid
|
||||||
|
// cell here because we don't want to lose precision - we can do these calcs relative to
|
||||||
|
// this cell, then add the grid cell offset at the end.
|
||||||
|
let this_transform = DAffine3::from_rotation_translation(
|
||||||
|
this_transform.rotation.as_dquat(),
|
||||||
|
this_transform.translation.as_dvec3(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the origin's double position in this reference frame
|
||||||
|
let origin_translation = this_frame.grid_position_double(
|
||||||
|
&this_frame.local_floating_origin.cell(),
|
||||||
|
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||||
|
);
|
||||||
|
let this_local_origin_transform = DAffine3::from_rotation_translation(
|
||||||
|
this_frame.local_floating_origin.rotation(),
|
||||||
|
origin_translation,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Multiply to move the origin into the parent's reference frame
|
||||||
|
let origin_affine = this_transform * this_local_origin_transform;
|
||||||
|
|
||||||
|
let (_, origin_rot, origin_trans) = origin_affine.to_scale_rotation_translation();
|
||||||
|
let (origin_cell_relative_to_this_cell, origin_translation_remainder) =
|
||||||
|
parent_frame.translation_to_grid(origin_trans);
|
||||||
|
|
||||||
|
// Up until now we have been computing as if this cell is located at the origin, to maximize
|
||||||
|
// precision. Now that we are done with floats, we can add the cell offset.
|
||||||
|
let parent_origin_cell = origin_cell_relative_to_this_cell + this_cell;
|
||||||
|
|
||||||
|
reference_frames.update(parent, |parent_frame, _, _| {
|
||||||
|
parent_frame.local_floating_origin.set(
|
||||||
|
parent_origin_cell,
|
||||||
|
origin_translation_remainder,
|
||||||
|
origin_rot,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to access a reference frame using a single system param. Needed because the reference frame
|
||||||
|
/// could either be a component or a resource (if at the root of the hierarchy).
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub struct ReferenceFrames<'w, 's, P: GridPrecision> {
|
||||||
|
parent: Query<'w, 's, Read<Parent>>,
|
||||||
|
frame_root: Res<'w, RootReferenceFrame<P>>,
|
||||||
|
frame_query: Query<'w, 's, (Entity, Read<ReferenceFrame<P>>)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, P: GridPrecision> ReferenceFrames<'w, 's, P> {
|
||||||
|
/// Get the reference frame from a handle.
|
||||||
|
pub fn resolve_handle(&self, handle: ReferenceFrameHandle) -> &ReferenceFrame<P> {
|
||||||
|
match handle {
|
||||||
|
ReferenceFrameHandle::Node(frame_entity) => self
|
||||||
|
.frame_query
|
||||||
|
.get(frame_entity)
|
||||||
|
.map(|(_entity, frame)| frame)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("{} {handle:?} failed to resolve.\n\tEnsure all GridPrecision components are using the <{}> generic, and all required components are present.\n\tQuery Error: {e}", std::any::type_name::<ReferenceFrameHandle>(), std::any::type_name::<P>())
|
||||||
|
}),
|
||||||
|
ReferenceFrameHandle::Root => {
|
||||||
|
&self.frame_root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to this entity's reference frame, if it exists.
|
||||||
|
#[inline]
|
||||||
|
pub fn get_handle(&self, this: Entity) -> Option<ReferenceFrameHandle> {
|
||||||
|
match self.parent.get(this).map(|parent| **parent) {
|
||||||
|
Err(_) => Some(ReferenceFrameHandle::Root),
|
||||||
|
Ok(parent) => match self.frame_query.contains(parent) {
|
||||||
|
true => Some(ReferenceFrameHandle::Node(parent)),
|
||||||
|
false => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a reference to this entity's reference frame, if it exists.
|
||||||
|
#[inline]
|
||||||
|
pub fn get(&self, this: Entity) -> Option<&ReferenceFrame<P>> {
|
||||||
|
self.get_handle(this)
|
||||||
|
.map(|handle| self.resolve_handle(handle))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used to access a reference frame. Needed because the reference frame could either be a
|
||||||
|
/// component, or a resource if at the root of the hierarchy.
|
||||||
|
#[derive(SystemParam)]
|
||||||
|
pub struct ReferenceFramesMut<'w, 's, P: GridPrecision> {
|
||||||
|
parent: Query<'w, 's, Read<Parent>>,
|
||||||
|
children: Query<'w, 's, Read<Children>>,
|
||||||
|
frame_root: ResMut<'w, RootReferenceFrame<P>>,
|
||||||
|
frame_query: Query<
|
||||||
|
'w,
|
||||||
|
's,
|
||||||
|
(
|
||||||
|
Entity,
|
||||||
|
Read<GridCell<P>>,
|
||||||
|
Read<Transform>,
|
||||||
|
Write<ReferenceFrame<P>>,
|
||||||
|
Option<Read<Parent>>,
|
||||||
|
),
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w, 's, P: GridPrecision> ReferenceFramesMut<'w, 's, P> {
|
||||||
|
/// Get mutable access to the [`ReferenceFrame`], and run the provided function or closure,
|
||||||
|
/// optionally returning data.
|
||||||
|
///
|
||||||
|
/// ## Panics
|
||||||
|
///
|
||||||
|
/// This will panic if the handle passed in is invalid.
|
||||||
|
///
|
||||||
|
/// ## Why a closure?
|
||||||
|
///
|
||||||
|
/// This expects a closure because the reference frame could be stored as a component or a
|
||||||
|
/// resource, making it difficult (impossible?) to return a mutable reference to the reference
|
||||||
|
/// frame when the types involved are different. The main issue seems to be that the component
|
||||||
|
/// is returned as a `Mut<T>`; getting a mutable reference to the internal value requires that
|
||||||
|
/// this function return a reference to a value owned by the function.
|
||||||
|
///
|
||||||
|
/// I tried returning an enum or a boxed trait object, but ran into issues expressing the
|
||||||
|
/// lifetimes. Worth revisiting if this turns out to be annoying, but seems pretty insignificant
|
||||||
|
/// at the time of writing.
|
||||||
|
#[inline]
|
||||||
|
pub fn update<T>(
|
||||||
|
&mut self,
|
||||||
|
handle: ReferenceFrameHandle,
|
||||||
|
mut func: impl FnMut(&mut ReferenceFrame<P>, &GridCell<P>, &Transform) -> T,
|
||||||
|
) -> T {
|
||||||
|
match handle {
|
||||||
|
ReferenceFrameHandle::Node(frame_entity) => self
|
||||||
|
.frame_query
|
||||||
|
.get_mut(frame_entity)
|
||||||
|
.map(|(_entity, cell, transform, mut frame, _parent)| {
|
||||||
|
func(frame.as_mut(), cell, transform)
|
||||||
|
})
|
||||||
|
.expect("The supplied reference frame handle to node is no longer valid."),
|
||||||
|
ReferenceFrameHandle::Root => func(
|
||||||
|
&mut self.frame_root,
|
||||||
|
&GridCell::default(), // the reference frame itself is not within another.
|
||||||
|
&Transform::default(), // the reference frame itself is not within another.
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the reference frame and the position of the reference frame from a handle.
|
||||||
|
pub fn get(
|
||||||
|
&self,
|
||||||
|
handle: ReferenceFrameHandle,
|
||||||
|
) -> (&ReferenceFrame<P>, GridCell<P>, Transform) {
|
||||||
|
match handle {
|
||||||
|
ReferenceFrameHandle::Node(frame_entity) => self
|
||||||
|
.frame_query
|
||||||
|
.get(frame_entity)
|
||||||
|
.map(|(_entity, cell, transform, frame, _parent)| (frame, *cell, *transform))
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
panic!("{} {handle:?} failed to resolve.\n\tEnsure all GridPrecision components are using the <{}> generic, and all required components are present.\n\tQuery Error: {e}", std::any::type_name::<ReferenceFrameHandle>(), std::any::type_name::<P>())
|
||||||
|
}),
|
||||||
|
ReferenceFrameHandle::Root => {
|
||||||
|
(&self.frame_root, GridCell::default(), Transform::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to this entity's reference frame, if it exists.
|
||||||
|
#[inline]
|
||||||
|
fn get_handle(&self, this: Entity) -> Option<ReferenceFrameHandle> {
|
||||||
|
match self.parent.get(this).map(|parent| **parent) {
|
||||||
|
Err(_) => Some(ReferenceFrameHandle::Root),
|
||||||
|
Ok(parent) => match self.frame_query.contains(parent) {
|
||||||
|
true => Some(ReferenceFrameHandle::Node(parent)),
|
||||||
|
false => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the parent reference frame of this reference frame, if it exists.
|
||||||
|
#[inline]
|
||||||
|
fn parent(&mut self, this: ReferenceFrameHandle) -> Option<ReferenceFrameHandle> {
|
||||||
|
match this {
|
||||||
|
ReferenceFrameHandle::Node(this) => self.get_handle(this),
|
||||||
|
ReferenceFrameHandle::Root => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get handles to all reference frames that are children of this reference frame. Applies a
|
||||||
|
/// filter to the returned children.
|
||||||
|
#[inline]
|
||||||
|
fn children_filtered(
|
||||||
|
&mut self,
|
||||||
|
this: ReferenceFrameHandle,
|
||||||
|
mut filter: impl FnMut(Entity) -> bool,
|
||||||
|
) -> Vec<ReferenceFrameHandle> {
|
||||||
|
match this {
|
||||||
|
ReferenceFrameHandle::Node(this) => self
|
||||||
|
.children
|
||||||
|
.get(this)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|c| c.iter())
|
||||||
|
.filter(|entity| filter(**entity))
|
||||||
|
.filter(|child| self.frame_query.contains(**child))
|
||||||
|
.map(|child| ReferenceFrameHandle::Node(*child))
|
||||||
|
.collect(),
|
||||||
|
ReferenceFrameHandle::Root => self
|
||||||
|
.frame_query
|
||||||
|
.iter()
|
||||||
|
.filter(|(entity, ..)| filter(*entity))
|
||||||
|
.filter(|(.., parent)| parent.is_none())
|
||||||
|
.map(|(entity, ..)| ReferenceFrameHandle::Node(entity))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get handles to all reference frames that are children of this reference frame.
|
||||||
|
#[inline]
|
||||||
|
fn children(&mut self, this: ReferenceFrameHandle) -> Vec<ReferenceFrameHandle> {
|
||||||
|
self.children_filtered(this, |_| true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get handles to all reference frames that are siblings of this reference frame.
|
||||||
|
#[inline]
|
||||||
|
fn siblings(&mut self, this: ReferenceFrameHandle) -> Vec<ReferenceFrameHandle> {
|
||||||
|
match this {
|
||||||
|
ReferenceFrameHandle::Node(this_entity) => {
|
||||||
|
if let Some(parent) = self.parent(this) {
|
||||||
|
self.children_filtered(parent, |e| e != this_entity)
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReferenceFrameHandle::Root => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||||
|
/// Update the [`LocalFloatingOrigin`] of every [`ReferenceFrame`] in the world. This does not
|
||||||
|
/// update any entity transforms, instead this is a preceding step that updates every reference
|
||||||
|
/// frame, so it knows where the floating origin is located with respect to that reference
|
||||||
|
/// frame. This is all done in high precision if possible, however any loss in precision will
|
||||||
|
/// only affect the rendering precision. The high precision coordinates ([`GridCell`] and
|
||||||
|
/// [`Transform`]) are the source of truth and never mutated.
|
||||||
|
pub fn update(
|
||||||
|
origin: Query<(Entity, &GridCell<P>), With<FloatingOrigin>>,
|
||||||
|
mut reference_frames: ReferenceFramesMut<P>,
|
||||||
|
mut frame_stack: Local<Vec<ReferenceFrameHandle>>,
|
||||||
|
) {
|
||||||
|
/// The maximum reference frame tree depth, defensively prevents infinite looping in case
|
||||||
|
/// there is a degenerate hierarchy. It might take a while, but at least it's not forever?
|
||||||
|
const MAX_REFERENCE_FRAME_DEPTH: usize = usize::MAX;
|
||||||
|
|
||||||
|
let (origin_entity, origin_cell) = origin
|
||||||
|
.get_single()
|
||||||
|
.expect("There can only be one entity with the `FloatingOrigin` component.");
|
||||||
|
|
||||||
|
let Some(mut this_frame) = reference_frames.get_handle(origin_entity) else {
|
||||||
|
error!("The floating origin is not in a valid reference frame. The floating origin entity must be a child of an entity with the `ReferenceFrame`, `GridCell`, and `Transform` components, or be at the root of the parent-child hierarchy.");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Prepare by resetting the `origin_transform` of the floating origin's reference frame.
|
||||||
|
// Because the floating origin is within this reference frame, there is no grid misalignment
|
||||||
|
// and thus no need for any floating offsets.
|
||||||
|
reference_frames.update(this_frame, |frame, _, _| {
|
||||||
|
frame
|
||||||
|
.local_floating_origin
|
||||||
|
.set(*origin_cell, Vec3::ZERO, DQuat::IDENTITY);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Seed the frame stack with the floating origin's reference frame. From this point out, we
|
||||||
|
// will only look at siblings and parents, which will allow us to visit the entire tree.
|
||||||
|
frame_stack.clear();
|
||||||
|
frame_stack.push(this_frame);
|
||||||
|
|
||||||
|
// Recurse up and across the tree, updating siblings and their children.
|
||||||
|
for _ in 0..MAX_REFERENCE_FRAME_DEPTH {
|
||||||
|
// We start by propagating up to the parent of this frame, then propagating down to the
|
||||||
|
// siblings of this frame (children of the parent that are not this frame).
|
||||||
|
if let Some(parent_frame) = reference_frames.parent(this_frame) {
|
||||||
|
this_frame.propagate_origin_to_parent(&mut reference_frames, parent_frame);
|
||||||
|
for sibling_frame in reference_frames.siblings(this_frame) {
|
||||||
|
// The siblings of this frame are also the children of the parent frame.
|
||||||
|
parent_frame.propagate_origin_to_child(&mut reference_frames, sibling_frame);
|
||||||
|
frame_stack.push(sibling_frame); // We'll recurse through children next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All of the reference frames pushed on the stack have been processed. We can now pop
|
||||||
|
// those off the stack and recursively process their children all the way out to the
|
||||||
|
// leaves of the tree.
|
||||||
|
while let Some(this_frame) = frame_stack.pop() {
|
||||||
|
for child_frame in reference_frames.children(this_frame) {
|
||||||
|
this_frame.propagate_origin_to_child(&mut reference_frames, child_frame);
|
||||||
|
frame_stack.push(child_frame) // Push processed child onto the stack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, now that the siblings of this frame have been recursively processed, we
|
||||||
|
// process the parent and set it as the current reference frame. Note that every time we
|
||||||
|
// step to a parent, "this frame" and all descendants have already been processed, so we
|
||||||
|
// only need to process the siblings.
|
||||||
|
match reference_frames.parent(this_frame) {
|
||||||
|
Some(parent_frame) => this_frame = parent_frame,
|
||||||
|
None => return, // We have reached the root of the tree, and can exit.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error!("Reached the maximum reference frame depth ({MAX_REFERENCE_FRAME_DEPTH}), and exited early to prevent an infinite loop. This might be caused by a degenerate hierarchy.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bevy::{ecs::system::SystemState, math::DVec3};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::*;
|
||||||
|
|
||||||
|
/// Test that the reference frame getters do what they say they do.
|
||||||
|
#[test]
|
||||||
|
fn frame_hierarchy_getters() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||||
|
|
||||||
|
let frame_bundle = (
|
||||||
|
Transform::default(),
|
||||||
|
GridCell::<i32>::default(),
|
||||||
|
ReferenceFrame::<i32>::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let child_1 = app.world.spawn(frame_bundle.clone()).id();
|
||||||
|
let child_2 = app.world.spawn(frame_bundle.clone()).id();
|
||||||
|
let parent = app.world.spawn(frame_bundle.clone()).id();
|
||||||
|
app.world
|
||||||
|
.entity_mut(parent)
|
||||||
|
.push_children(&[child_1, child_2]);
|
||||||
|
|
||||||
|
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||||
|
let mut ref_frame = state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
// Children
|
||||||
|
let result = ref_frame.children(ReferenceFrameHandle::Root);
|
||||||
|
assert_eq!(result, vec![ReferenceFrameHandle::Node(parent)]);
|
||||||
|
let result = ref_frame.children(ReferenceFrameHandle::Node(parent));
|
||||||
|
assert!(result.contains(&ReferenceFrameHandle::Node(child_1)));
|
||||||
|
assert!(result.contains(&ReferenceFrameHandle::Node(child_2)));
|
||||||
|
let result = ref_frame.children(ReferenceFrameHandle::Node(child_1));
|
||||||
|
assert_eq!(result, Vec::new());
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
let result = ref_frame.parent(ReferenceFrameHandle::Root);
|
||||||
|
assert_eq!(result, None);
|
||||||
|
let result = ref_frame.parent(ReferenceFrameHandle::Node(parent));
|
||||||
|
assert_eq!(result, Some(ReferenceFrameHandle::Root));
|
||||||
|
let result = ref_frame.parent(ReferenceFrameHandle::Node(child_1));
|
||||||
|
assert_eq!(result, Some(ReferenceFrameHandle::Node(parent)));
|
||||||
|
|
||||||
|
// Siblings
|
||||||
|
let result = ref_frame.siblings(ReferenceFrameHandle::Root);
|
||||||
|
assert_eq!(result, vec![]);
|
||||||
|
let result = ref_frame.siblings(ReferenceFrameHandle::Node(parent));
|
||||||
|
assert_eq!(result, vec![]);
|
||||||
|
let result = ref_frame.siblings(ReferenceFrameHandle::Node(child_1));
|
||||||
|
assert_eq!(result, vec![ReferenceFrameHandle::Node(child_2)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn child_propagation() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||||
|
|
||||||
|
let root = ReferenceFrameHandle::Root;
|
||||||
|
|
||||||
|
app.insert_resource(RootReferenceFrame(ReferenceFrame {
|
||||||
|
local_floating_origin: LocalFloatingOrigin::new(
|
||||||
|
GridCell::<i32>::new(1_000_000, -1, -1),
|
||||||
|
Vec3::ZERO,
|
||||||
|
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||||
|
),
|
||||||
|
..default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let child = app
|
||||||
|
.world
|
||||||
|
.spawn((
|
||||||
|
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
|
||||||
|
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
|
||||||
|
GridCell::<i32>::new(1_000_000, 0, 0),
|
||||||
|
ReferenceFrame::<i32>::default(),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let child = ReferenceFrameHandle::Node(child);
|
||||||
|
|
||||||
|
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||||
|
let mut reference_frames = state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
// The function we are testing
|
||||||
|
root.propagate_origin_to_child(&mut reference_frames, child);
|
||||||
|
|
||||||
|
let (child_frame, ..) = reference_frames.get(child);
|
||||||
|
|
||||||
|
let computed_grid = child_frame.local_floating_origin.cell();
|
||||||
|
let correct_grid = GridCell::new(-1, 0, -1);
|
||||||
|
assert_eq!(computed_grid, correct_grid);
|
||||||
|
|
||||||
|
let computed_rot = child_frame.local_floating_origin.rotation();
|
||||||
|
let correct_rot = DQuat::from_rotation_z(std::f64::consts::PI);
|
||||||
|
let rot_error = computed_rot.angle_between(correct_rot);
|
||||||
|
assert!(rot_error < 1e-10);
|
||||||
|
|
||||||
|
// Even though we are 2 billion units from the origin, our precision is still pretty good.
|
||||||
|
// The loss of precision is coming from the affine multiplication that moves the origin into
|
||||||
|
// the child's reference frame. The good news is that precision loss only scales with the
|
||||||
|
// distance of the origin to the child (in the child's reference frame). In this test we are
|
||||||
|
// saying that the floating origin is - with respect to the root - pretty near the child.
|
||||||
|
// Even though the child and floating origin are very far from the origin, we only lose
|
||||||
|
// precision based on how for the origin is from the child.
|
||||||
|
let computed_trans = child_frame.local_floating_origin.translation();
|
||||||
|
let correct_trans = Vec3::new(-1.0, 1.0, 0.0);
|
||||||
|
let trans_error = computed_trans.distance(correct_trans);
|
||||||
|
assert!(trans_error < 1e-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parent_propagation() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(FloatingOriginPlugin::<i64>::default());
|
||||||
|
|
||||||
|
let root = ReferenceFrameHandle::Root;
|
||||||
|
|
||||||
|
let child = app
|
||||||
|
.world
|
||||||
|
.spawn((
|
||||||
|
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
|
||||||
|
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
|
||||||
|
GridCell::<i64>::new(150_000_003_000, 0, 0), // roughly radius of earth orbit
|
||||||
|
ReferenceFrame {
|
||||||
|
local_floating_origin: LocalFloatingOrigin::new(
|
||||||
|
GridCell::<i64>::new(0, 3_000, 0),
|
||||||
|
Vec3::new(5.0, 5.0, 0.0),
|
||||||
|
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||||
|
),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let child = ReferenceFrameHandle::Node(child);
|
||||||
|
|
||||||
|
let mut state = SystemState::<ReferenceFramesMut<i64>>::new(&mut app.world);
|
||||||
|
let mut reference_frames = state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
// The function we are testing
|
||||||
|
child.propagate_origin_to_parent(&mut reference_frames, root);
|
||||||
|
|
||||||
|
let (root_frame, ..) = reference_frames.get(root);
|
||||||
|
|
||||||
|
let computed_grid = root_frame.local_floating_origin.cell();
|
||||||
|
let correct_grid = GridCell::new(150_000_000_000, 0, 0);
|
||||||
|
assert_eq!(computed_grid, correct_grid);
|
||||||
|
|
||||||
|
let computed_rot = root_frame.local_floating_origin.rotation();
|
||||||
|
let correct_rot = DQuat::IDENTITY;
|
||||||
|
let rot_error = computed_rot.angle_between(correct_rot);
|
||||||
|
assert!(rot_error < 1e-7);
|
||||||
|
|
||||||
|
// This is the error of the position of the floating origin if the origin was a person
|
||||||
|
// standing on earth, and their position was resampled with respect to the sun. This is 0.3
|
||||||
|
// meters, but recall that this will be the error when positioning the other planets in the
|
||||||
|
// solar system when rendering.
|
||||||
|
//
|
||||||
|
// This error scales with the distance of the floating origin from the origin of its
|
||||||
|
// reference frame, in this case the radius of the earth, not the radius of the orbit.
|
||||||
|
let computed_trans = root_frame.local_floating_origin.translation();
|
||||||
|
let correct_trans = Vec3::new(-4.0, 6.0, 0.0);
|
||||||
|
let trans_error = computed_trans.distance(correct_trans);
|
||||||
|
assert!(trans_error < 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn origin_transform() {
|
||||||
|
let mut app = App::new();
|
||||||
|
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||||
|
|
||||||
|
let root = ReferenceFrameHandle::Root;
|
||||||
|
|
||||||
|
app.insert_resource(RootReferenceFrame(ReferenceFrame {
|
||||||
|
local_floating_origin: LocalFloatingOrigin::new(
|
||||||
|
GridCell::<i32>::new(0, 0, 0),
|
||||||
|
Vec3::new(1.0, 1.0, 0.0),
|
||||||
|
DQuat::from_rotation_z(0.0),
|
||||||
|
),
|
||||||
|
..default()
|
||||||
|
}));
|
||||||
|
|
||||||
|
let child = app
|
||||||
|
.world
|
||||||
|
.spawn((
|
||||||
|
Transform::default()
|
||||||
|
.with_rotation(Quat::from_rotation_z(-std::f32::consts::FRAC_PI_2))
|
||||||
|
.with_translation(Vec3::new(3.0, 3.0, 0.0)),
|
||||||
|
GridCell::<i32>::new(0, 0, 0),
|
||||||
|
ReferenceFrame::<i32>::default(),
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let child = ReferenceFrameHandle::Node(child);
|
||||||
|
|
||||||
|
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||||
|
let mut reference_frames = state.get_mut(&mut app.world);
|
||||||
|
|
||||||
|
root.propagate_origin_to_child(&mut reference_frames, child);
|
||||||
|
|
||||||
|
let (child_frame, ..) = reference_frames.get(child);
|
||||||
|
let child_local_point = DVec3::new(5.0, 5.0, 0.0);
|
||||||
|
|
||||||
|
let computed_transform = child_frame
|
||||||
|
.local_floating_origin
|
||||||
|
.reference_frame_transform();
|
||||||
|
let computed_pos = computed_transform.transform_point3(child_local_point);
|
||||||
|
|
||||||
|
let correct_transform = DAffine3::from_rotation_translation(
|
||||||
|
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||||
|
DVec3::new(2.0, 2.0, 0.0),
|
||||||
|
);
|
||||||
|
let correct_pos = correct_transform.transform_point3(child_local_point);
|
||||||
|
|
||||||
|
assert!((computed_pos - correct_pos).length() < 1e-6);
|
||||||
|
assert!((computed_pos - DVec3::new(7.0, -3.0, 0.0)).length() < 1e-6);
|
||||||
|
}
|
||||||
|
}
|
||||||
173
src/reference_frame/mod.rs
Normal file
173
src/reference_frame/mod.rs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
//! Adds the concept of hierarchical, nesting [`ReferenceFrame`]s, to group entities that move
|
||||||
|
//! through space together, like entities on a planet, rotating about the planet's axis, and,
|
||||||
|
//! orbiting a star.
|
||||||
|
|
||||||
|
use bevy::{
|
||||||
|
ecs::{component::Component, system::Resource},
|
||||||
|
math::{Affine3A, DAffine3, DVec3, Vec3},
|
||||||
|
prelude::{Deref, DerefMut},
|
||||||
|
reflect::Reflect,
|
||||||
|
transform::components::{GlobalTransform, Transform},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{GridCell, GridPrecision};
|
||||||
|
|
||||||
|
use self::local_origin::LocalFloatingOrigin;
|
||||||
|
|
||||||
|
pub mod local_origin;
|
||||||
|
|
||||||
|
/// All entities that have no parent are implicitly in the root [`ReferenceFrame`].
|
||||||
|
///
|
||||||
|
/// Because this relationship is implicit, it lives outside of the entity/component hierarchy, and
|
||||||
|
/// is a singleton; this is why the root reference frame is a resource unlike all other
|
||||||
|
/// [`ReferenceFrame`]s which are components.
|
||||||
|
#[derive(Debug, Clone, Resource, Reflect, Deref, DerefMut)]
|
||||||
|
pub struct RootReferenceFrame<P: GridPrecision>(pub(crate) ReferenceFrame<P>);
|
||||||
|
|
||||||
|
/// A component that defines a reference frame for children of this entity with [`GridCell`]s.
|
||||||
|
///
|
||||||
|
/// Entities without a parent are implicitly in the [`RootReferenceFrame`].
|
||||||
|
///
|
||||||
|
/// ## Motivation
|
||||||
|
///
|
||||||
|
/// Reference frames are hierarchical, allowing more precision for objects with similar relative
|
||||||
|
/// velocities. Entities in the same reference frame as the [`crate::FloatingOrigin`] will be
|
||||||
|
/// rendered with the most precision. Reference frames are transformed relative to each other
|
||||||
|
/// using 64 bit float transforms.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// You can use reference frames to ensure all entities on a planet, and the planet itself, are in
|
||||||
|
/// the same rotating reference frame, instead of moving rapidly through space around a star, or
|
||||||
|
/// worse, around the center of the galaxy.
|
||||||
|
#[derive(Debug, Clone, Reflect, Component)]
|
||||||
|
pub struct ReferenceFrame<P: GridPrecision> {
|
||||||
|
/// The high-precision position of the floating origin's current grid cell local to this
|
||||||
|
/// reference frame.
|
||||||
|
local_floating_origin: LocalFloatingOrigin<P>,
|
||||||
|
/// Defines the uniform scale of the grid by the length of the edge of a grid cell.
|
||||||
|
cell_edge_length: f32,
|
||||||
|
/// How far an entity can move from the origin before its grid cell is recomputed.
|
||||||
|
maximum_distance_from_origin: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: GridPrecision> Default for ReferenceFrame<P> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(2_000f32, 100f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: GridPrecision> ReferenceFrame<P> {
|
||||||
|
/// Construct a new [`ReferenceFrame`]. The properties of a reference frame cannot be changed
|
||||||
|
/// after construction.
|
||||||
|
pub fn new(cell_edge_length: f32, switching_threshold: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
local_floating_origin: LocalFloatingOrigin::default(),
|
||||||
|
cell_edge_length,
|
||||||
|
maximum_distance_from_origin: cell_edge_length / 2.0 + switching_threshold,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the position of the floating origin relative to the current reference frame.
|
||||||
|
pub fn local_floating_origin(&self) -> &LocalFloatingOrigin<P> {
|
||||||
|
&self.local_floating_origin
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the size of each cell this reference frame's grid.
|
||||||
|
pub fn cell_edge_length(&self) -> f32 {
|
||||||
|
self.cell_edge_length
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the reference frame's [`Self::maximum_distance_from_origin`].
|
||||||
|
pub fn maximum_distance_from_origin(&self) -> f32 {
|
||||||
|
self.maximum_distance_from_origin
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the double precision position of an entity's [`Transform`] with respect to the given
|
||||||
|
/// [`GridCell`] within this reference frame.
|
||||||
|
pub fn grid_position_double(&self, pos: &GridCell<P>, transform: &Transform) -> DVec3 {
|
||||||
|
DVec3 {
|
||||||
|
x: pos.x.as_f64() * self.cell_edge_length as f64 + transform.translation.x as f64,
|
||||||
|
y: pos.y.as_f64() * self.cell_edge_length as f64 + transform.translation.y as f64,
|
||||||
|
z: pos.z.as_f64() * self.cell_edge_length as f64 + transform.translation.z as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the single precision position of an entity's [`Transform`] with respect to the given
|
||||||
|
/// [`GridCell`].
|
||||||
|
pub fn grid_position(&self, pos: &GridCell<P>, transform: &Transform) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: pos.x.as_f64() as f32 * self.cell_edge_length + transform.translation.x,
|
||||||
|
y: pos.y.as_f64() as f32 * self.cell_edge_length + transform.translation.y,
|
||||||
|
z: pos.z.as_f64() as f32 * self.cell_edge_length + transform.translation.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the floating point position of a [`GridCell`].
|
||||||
|
pub fn grid_to_float(&self, pos: &GridCell<P>) -> DVec3 {
|
||||||
|
DVec3 {
|
||||||
|
x: pos.x.as_f64() * self.cell_edge_length as f64,
|
||||||
|
y: pos.y.as_f64() * self.cell_edge_length as f64,
|
||||||
|
z: pos.z.as_f64() * self.cell_edge_length as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a large translation into a small translation relative to a grid cell.
|
||||||
|
pub fn translation_to_grid(&self, input: impl Into<DVec3>) -> (GridCell<P>, Vec3) {
|
||||||
|
let l = self.cell_edge_length as f64;
|
||||||
|
let input = input.into();
|
||||||
|
let DVec3 { x, y, z } = input;
|
||||||
|
|
||||||
|
if input.abs().max_element() < self.maximum_distance_from_origin as f64 {
|
||||||
|
return (GridCell::default(), input.as_vec3());
|
||||||
|
}
|
||||||
|
|
||||||
|
let x_r = (x / l).round();
|
||||||
|
let y_r = (y / l).round();
|
||||||
|
let z_r = (z / l).round();
|
||||||
|
let t_x = x - x_r * l;
|
||||||
|
let t_y = y - y_r * l;
|
||||||
|
let t_z = z - z_r * l;
|
||||||
|
|
||||||
|
(
|
||||||
|
GridCell {
|
||||||
|
x: P::from_f32(x_r as f32),
|
||||||
|
y: P::from_f32(y_r as f32),
|
||||||
|
z: P::from_f32(z_r as f32),
|
||||||
|
},
|
||||||
|
Vec3::new(t_x as f32, t_y as f32, t_z as f32),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a large translation into a small translation relative to a grid cell.
|
||||||
|
pub fn imprecise_translation_to_grid(&self, input: Vec3) -> (GridCell<P>, Vec3) {
|
||||||
|
self.translation_to_grid(input.as_dvec3())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute the [`GlobalTransform`] of an entity in this reference frame.
|
||||||
|
pub fn global_transform(
|
||||||
|
&self,
|
||||||
|
local_cell: &GridCell<P>,
|
||||||
|
local_transform: &Transform,
|
||||||
|
) -> GlobalTransform {
|
||||||
|
// The reference frame transform from the floating origin's reference frame, to the local
|
||||||
|
// reference frame.
|
||||||
|
let transform_origin = self.local_floating_origin().reference_frame_transform();
|
||||||
|
// The grid cell offset of this entity relative to the floating origin's cell in this local
|
||||||
|
// reference frame.
|
||||||
|
let cell_origin_relative = *local_cell - self.local_floating_origin().cell();
|
||||||
|
let grid_offset = self.grid_to_float(&cell_origin_relative);
|
||||||
|
let local_transform = DAffine3::from_scale_rotation_translation(
|
||||||
|
local_transform.scale.as_dvec3(),
|
||||||
|
local_transform.rotation.as_dquat(),
|
||||||
|
local_transform.translation.as_dvec3() + grid_offset,
|
||||||
|
);
|
||||||
|
let global_64 = transform_origin * local_transform;
|
||||||
|
|
||||||
|
Affine3A {
|
||||||
|
matrix3: global_64.matrix3.as_mat3().into(),
|
||||||
|
translation: global_64.translation.as_vec3a(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,8 +5,8 @@ use bevy::ecs::query::QueryData;
|
|||||||
use bevy::math::DVec3;
|
use bevy::math::DVec3;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::precision::GridPrecision;
|
use crate::GridCell;
|
||||||
use crate::{FloatingOriginSettings, GridCell};
|
use crate::{precision::GridPrecision, reference_frame::ReferenceFrame};
|
||||||
|
|
||||||
#[derive(QueryData)]
|
#[derive(QueryData)]
|
||||||
#[query_data(mutable)]
|
#[query_data(mutable)]
|
||||||
@ -23,13 +23,13 @@ pub struct GridTransform<P: GridPrecision> {
|
|||||||
|
|
||||||
impl<'w, P: GridPrecision> GridTransformItem<'w, P> {
|
impl<'w, P: GridPrecision> GridTransformItem<'w, P> {
|
||||||
/// Compute the global position with double precision.
|
/// Compute the global position with double precision.
|
||||||
pub fn position_double(&self, settings: &FloatingOriginSettings) -> DVec3 {
|
pub fn position_double(&self, reference_frame: &ReferenceFrame<P>) -> DVec3 {
|
||||||
settings.grid_position_double(&self.cell, &self.transform)
|
reference_frame.grid_position_double(&self.cell, &self.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the global position.
|
/// Compute the global position.
|
||||||
pub fn position(&self, settings: &FloatingOriginSettings) -> Vec3 {
|
pub fn position(&self, reference_frame: &ReferenceFrame<P>) -> Vec3 {
|
||||||
settings.grid_position(&self.cell, &self.transform)
|
reference_frame.grid_position(&self.cell, &self.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the fields to work with.
|
/// Get a copy of the fields to work with.
|
||||||
@ -43,13 +43,13 @@ impl<'w, P: GridPrecision> GridTransformItem<'w, P> {
|
|||||||
|
|
||||||
impl<'w, P: GridPrecision> GridTransformReadOnlyItem<'w, P> {
|
impl<'w, P: GridPrecision> GridTransformReadOnlyItem<'w, P> {
|
||||||
/// Compute the global position with double precision.
|
/// Compute the global position with double precision.
|
||||||
pub fn position_double(&self, settings: &FloatingOriginSettings) -> DVec3 {
|
pub fn position_double(&self, reference_frame: &ReferenceFrame<P>) -> DVec3 {
|
||||||
settings.grid_position_double(self.cell, self.transform)
|
reference_frame.grid_position_double(self.cell, self.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the global position.
|
/// Compute the global position.
|
||||||
pub fn position(&self, settings: &FloatingOriginSettings) -> Vec3 {
|
pub fn position(&self, reference_frame: &ReferenceFrame<P>) -> Vec3 {
|
||||||
settings.grid_position(self.cell, self.transform)
|
reference_frame.grid_position(self.cell, self.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a copy of the fields to work with.
|
/// Get a copy of the fields to work with.
|
||||||
@ -98,12 +98,12 @@ impl<P: GridPrecision> std::ops::Add for GridTransformOwned<P> {
|
|||||||
|
|
||||||
impl<P: GridPrecision> GridTransformOwned<P> {
|
impl<P: GridPrecision> GridTransformOwned<P> {
|
||||||
/// Compute the global position with double precision.
|
/// Compute the global position with double precision.
|
||||||
pub fn position_double(&self, space: &FloatingOriginSettings) -> DVec3 {
|
pub fn position_double(&self, reference_frame: &ReferenceFrame<P>) -> DVec3 {
|
||||||
space.grid_position_double(&self.cell, &self.transform)
|
reference_frame.grid_position_double(&self.cell, &self.transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the global position.
|
/// Compute the global position.
|
||||||
pub fn position(&self, space: &FloatingOriginSettings) -> Vec3 {
|
pub fn position(&self, reference_frame: &ReferenceFrame<P>) -> Vec3 {
|
||||||
space.grid_position(&self.cell, &self.transform)
|
reference_frame.grid_position(&self.cell, &self.transform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user