mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-10 23:48:27 +00:00
Yeet Precision Generics (#40)
This commit is contained in:
parent
2a1cb54e63
commit
f6d8bf0649
10
.github/workflows/rust.yml
vendored
10
.github/workflows/rust.yml
vendored
@ -29,7 +29,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: cargo check --all-features --all-targets
|
||||
- run: cargo check --features=all --all-targets
|
||||
|
||||
check-no-defaults:
|
||||
runs-on: ubuntu-latest
|
||||
@ -48,7 +48,7 @@ jobs:
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: rustup component add clippy
|
||||
- run: cargo clippy --all-features --all-targets -- -D warnings
|
||||
- run: cargo clippy --features=all --all-targets -- -D warnings
|
||||
|
||||
doc:
|
||||
runs-on: ubuntu-latest
|
||||
@ -57,7 +57,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: cargo doc --all-features --no-deps
|
||||
- run: cargo doc --features=all --no-deps
|
||||
env:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
|
||||
@ -68,7 +68,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: cargo test --all-features
|
||||
- run: cargo test --features=all
|
||||
|
||||
doctest:
|
||||
runs-on: ubuntu-latest
|
||||
@ -77,4 +77,4 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: cargo test --all-features --doc
|
||||
- run: cargo test --features=all --doc
|
||||
@ -10,8 +10,14 @@ documentation = "https://docs.rs/crate/big_space/latest"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
all = ["debug", "camera"] # Can't use all-features, integer type features are incompatible.
|
||||
debug = ["bevy_gizmos", "bevy_color"]
|
||||
camera = ["bevy_render", "bevy_time", "bevy_input"]
|
||||
i8 = []
|
||||
i16 = []
|
||||
i32 = []
|
||||
i64 = []
|
||||
i128 = []
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1" # Less deps than pulling in bevy_log
|
||||
@ -69,8 +75,10 @@ doc-scrape-examples = false
|
||||
[[example]]
|
||||
name = "demo"
|
||||
path = "examples/demo.rs"
|
||||
required-features = ["i128"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "error_child"
|
||||
path = "examples/error_child.rs"
|
||||
@ -84,6 +92,7 @@ doc-scrape-examples = false
|
||||
[[example]]
|
||||
name = "infinite"
|
||||
path = "examples/infinite.rs"
|
||||
required-features = ["i8"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
|
||||
@ -38,10 +38,10 @@ fn deep_hierarchy(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(format!("deep_hierarchy {N_SPAWN}"));
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn_big_space::<i32>(Grid::new(10000.0, 0.0), |root| {
|
||||
commands.spawn_big_space(Grid::new(10000.0, 0.0), |root| {
|
||||
let mut parent = root.spawn_grid_default(()).id();
|
||||
for _ in 0..N_SPAWN {
|
||||
let child = root.commands().spawn(BigGridBundle::<i32>::default()).id();
|
||||
let child = root.commands().spawn(BigGridBundle::default()).id();
|
||||
root.commands().entity(parent).add_child(child);
|
||||
parent = child;
|
||||
}
|
||||
@ -58,8 +58,8 @@ fn deep_hierarchy(c: &mut Criterion) {
|
||||
let mut app = App::new();
|
||||
app.add_plugins((
|
||||
MinimalPlugins,
|
||||
GridHashPlugin::<i32>::default(),
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
GridHashPlugin::<()>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, translate)
|
||||
@ -80,7 +80,7 @@ fn wide_hierarchy(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group(format!("wide_hierarchy {N_SPAWN}"));
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn_big_space::<i32>(Grid::new(10000.0, 0.0), |root| {
|
||||
commands.spawn_big_space(Grid::new(10000.0, 0.0), |root| {
|
||||
for _ in 0..N_SPAWN {
|
||||
root.spawn_spatial(());
|
||||
}
|
||||
@ -97,8 +97,8 @@ fn wide_hierarchy(c: &mut Criterion) {
|
||||
let mut app = App::new();
|
||||
app.add_plugins((
|
||||
MinimalPlugins,
|
||||
GridHashPlugin::<i32>::default(),
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
GridHashPlugin::<()>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, translate)
|
||||
@ -115,20 +115,20 @@ fn wide_hierarchy(c: &mut Criterion) {
|
||||
fn spatial_hashing(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("spatial_hashing");
|
||||
|
||||
const HALF_WIDTH: i32 = 100;
|
||||
const HALF_WIDTH: i64 = 100;
|
||||
/// Total number of entities to spawn
|
||||
const N_SPAWN: usize = 10_000;
|
||||
/// Number of entities that move into a different cell each update
|
||||
const N_MOVE: usize = 1_000;
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn_big_space::<i32>(Grid::new(1.0, 0.0), |root| {
|
||||
commands.spawn_big_space(Grid::new(1.0, 0.0), |root| {
|
||||
let rng = Rng::with_seed(342525);
|
||||
let values: Vec<_> = repeat_with(|| {
|
||||
[
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
]
|
||||
})
|
||||
.take(N_SPAWN)
|
||||
@ -140,14 +140,14 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn translate(mut cells: Query<&mut GridCell<i32>>) {
|
||||
fn translate(mut cells: Query<&mut GridCell>) {
|
||||
cells.iter_mut().take(N_MOVE).for_each(|mut cell| {
|
||||
*cell += GridCell::ONE;
|
||||
})
|
||||
}
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Startup, setup)
|
||||
.update();
|
||||
|
||||
@ -164,7 +164,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
});
|
||||
});
|
||||
|
||||
let map = app.world().resource::<GridHashMap<i32>>();
|
||||
let map = app.world().resource::<GridHashMap>();
|
||||
let first = map
|
||||
.all_entries()
|
||||
.find(|(_, entry)| !entry.entities.is_empty())
|
||||
@ -185,8 +185,8 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
});
|
||||
});
|
||||
|
||||
// let parent = app .world_mut() .query::<&GridHash<i32>>() .get(app.world(), ent)
|
||||
// .unwrap(); let map = app.world().resource::<GridHashMap<i32>>(); let entry =
|
||||
// let parent = app .world_mut() .query::<&GridHash>() .get(app.world(), ent)
|
||||
// .unwrap(); let map = app.world().resource::<GridHashMap>(); let entry =
|
||||
// map.get(parent).unwrap();
|
||||
|
||||
// group.bench_function("Neighbors radius: 4", |b| {
|
||||
@ -204,8 +204,8 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
// });
|
||||
// });
|
||||
|
||||
fn setup_uniform<const HALF_EXTENT: i32>(mut commands: Commands) {
|
||||
commands.spawn_big_space::<i32>(Grid::new(1.0, 0.0), |root| {
|
||||
fn setup_uniform<const HALF_EXTENT: i64>(mut commands: Commands) {
|
||||
commands.spawn_big_space(Grid::new(1.0, 0.0), |root| {
|
||||
for x in HALF_EXTENT.neg()..HALF_EXTENT {
|
||||
for y in HALF_EXTENT.neg()..HALF_EXTENT {
|
||||
for z in HALF_EXTENT.neg()..HALF_EXTENT {
|
||||
@ -219,7 +219,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
// Uniform Grid Population 1_000
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Startup, setup_uniform::<5>)
|
||||
.update();
|
||||
|
||||
@ -227,7 +227,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<BigSpace>>()
|
||||
.single(app.world());
|
||||
let spatial_map = app.world().resource::<GridHashMap<i32>>();
|
||||
let spatial_map = app.world().resource::<GridHashMap>();
|
||||
let hash = GridHash::__new_manual(parent, &GridCell { x: 0, y: 0, z: 0 });
|
||||
let entry = spatial_map.get(&hash).unwrap();
|
||||
|
||||
@ -247,7 +247,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
// Uniform Grid Population 1_000_000
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Startup, setup_uniform::<50>)
|
||||
.update();
|
||||
|
||||
@ -255,7 +255,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<BigSpace>>()
|
||||
.single(app.world());
|
||||
let spatial_map = app.world().resource::<GridHashMap<i32>>();
|
||||
let spatial_map = app.world().resource::<GridHashMap>();
|
||||
let hash = GridHash::__new_manual(parent, &GridCell { x: 0, y: 0, z: 0 });
|
||||
let entry = spatial_map.get(&hash).unwrap();
|
||||
|
||||
@ -279,7 +279,7 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
const N_ENTITIES: usize = 100_000;
|
||||
const N_PLAYERS: usize = 100;
|
||||
const N_MOVE: usize = 1_000;
|
||||
const HALF_WIDTH: i32 = 100;
|
||||
const HALF_WIDTH: i64 = 100;
|
||||
|
||||
#[derive(Component)]
|
||||
struct Player;
|
||||
@ -288,15 +288,15 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
let rng = Rng::with_seed(342525);
|
||||
let values: Vec<_> = repeat_with(|| {
|
||||
[
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
]
|
||||
})
|
||||
.take(N_ENTITIES)
|
||||
.collect();
|
||||
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
for (i, pos) in values.iter().enumerate() {
|
||||
let mut cmd = root.spawn_spatial(GridCell::new(pos[0], pos[1], pos[2]));
|
||||
if i < N_PLAYERS {
|
||||
@ -306,7 +306,7 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
fn translate(mut cells: Query<&mut GridCell<i32>>) {
|
||||
fn translate(mut cells: Query<&mut GridCell>) {
|
||||
cells.iter_mut().take(N_MOVE).for_each(|mut cell| {
|
||||
*cell += IVec3::ONE;
|
||||
});
|
||||
@ -317,7 +317,7 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
.add_systems(Update, translate)
|
||||
.update();
|
||||
app.update();
|
||||
app.add_plugins((GridHashPlugin::<i32>::default(),));
|
||||
app.add_plugins((GridHashPlugin::<()>::default(),));
|
||||
group.bench_function("No Filter Plugin", |b| {
|
||||
b.iter(|| {
|
||||
black_box(app.update());
|
||||
@ -329,7 +329,7 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
.add_systems(Update, translate)
|
||||
.update();
|
||||
app.update();
|
||||
app.add_plugins((GridHashPlugin::<i32, With<Player>>::default(),));
|
||||
app.add_plugins((GridHashPlugin::<With<Player>>::default(),));
|
||||
group.bench_function("With Player Plugin", |b| {
|
||||
b.iter(|| {
|
||||
black_box(app.update());
|
||||
@ -341,7 +341,7 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
.add_systems(Update, translate)
|
||||
.update();
|
||||
app.update();
|
||||
app.add_plugins((GridHashPlugin::<i32, Without<Player>>::default(),));
|
||||
app.add_plugins((GridHashPlugin::<Without<Player>>::default(),));
|
||||
group.bench_function("Without Player Plugin", |b| {
|
||||
b.iter(|| {
|
||||
black_box(app.update());
|
||||
@ -353,9 +353,9 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
.add_systems(Update, translate)
|
||||
.update();
|
||||
app.update();
|
||||
app.add_plugins((GridHashPlugin::<i32>::default(),))
|
||||
.add_plugins((GridHashPlugin::<i32, With<Player>>::default(),))
|
||||
.add_plugins((GridHashPlugin::<i32, Without<Player>>::default(),));
|
||||
app.add_plugins((GridHashPlugin::<()>::default(),))
|
||||
.add_plugins((GridHashPlugin::<With<Player>>::default(),))
|
||||
.add_plugins((GridHashPlugin::<Without<Player>>::default(),));
|
||||
group.bench_function("All Plugins", |b| {
|
||||
b.iter(|| {
|
||||
black_box(app.update());
|
||||
@ -383,7 +383,7 @@ fn vs_bevy(c: &mut Criterion) {
|
||||
}
|
||||
|
||||
fn setup_big(mut commands: Commands) {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
for _ in 0..N_ENTITIES {
|
||||
root.spawn_spatial(());
|
||||
}
|
||||
@ -403,7 +403,7 @@ fn vs_bevy(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins((MinimalPlugins, BigSpacePlugin::<i32>::default()))
|
||||
app.add_plugins((MinimalPlugins, BigSpacePlugin::default()))
|
||||
.add_systems(Startup, setup_big)
|
||||
.update();
|
||||
|
||||
@ -432,7 +432,7 @@ fn vs_bevy(c: &mut Criterion) {
|
||||
});
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins((MinimalPlugins, BigSpacePlugin::<i32>::default()))
|
||||
app.add_plugins((MinimalPlugins, BigSpacePlugin::default()))
|
||||
.add_systems(Startup, setup_big)
|
||||
.add_systems(Update, translate)
|
||||
.update();
|
||||
|
||||
@ -7,8 +7,8 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (movement, rotation))
|
||||
@ -64,7 +64,7 @@ fn setup(
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn_big_space::<i64>(Grid::new(1.0, 0.01), |root| {
|
||||
commands.spawn_big_space(Grid::new(1.0, 0.01), |root| {
|
||||
root.spawn_spatial((
|
||||
Mesh3d(mesh_handle.clone()),
|
||||
MeshMaterial3d(matl_handle.clone()),
|
||||
|
||||
@ -14,9 +14,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i128>::default(),
|
||||
FloatingOriginDebugPlugin::<i128>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i128>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.add_systems(Startup, (setup, ui_setup))
|
||||
@ -33,7 +33,7 @@ fn setup(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn_big_space_default::<i128>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial((
|
||||
Camera3d::default(),
|
||||
Projection::Perspective(PerspectiveProjection {
|
||||
@ -150,9 +150,9 @@ fn ui_text_system(
|
||||
(With<BigSpaceDebugText>, Without<FunFactText>),
|
||||
>,
|
||||
mut fun_text: Query<&mut Text, (With<FunFactText>, Without<BigSpaceDebugText>)>,
|
||||
grids: Grids<i128>,
|
||||
grids: Grids,
|
||||
time: Res<Time>,
|
||||
origin: Query<(Entity, GridTransformReadOnly<i128>), With<FloatingOrigin>>,
|
||||
origin: Query<(Entity, GridTransformReadOnly), With<FloatingOrigin>>,
|
||||
camera: Query<&CameraController>,
|
||||
objects: Query<&Transform, With<Mesh3d>>,
|
||||
) {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
//! This example demonstrates what floating point error in rendering looks like. You can press
|
||||
//! spacebar to smoothly switch between enabling and disabling the floating origin.
|
||||
//! space bar to smoothly switch between enabling and disabling the floating origin.
|
||||
//!
|
||||
//! Instead of disabling the plugin outright, this example simply moves the floating origin
|
||||
//! independently from the camera, which is equivalent to what would happen when moving far from the
|
||||
//! independently of the camera, which is equivalent to what would happen when moving far from the
|
||||
//! origin when not using this plugin.
|
||||
|
||||
use bevy::prelude::*;
|
||||
@ -10,7 +10,7 @@ use big_space::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins, BigSpacePlugin::<i128>::default()))
|
||||
.add_plugins((DefaultPlugins, BigSpacePlugin::default()))
|
||||
.add_systems(Startup, (setup_scene, setup_ui))
|
||||
.add_systems(Update, (rotator_system, toggle_plugin))
|
||||
.run();
|
||||
@ -22,18 +22,19 @@ fn main() {
|
||||
/// floating point error when we disable this plugin.
|
||||
///
|
||||
/// This plugin can function much further from the origin without any issues. Try setting this to:
|
||||
/// 10_000_000_000_000_000_000_000_000_000_000_000_000
|
||||
const DISTANCE: i128 = 2_000_000;
|
||||
/// 10_000_000_000_000_000 with the default i64 feature, or
|
||||
/// 10_000_000_000_000_000_000_000_000_000_000_000_000 with the i128 feature.
|
||||
const DISTANCE: GridPrecision = 2_000_000;
|
||||
|
||||
/// Move the floating origin back to the "true" origin when the user presses the spacebar to emulate
|
||||
/// disabling the plugin. Normally you would make your active camera the floating origin to avoid
|
||||
/// this issue.
|
||||
fn toggle_plugin(
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
grids: Grids<i128>,
|
||||
grids: Grids,
|
||||
mut text: Query<&mut Text>,
|
||||
mut disabled: Local<bool>,
|
||||
mut floating_origin: Query<(Entity, &mut GridCell<i128>), With<FloatingOrigin>>,
|
||||
mut floating_origin: Query<(Entity, &mut GridCell), With<FloatingOrigin>>,
|
||||
) {
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
*disabled = !*disabled;
|
||||
@ -42,7 +43,7 @@ fn toggle_plugin(
|
||||
let this_grid = grids.parent_grid(floating_origin.single().0).unwrap();
|
||||
|
||||
let mut origin_cell = floating_origin.single_mut().1;
|
||||
let index_max = DISTANCE / this_grid.cell_edge_length() as i128;
|
||||
let index_max = DISTANCE / this_grid.cell_edge_length() as GridPrecision;
|
||||
let increment = index_max / 100;
|
||||
|
||||
let msg = if *disabled {
|
||||
@ -64,9 +65,10 @@ fn toggle_plugin(
|
||||
"Floating Origin Enabled"
|
||||
};
|
||||
|
||||
let dist = index_max.saturating_sub(origin_cell.x) * this_grid.cell_edge_length() as i128;
|
||||
let dist =
|
||||
index_max.saturating_sub(origin_cell.x) * this_grid.cell_edge_length() as GridPrecision;
|
||||
|
||||
let thousands = |num: i128| {
|
||||
let thousands = |num: GridPrecision| {
|
||||
num.to_string()
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
@ -107,9 +109,9 @@ fn setup_ui(mut commands: Commands) {
|
||||
}
|
||||
|
||||
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn_big_space_default::<i128>(|root| {
|
||||
let d = DISTANCE / root.grid().cell_edge_length() as i128;
|
||||
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
|
||||
commands.spawn_big_space_default(|root| {
|
||||
let d = DISTANCE / root.grid().cell_edge_length() as GridPrecision;
|
||||
let distant_grid_cell = GridCell::new(d, d, d);
|
||||
|
||||
// Normally, we would put the floating origin on the camera. However in this example, we
|
||||
// want to show what happens as the camera is far from the origin, to emulate what
|
||||
|
||||
@ -7,9 +7,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i64>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -37,7 +37,7 @@ fn setup_scene(
|
||||
) {
|
||||
let mesh_handle = meshes.add(Sphere::new(SPHERE_RADIUS).mesh());
|
||||
|
||||
commands.spawn_big_space::<i64>(Grid::new(SPHERE_RADIUS * 100.0, 0.0), |root_grid| {
|
||||
commands.spawn_big_space(Grid::new(SPHERE_RADIUS * 100.0, 0.0), |root_grid| {
|
||||
root_grid.spawn_spatial((
|
||||
Mesh3d(mesh_handle.clone()),
|
||||
MeshMaterial3d(materials.add(Color::from(palettes::css::BLUE))),
|
||||
|
||||
@ -7,9 +7,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i8>::default(),
|
||||
FloatingOriginDebugPlugin::<i8>::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::<i8>::default(), // Compatible controller
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -23,7 +23,7 @@ fn setup_scene(
|
||||
let sphere = Mesh3d(meshes.add(Sphere::default()));
|
||||
let matl = MeshMaterial3d(materials.add(Color::WHITE));
|
||||
|
||||
commands.spawn_big_space::<i8>(Grid::default(), |root_grid| {
|
||||
commands.spawn_big_space(Grid::default(), |root_grid| {
|
||||
let width = || -8..8;
|
||||
for (x, y, z) in width()
|
||||
.flat_map(|x| width().map(move |y| (x, y)))
|
||||
@ -32,7 +32,7 @@ fn setup_scene(
|
||||
root_grid.spawn_spatial((
|
||||
sphere.clone(),
|
||||
matl.clone(),
|
||||
GridCell::<i8> {
|
||||
GridCell {
|
||||
x: x * 16,
|
||||
y: y * 16,
|
||||
z: z * 16,
|
||||
|
||||
@ -11,9 +11,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i64>::default(),
|
||||
FloatingOriginDebugPlugin::<i64>::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(), // Compatible controller
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -32,7 +32,7 @@ fn setup_scene(
|
||||
// A world can have multiple independent BigSpaces, with their own floating origins. This can
|
||||
// come in handy if you want to have two cameras very far from each other, rendering at the same
|
||||
// time like split screen, or portals.
|
||||
commands.spawn_big_space_default::<i64>(|root_grid| {
|
||||
commands.spawn_big_space_default(|root_grid| {
|
||||
// Because BIG_DISTANCE is so large, we want to avoid using bevy's f32 transforms alone and
|
||||
// experience rounding errors. Instead, we use this helper to convert an f64 position into a
|
||||
// grid cell and f32 offset.
|
||||
|
||||
@ -8,9 +8,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i64>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::default(),
|
||||
bevy_hanabi::HanabiPlugin, // TODO fix once hanabi updates to bevy 0.15
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
@ -28,7 +28,7 @@ fn setup_scene(
|
||||
mut effects: ResMut<Assets<bevy_hanabi::EffectAsset>>,
|
||||
) {
|
||||
let effect = effects.add(particle_effect());
|
||||
commands.spawn_big_space_default::<i64>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial(DirectionalLight::default());
|
||||
root.spawn_spatial((
|
||||
Mesh3d(meshes.add(Sphere::default())),
|
||||
|
||||
@ -16,8 +16,8 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i64>::new(true),
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||
BigSpacePlugin::new(true),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.insert_resource(AmbientLight {
|
||||
@ -35,7 +35,7 @@ fn main() {
|
||||
cursor_grab_system,
|
||||
springy_ship
|
||||
.after(big_space::camera::default_camera_inputs)
|
||||
.before(big_space::camera::camera_controller::<i64>),
|
||||
.before(big_space::camera::camera_controller),
|
||||
),
|
||||
)
|
||||
.register_type::<Sun>()
|
||||
@ -138,7 +138,7 @@ fn spawn_solar_system(
|
||||
.build(),
|
||||
));
|
||||
|
||||
commands.spawn_big_space_default::<i64>(|root_grid| {
|
||||
commands.spawn_big_space_default(|root_grid| {
|
||||
root_grid.with_grid_default(|sun| {
|
||||
sun.insert((Sun, Name::new("Sun")));
|
||||
sun.spawn_spatial((
|
||||
|
||||
@ -17,9 +17,9 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i128>::default(),
|
||||
FloatingOriginDebugPlugin::<i128>::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::<i128>::default(), // Compatible controller
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.add_systems(Update, (bounce_atoms, toggle_cam_pos))
|
||||
@ -39,7 +39,7 @@ fn setup_scene(
|
||||
// Because we are working on such small scales, we need to make the grid very small. This
|
||||
// ensures that the maximum floating point error is also very small, because no entities can
|
||||
// ever get farther than `SMALL_SCALE * 500` units from the origin.
|
||||
let small_grid = Grid::<i128>::new(PROTON_DIA * 5_000.0, 0.0);
|
||||
let small_grid = Grid::new(PROTON_DIA * 5_000.0, 0.0);
|
||||
|
||||
commands.spawn_big_space(small_grid, |root_grid| {
|
||||
root_grid.spawn_spatial(DirectionalLight::default());
|
||||
@ -99,9 +99,9 @@ fn bounce_atoms(mut atoms: Query<&mut Transform, With<Proton>>, time: Res<Time>)
|
||||
}
|
||||
|
||||
fn toggle_cam_pos(
|
||||
mut cam: Query<&mut GridCell<i128>, With<Camera>>,
|
||||
mut cam: Query<&mut GridCell, With<Camera>>,
|
||||
mut toggle: Local<bool>,
|
||||
grid: Query<&Grid<i128>>,
|
||||
grid: Query<&Grid>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
protons: Query<&GlobalTransform, With<Proton>>,
|
||||
) {
|
||||
|
||||
@ -22,10 +22,10 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
GridHashPlugin::<i32>::default(),
|
||||
GridPartitionPlugin::<i32>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i32>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
GridHashPlugin::<()>::default(),
|
||||
GridPartitionPlugin::<()>::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, (spawn, setup_ui))
|
||||
.add_systems(
|
||||
@ -86,9 +86,9 @@ impl FromWorld for MaterialPresets {
|
||||
|
||||
fn draw_partitions(
|
||||
mut gizmos: Gizmos,
|
||||
partitions: Res<GridPartitionMap<i32>>,
|
||||
grids: Query<(&GlobalTransform, &Grid<i32>)>,
|
||||
camera: Query<&GridHash<i32>, With<Camera>>,
|
||||
partitions: Res<GridPartitionMap>,
|
||||
grids: Query<(&GlobalTransform, &Grid)>,
|
||||
camera: Query<&GridHash, With<Camera>>,
|
||||
) {
|
||||
for (id, p) in partitions.iter().take(10_000) {
|
||||
let Ok((transform, grid)) = grids.get(p.grid()) else {
|
||||
@ -105,7 +105,7 @@ fn draw_partitions(
|
||||
.filter(|hash| *hash != camera.single())
|
||||
.take(1_000)
|
||||
.for_each(|h| {
|
||||
let center = [h.cell().x, h.cell().y, h.cell().z];
|
||||
let center = [h.cell().x as i32, h.cell().y as i32, h.cell().z as i32];
|
||||
let local_trans = Transform::from_translation(IVec3::from(center).as_vec3() * l)
|
||||
.with_scale(Vec3::splat(l));
|
||||
gizmos.cuboid(
|
||||
@ -114,8 +114,8 @@ fn draw_partitions(
|
||||
);
|
||||
});
|
||||
|
||||
let min = IVec3::from([p.min().x, p.min().y, p.min().z]).as_vec3() * l;
|
||||
let max = IVec3::from([p.max().x, p.max().y, p.max().z]).as_vec3() * l;
|
||||
let min = IVec3::from([p.min().x as i32, p.min().y as i32, p.min().z as i32]).as_vec3() * l;
|
||||
let max = IVec3::from([p.max().x as i32, p.max().y as i32, p.max().z as i32]).as_vec3() * l;
|
||||
|
||||
let size = max - min;
|
||||
let center = min + (size) * 0.5;
|
||||
@ -133,15 +133,15 @@ fn draw_partitions(
|
||||
fn move_player(
|
||||
time: Res<Time>,
|
||||
mut _gizmos: Gizmos,
|
||||
mut player: Query<(&mut Transform, &mut GridCell<i32>, &Parent, &GridHash<i32>), With<Player>>,
|
||||
mut player: Query<(&mut Transform, &mut GridCell, &Parent, &GridHash), With<Player>>,
|
||||
mut non_player: Query<
|
||||
(&mut Transform, &mut GridCell<i32>, &Parent),
|
||||
(&mut Transform, &mut GridCell, &Parent),
|
||||
(Without<Player>, With<NonPlayer>),
|
||||
>,
|
||||
mut materials: Query<&mut MeshMaterial3d<StandardMaterial>, Without<Player>>,
|
||||
mut neighbors: Local<Vec<Entity>>,
|
||||
grids: Query<&Grid<i32>>,
|
||||
hash_grid: Res<GridHashMap<i32>>,
|
||||
grids: Query<&Grid>,
|
||||
hash_grid: Res<GridHashMap>,
|
||||
material_presets: Res<MaterialPresets>,
|
||||
mut text: Query<&mut Text>,
|
||||
hash_stats: Res<big_space::timing::SmoothedStat<big_space::timing::GridHashStats>>,
|
||||
@ -257,7 +257,7 @@ Total: {: >22.1?}",
|
||||
}
|
||||
|
||||
fn spawn(mut commands: Commands) {
|
||||
commands.spawn_big_space::<i32>(Grid::new(CELL_WIDTH, 0.0), |root| {
|
||||
commands.spawn_big_space(Grid::new(CELL_WIDTH, 0.0), |root| {
|
||||
root.spawn_spatial((
|
||||
FloatingOrigin,
|
||||
Camera3d::default(),
|
||||
@ -273,7 +273,7 @@ fn spawn(mut commands: Commands) {
|
||||
.with_speed(15.0),
|
||||
Fxaa::default(),
|
||||
Bloom::default(),
|
||||
GridCell::new(0, 0, HALF_WIDTH as i32 / 2),
|
||||
GridCell::new(0, 0, HALF_WIDTH as GridPrecision / 2),
|
||||
))
|
||||
.with_children(|b| {
|
||||
b.spawn(DirectionalLight::default());
|
||||
@ -287,7 +287,7 @@ fn spawn_spheres(
|
||||
mut commands: Commands,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
material_presets: Res<MaterialPresets>,
|
||||
mut grid: Query<(Entity, &Grid<i32>, &mut Children)>,
|
||||
mut grid: Query<(Entity, &Grid, &mut Children)>,
|
||||
non_players: Query<(), With<NonPlayer>>,
|
||||
) {
|
||||
let n_entities = non_players.iter().len().max(1);
|
||||
@ -306,12 +306,12 @@ fn spawn_spheres(
|
||||
|
||||
let new_children = sample_noise(n_spawn, &Simplex::new(345612), &Rng::new())
|
||||
.map(|value| {
|
||||
let hash = GridHash::<i32>::__new_manual(entity, &GridCell::default());
|
||||
let hash = GridHash::__new_manual(entity, &GridCell::default());
|
||||
commands
|
||||
.spawn((
|
||||
Transform::from_xyz(value.x, value.y, value.z),
|
||||
GlobalTransform::default(),
|
||||
GridCell::<i32>::default(),
|
||||
GridCell::default(),
|
||||
FastGridHash::from(hash),
|
||||
hash,
|
||||
NonPlayer,
|
||||
|
||||
@ -16,16 +16,16 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
FloatingOriginDebugPlugin::<i32>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i32>::default(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, set_camera_viewports)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
update_cameras
|
||||
.after(big_space::camera::camera_controller::<i32>)
|
||||
.after(big_space::camera::camera_controller)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.run();
|
||||
@ -55,7 +55,7 @@ fn setup(
|
||||
));
|
||||
|
||||
// Big Space 1
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial((
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(1_000_000.0 - 10.0, 100_005.0, 0.0)
|
||||
@ -106,7 +106,7 @@ fn setup(
|
||||
});
|
||||
|
||||
// Big Space 2
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial((
|
||||
Camera3d::default(),
|
||||
Camera {
|
||||
@ -162,9 +162,9 @@ fn setup(
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn update_cameras(
|
||||
left: Query<GridTransformReadOnly<i32>, With<LeftCamera>>,
|
||||
left: Query<GridTransformReadOnly, With<LeftCamera>>,
|
||||
mut left_rep: Query<
|
||||
GridTransform<i32>,
|
||||
GridTransform,
|
||||
(
|
||||
With<LeftCameraReplicated>,
|
||||
Without<RightCameraReplicated>,
|
||||
@ -172,9 +172,9 @@ fn update_cameras(
|
||||
Without<RightCamera>,
|
||||
),
|
||||
>,
|
||||
right: Query<GridTransformReadOnly<i32>, With<RightCamera>>,
|
||||
right: Query<GridTransformReadOnly, With<RightCamera>>,
|
||||
mut right_rep: Query<
|
||||
GridTransform<i32>,
|
||||
GridTransform,
|
||||
(
|
||||
With<RightCameraReplicated>,
|
||||
Without<LeftCameraReplicated>,
|
||||
|
||||
@ -8,7 +8,7 @@ use bevy_transform::prelude::*;
|
||||
///
|
||||
/// This is the floating origin equivalent of the `bevy` `SpatialBundle`.
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigSpatialBundle<P: GridPrecision> {
|
||||
pub struct BigSpatialBundle {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: bevy_render::view::Visibility,
|
||||
@ -23,7 +23,7 @@ pub struct BigSpatialBundle<P: GridPrecision> {
|
||||
/// The global transform of the entity.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// The grid position of the entity
|
||||
pub cell: GridCell<P>,
|
||||
pub cell: GridCell,
|
||||
}
|
||||
|
||||
/// A `SpatialBundle` that also has a grid, allowing other high precision spatial bundles to be
|
||||
@ -31,7 +31,7 @@ pub struct BigSpatialBundle<P: GridPrecision> {
|
||||
///
|
||||
/// This is the floating origin equivalent of the `bevy` `SpatialBundle`.
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigGridBundle<P: GridPrecision> {
|
||||
pub struct BigGridBundle {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: bevy_render::view::Visibility,
|
||||
@ -40,19 +40,19 @@ pub struct BigGridBundle<P: GridPrecision> {
|
||||
/// The global transform of the entity for rendering, computed relative to the floating origin.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// The grid position of the grid within its parent grid.
|
||||
pub cell: GridCell<P>,
|
||||
pub cell: GridCell,
|
||||
/// The grid.
|
||||
pub grid: Grid<P>,
|
||||
pub grid: Grid,
|
||||
}
|
||||
|
||||
/// The root of any [`BigSpace`] needs these components to function.
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigSpaceRootBundle<P: GridPrecision> {
|
||||
pub struct BigSpaceRootBundle {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: bevy_render::view::Visibility,
|
||||
/// The root grid
|
||||
pub grid: Grid<P>,
|
||||
pub grid: Grid,
|
||||
/// The rendered position of the root grid relative to the floating origin.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// Tracks the current floating origin.
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
//! Provides a camera controller compatible with the floating origin plugin.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
@ -19,17 +17,17 @@ use bevy_utils::HashSet;
|
||||
|
||||
/// Adds the `big_space` camera controller
|
||||
#[derive(Default)]
|
||||
pub struct CameraControllerPlugin<P: GridPrecision>(PhantomData<P>);
|
||||
impl<P: GridPrecision> Plugin for CameraControllerPlugin<P> {
|
||||
pub struct CameraControllerPlugin(());
|
||||
impl Plugin for CameraControllerPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<CameraInput>().add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
default_camera_inputs
|
||||
.before(camera_controller::<P>)
|
||||
.before(camera_controller)
|
||||
.run_if(|input: Res<CameraInput>| !input.defaults_disabled),
|
||||
nearest_objects_in_grid::<P>.before(camera_controller::<P>),
|
||||
camera_controller::<P>.before(TransformSystem::TransformPropagate),
|
||||
nearest_objects_in_grid.before(camera_controller),
|
||||
camera_controller.before(TransformSystem::TransformPropagate),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -211,7 +209,7 @@ pub fn default_camera_inputs(
|
||||
}
|
||||
|
||||
/// Find the object nearest the camera, within the same grid as the camera.
|
||||
pub fn nearest_objects_in_grid<P: GridPrecision>(
|
||||
pub fn nearest_objects_in_grid(
|
||||
objects: Query<(
|
||||
Entity,
|
||||
&Transform,
|
||||
@ -260,16 +258,11 @@ pub fn nearest_objects_in_grid<P: GridPrecision>(
|
||||
}
|
||||
|
||||
/// Uses [`CameraInput`] state to update the camera position.
|
||||
pub fn camera_controller<P: GridPrecision>(
|
||||
pub fn camera_controller(
|
||||
time: Res<Time>,
|
||||
grids: crate::grid::local_origin::Grids<P>,
|
||||
grids: crate::grid::local_origin::Grids,
|
||||
mut input: ResMut<CameraInput>,
|
||||
mut camera: Query<(
|
||||
Entity,
|
||||
&mut GridCell<P>,
|
||||
&mut Transform,
|
||||
&mut CameraController,
|
||||
)>,
|
||||
mut camera: Query<(Entity, &mut GridCell, &mut Transform, &mut CameraController)>,
|
||||
) {
|
||||
for (camera, mut cell, mut transform, mut controller) in camera.iter_mut() {
|
||||
let Some(grid) = grids.parent_grid(camera) else {
|
||||
|
||||
@ -5,35 +5,23 @@ use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use smallvec::SmallVec;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Adds `big_space` commands to bevy's `Commands`.
|
||||
pub trait BigSpaceCommands {
|
||||
/// Spawn a root [`BigSpace`] [`Grid`].
|
||||
fn spawn_big_space<P: GridPrecision>(
|
||||
&mut self,
|
||||
root_grid: Grid<P>,
|
||||
child_builder: impl FnOnce(&mut GridCommands<P>),
|
||||
);
|
||||
fn spawn_big_space(&mut self, root_grid: Grid, child_builder: impl FnOnce(&mut GridCommands));
|
||||
|
||||
/// Spawn a root [`BigSpace`] with default [`Grid`] settings.
|
||||
fn spawn_big_space_default<P: GridPrecision>(
|
||||
&mut self,
|
||||
child_builder: impl FnOnce(&mut GridCommands<P>),
|
||||
);
|
||||
fn spawn_big_space_default(&mut self, child_builder: impl FnOnce(&mut GridCommands));
|
||||
|
||||
/// Access the [`GridCommands`] of an entity by passing in the [`Entity`] and [`Grid`]. Note
|
||||
/// that the value of `grid` will be inserted in this entity when the command is applied.
|
||||
fn grid<P: GridPrecision>(&mut self, entity: Entity, grid: Grid<P>) -> GridCommands<P>;
|
||||
fn grid(&mut self, entity: Entity, grid: Grid) -> GridCommands;
|
||||
}
|
||||
|
||||
impl BigSpaceCommands for Commands<'_, '_> {
|
||||
fn spawn_big_space<P: GridPrecision>(
|
||||
&mut self,
|
||||
grid: Grid<P>,
|
||||
root_grid: impl FnOnce(&mut GridCommands<P>),
|
||||
) {
|
||||
let mut entity_commands = self.spawn(BigSpaceRootBundle::<P>::default());
|
||||
fn spawn_big_space(&mut self, grid: Grid, root_grid: impl FnOnce(&mut GridCommands)) {
|
||||
let mut entity_commands = self.spawn(BigSpaceRootBundle::default());
|
||||
let mut cmd = GridCommands {
|
||||
entity: entity_commands.id(),
|
||||
commands: entity_commands.commands(),
|
||||
@ -43,14 +31,11 @@ impl BigSpaceCommands for Commands<'_, '_> {
|
||||
root_grid(&mut cmd);
|
||||
}
|
||||
|
||||
fn spawn_big_space_default<P: GridPrecision>(
|
||||
&mut self,
|
||||
child_builder: impl FnOnce(&mut GridCommands<P>),
|
||||
) {
|
||||
fn spawn_big_space_default(&mut self, child_builder: impl FnOnce(&mut GridCommands)) {
|
||||
self.spawn_big_space(Grid::default(), child_builder);
|
||||
}
|
||||
|
||||
fn grid<P: GridPrecision>(&mut self, entity: Entity, grid: Grid<P>) -> GridCommands<P> {
|
||||
fn grid(&mut self, entity: Entity, grid: Grid) -> GridCommands {
|
||||
GridCommands {
|
||||
entity,
|
||||
commands: self.reborrow(),
|
||||
@ -61,16 +46,16 @@ impl BigSpaceCommands for Commands<'_, '_> {
|
||||
}
|
||||
|
||||
/// Build [`big_space`](crate) hierarchies more easily, with access to grids.
|
||||
pub struct GridCommands<'a, P: GridPrecision> {
|
||||
pub struct GridCommands<'a> {
|
||||
entity: Entity,
|
||||
commands: Commands<'a, 'a>,
|
||||
grid: Grid<P>,
|
||||
grid: Grid,
|
||||
children: SmallVec<[Entity; 8]>,
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
impl<'a> GridCommands<'a> {
|
||||
/// Get a reference to the current grid.
|
||||
pub fn grid(&mut self) -> &Grid<P> {
|
||||
pub fn grid(&mut self) -> &Grid {
|
||||
&self.grid
|
||||
}
|
||||
|
||||
@ -82,26 +67,25 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
|
||||
/// Spawn an entity in this grid.
|
||||
#[inline]
|
||||
pub fn spawn(&mut self, bundle: impl Bundle) -> SpatialEntityCommands<P> {
|
||||
pub fn spawn(&mut self, bundle: impl Bundle) -> SpatialEntityCommands {
|
||||
let entity = self.commands.spawn(bundle).id();
|
||||
self.children.push(entity);
|
||||
SpatialEntityCommands {
|
||||
entity,
|
||||
commands: self.commands.reborrow(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a high-precision spatial entity ([`GridCell`]) to this grid, and insert the provided
|
||||
/// bundle.
|
||||
#[inline]
|
||||
pub fn spawn_spatial(&mut self, bundle: impl Bundle) -> SpatialEntityCommands<P> {
|
||||
pub fn spawn_spatial(&mut self, bundle: impl Bundle) -> SpatialEntityCommands {
|
||||
let entity = self
|
||||
.spawn((
|
||||
#[cfg(feature = "bevy_render")]
|
||||
bevy_render::view::Visibility::default(),
|
||||
Transform::default(),
|
||||
GridCell::<P>::default(),
|
||||
GridCell::default(),
|
||||
))
|
||||
.insert(bundle)
|
||||
.id();
|
||||
@ -109,7 +93,6 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
SpatialEntityCommands {
|
||||
entity,
|
||||
commands: self.commands.reborrow(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,10 +106,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
/// to it via the closure. This allows you to insert bundles on this new spatial entities, and
|
||||
/// add more children to it.
|
||||
#[inline]
|
||||
pub fn with_spatial(
|
||||
&mut self,
|
||||
spatial: impl FnOnce(&mut SpatialEntityCommands<P>),
|
||||
) -> &mut Self {
|
||||
pub fn with_spatial(&mut self, spatial: impl FnOnce(&mut SpatialEntityCommands)) -> &mut Self {
|
||||
spatial(&mut self.spawn_spatial(()));
|
||||
self
|
||||
}
|
||||
@ -137,8 +117,8 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
#[inline]
|
||||
pub fn with_grid(
|
||||
&mut self,
|
||||
new_grid: Grid<P>,
|
||||
builder: impl FnOnce(&mut GridCommands<P>),
|
||||
new_grid: Grid,
|
||||
builder: impl FnOnce(&mut GridCommands),
|
||||
) -> &mut Self {
|
||||
builder(&mut self.spawn_grid(new_grid, ()));
|
||||
self
|
||||
@ -146,20 +126,20 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
|
||||
/// Same as [`Self::with_grid`], but using the default [`Grid`] value.
|
||||
#[inline]
|
||||
pub fn with_grid_default(&mut self, builder: impl FnOnce(&mut GridCommands<P>)) -> &mut Self {
|
||||
pub fn with_grid_default(&mut self, builder: impl FnOnce(&mut GridCommands)) -> &mut Self {
|
||||
self.with_grid(Grid::default(), builder)
|
||||
}
|
||||
|
||||
/// Spawn a grid as a child of the current grid.
|
||||
#[inline]
|
||||
pub fn spawn_grid(&mut self, new_grid: Grid<P>, bundle: impl Bundle) -> GridCommands<P> {
|
||||
pub fn spawn_grid(&mut self, new_grid: Grid, bundle: impl Bundle) -> GridCommands {
|
||||
let entity = self
|
||||
.spawn((
|
||||
#[cfg(feature = "bevy_render")]
|
||||
bevy_render::view::Visibility::default(),
|
||||
Transform::default(),
|
||||
GridCell::<P>::default(),
|
||||
Grid::<P>::default(),
|
||||
GridCell::default(),
|
||||
Grid::default(),
|
||||
))
|
||||
.insert(bundle)
|
||||
.id();
|
||||
@ -173,7 +153,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
}
|
||||
|
||||
/// Spawn a grid as a child of the current grid.
|
||||
pub fn spawn_grid_default(&mut self, bundle: impl Bundle) -> GridCommands<P> {
|
||||
pub fn spawn_grid_default(&mut self, bundle: impl Bundle) -> GridCommands {
|
||||
self.spawn_grid(Grid::default(), bundle)
|
||||
}
|
||||
|
||||
@ -192,7 +172,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
|
||||
}
|
||||
|
||||
/// Insert the grid on drop.
|
||||
impl<P: GridPrecision> Drop for GridCommands<'_, P> {
|
||||
impl Drop for GridCommands<'_> {
|
||||
fn drop(&mut self) {
|
||||
let entity = self.entity;
|
||||
self.commands
|
||||
@ -203,13 +183,12 @@ impl<P: GridPrecision> Drop for GridCommands<'_, P> {
|
||||
}
|
||||
|
||||
/// Build [`big_space`](crate) hierarchies more easily, with access to grids.
|
||||
pub struct SpatialEntityCommands<'a, P: GridPrecision> {
|
||||
pub struct SpatialEntityCommands<'a> {
|
||||
entity: Entity,
|
||||
commands: Commands<'a, 'a>,
|
||||
phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> SpatialEntityCommands<'a, P> {
|
||||
impl<'a> SpatialEntityCommands<'a> {
|
||||
/// Insert a component on this grid
|
||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.entity(self.entity).insert(bundle);
|
||||
|
||||
18
src/debug.rs
18
src/debug.rs
@ -1,7 +1,5 @@
|
||||
//! Contains tools for debugging the floating origin.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_color::prelude::*;
|
||||
@ -13,14 +11,14 @@ use bevy_transform::prelude::*;
|
||||
|
||||
/// This plugin will render the bounds of occupied grid cells.
|
||||
#[derive(Default)]
|
||||
pub struct FloatingOriginDebugPlugin<P: GridPrecision>(PhantomData<P>);
|
||||
impl<P: GridPrecision> Plugin for FloatingOriginDebugPlugin<P> {
|
||||
pub struct FloatingOriginDebugPlugin(());
|
||||
impl Plugin for FloatingOriginDebugPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_gizmo_group::<BigSpaceGizmoConfig>()
|
||||
.add_systems(Startup, setup_gizmos)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(update_debug_bounds::<P>, update_grid_axes::<P>)
|
||||
(update_debug_bounds, update_grid_axes)
|
||||
.chain()
|
||||
.after(bevy_transform::TransformSystem::TransformPropagate),
|
||||
);
|
||||
@ -35,10 +33,10 @@ fn setup_gizmos(mut store: ResMut<GizmoConfigStore>) {
|
||||
}
|
||||
|
||||
/// Update the rendered debug bounds to only highlight occupied [`GridCell`]s.
|
||||
fn update_debug_bounds<P: GridPrecision>(
|
||||
fn update_debug_bounds(
|
||||
mut gizmos: Gizmos<BigSpaceGizmoConfig>,
|
||||
grids: Grids<P>,
|
||||
occupied_cells: Query<(Entity, &GridCell<P>, Option<&FloatingOrigin>)>,
|
||||
grids: Grids,
|
||||
occupied_cells: Query<(Entity, &GridCell, Option<&FloatingOrigin>)>,
|
||||
) {
|
||||
for (cell_entity, cell, origin) in occupied_cells.iter() {
|
||||
let Some(grid) = grids.parent_grid(cell_entity) else {
|
||||
@ -62,9 +60,9 @@ struct BigSpaceGizmoConfig;
|
||||
impl GizmoConfigGroup for BigSpaceGizmoConfig {}
|
||||
|
||||
/// Draw axes for grids.
|
||||
fn update_grid_axes<P: GridPrecision>(
|
||||
fn update_grid_axes(
|
||||
mut gizmos: Gizmos<BigSpaceGizmoConfig>,
|
||||
grids: Query<(&GlobalTransform, &Grid<P>)>,
|
||||
grids: Query<(&GlobalTransform, &Grid)>,
|
||||
) {
|
||||
for (transform, grid) in grids.iter() {
|
||||
let start = transform.translation();
|
||||
|
||||
148
src/grid/cell.rs
148
src/grid/cell.rs
@ -1,25 +1,13 @@
|
||||
//! Contains the grid cell implementation
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_math::{DVec3, IVec3};
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::Instant;
|
||||
|
||||
/// Marks entities with any generic [`GridCell`] component. Allows you to query for high precision
|
||||
/// spatial entities of any [`GridPrecision`].
|
||||
///
|
||||
/// Also useful for filtering. You might want to run queries on things without a grid cell, however
|
||||
/// there could by many generic types of grid cell. `Without<GridCellAny>` will cover all of these
|
||||
/// cases.
|
||||
///
|
||||
/// This is automatically added and removed by the component lifecycle hooks on [`GridCell`].
|
||||
#[derive(Component, Default, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct GridCellAny;
|
||||
|
||||
/// Locates an entity in a cell within its parent's [`Grid`]. The [`Transform`] of an entity with
|
||||
/// this component is a transformation from the center of this cell.
|
||||
///
|
||||
@ -39,51 +27,33 @@ pub struct GridCellAny;
|
||||
#[derive(Component, Default, Debug, PartialEq, Eq, Clone, Copy, Hash, Reflect)]
|
||||
#[reflect(Component, Default, PartialEq)]
|
||||
#[require(Transform, GlobalTransform)]
|
||||
#[component(storage = "Table", on_add = Self::on_add, on_remove = Self::on_remove)]
|
||||
pub struct GridCell<P: GridPrecision> {
|
||||
pub struct GridCell {
|
||||
/// The x-index of the cell.
|
||||
pub x: P,
|
||||
pub x: GridPrecision,
|
||||
/// The y-index of the cell.
|
||||
pub y: P,
|
||||
pub y: GridPrecision,
|
||||
/// The z-index of the cell.
|
||||
pub z: P,
|
||||
pub z: GridPrecision,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridCell<P> {
|
||||
fn on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
assert!(world.get::<GridCellAny>(entity).is_none(), "Adding multiple GridCell<P>s with different generic values on the same entity is not supported");
|
||||
world.commands().entity(entity).insert(GridCellAny);
|
||||
}
|
||||
|
||||
fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
|
||||
world.commands().entity(entity).remove::<GridCellAny>();
|
||||
}
|
||||
|
||||
impl GridCell {
|
||||
/// Construct a new [`GridCell`].
|
||||
pub fn new(x: P, y: P, z: P) -> Self {
|
||||
pub fn new(x: GridPrecision, y: GridPrecision, z: GridPrecision) -> Self {
|
||||
Self { x, y, z }
|
||||
}
|
||||
|
||||
/// The origin [`GridCell`].
|
||||
pub const ZERO: Self = GridCell {
|
||||
x: P::ZERO,
|
||||
y: P::ZERO,
|
||||
z: P::ZERO,
|
||||
};
|
||||
pub const ZERO: Self = GridCell { x: 0, y: 0, z: 0 };
|
||||
|
||||
/// A unit value [`GridCell`]. Useful for offsets.
|
||||
pub const ONE: Self = GridCell {
|
||||
x: P::ONE,
|
||||
y: P::ONE,
|
||||
z: P::ONE,
|
||||
};
|
||||
pub const ONE: Self = GridCell { x: 1, y: 1, z: 1 };
|
||||
|
||||
/// Convert this grid cell to a floating point translation within this `grid`.
|
||||
pub fn as_dvec3(&self, grid: &Grid<P>) -> DVec3 {
|
||||
pub fn as_dvec3(&self, grid: &Grid) -> DVec3 {
|
||||
DVec3 {
|
||||
x: self.x.as_f64() * grid.cell_edge_length() as f64,
|
||||
y: self.y.as_f64() * grid.cell_edge_length() as f64,
|
||||
z: self.z.as_f64() * grid.cell_edge_length() as f64,
|
||||
x: self.x as f64 * grid.cell_edge_length() as f64,
|
||||
y: self.y as f64 * grid.cell_edge_length() as f64,
|
||||
z: self.z as f64 * grid.cell_edge_length() as f64,
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +83,7 @@ impl<P: GridPrecision> GridCell<P> {
|
||||
/// [`Grid`], it will be relocated to the nearest grid cell to reduce the size of the transform.
|
||||
pub fn recenter_large_transforms(
|
||||
mut stats: ResMut<crate::timing::PropagationStats>,
|
||||
grids: Query<&Grid<P>>,
|
||||
grids: Query<&Grid>,
|
||||
mut changed_transform: Query<(&mut Self, &mut Transform, &Parent), Changed<Transform>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
@ -141,8 +111,8 @@ impl<P: GridPrecision> GridCell<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Add for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
GridCell {
|
||||
@ -153,20 +123,20 @@ impl<P: GridPrecision> std::ops::Add for GridCell<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add<IVec3> for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Add<IVec3> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: IVec3) -> Self::Output {
|
||||
GridCell {
|
||||
x: self.x.wrapping_add_i32(rhs.x),
|
||||
y: self.y.wrapping_add_i32(rhs.y),
|
||||
z: self.z.wrapping_add_i32(rhs.z),
|
||||
x: self.x.wrapping_add(rhs.x as GridPrecision),
|
||||
y: self.y.wrapping_add(rhs.y as GridPrecision),
|
||||
z: self.z.wrapping_add(rhs.z as GridPrecision),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Sub for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
GridCell {
|
||||
@ -177,114 +147,94 @@ impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub<IVec3> for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Sub<IVec3> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: IVec3) -> Self::Output {
|
||||
GridCell {
|
||||
x: self.x.wrapping_add_i32(-rhs.x),
|
||||
y: self.y.wrapping_add_i32(-rhs.y),
|
||||
z: self.z.wrapping_add_i32(-rhs.z),
|
||||
x: self.x.wrapping_add(-rhs.x as GridPrecision),
|
||||
y: self.y.wrapping_add(-rhs.y as GridPrecision),
|
||||
z: self.z.wrapping_add(-rhs.z as GridPrecision),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Add for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
(*self).add(*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add<IVec3> for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Add<IVec3> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: IVec3) -> Self::Output {
|
||||
(*self).add(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Sub for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
(*self).sub(*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub<IVec3> for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Sub<IVec3> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: IVec3) -> Self::Output {
|
||||
(*self).sub(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::AddAssign for GridCell<P> {
|
||||
impl std::ops::AddAssign for GridCell {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
use std::ops::Add;
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::AddAssign<IVec3> for GridCell<P> {
|
||||
impl std::ops::AddAssign<IVec3> for GridCell {
|
||||
fn add_assign(&mut self, rhs: IVec3) {
|
||||
use std::ops::Add;
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::SubAssign for GridCell<P> {
|
||||
impl std::ops::SubAssign for GridCell {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
use std::ops::Sub;
|
||||
*self = self.sub(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::SubAssign<IVec3> for GridCell<P> {
|
||||
impl std::ops::SubAssign<IVec3> for GridCell {
|
||||
fn sub_assign(&mut self, rhs: IVec3) {
|
||||
use std::ops::Sub;
|
||||
*self = self.sub(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Mul<P> for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Mul<GridPrecision> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn mul(self, rhs: P) -> Self::Output {
|
||||
fn mul(self, rhs: GridPrecision) -> Self::Output {
|
||||
GridCell {
|
||||
x: GridPrecision::mul(self.x, rhs),
|
||||
y: GridPrecision::mul(self.y, rhs),
|
||||
z: GridPrecision::mul(self.z, rhs),
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
z: self.z * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Mul<P> for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
impl std::ops::Mul<GridPrecision> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn mul(self, rhs: P) -> Self::Output {
|
||||
fn mul(self, rhs: GridPrecision) -> Self::Output {
|
||||
(*self).mul(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Adding multiple GridCell<P>s with different generic values on the same entity is not supported"
|
||||
)]
|
||||
fn disallow_multiple_grid_cells_on_same_entity() {
|
||||
App::new()
|
||||
.add_systems(Startup, |mut commands: Commands| {
|
||||
commands
|
||||
.spawn_empty()
|
||||
.insert(super::GridCell::<i8>::default())
|
||||
.insert(super::GridCell::<i16>::default());
|
||||
})
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,9 +38,9 @@ mod inner {
|
||||
/// other - consider the grid of a planet, spinning about its axis and orbiting about a star, it
|
||||
/// will not align with the grid of the star system!
|
||||
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
|
||||
pub struct LocalFloatingOrigin<P: GridPrecision> {
|
||||
pub struct LocalFloatingOrigin {
|
||||
/// The local cell that the floating origin's grid cell origin falls into.
|
||||
cell: GridCell<P>,
|
||||
cell: GridCell,
|
||||
/// The translation of the floating origin's grid cell relative to the origin of
|
||||
/// [`LocalFloatingOrigin::cell`].
|
||||
translation: Vec3,
|
||||
@ -73,7 +73,7 @@ mod inner {
|
||||
is_local_origin_unchanged: bool,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||
impl LocalFloatingOrigin {
|
||||
/// The grid transform from the local grid, to the floating origin's grid. See
|
||||
/// [Self::grid_transform].
|
||||
#[inline]
|
||||
@ -83,7 +83,7 @@ mod inner {
|
||||
|
||||
/// Gets [`Self::cell`].
|
||||
#[inline]
|
||||
pub fn cell(&self) -> GridCell<P> {
|
||||
pub fn cell(&self) -> GridCell {
|
||||
self.cell
|
||||
}
|
||||
|
||||
@ -102,7 +102,7 @@ mod inner {
|
||||
/// Update this local floating origin, and compute the new inverse transform.
|
||||
pub fn set(
|
||||
&mut self,
|
||||
translation_grid: GridCell<P>,
|
||||
translation_grid: GridCell,
|
||||
translation_float: Vec3,
|
||||
rotation_float: DQuat,
|
||||
) {
|
||||
@ -120,7 +120,7 @@ mod inner {
|
||||
}
|
||||
|
||||
/// Create a new [`LocalFloatingOrigin`].
|
||||
pub fn new(cell: GridCell<P>, translation: Vec3, rotation: DQuat) -> Self {
|
||||
pub fn new(cell: GridCell, translation: Vec3, rotation: DQuat) -> Self {
|
||||
let grid_transform = DAffine3 {
|
||||
matrix3: DMat3::from_quat(rotation),
|
||||
translation: translation.as_dvec3(),
|
||||
@ -144,9 +144,9 @@ mod inner {
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_origin_to_parent<P: GridPrecision>(
|
||||
fn propagate_origin_to_parent(
|
||||
this_grid_entity: Entity,
|
||||
grids: &mut GridsMut<P>,
|
||||
grids: &mut GridsMut,
|
||||
parent_grid_entity: Entity,
|
||||
) {
|
||||
let (this_grid, this_cell, this_transform) = grids.get(this_grid_entity);
|
||||
@ -190,9 +190,9 @@ fn propagate_origin_to_parent<P: GridPrecision>(
|
||||
});
|
||||
}
|
||||
|
||||
fn propagate_origin_to_child<P: GridPrecision>(
|
||||
fn propagate_origin_to_child(
|
||||
this_grid_entity: Entity,
|
||||
grids: &mut GridsMut<P>,
|
||||
grids: &mut GridsMut,
|
||||
child_grid_entity: Entity,
|
||||
) {
|
||||
let (this_grid, _this_cell, _this_transform) = grids.get(this_grid_entity);
|
||||
@ -241,14 +241,14 @@ fn propagate_origin_to_child<P: GridPrecision>(
|
||||
|
||||
/// A system param for more easily navigating a hierarchy of [`Grid`]s.
|
||||
#[derive(SystemParam)]
|
||||
pub struct Grids<'w, 's, P: GridPrecision> {
|
||||
pub struct Grids<'w, 's> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
grid_query: Query<'w, 's, (Entity, Read<Grid<P>>, Option<Read<Parent>>)>,
|
||||
grid_query: Query<'w, 's, (Entity, Read<Grid>, Option<Read<Parent>>)>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> Grids<'_, '_, P> {
|
||||
impl Grids<'_, '_> {
|
||||
/// Get a [`Grid`] from its `Entity`.
|
||||
pub fn get(&self, grid_entity: Entity) -> &Grid<P> {
|
||||
pub fn get(&self, grid_entity: Entity) -> &Grid {
|
||||
self.grid_query
|
||||
.get(grid_entity)
|
||||
.map(|(_entity, grid, _parent)| grid)
|
||||
@ -258,7 +258,7 @@ impl<P: GridPrecision> Grids<'_, '_, P> {
|
||||
}
|
||||
|
||||
/// Get the [`Grid`] that `this` `Entity` is a child of, if it exists.
|
||||
pub fn parent_grid(&self, this: Entity) -> Option<&Grid<P>> {
|
||||
pub fn parent_grid(&self, this: Entity) -> Option<&Grid> {
|
||||
self.parent_grid_entity(this)
|
||||
.map(|grid_entity| self.get(grid_entity))
|
||||
}
|
||||
@ -316,13 +316,13 @@ impl<P: GridPrecision> Grids<'_, '_, P> {
|
||||
|
||||
/// A system param for more easily navigating a hierarchy of grids mutably.
|
||||
#[derive(SystemParam)]
|
||||
pub struct GridsMut<'w, 's, P: GridPrecision> {
|
||||
pub struct GridsMut<'w, 's> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
position: Query<'w, 's, (Read<GridCell<P>>, Read<Transform>), With<Grid<P>>>,
|
||||
grid_query: Query<'w, 's, (Entity, Write<Grid<P>>, Option<Read<Parent>>)>,
|
||||
position: Query<'w, 's, (Read<GridCell>, Read<Transform>), With<Grid>>,
|
||||
grid_query: Query<'w, 's, (Entity, Write<Grid>, Option<Read<Parent>>)>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
impl GridsMut<'_, '_> {
|
||||
/// Get mutable access to the [`Grid`], and run the provided function or closure, optionally
|
||||
/// returning data.
|
||||
///
|
||||
@ -332,7 +332,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
pub fn update<T>(
|
||||
&mut self,
|
||||
grid_entity: Entity,
|
||||
mut func: impl FnMut(&mut Grid<P>, &GridCell<P>, &Transform) -> T,
|
||||
mut func: impl FnMut(&mut Grid, &GridCell, &Transform) -> T,
|
||||
) -> T {
|
||||
let (cell, transform) = self.position(grid_entity);
|
||||
self.grid_query
|
||||
@ -342,7 +342,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
}
|
||||
|
||||
/// Get the grid and the position of the grid from its `Entity`.
|
||||
pub fn get(&self, grid_entity: Entity) -> (&Grid<P>, GridCell<P>, Transform) {
|
||||
pub fn get(&self, grid_entity: Entity) -> (&Grid, GridCell, Transform) {
|
||||
let (cell, transform) = self.position(grid_entity);
|
||||
self.grid_query
|
||||
.get(grid_entity)
|
||||
@ -356,7 +356,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
/// they are missing.
|
||||
///
|
||||
/// Needed because the root grid should not have a grid cell or transform.
|
||||
pub fn position(&self, grid_entity: Entity) -> (GridCell<P>, Transform) {
|
||||
pub fn position(&self, grid_entity: Entity) -> (GridCell, Transform) {
|
||||
let (cell, transform) = (GridCell::default(), Transform::default());
|
||||
let (cell, transform) = self.position.get(grid_entity).unwrap_or_else(|_| {
|
||||
assert!(self.parent.get(grid_entity).is_err(), "Grid entity {grid_entity:?} is missing a GridCell and Transform. This is valid only if this is a root grid, but this is not.");
|
||||
@ -366,7 +366,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
}
|
||||
|
||||
/// Get the [`Grid`] that `this` `Entity` is a child of, if it exists.
|
||||
pub fn parent_grid(&self, this: Entity) -> Option<(&Grid<P>, GridCell<P>, Transform)> {
|
||||
pub fn parent_grid(&self, this: Entity) -> Option<(&Grid, GridCell, Transform)> {
|
||||
self.parent_grid_entity(this)
|
||||
.map(|grid_entity| self.get(grid_entity))
|
||||
}
|
||||
@ -421,7 +421,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||
impl LocalFloatingOrigin {
|
||||
/// Update the [`LocalFloatingOrigin`] of every [`Grid`] in the world. This does not update any
|
||||
/// entity transforms, instead this is a preceding step that updates every reference grid, so it
|
||||
/// knows where the floating origin is located with respect to that reference grid. This is all
|
||||
@ -430,10 +430,10 @@ impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||
/// source of truth and never mutated.
|
||||
pub fn compute_all(
|
||||
mut stats: ResMut<crate::timing::PropagationStats>,
|
||||
mut grids: GridsMut<P>,
|
||||
mut grids: GridsMut,
|
||||
mut grid_stack: Local<Vec<Entity>>,
|
||||
mut scratch_buffer: Local<Vec<Entity>>,
|
||||
cells: Query<(Entity, Ref<GridCell<P>>)>,
|
||||
cells: Query<(Entity, Ref<GridCell>)>,
|
||||
roots: Query<(Entity, &BigSpace)>,
|
||||
parents: Query<&Parent>,
|
||||
) {
|
||||
@ -524,13 +524,9 @@ mod tests {
|
||||
#[test]
|
||||
fn grid_hierarchy_getters() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let grid_bundle = (
|
||||
Transform::default(),
|
||||
GridCell::<i32>::default(),
|
||||
Grid::<i32>::default(),
|
||||
);
|
||||
let grid_bundle = (Transform::default(), GridCell::default(), Grid::default());
|
||||
|
||||
let child_1 = app.world_mut().spawn(grid_bundle.clone()).id();
|
||||
let child_2 = app.world_mut().spawn(grid_bundle.clone()).id();
|
||||
@ -542,7 +538,7 @@ mod tests {
|
||||
.entity_mut(parent)
|
||||
.add_children(&[child_1, child_2]);
|
||||
|
||||
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
|
||||
let mut state = SystemState::<GridsMut>::new(app.world_mut());
|
||||
let mut grids = state.get_mut(app.world_mut());
|
||||
|
||||
// Children
|
||||
@ -573,11 +569,11 @@ mod tests {
|
||||
#[test]
|
||||
fn child_propagation() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let root_grid = Grid {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i32>::new(1_000_000, -1, -1),
|
||||
GridCell::new(1_000_000, -1, -1),
|
||||
Vec3::ZERO,
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
),
|
||||
@ -585,7 +581,7 @@ mod tests {
|
||||
};
|
||||
let root = app
|
||||
.world_mut()
|
||||
.spawn((Transform::default(), GridCell::<i32>::default(), root_grid))
|
||||
.spawn((Transform::default(), GridCell::default(), root_grid))
|
||||
.id();
|
||||
|
||||
let child = app
|
||||
@ -593,14 +589,14 @@ mod tests {
|
||||
.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),
|
||||
Grid::<i32>::default(),
|
||||
GridCell::new(1_000_000, 0, 0),
|
||||
Grid::default(),
|
||||
))
|
||||
.id();
|
||||
|
||||
app.world_mut().entity_mut(root).add_child(child);
|
||||
|
||||
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
|
||||
let mut state = SystemState::<GridsMut>::new(app.world_mut());
|
||||
let mut grids = state.get_mut(app.world_mut());
|
||||
|
||||
// The function we are testing
|
||||
@ -633,13 +629,9 @@ mod tests {
|
||||
#[test]
|
||||
fn parent_propagation() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i64>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let grid_bundle = (
|
||||
Transform::default(),
|
||||
GridCell::<i64>::default(),
|
||||
Grid::<i64>::default(),
|
||||
);
|
||||
let grid_bundle = (Transform::default(), GridCell::default(), Grid::default());
|
||||
let root = app.world_mut().spawn(grid_bundle.clone()).id();
|
||||
|
||||
let child = app
|
||||
@ -647,10 +639,10 @@ mod tests {
|
||||
.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
|
||||
GridCell::new(150_000_003_000, 0, 0), // roughly radius of earth orbit
|
||||
Grid {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i64>::new(0, 3_000, 0),
|
||||
GridCell::new(0, 3_000, 0),
|
||||
Vec3::new(5.0, 5.0, 0.0),
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
),
|
||||
@ -661,7 +653,7 @@ mod tests {
|
||||
|
||||
app.world_mut().entity_mut(root).add_child(child);
|
||||
|
||||
let mut state = SystemState::<GridsMut<i64>>::new(app.world_mut());
|
||||
let mut state = SystemState::<GridsMut>::new(app.world_mut());
|
||||
let mut grids = state.get_mut(app.world_mut());
|
||||
|
||||
// The function we are testing
|
||||
@ -694,16 +686,16 @@ mod tests {
|
||||
#[test]
|
||||
fn origin_transform() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let root = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::default(),
|
||||
GridCell::<i32>::default(),
|
||||
GridCell::default(),
|
||||
Grid {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i32>::new(0, 0, 0),
|
||||
GridCell::new(0, 0, 0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
DQuat::from_rotation_z(0.0),
|
||||
),
|
||||
@ -718,14 +710,14 @@ mod tests {
|
||||
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),
|
||||
Grid::<i32>::default(),
|
||||
GridCell::new(0, 0, 0),
|
||||
Grid::default(),
|
||||
))
|
||||
.id();
|
||||
|
||||
app.world_mut().entity_mut(root).add_child(child);
|
||||
|
||||
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
|
||||
let mut state = SystemState::<GridsMut>::new(app.world_mut());
|
||||
let mut grids = state.get_mut(app.world_mut());
|
||||
|
||||
propagate_origin_to_child(root, &mut grids, child);
|
||||
|
||||
@ -30,22 +30,22 @@ pub mod propagation;
|
||||
#[reflect(Component)]
|
||||
// We do not require the Transform, GlobalTransform, or GridCell, because these are not required in
|
||||
// all cases: e.g. BigSpace should not have a Transform or GridCell.
|
||||
pub struct Grid<P: GridPrecision> {
|
||||
pub struct Grid {
|
||||
/// The high-precision position of the floating origin's current grid cell local to this grid.
|
||||
local_floating_origin: LocalFloatingOrigin<P>,
|
||||
local_floating_origin: LocalFloatingOrigin,
|
||||
/// 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 Grid<P> {
|
||||
impl Default for Grid {
|
||||
fn default() -> Self {
|
||||
Self::new(2_000f32, 100f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> Grid<P> {
|
||||
impl Grid {
|
||||
/// Construct a new [`Grid`]. The properties of a grid cannot be changed after construction.
|
||||
pub fn new(cell_edge_length: f32, switching_threshold: f32) -> Self {
|
||||
Self {
|
||||
@ -57,7 +57,7 @@ impl<P: GridPrecision> Grid<P> {
|
||||
|
||||
/// Get the position of the floating origin relative to the current grid.
|
||||
#[inline]
|
||||
pub fn local_floating_origin(&self) -> &LocalFloatingOrigin<P> {
|
||||
pub fn local_floating_origin(&self) -> &LocalFloatingOrigin {
|
||||
&self.local_floating_origin
|
||||
}
|
||||
|
||||
@ -76,37 +76,37 @@ impl<P: GridPrecision> Grid<P> {
|
||||
/// Compute the double precision position of an entity's [`Transform`] with respect to the given
|
||||
/// [`GridCell`] within this grid.
|
||||
#[inline]
|
||||
pub fn grid_position_double(&self, pos: &GridCell<P>, transform: &Transform) -> DVec3 {
|
||||
pub fn grid_position_double(&self, pos: &GridCell, 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,
|
||||
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`].
|
||||
#[inline]
|
||||
pub fn grid_position(&self, pos: &GridCell<P>, transform: &Transform) -> Vec3 {
|
||||
pub fn grid_position(&self, pos: &GridCell, 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,
|
||||
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 cell_to_float(&self, pos: &GridCell<P>) -> DVec3 {
|
||||
pub fn cell_to_float(&self, pos: &GridCell) -> DVec3 {
|
||||
DVec3 {
|
||||
x: pos.x.as_f64(),
|
||||
y: pos.y.as_f64(),
|
||||
z: pos.z.as_f64(),
|
||||
x: pos.x as f64,
|
||||
y: pos.y 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.
|
||||
#[inline]
|
||||
pub fn translation_to_grid(&self, input: impl Into<DVec3>) -> (GridCell<P>, Vec3) {
|
||||
pub fn translation_to_grid(&self, input: impl Into<DVec3>) -> (GridCell, Vec3) {
|
||||
let l = self.cell_edge_length as f64;
|
||||
let input = input.into();
|
||||
let DVec3 { x, y, z } = input;
|
||||
@ -124,9 +124,9 @@ impl<P: GridPrecision> Grid<P> {
|
||||
|
||||
(
|
||||
GridCell {
|
||||
x: P::from_f64(x_r),
|
||||
y: P::from_f64(y_r),
|
||||
z: P::from_f64(z_r),
|
||||
x: x_r as GridPrecision,
|
||||
y: y_r as GridPrecision,
|
||||
z: z_r as GridPrecision,
|
||||
},
|
||||
Vec3::new(t_x as f32, t_y as f32, t_z as f32),
|
||||
)
|
||||
@ -134,7 +134,7 @@ impl<P: GridPrecision> Grid<P> {
|
||||
|
||||
/// Convert a large translation into a small translation relative to a grid cell.
|
||||
#[inline]
|
||||
pub fn imprecise_translation_to_grid(&self, input: Vec3) -> (GridCell<P>, Vec3) {
|
||||
pub fn imprecise_translation_to_grid(&self, input: Vec3) -> (GridCell, Vec3) {
|
||||
self.translation_to_grid(input.as_dvec3())
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ impl<P: GridPrecision> Grid<P> {
|
||||
#[inline]
|
||||
pub fn global_transform(
|
||||
&self,
|
||||
local_cell: &GridCell<P>,
|
||||
local_cell: &GridCell,
|
||||
local_transform: &Transform,
|
||||
) -> GlobalTransform {
|
||||
// The grid transform from the floating origin's grid, to the local grid.
|
||||
|
||||
@ -16,20 +16,20 @@ use bevy_transform::prelude::*;
|
||||
#[derive(Component, Default, Reflect)]
|
||||
pub struct LowPrecisionRoot;
|
||||
|
||||
impl<P: GridPrecision> Grid<P> {
|
||||
impl Grid {
|
||||
/// Update the `GlobalTransform` of entities with a [`GridCell`], using the [`Grid`] the entity
|
||||
/// belongs to.
|
||||
pub fn propagate_high_precision(
|
||||
mut stats: ResMut<crate::timing::PropagationStats>,
|
||||
grids: Query<&Grid<P>>,
|
||||
grids: Query<&Grid>,
|
||||
mut entities: ParamSet<(
|
||||
Query<(
|
||||
Ref<GridCell<P>>,
|
||||
Ref<GridCell>,
|
||||
Ref<Transform>,
|
||||
Ref<Parent>,
|
||||
&mut GlobalTransform,
|
||||
)>,
|
||||
Query<(&Grid<P>, &mut GlobalTransform), With<BigSpace>>,
|
||||
Query<(&Grid, &mut GlobalTransform), With<BigSpace>>,
|
||||
)>,
|
||||
) {
|
||||
let start = bevy_utils::Instant::now();
|
||||
@ -93,13 +93,13 @@ impl<P: GridPrecision> Grid<P> {
|
||||
pub fn tag_low_precision_roots(
|
||||
mut stats: ResMut<crate::timing::PropagationStats>,
|
||||
mut commands: Commands,
|
||||
valid_parent: Query<(), (With<GridCell<P>>, With<GlobalTransform>, With<Children>)>,
|
||||
valid_parent: Query<(), (With<GridCell>, With<GlobalTransform>, With<Children>)>,
|
||||
unmarked: Query<
|
||||
(Entity, &Parent),
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
Without<GridCellAny>,
|
||||
Without<GridCell>,
|
||||
Without<LowPrecisionRoot>,
|
||||
Or<(Changed<Parent>, Added<Transform>)>,
|
||||
),
|
||||
@ -111,7 +111,7 @@ impl<P: GridPrecision> Grid<P> {
|
||||
Or<(
|
||||
Without<Transform>,
|
||||
Without<GlobalTransform>,
|
||||
With<GridCell<P>>,
|
||||
With<GridCell>,
|
||||
Without<Parent>,
|
||||
)>,
|
||||
),
|
||||
@ -147,7 +147,7 @@ impl<P: GridPrecision> Grid<P> {
|
||||
(
|
||||
// A root big space does not have a grid cell, and not all high precision entities
|
||||
// have a grid
|
||||
Or<(With<Grid<P>>, With<GridCell<P>>)>,
|
||||
Or<(With<Grid>, With<GridCell>)>,
|
||||
),
|
||||
>,
|
||||
roots: Query<(Entity, &Parent), With<LowPrecisionRoot>>,
|
||||
@ -155,9 +155,8 @@ impl<P: GridPrecision> Grid<P> {
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCellAny>,
|
||||
Without<GridCell<P>>, // Used to prove access to GlobalTransform is disjoint
|
||||
Without<Grid<P>>,
|
||||
Without<GridCell>, // Used to prove access to GlobalTransform is disjoint
|
||||
Without<Grid>,
|
||||
),
|
||||
>,
|
||||
parent_query: Query<
|
||||
@ -165,8 +164,8 @@ impl<P: GridPrecision> Grid<P> {
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
Without<GridCellAny>,
|
||||
Without<Grid<P>>,
|
||||
Without<GridCell>,
|
||||
Without<Grid>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
@ -228,9 +227,8 @@ impl<P: GridPrecision> Grid<P> {
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCellAny>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<GridCell<P>>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<Grid<P>>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<GridCell>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<Grid>, // ***ADDED*** Only recurse low-precision entities
|
||||
),
|
||||
>,
|
||||
parent_query: &Query<
|
||||
@ -238,8 +236,8 @@ impl<P: GridPrecision> Grid<P> {
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
Without<GridCellAny>,
|
||||
Without<Grid<P>>,
|
||||
Without<GridCell>,
|
||||
Without<Grid>,
|
||||
),
|
||||
>,
|
||||
entity: Entity,
|
||||
@ -314,9 +312,10 @@ mod tests {
|
||||
struct Test;
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default())
|
||||
.add_systems(Startup, |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
app.add_plugins(BigSpacePlugin::default()).add_systems(
|
||||
Startup,
|
||||
|mut commands: Commands| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial(FloatingOrigin);
|
||||
root.spawn_spatial((
|
||||
Transform::from_xyz(3.0, 3.0, 3.0),
|
||||
@ -330,7 +329,8 @@ mod tests {
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
app.update();
|
||||
|
||||
|
||||
@ -28,14 +28,14 @@ impl Hash for FastGridHash {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> PartialEq<GridHash<P>> for FastGridHash {
|
||||
fn eq(&self, other: &GridHash<P>) -> bool {
|
||||
impl PartialEq<GridHash> for FastGridHash {
|
||||
fn eq(&self, other: &GridHash) -> bool {
|
||||
self.0 == other.pre_hash
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> From<GridHash<P>> for FastGridHash {
|
||||
fn from(value: GridHash<P>) -> Self {
|
||||
impl From<GridHash> for FastGridHash {
|
||||
fn from(value: GridHash) -> Self {
|
||||
Self(value.pre_hash)
|
||||
}
|
||||
}
|
||||
@ -50,9 +50,9 @@ impl<P: GridPrecision> From<GridHash<P>> for FastGridHash {
|
||||
/// the [`Parent`] of the entity to uniquely identify its position. These two values are then hashed
|
||||
/// and stored in this spatial hash component.
|
||||
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
||||
pub struct GridHash<P: GridPrecision> {
|
||||
pub struct GridHash {
|
||||
// Needed for equality checks
|
||||
cell: GridCell<P>,
|
||||
cell: GridCell,
|
||||
// Needed for equality checks
|
||||
grid: Entity,
|
||||
// The hashed value of the `cell` and `grid` fields. Hash collisions are possible, especially
|
||||
@ -62,7 +62,7 @@ pub struct GridHash<P: GridPrecision> {
|
||||
pre_hash: u64,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> PartialEq for GridHash<P> {
|
||||
impl PartialEq for GridHash {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// Comparing the hash is redundant.
|
||||
@ -74,27 +74,27 @@ impl<P: GridPrecision> PartialEq for GridHash<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> Eq for GridHash<P> {}
|
||||
impl Eq for GridHash {}
|
||||
|
||||
impl<P: GridPrecision> Hash for GridHash<P> {
|
||||
impl Hash for GridHash {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.pre_hash);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridHash<P> {
|
||||
impl GridHash {
|
||||
/// Generate a new hash from parts.
|
||||
///
|
||||
/// Intentionally left private, so we can ensure the only place these are constructed/mutated is
|
||||
/// this module. This allows us to optimize change detection using [`ChangedGridHashes`].
|
||||
#[inline]
|
||||
pub(super) fn new(parent: &Parent, cell: &GridCell<P>) -> Self {
|
||||
pub(super) fn new(parent: &Parent, cell: &GridCell) -> Self {
|
||||
Self::from_parent(parent.get(), cell)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn from_parent(parent: Entity, cell: &GridCell<P>) -> Self {
|
||||
pub(super) fn from_parent(parent: Entity, cell: &GridCell) -> Self {
|
||||
let hasher = &mut AHasher::default();
|
||||
hasher.write_u64(parent.to_bits());
|
||||
cell.hash(hasher);
|
||||
@ -108,7 +108,7 @@ impl<P: GridPrecision> GridHash<P> {
|
||||
|
||||
/// Do not use this to manually construct this component. You've been warned.
|
||||
#[doc(hidden)]
|
||||
pub fn __new_manual(parent: Entity, cell: &GridCell<P>) -> Self {
|
||||
pub fn __new_manual(parent: Entity, cell: &GridCell) -> Self {
|
||||
Self::from_parent(parent, cell)
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ impl<P: GridPrecision> GridHash<P> {
|
||||
|
||||
/// Returns an iterator over all neighboring grid cells and their hashes, within the
|
||||
/// `cell_radius`. This iterator will not visit `cell`.
|
||||
pub fn adjacent(&self, cell_radius: u8) -> impl Iterator<Item = GridHash<P>> + '_ {
|
||||
pub fn adjacent(&self, cell_radius: u8) -> impl Iterator<Item = GridHash> + '_ {
|
||||
let radius = cell_radius as i32;
|
||||
let search_width = 1 + 2 * radius;
|
||||
let search_volume = search_width.pow(3);
|
||||
@ -158,21 +158,15 @@ impl<P: GridPrecision> GridHash<P> {
|
||||
/// [`GridHashMapFilter`].
|
||||
pub(super) fn update<F: GridHashMapFilter>(
|
||||
mut commands: Commands,
|
||||
mut changed_hashes: ResMut<ChangedGridHashes<P, F>>,
|
||||
mut changed_hashes: ResMut<ChangedGridHashes<F>>,
|
||||
mut spatial_entities: Query<
|
||||
(
|
||||
Entity,
|
||||
&Parent,
|
||||
&GridCell<P>,
|
||||
&mut GridHash<P>,
|
||||
&mut FastGridHash,
|
||||
),
|
||||
(F, Or<(Changed<Parent>, Changed<GridCell<P>>)>),
|
||||
(Entity, &Parent, &GridCell, &mut GridHash, &mut FastGridHash),
|
||||
(F, Or<(Changed<Parent>, Changed<GridCell>)>),
|
||||
>,
|
||||
added_entities: Query<(Entity, &Parent, &GridCell<P>), (F, Without<GridHash<P>>)>,
|
||||
added_entities: Query<(Entity, &Parent, &GridCell), (F, Without<GridHash>)>,
|
||||
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
|
||||
mut thread_updated_hashes: Local<Parallel<Vec<Entity>>>,
|
||||
mut thread_commands: Local<Parallel<Vec<(Entity, GridHash<P>, FastGridHash)>>>,
|
||||
mut thread_commands: Local<Parallel<Vec<(Entity, GridHash, FastGridHash)>>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
|
||||
@ -208,7 +202,7 @@ impl<P: GridPrecision> GridHash<P> {
|
||||
}
|
||||
|
||||
/// The [`GridCell`] associated with this spatial hash.
|
||||
pub fn cell(&self) -> GridCell<P> {
|
||||
pub fn cell(&self) -> GridCell {
|
||||
self.cell
|
||||
}
|
||||
|
||||
|
||||
109
src/hash/map.rs
109
src/hash/map.rs
@ -2,6 +2,7 @@
|
||||
|
||||
use std::{collections::VecDeque, marker::PhantomData, time::Instant};
|
||||
|
||||
use super::GridHashMapFilter;
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::{entity::EntityHash, prelude::*};
|
||||
use bevy_utils::{
|
||||
@ -9,22 +10,20 @@ use bevy_utils::{
|
||||
PassHash,
|
||||
};
|
||||
|
||||
use super::GridHashMapFilter;
|
||||
|
||||
/// An entry in a [`GridHashMap`], accessed with a [`GridHash`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GridHashEntry<P: GridPrecision> {
|
||||
pub struct GridHashEntry {
|
||||
/// All the entities located in this grid cell.
|
||||
pub entities: HashSet<Entity, EntityHash>,
|
||||
/// Precomputed hashes to direct neighbors.
|
||||
// TODO: computation cheap, heap slow. Can this be replaced with a u32 bitmask of occupied cells
|
||||
// (only need 26 bits), with the hashes computed based on the neighbor's relative position?
|
||||
pub occupied_neighbors: Vec<GridHash<P>>,
|
||||
pub occupied_neighbors: Vec<GridHash>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridHashEntry<P> {
|
||||
impl GridHashEntry {
|
||||
/// Find an occupied neighbor's index in the list.
|
||||
fn neighbor_index(&self, hash: &GridHash<P>) -> Option<usize> {
|
||||
fn neighbor_index(&self, hash: &GridHash) -> Option<usize> {
|
||||
self.occupied_neighbors
|
||||
.iter()
|
||||
.enumerate()
|
||||
@ -37,8 +36,8 @@ impl<P: GridPrecision> GridHashEntry<P> {
|
||||
/// See [`GridHashMap::nearby`].
|
||||
pub fn nearby<'a, F: GridHashMapFilter>(
|
||||
&'a self,
|
||||
map: &'a GridHashMap<P, F>,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
|
||||
map: &'a GridHashMap<F>,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
|
||||
map.nearby(self)
|
||||
}
|
||||
}
|
||||
@ -59,14 +58,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for &'a GridHashEntry<P> {
|
||||
impl<'a> SpatialEntryToEntities<'a> for &'a GridHashEntry {
|
||||
#[inline]
|
||||
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
|
||||
self.entities.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for Neighbor<'a, P> {
|
||||
impl<'a> SpatialEntryToEntities<'a> for Neighbor<'a> {
|
||||
#[inline]
|
||||
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
|
||||
self.1.entities.iter().copied()
|
||||
@ -75,23 +74,21 @@ impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for Neighbor<'a, P> {
|
||||
|
||||
/// A global spatial hash map for quickly finding entities in a grid cell.
|
||||
#[derive(Resource, Clone)]
|
||||
pub struct GridHashMap<P, F = ()>
|
||||
pub struct GridHashMap<F = ()>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
/// The primary hash map for looking up entities by their [`GridHash`].
|
||||
map: InnerGridHashMap<P>,
|
||||
map: InnerGridHashMap,
|
||||
/// A reverse lookup to find the latest spatial hash associated with an entity that this map is
|
||||
/// aware of. This is needed to remove or move an entity when its cell changes, because once it
|
||||
/// changes in the ECS, we need to know its *previous* value when it was inserted in this map.
|
||||
reverse_map: HashMap<Entity, GridHash<P>, PassHash>,
|
||||
reverse_map: HashMap<Entity, GridHash, PassHash>,
|
||||
spooky: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<P, F> std::fmt::Debug for GridHashMap<P, F>
|
||||
impl<F> std::fmt::Debug for GridHashMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@ -102,9 +99,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, F> Default for GridHashMap<P, F>
|
||||
impl<F> Default for GridHashMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn default() -> Self {
|
||||
@ -116,27 +112,26 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, F> GridHashMap<P, F>
|
||||
impl<F> GridHashMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
/// Get information about all entities located at this [`GridHash`], as well as its
|
||||
/// neighbors.
|
||||
#[inline]
|
||||
pub fn get(&self, hash: &GridHash<P>) -> Option<&GridHashEntry<P>> {
|
||||
pub fn get(&self, hash: &GridHash) -> Option<&GridHashEntry> {
|
||||
self.map.inner.get(hash)
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`GridHash`] is occupied.
|
||||
#[inline]
|
||||
pub fn contains(&self, hash: &GridHash<P>) -> bool {
|
||||
pub fn contains(&self, hash: &GridHash) -> bool {
|
||||
self.map.inner.contains_key(hash)
|
||||
}
|
||||
|
||||
/// An iterator visiting all spatial hash cells and their contents in arbitrary order.
|
||||
#[inline]
|
||||
pub fn all_entries(&self) -> impl Iterator<Item = (&GridHash<P>, &GridHashEntry<P>)> {
|
||||
pub fn all_entries(&self) -> impl Iterator<Item = (&GridHash, &GridHashEntry)> {
|
||||
self.map.inner.iter()
|
||||
}
|
||||
|
||||
@ -153,8 +148,8 @@ where
|
||||
#[inline]
|
||||
pub fn nearby<'a>(
|
||||
&'a self,
|
||||
entry: &'a GridHashEntry<P>,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
|
||||
entry: &'a GridHashEntry,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
|
||||
// Use `std::iter::once` to avoid returning a function-local variable.
|
||||
Iterator::chain(
|
||||
std::iter::once(entry),
|
||||
@ -179,9 +174,9 @@ where
|
||||
#[inline]
|
||||
pub fn within_cube<'a>(
|
||||
&'a self,
|
||||
center: &'a GridHash<P>,
|
||||
center: &'a GridHash,
|
||||
radius: u8,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
|
||||
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
|
||||
// Use `std::iter::once` to avoid returning a function-local variable.
|
||||
Iterator::chain(std::iter::once(*center), center.adjacent(radius))
|
||||
.filter_map(|hash| self.get(&hash))
|
||||
@ -206,9 +201,9 @@ where
|
||||
#[doc(alias = "bfs")]
|
||||
pub fn flood<'a>(
|
||||
&'a self,
|
||||
seed: &GridHash<P>,
|
||||
max_depth: Option<P>,
|
||||
) -> impl Iterator<Item = Neighbor<'a, P>> {
|
||||
seed: &GridHash,
|
||||
max_depth: Option<GridPrecision>,
|
||||
) -> impl Iterator<Item = Neighbor<'a>> {
|
||||
let starting_cell_cell = seed.cell();
|
||||
ContiguousNeighborsIter {
|
||||
initial_hash: Some(*seed),
|
||||
@ -231,7 +226,7 @@ where
|
||||
///
|
||||
/// Useful for incrementally updating data structures that extend the functionality of
|
||||
/// [`GridHashMap`]. Updated in [`GridHashMapSystem::UpdateMap`].
|
||||
pub fn just_inserted(&self) -> &HashSet<GridHash<P>, PassHash> {
|
||||
pub fn just_inserted(&self) -> &HashSet<GridHash, PassHash> {
|
||||
&self.map.just_inserted
|
||||
}
|
||||
|
||||
@ -241,24 +236,23 @@ where
|
||||
///
|
||||
/// Useful for incrementally updating data structures that extend the functionality of
|
||||
/// [`GridHashMap`]. Updated in [`GridHashMapSystem::UpdateMap`].
|
||||
pub fn just_removed(&self) -> &HashSet<GridHash<P>, PassHash> {
|
||||
pub fn just_removed(&self) -> &HashSet<GridHash, PassHash> {
|
||||
&self.map.just_removed
|
||||
}
|
||||
}
|
||||
|
||||
/// Private Systems
|
||||
impl<P, F> GridHashMap<P, F>
|
||||
impl<F> GridHashMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
/// Update the [`GridHashMap`] with entities that have changed [`GridHash`]es, and meet the
|
||||
/// optional [`GridHashMapFilter`].
|
||||
pub(super) fn update(
|
||||
mut spatial_map: ResMut<Self>,
|
||||
mut changed_hashes: ResMut<super::ChangedGridHashes<P, F>>,
|
||||
all_hashes: Query<(Entity, &GridHash<P>), F>,
|
||||
mut removed: RemovedComponents<GridHash<P>>,
|
||||
mut changed_hashes: ResMut<super::ChangedGridHashes<F>>,
|
||||
all_hashes: Query<(Entity, &GridHash), F>,
|
||||
mut removed: RemovedComponents<GridHash>,
|
||||
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
@ -290,14 +284,13 @@ where
|
||||
}
|
||||
|
||||
/// Private Methods
|
||||
impl<P, F> GridHashMap<P, F>
|
||||
impl<F> GridHashMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
/// Insert an entity into the [`GridHashMap`], updating any existing entries.
|
||||
#[inline]
|
||||
fn insert(&mut self, entity: Entity, hash: GridHash<P>) {
|
||||
fn insert(&mut self, entity: Entity, hash: GridHash) {
|
||||
// If this entity is already in the maps, we need to remove and update it.
|
||||
if let Some(old_hash) = self.reverse_map.get_mut(&entity) {
|
||||
if hash.eq(old_hash) {
|
||||
@ -350,24 +343,24 @@ where
|
||||
// Z-order in *another* collection (BTreeMap?) to improve locality for sequential lookups of
|
||||
// spatial neighbors. Would ordering cause hitches with insertions?
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct InnerGridHashMap<P: GridPrecision> {
|
||||
inner: HashMap<GridHash<P>, GridHashEntry<P>, PassHash>,
|
||||
struct InnerGridHashMap {
|
||||
inner: HashMap<GridHash, GridHashEntry, PassHash>,
|
||||
/// Creating and freeing hash sets is expensive. To reduce time spent allocating and running
|
||||
/// destructors, we save any hash sets that would otherwise be thrown away. The next time we
|
||||
/// need to construct a new hash set of entities, we can grab one here.
|
||||
///
|
||||
/// <https://en.wikipedia.org/wiki/Object_pool_pattern>.
|
||||
hash_set_pool: Vec<HashSet<Entity, EntityHash>>,
|
||||
neighbor_pool: Vec<Vec<GridHash<P>>>,
|
||||
neighbor_pool: Vec<Vec<GridHash>>,
|
||||
/// Cells that were added because they were empty but now contain entities.
|
||||
just_inserted: HashSet<GridHash<P>, PassHash>,
|
||||
just_inserted: HashSet<GridHash, PassHash>,
|
||||
/// Cells that were removed because all entities vacated the cell.
|
||||
just_removed: HashSet<GridHash<P>, PassHash>,
|
||||
just_removed: HashSet<GridHash, PassHash>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> InnerGridHashMap<P> {
|
||||
impl InnerGridHashMap {
|
||||
#[inline]
|
||||
fn insert(&mut self, entity: Entity, hash: GridHash<P>) {
|
||||
fn insert(&mut self, entity: Entity, hash: GridHash) {
|
||||
if let Some(entry) = self.inner.get_mut(&hash) {
|
||||
entry.entities.insert(entity);
|
||||
} else {
|
||||
@ -378,7 +371,7 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert_entry(&mut self, hash: GridHash<P>, entities: HashSet<Entity, EntityHash>) {
|
||||
fn insert_entry(&mut self, hash: GridHash, entities: HashSet<Entity, EntityHash>) {
|
||||
let mut occupied_neighbors = self.neighbor_pool.pop().unwrap_or_default();
|
||||
occupied_neighbors.extend(hash.adjacent(1).filter(|neighbor| {
|
||||
self.inner
|
||||
@ -406,7 +399,7 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn remove(&mut self, entity: Entity, old_hash: GridHash<P>) {
|
||||
fn remove(&mut self, entity: Entity, old_hash: GridHash) {
|
||||
if let Some(entry) = self.inner.get_mut(&old_hash) {
|
||||
entry.entities.remove(&entity);
|
||||
if !entry.entities.is_empty() {
|
||||
@ -443,26 +436,24 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
|
||||
}
|
||||
|
||||
/// An iterator over the neighbors of a cell, breadth-first.
|
||||
pub struct ContiguousNeighborsIter<'a, P, F>
|
||||
pub struct ContiguousNeighborsIter<'a, F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
initial_hash: Option<GridHash<P>>,
|
||||
spatial_map: &'a GridHashMap<P, F>,
|
||||
stack: VecDeque<Neighbor<'a, P>>,
|
||||
visited_cells: HashSet<GridHash<P>>,
|
||||
initial_hash: Option<GridHash>,
|
||||
spatial_map: &'a GridHashMap<F>,
|
||||
stack: VecDeque<Neighbor<'a>>,
|
||||
visited_cells: HashSet<GridHash>,
|
||||
}
|
||||
|
||||
/// Newtype used for adding useful extensions like `.entities()`.
|
||||
pub struct Neighbor<'a, P: GridPrecision>(pub GridHash<P>, pub &'a GridHashEntry<P>);
|
||||
pub struct Neighbor<'a>(pub GridHash, pub &'a GridHashEntry);
|
||||
|
||||
impl<'a, P, F> Iterator for ContiguousNeighborsIter<'a, P, F>
|
||||
impl<'a, F> Iterator for ContiguousNeighborsIter<'a, F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
type Item = Neighbor<'a, P>;
|
||||
type Item = Neighbor<'a>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(hash) = self.initial_hash.take() {
|
||||
|
||||
@ -20,27 +20,25 @@ pub mod partition;
|
||||
/// If you are adding multiple copies of this plugin with different filters, there are optimizations
|
||||
/// in place to avoid duplicating work. However, you should still take care to avoid excessively
|
||||
/// overlapping filters.
|
||||
pub struct GridHashPlugin<P, F = ()>(PhantomData<(P, F)>)
|
||||
pub struct GridHashPlugin<F = ()>(PhantomData<F>)
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter;
|
||||
|
||||
impl<P, F> Plugin for GridHashPlugin<P, F>
|
||||
impl<F> Plugin for GridHashPlugin<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<GridHashMap<P, F>>()
|
||||
.init_resource::<ChangedGridHashes<P, F>>()
|
||||
.register_type::<GridHash<P>>()
|
||||
app.init_resource::<GridHashMap<F>>()
|
||||
.init_resource::<ChangedGridHashes<F>>()
|
||||
.register_type::<GridHash>()
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
GridHash::<P>::update::<F>
|
||||
GridHash::update::<F>
|
||||
.in_set(GridHashMapSystem::UpdateHash)
|
||||
.after(FloatingOriginSystem::RecenterLargeTransforms),
|
||||
GridHashMap::<P, F>::update
|
||||
GridHashMap::<F>::update
|
||||
.in_set(GridHashMapSystem::UpdateMap)
|
||||
.after(GridHashMapSystem::UpdateHash),
|
||||
),
|
||||
@ -48,7 +46,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision, F: GridHashMapFilter> Default for GridHashPlugin<P, F> {
|
||||
impl<F: GridHashMapFilter> Default for GridHashPlugin<F> {
|
||||
fn default() -> Self {
|
||||
Self(PhantomData)
|
||||
}
|
||||
@ -91,12 +89,12 @@ impl<T: QueryFilter + Send + Sync + 'static> GridHashMapFilter for T {}
|
||||
/// It may be possible to remove this if bevy gets archetype change detection, or observers that can
|
||||
/// react to a component being mutated. For now, this performs well enough.
|
||||
#[derive(Resource)]
|
||||
struct ChangedGridHashes<P: GridPrecision, F: GridHashMapFilter> {
|
||||
struct ChangedGridHashes<F: GridHashMapFilter> {
|
||||
updated: Vec<Entity>,
|
||||
spooky: PhantomData<(P, F)>,
|
||||
spooky: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision, F: GridHashMapFilter> Default for ChangedGridHashes<P, F> {
|
||||
impl<F: GridHashMapFilter> Default for ChangedGridHashes<F> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
updated: Vec::new(),
|
||||
@ -124,38 +122,30 @@ mod tests {
|
||||
static ENTITY: OnceLock<Entity> = OnceLock::new();
|
||||
|
||||
let setup = |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
let entity = root.spawn_spatial(GridCell::<i32>::ZERO).id();
|
||||
commands.spawn_big_space_default(|root| {
|
||||
let entity = root.spawn_spatial(GridCell::ZERO).id();
|
||||
ENTITY.set(entity).ok();
|
||||
});
|
||||
};
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Update, setup)
|
||||
.update();
|
||||
|
||||
let hash = *app
|
||||
.world()
|
||||
.entity(*ENTITY.get().unwrap())
|
||||
.get::<GridHash<i32>>()
|
||||
.get::<GridHash>()
|
||||
.unwrap();
|
||||
|
||||
assert!(app
|
||||
.world()
|
||||
.resource::<GridHashMap<i32>>()
|
||||
.get(&hash)
|
||||
.is_some());
|
||||
assert!(app.world().resource::<GridHashMap>().get(&hash).is_some());
|
||||
|
||||
app.world_mut().despawn(*ENTITY.get().unwrap());
|
||||
|
||||
app.update();
|
||||
|
||||
assert!(app
|
||||
.world()
|
||||
.resource::<GridHashMap<i32>>()
|
||||
.get(&hash)
|
||||
.is_none());
|
||||
assert!(app.world().resource::<GridHashMap>().get(&hash).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -177,7 +167,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let setup = |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
let a = root.spawn_spatial(GridCell::new(0, 1, 2)).id();
|
||||
let b = root.spawn_spatial(GridCell::new(0, 1, 2)).id();
|
||||
let c = root.spawn_spatial(GridCell::new(5, 5, 5)).id();
|
||||
@ -194,12 +184,12 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Update, setup);
|
||||
|
||||
app.update();
|
||||
|
||||
let mut spatial_hashes = app.world_mut().query::<&GridHash<i32>>();
|
||||
let mut spatial_hashes = app.world_mut().query::<&GridHash>();
|
||||
|
||||
let parent = app.world().resource::<ParentSet>().clone();
|
||||
let child = app.world().resource::<ChildSet>().clone();
|
||||
@ -236,7 +226,7 @@ mod tests {
|
||||
|
||||
let entities = &app
|
||||
.world()
|
||||
.resource::<GridHashMap<i32>>()
|
||||
.resource::<GridHashMap>()
|
||||
.get(spatial_hashes.get(app.world(), parent.a).unwrap())
|
||||
.unwrap()
|
||||
.entities;
|
||||
@ -261,7 +251,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let setup = |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
let a = root.spawn_spatial(GridCell::new(0, 0, 0)).id();
|
||||
let b = root.spawn_spatial(GridCell::new(1, 1, 1)).id();
|
||||
let c = root.spawn_spatial(GridCell::new(2, 2, 2)).id();
|
||||
@ -271,7 +261,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins(GridHashPlugin::<i32>::default())
|
||||
app.add_plugins(GridHashPlugin::<()>::default())
|
||||
.add_systems(Startup, setup);
|
||||
|
||||
app.update();
|
||||
@ -283,7 +273,7 @@ mod tests {
|
||||
.get(app.world(), entities.a)
|
||||
.unwrap();
|
||||
|
||||
let map = app.world().resource::<GridHashMap<i32>>();
|
||||
let map = app.world().resource::<GridHashMap>();
|
||||
let entry = map.get(&GridHash::new(parent, &GridCell::ZERO)).unwrap();
|
||||
let neighbors: HashSet<Entity> = map.nearby(entry).entities().collect();
|
||||
|
||||
@ -311,40 +301,40 @@ mod tests {
|
||||
static ROOT: OnceLock<Entity> = OnceLock::new();
|
||||
|
||||
let setup = |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
root.spawn_spatial((GridCell::<i32>::ZERO, Player));
|
||||
root.spawn_spatial(GridCell::<i32>::ZERO);
|
||||
root.spawn_spatial(GridCell::<i32>::ZERO);
|
||||
commands.spawn_big_space_default(|root| {
|
||||
root.spawn_spatial((GridCell::ZERO, Player));
|
||||
root.spawn_spatial(GridCell::ZERO);
|
||||
root.spawn_spatial(GridCell::ZERO);
|
||||
ROOT.set(root.id()).ok();
|
||||
});
|
||||
};
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins((
|
||||
GridHashPlugin::<i32>::default(),
|
||||
GridHashPlugin::<i32, With<Player>>::default(),
|
||||
GridHashPlugin::<i32, Without<Player>>::default(),
|
||||
GridHashPlugin::<()>::default(),
|
||||
GridHashPlugin::<With<Player>>::default(),
|
||||
GridHashPlugin::<Without<Player>>::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.update();
|
||||
|
||||
let zero_hash = GridHash::from_parent(*ROOT.get().unwrap(), &GridCell::ZERO);
|
||||
|
||||
let map = app.world().resource::<GridHashMap<i32>>();
|
||||
let map = app.world().resource::<GridHashMap>();
|
||||
assert_eq!(
|
||||
map.get(&zero_hash).unwrap().entities.iter().count(),
|
||||
3,
|
||||
"There are a total of 3 spatial entities"
|
||||
);
|
||||
|
||||
let map = app.world().resource::<GridHashMap<i32, With<Player>>>();
|
||||
let map = app.world().resource::<GridHashMap<With<Player>>>();
|
||||
assert_eq!(
|
||||
map.get(&zero_hash).unwrap().entities.iter().count(),
|
||||
1,
|
||||
"There is only one entity with the Player component"
|
||||
);
|
||||
|
||||
let map = app.world().resource::<GridHashMap<i32, Without<Player>>>();
|
||||
let map = app.world().resource::<GridHashMap<Without<Player>>>();
|
||||
assert_eq!(
|
||||
map.get(&zero_hash).unwrap().entities.iter().count(),
|
||||
2,
|
||||
@ -366,7 +356,7 @@ mod tests {
|
||||
}
|
||||
|
||||
let setup = |mut commands: Commands| {
|
||||
commands.spawn_big_space_default::<i32>(|root| {
|
||||
commands.spawn_big_space_default(|root| {
|
||||
let a = root.spawn_spatial(GridCell::new(0, 0, 0)).id();
|
||||
let b = root.spawn_spatial(GridCell::new(1, 1, 1)).id();
|
||||
let c = root.spawn_spatial(GridCell::new(2, 2, 2)).id();
|
||||
@ -376,18 +366,15 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_plugins((
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
GridHashPlugin::<i32>::default(),
|
||||
))
|
||||
.add_systems(Startup, setup);
|
||||
app.add_plugins((BigSpacePlugin::default(), GridHashPlugin::<()>::default()))
|
||||
.add_systems(Startup, setup);
|
||||
|
||||
app.update();
|
||||
|
||||
let entities = app.world().resource::<Entities>().clone();
|
||||
let get_hash = |app: &mut App, entity| {
|
||||
*app.world_mut()
|
||||
.query::<&GridHash<i32>>()
|
||||
.query::<&GridHash>()
|
||||
.get(app.world(), entity)
|
||||
.unwrap()
|
||||
};
|
||||
@ -395,7 +382,7 @@ mod tests {
|
||||
let a_hash_t0 = get_hash(&mut app, entities.a);
|
||||
let b_hash_t0 = get_hash(&mut app, entities.b);
|
||||
let c_hash_t0 = get_hash(&mut app, entities.c);
|
||||
let map = app.world().resource::<GridHashMap<i32>>();
|
||||
let map = app.world().resource::<GridHashMap>();
|
||||
assert!(map.just_inserted().contains(&a_hash_t0));
|
||||
assert!(map.just_inserted().contains(&b_hash_t0));
|
||||
assert!(map.just_inserted().contains(&c_hash_t0));
|
||||
@ -403,7 +390,7 @@ mod tests {
|
||||
// Move entities and run an update
|
||||
app.world_mut()
|
||||
.entity_mut(entities.a)
|
||||
.get_mut::<GridCell<i32>>()
|
||||
.get_mut::<GridCell>()
|
||||
.unwrap()
|
||||
.z += 1;
|
||||
app.world_mut()
|
||||
@ -417,7 +404,7 @@ mod tests {
|
||||
let a_hash_t1 = get_hash(&mut app, entities.a);
|
||||
let b_hash_t1 = get_hash(&mut app, entities.b);
|
||||
let c_hash_t1 = get_hash(&mut app, entities.c);
|
||||
let map = app.world().resource::<GridHashMap<i32>>();
|
||||
let map = app.world().resource::<GridHashMap>();
|
||||
|
||||
// Last grid
|
||||
assert!(map.just_removed().contains(&a_hash_t0)); // Moved cell
|
||||
|
||||
@ -10,19 +10,17 @@ use bevy_utils::{
|
||||
Instant, PassHash,
|
||||
};
|
||||
|
||||
use super::{GridCell, GridHash, GridHashMap, GridHashMapFilter, GridHashMapSystem, GridPrecision};
|
||||
use super::{GridCell, GridHash, GridHashMap, GridHashMapFilter, GridHashMapSystem};
|
||||
|
||||
pub use private::GridPartition;
|
||||
|
||||
/// Adds support for spatial partitioning. Requires [`GridHashPlugin`](super::GridHashPlugin).
|
||||
pub struct GridPartitionPlugin<P, F = ()>(PhantomData<(P, F)>)
|
||||
pub struct GridPartitionPlugin<F = ()>(PhantomData<F>)
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter;
|
||||
|
||||
impl<P, F> Default for GridPartitionPlugin<P, F>
|
||||
impl<F> Default for GridPartitionPlugin<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn default() -> Self {
|
||||
@ -30,15 +28,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, F> Plugin for GridPartitionPlugin<P, F>
|
||||
impl<F> Plugin for GridPartitionPlugin<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<GridPartitionMap<P, F>>().add_systems(
|
||||
app.init_resource::<GridPartitionMap<F>>().add_systems(
|
||||
PostUpdate,
|
||||
GridPartitionMap::<P, F>::update
|
||||
GridPartitionMap::<F>::update
|
||||
.in_set(GridHashMapSystem::UpdatePartition)
|
||||
.after(GridHashMapSystem::UpdateMap),
|
||||
);
|
||||
@ -68,22 +65,20 @@ impl Hash for GridPartitionId {
|
||||
/// Partitions divide space into independent groups of cells.
|
||||
///
|
||||
/// The map depends on and is built from a corresponding [`GridHashMap`] with the same
|
||||
/// `P:`[`GridPrecision`] and `F:`[`GridHashMapFilter`].
|
||||
/// `F:`[`GridHashMapFilter`].
|
||||
#[derive(Resource)]
|
||||
pub struct GridPartitionMap<P, F = ()>
|
||||
pub struct GridPartitionMap<F = ()>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
partitions: HashMap<GridPartitionId, GridPartition<P>>,
|
||||
reverse_map: HashMap<GridHash<P>, GridPartitionId, PassHash>,
|
||||
partitions: HashMap<GridPartitionId, GridPartition>,
|
||||
reverse_map: HashMap<GridHash, GridPartitionId, PassHash>,
|
||||
next_partition: u64,
|
||||
spooky: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<P, F> Default for GridPartitionMap<P, F>
|
||||
impl<F> Default for GridPartitionMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn default() -> Self {
|
||||
@ -96,44 +91,42 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, F> Deref for GridPartitionMap<P, F>
|
||||
impl<F> Deref for GridPartitionMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
type Target = HashMap<GridPartitionId, GridPartition<P>>;
|
||||
type Target = HashMap<GridPartitionId, GridPartition>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.partitions
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, F> GridPartitionMap<P, F>
|
||||
impl<F> GridPartitionMap<F>
|
||||
where
|
||||
P: GridPrecision,
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
/// Returns a reference to the [`GridPartition`], if it exists.
|
||||
#[inline]
|
||||
pub fn resolve(&self, id: &GridPartitionId) -> Option<&GridPartition<P>> {
|
||||
pub fn resolve(&self, id: &GridPartitionId) -> Option<&GridPartition> {
|
||||
self.partitions.get(id)
|
||||
}
|
||||
|
||||
/// Searches for the [`GridPartition`] that contains this `hash`, returning the partition's
|
||||
/// [`GridPartitionId`] if the hash is found in any partition.
|
||||
#[inline]
|
||||
pub fn get(&self, hash: &GridHash<P>) -> Option<&GridPartitionId> {
|
||||
pub fn get(&self, hash: &GridHash) -> Option<&GridPartitionId> {
|
||||
self.reverse_map.get(hash)
|
||||
}
|
||||
|
||||
/// Iterates over all [`GridPartition`]s.
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&GridPartitionId, &GridPartition<P>)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&GridPartitionId, &GridPartition)> {
|
||||
self.partitions.iter()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn insert(&mut self, partition: GridPartitionId, set: HashSet<GridHash<P>, PassHash>) {
|
||||
fn insert(&mut self, partition: GridPartitionId, set: HashSet<GridHash, PassHash>) {
|
||||
let Some(hash) = set.iter().next() else {
|
||||
return;
|
||||
};
|
||||
@ -151,7 +144,7 @@ where
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn push(&mut self, partition: &GridPartitionId, hash: &GridHash<P>) {
|
||||
fn push(&mut self, partition: &GridPartitionId, hash: &GridHash) {
|
||||
if let Some(partition) = self.partitions.get_mut(partition) {
|
||||
partition.insert(*hash)
|
||||
} else {
|
||||
@ -161,7 +154,7 @@ where
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn remove(&mut self, hash: &GridHash<P>) {
|
||||
fn remove(&mut self, hash: &GridHash) {
|
||||
let Some(old_id) = self.reverse_map.remove(hash) else {
|
||||
return;
|
||||
};
|
||||
@ -217,12 +210,12 @@ where
|
||||
fn update(
|
||||
mut partition_map: ResMut<Self>,
|
||||
mut timing: ResMut<crate::timing::GridHashStats>,
|
||||
hash_grid: Res<GridHashMap<P, F>>,
|
||||
hash_grid: Res<GridHashMap<F>>,
|
||||
// Scratch space allocations
|
||||
mut added_neighbors: Local<Vec<GridPartitionId>>,
|
||||
mut adjacent_to_removals: Local<HashMap<GridPartitionId, HashSet<GridHash<P>, PassHash>>>,
|
||||
mut split_candidates: Local<Vec<(GridPartitionId, HashSet<GridHash<P>, PassHash>)>>,
|
||||
mut split_results: Local<Vec<Vec<SplitResult<P>>>>,
|
||||
mut adjacent_to_removals: Local<HashMap<GridPartitionId, HashSet<GridHash, PassHash>>>,
|
||||
mut split_candidates: Local<Vec<(GridPartitionId, HashSet<GridHash, PassHash>)>>,
|
||||
mut split_results: Local<Vec<Vec<SplitResult>>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
for added_hash in hash_grid.just_inserted().iter() {
|
||||
@ -353,37 +346,39 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
struct SplitResult<P: GridPrecision> {
|
||||
struct SplitResult {
|
||||
original_partition_id: GridPartitionId,
|
||||
new_partitions: Vec<HashSet<GridHash<P>, PassHash>>,
|
||||
new_partitions: Vec<HashSet<GridHash, PassHash>>,
|
||||
}
|
||||
|
||||
/// A private module to ensure the internal fields of the partition are not accessed directly.
|
||||
/// Needed to ensure invariants are upheld.
|
||||
mod private {
|
||||
use super::{GridCell, GridHash, GridPrecision};
|
||||
use super::{GridCell, GridHash};
|
||||
use crate::precision::GridPrecision;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_utils::{hashbrown::HashSet, PassHash};
|
||||
|
||||
/// A group of nearby [`GridCell`](crate::GridCell)s in an island disconnected from all other
|
||||
/// [`GridCell`](crate::GridCell)s.
|
||||
#[derive(Debug)]
|
||||
pub struct GridPartition<P: GridPrecision> {
|
||||
pub struct GridPartition {
|
||||
grid: Entity,
|
||||
tables: Vec<HashSet<GridHash<P>, PassHash>>,
|
||||
min: GridCell<P>,
|
||||
max: GridCell<P>,
|
||||
tables: Vec<HashSet<GridHash, PassHash>>,
|
||||
min: GridCell,
|
||||
max: GridCell,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridPartition<P> {
|
||||
impl GridPartition {
|
||||
/// Returns `true` if the `hash` is in this partition.
|
||||
#[inline]
|
||||
pub fn contains(&self, hash: &GridHash<P>) -> bool {
|
||||
pub fn contains(&self, hash: &GridHash) -> bool {
|
||||
self.tables.iter().any(|table| table.contains(hash))
|
||||
}
|
||||
|
||||
/// Iterates over all [`GridHash`]s in this partition.
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = &GridHash<P>> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &GridHash> {
|
||||
self.tables.iter().flat_map(|table| table.iter())
|
||||
}
|
||||
|
||||
@ -400,12 +395,12 @@ mod private {
|
||||
}
|
||||
|
||||
/// The maximum grid cell extent of the partition.
|
||||
pub fn max(&self) -> GridCell<P> {
|
||||
pub fn max(&self) -> GridCell {
|
||||
self.max
|
||||
}
|
||||
|
||||
/// The minimum grid cell extent of the partition.
|
||||
pub fn min(&self) -> GridCell<P> {
|
||||
pub fn min(&self) -> GridCell {
|
||||
self.min
|
||||
}
|
||||
|
||||
@ -416,12 +411,12 @@ mod private {
|
||||
}
|
||||
|
||||
/// Private internal methods
|
||||
impl<P: GridPrecision> GridPartition<P> {
|
||||
impl GridPartition {
|
||||
pub(crate) fn new(
|
||||
grid: Entity,
|
||||
tables: Vec<HashSet<GridHash<P>, PassHash>>,
|
||||
min: GridCell<P>,
|
||||
max: GridCell<P>,
|
||||
tables: Vec<HashSet<GridHash, PassHash>>,
|
||||
min: GridCell,
|
||||
max: GridCell,
|
||||
) -> Self {
|
||||
Self {
|
||||
grid,
|
||||
@ -441,7 +436,7 @@ mod private {
|
||||
const MIN_TABLE_SIZE: usize = 20_000;
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn insert(&mut self, cell: GridHash<P>) {
|
||||
pub(crate) fn insert(&mut self, cell: GridHash) {
|
||||
if self.contains(&cell) {
|
||||
return;
|
||||
}
|
||||
@ -467,7 +462,7 @@ mod private {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn extend(&mut self, mut partition: GridPartition<P>) {
|
||||
pub(crate) fn extend(&mut self, mut partition: GridPartition) {
|
||||
for mut table in partition.tables.drain(..) {
|
||||
if table.len() < Self::MIN_TABLE_SIZE {
|
||||
if let Some(i) = self.smallest_table() {
|
||||
@ -487,7 +482,7 @@ mod private {
|
||||
|
||||
/// Removes a grid hash from the partition. Returns whether the value was present.
|
||||
#[inline]
|
||||
pub(crate) fn remove(&mut self, hash: &GridHash<P>) -> bool {
|
||||
pub(crate) fn remove(&mut self, hash: &GridHash) -> bool {
|
||||
let Some(i_table) = self
|
||||
.tables
|
||||
.iter_mut()
|
||||
@ -524,7 +519,7 @@ mod private {
|
||||
{
|
||||
self.min = min
|
||||
} else {
|
||||
self.min = GridCell::ONE * P::from_f64(1e10);
|
||||
self.min = GridCell::ONE * 1e10f64 as GridPrecision;
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,7 +534,7 @@ mod private {
|
||||
{
|
||||
self.max = max
|
||||
} else {
|
||||
self.min = GridCell::ONE * P::from_f64(-1e10);
|
||||
self.min = GridCell::ONE * -1e10 as GridPrecision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
src/lib.rs
59
src/lib.rs
@ -106,7 +106,7 @@
|
||||
//! grid, in high precision.
|
||||
//!
|
||||
//! Entities at the root of bevy's entity hierarchy are not in a grid. This allows plugins from the
|
||||
//! rest of the ecosystem to operate normally, such as bevy_ui, which relies on the built in
|
||||
//! rest of the ecosystem to operate normally, such as bevy_ui, which relies on the built-in
|
||||
//! transform propagation system. This also means that if you don't need to place entities in a
|
||||
//! high-precision grid, you don't have to, as the process is opt-in. The high-precision
|
||||
//! hierarchical grids are explicit. Each high-precision tree must have a [`BigSpace`] at the root,
|
||||
@ -209,7 +209,6 @@ pub mod floating_origins;
|
||||
pub mod grid;
|
||||
pub mod hash;
|
||||
pub mod plugin;
|
||||
pub mod precision;
|
||||
pub mod timing;
|
||||
pub mod validation;
|
||||
pub mod world_query;
|
||||
@ -230,7 +229,7 @@ pub mod prelude {
|
||||
pub use debug::FloatingOriginDebugPlugin;
|
||||
pub use floating_origins::{BigSpace, FloatingOrigin};
|
||||
pub use grid::{
|
||||
cell::{GridCell, GridCellAny},
|
||||
cell::GridCell,
|
||||
local_origin::{Grids, GridsMut, LocalFloatingOrigin},
|
||||
Grid,
|
||||
};
|
||||
@ -244,3 +243,57 @@ pub mod prelude {
|
||||
pub use precision::GridPrecision;
|
||||
pub use world_query::{GridTransform, GridTransformOwned, GridTransformReadOnly};
|
||||
}
|
||||
|
||||
/// Contains [`GridPrecision`], allowing `big_space` to work with many grid integer sizes.
|
||||
///
|
||||
/// The integer type used is controlled with compile time feature flags like `i8`. The crate
|
||||
/// defaults to `i64` grids if none is specified.
|
||||
///
|
||||
/// Larger grids result in a larger usable volume, at the cost of increased memory usage. In
|
||||
/// addition, some platforms may be unable to use larger numeric types (e.g. [`i128`]).
|
||||
///
|
||||
/// [`big_space`](crate) is generic over a few integer types to allow you to select the grid size
|
||||
/// you need. Assuming you are using a grid cell edge length of 10,000 meters, and `1.0` == 1 meter,
|
||||
/// these correspond to a total usable volume of a cube with the following edge lengths:
|
||||
///
|
||||
/// - `i8`: 2,560 km = 74% of the diameter of the Moon
|
||||
/// - `i16`: 655,350 km = 85% of the diameter of the Moon's orbit around Earth
|
||||
/// - `i32`: 0.0045 light years = ~4 times the width of the solar system
|
||||
/// - `i64`: 19.5 million light years = ~100 times the width of the milky way galaxy
|
||||
/// - `i128`: 3.6e+26 light years = ~3.9e+15 times the width of the observable universe
|
||||
///
|
||||
/// where `usable_edge_length = 2^(integer_bits) * cell_edge_length`, resulting in a worst case
|
||||
/// precision of 0.5mm in any of these cases.
|
||||
///
|
||||
/// This can also be used for small scales. With a cell edge length of `1e-11`, and using `i128`,
|
||||
/// there is enough precision to render objects the size of protons anywhere in the observable
|
||||
/// universe.
|
||||
pub mod precision {
|
||||
#[allow(unused_imports)] // Docs
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "i8")]
|
||||
/// Adds 8 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i8;
|
||||
#[cfg(feature = "i16")]
|
||||
/// Adds 16 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i16;
|
||||
#[cfg(feature = "i32")]
|
||||
/// Adds 32 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i32;
|
||||
#[cfg(feature = "i64")]
|
||||
/// Adds 64 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i64;
|
||||
#[cfg(feature = "i128")]
|
||||
/// Adds 128 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i128;
|
||||
#[cfg(not(any(
|
||||
feature = "i8",
|
||||
feature = "i16",
|
||||
feature = "i32",
|
||||
feature = "i64",
|
||||
feature = "i128"
|
||||
)))]
|
||||
/// Adds 64 bits of precision to bevy's [`Transform`]. See [`precision`].
|
||||
pub type GridPrecision = i64;
|
||||
}
|
||||
|
||||
@ -3,30 +3,25 @@
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
/// Add this plugin to your [`App`] for floating origin functionality.
|
||||
pub struct BigSpacePlugin<P: GridPrecision> {
|
||||
phantom: PhantomData<P>,
|
||||
pub struct BigSpacePlugin {
|
||||
validate_hierarchies: bool,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> BigSpacePlugin<P> {
|
||||
impl BigSpacePlugin {
|
||||
/// Create a big space plugin, and specify whether hierarchy validation should be enabled.
|
||||
pub fn new(validate_hierarchies: bool) -> Self {
|
||||
Self {
|
||||
phantom: PhantomData::<P>,
|
||||
validate_hierarchies,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> Default for BigSpacePlugin<P> {
|
||||
impl Default for BigSpacePlugin {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
phantom: PhantomData,
|
||||
validate_hierarchies: cfg!(debug_assertions),
|
||||
}
|
||||
}
|
||||
@ -42,9 +37,7 @@ pub enum FloatingOriginSystem {
|
||||
PropagateLowPrecision,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetTypeRegistration> Plugin
|
||||
for BigSpacePlugin<P>
|
||||
{
|
||||
impl Plugin for BigSpacePlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Silence bevy's built-in error spam about GlobalTransforms in the hierarchy
|
||||
app.insert_resource(bevy_hierarchy::ReportHierarchyIssue::<GlobalTransform>::new(false));
|
||||
@ -54,21 +47,21 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
|
||||
|
||||
let system_set_config = || {
|
||||
(
|
||||
Grid::<P>::tag_low_precision_roots // loose ordering on this set
|
||||
Grid::tag_low_precision_roots // loose ordering on this set
|
||||
.after(FloatingOriginSystem::Init)
|
||||
.before(FloatingOriginSystem::PropagateLowPrecision),
|
||||
(
|
||||
GridCell::<P>::recenter_large_transforms,
|
||||
GridCell::recenter_large_transforms,
|
||||
BigSpace::find_floating_origin,
|
||||
)
|
||||
.in_set(FloatingOriginSystem::RecenterLargeTransforms),
|
||||
LocalFloatingOrigin::<P>::compute_all
|
||||
LocalFloatingOrigin::compute_all
|
||||
.in_set(FloatingOriginSystem::LocalFloatingOrigins)
|
||||
.after(FloatingOriginSystem::RecenterLargeTransforms),
|
||||
Grid::<P>::propagate_high_precision
|
||||
Grid::propagate_high_precision
|
||||
.in_set(FloatingOriginSystem::PropagateHighPrecision)
|
||||
.after(FloatingOriginSystem::LocalFloatingOrigins),
|
||||
Grid::<P>::propagate_low_precision
|
||||
Grid::propagate_low_precision
|
||||
.in_set(FloatingOriginSystem::PropagateLowPrecision)
|
||||
.after(FloatingOriginSystem::PropagateHighPrecision),
|
||||
)
|
||||
@ -79,9 +72,8 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
|
||||
// Reflect
|
||||
.register_type::<Transform>()
|
||||
.register_type::<GlobalTransform>()
|
||||
.register_type::<GridCell<P>>()
|
||||
.register_type::<GridCellAny>()
|
||||
.register_type::<Grid<P>>()
|
||||
.register_type::<GridCell>()
|
||||
.register_type::<Grid>()
|
||||
.register_type::<BigSpace>()
|
||||
.register_type::<FloatingOrigin>()
|
||||
// Meat of the plugin, once on startup, as well as every update
|
||||
@ -90,7 +82,7 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
|
||||
// Validation
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
crate::validation::validate_hierarchy::<crate::validation::SpatialHierarchyRoot<P>>
|
||||
crate::validation::validate_hierarchy::<crate::validation::SpatialHierarchyRoot>
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.run_if({
|
||||
let run = self.validate_hierarchies;
|
||||
|
||||
255
src/precision.rs
255
src/precision.rs
@ -1,255 +0,0 @@
|
||||
//! Contains the [`GridPrecision`] trait and its implementations.
|
||||
|
||||
use std::{
|
||||
hash::Hash,
|
||||
ops::{Add, Mul},
|
||||
};
|
||||
|
||||
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath, Typed};
|
||||
|
||||
/// Used to make the floating origin plugin generic over many grid sizes.
|
||||
///
|
||||
/// Larger grids result in a larger useable volume, at the cost of increased memory usage. In
|
||||
/// addition, some platforms may be unable to use larger numeric types (e.g. [`i128`]).
|
||||
///
|
||||
/// [`big_space`](crate) is generic over a few integer types to allow you to select the grid size
|
||||
/// you need. Assuming you are using a grid cell edge length of 10,000 meters, and `1.0` == 1 meter,
|
||||
/// these correspond to a total usable volume of a cube with the following edge lengths:
|
||||
///
|
||||
/// - `i8`: 2,560 km = 74% of the diameter of the Moon
|
||||
/// - `i16`: 655,350 km = 85% of the diameter of the Moon's orbit around Earth
|
||||
/// - `i32`: 0.0045 light years = ~4 times the width of the solar system
|
||||
/// - `i64`: 19.5 million light years = ~100 times the width of the milky way galaxy
|
||||
/// - `i128`: 3.6e+26 light years = ~3.9e+15 times the width of the observable universe
|
||||
///
|
||||
/// where `usable_edge_length = 2^(integer_bits) * cell_edge_length`, resulting in a worst case
|
||||
/// precision of 0.5mm in any of these cases.
|
||||
///
|
||||
/// This can also be used for small scales. With a cell edge length of `1e-11`, and using `i128`,
|
||||
/// there is enough precision to render objects the size of protons anywhere in the observable
|
||||
/// universe.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Be sure you are using the same grid index precision everywhere. It might be a good idea to
|
||||
/// define a type alias!
|
||||
///
|
||||
/// ```
|
||||
/// # use big_space::prelude::*;
|
||||
/// type GalacticGrid = GridCell<i64>;
|
||||
/// ```
|
||||
///
|
||||
/// Additionally, consider using the provided command extensions in [`crate::commands`] to
|
||||
/// completely eliminate the use of this generic, and prevent many errors.
|
||||
pub trait GridPrecision:
|
||||
Default
|
||||
+ PartialEq
|
||||
+ Eq
|
||||
+ PartialOrd
|
||||
+ Ord
|
||||
+ Hash
|
||||
+ Copy
|
||||
+ Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ Reflect
|
||||
+ FromReflect
|
||||
+ GetTypeRegistration
|
||||
+ TypePath
|
||||
+ Typed
|
||||
+ Add
|
||||
+ Add<Self, Output = Self>
|
||||
+ Mul<Self, Output = Self>
|
||||
+ std::fmt::Debug
|
||||
+ std::fmt::Display
|
||||
+ 'static
|
||||
{
|
||||
/// The zero value for this type.
|
||||
const ZERO: Self;
|
||||
/// The value of `1` for this type.
|
||||
const ONE: Self;
|
||||
/// Adds `rhs` to `self`, wrapping when overflow would occur.
|
||||
fn wrapping_add(self, rhs: Self) -> Self;
|
||||
/// Adds `rhs` to `self`, wrapping when overflow would occur.
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self;
|
||||
/// Subtracts `rhs` from `self`, wrapping when overflow would occur.
|
||||
fn wrapping_sub(self, rhs: Self) -> Self;
|
||||
/// Multiplies `self` by `rhs`.
|
||||
fn mul(self, rhs: Self) -> Self;
|
||||
/// Casts `self` as a double precision float.
|
||||
fn as_f64(self) -> f64;
|
||||
/// Casts a double precision float into `Self`.
|
||||
fn from_f64(input: f64) -> Self;
|
||||
/// Casts a single precision float into `Self`.
|
||||
fn from_f32(input: f32) -> Self;
|
||||
}
|
||||
|
||||
impl GridPrecision for i8 {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
|
||||
#[inline]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
Self::wrapping_add(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self {
|
||||
Self::wrapping_add(self, rhs as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_sub(self, rhs: Self) -> Self {
|
||||
Self::wrapping_sub(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
self * rhs
|
||||
}
|
||||
#[inline]
|
||||
fn as_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(input: f64) -> Self {
|
||||
input as Self
|
||||
}
|
||||
#[inline]
|
||||
fn from_f32(input: f32) -> Self {
|
||||
input as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl GridPrecision for i16 {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
|
||||
#[inline]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
Self::wrapping_add(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self {
|
||||
Self::wrapping_add(self, rhs as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_sub(self, rhs: Self) -> Self {
|
||||
Self::wrapping_sub(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
self * rhs
|
||||
}
|
||||
#[inline]
|
||||
fn as_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(input: f64) -> Self {
|
||||
input as Self
|
||||
}
|
||||
#[inline]
|
||||
fn from_f32(input: f32) -> Self {
|
||||
input as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl GridPrecision for i32 {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
|
||||
#[inline]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
Self::wrapping_add(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self {
|
||||
Self::wrapping_add(self, rhs as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_sub(self, rhs: Self) -> Self {
|
||||
Self::wrapping_sub(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
self * rhs
|
||||
}
|
||||
#[inline]
|
||||
fn as_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(input: f64) -> Self {
|
||||
input as Self
|
||||
}
|
||||
#[inline]
|
||||
fn from_f32(input: f32) -> Self {
|
||||
input as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl GridPrecision for i64 {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
|
||||
#[inline]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
Self::wrapping_add(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self {
|
||||
Self::wrapping_add(self, rhs as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_sub(self, rhs: Self) -> Self {
|
||||
Self::wrapping_sub(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
self * rhs
|
||||
}
|
||||
#[inline]
|
||||
fn as_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(input: f64) -> Self {
|
||||
input as Self
|
||||
}
|
||||
#[inline]
|
||||
fn from_f32(input: f32) -> Self {
|
||||
input as Self
|
||||
}
|
||||
}
|
||||
|
||||
impl GridPrecision for i128 {
|
||||
const ZERO: Self = 0;
|
||||
const ONE: Self = 1;
|
||||
|
||||
#[inline]
|
||||
fn wrapping_add(self, rhs: Self) -> Self {
|
||||
Self::wrapping_add(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_add_i32(self, rhs: i32) -> Self {
|
||||
Self::wrapping_add(self, rhs as Self)
|
||||
}
|
||||
#[inline]
|
||||
fn wrapping_sub(self, rhs: Self) -> Self {
|
||||
Self::wrapping_sub(self, rhs)
|
||||
}
|
||||
#[inline]
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
self * rhs
|
||||
}
|
||||
#[inline]
|
||||
fn as_f64(self) -> f64 {
|
||||
self as f64
|
||||
}
|
||||
#[inline]
|
||||
fn from_f64(input: f64) -> Self {
|
||||
input as Self
|
||||
}
|
||||
#[inline]
|
||||
fn from_f32(input: f32) -> Self {
|
||||
input as Self
|
||||
}
|
||||
}
|
||||
16
src/tests.rs
16
src/tests.rs
@ -4,13 +4,13 @@ use bevy::prelude::*;
|
||||
#[test]
|
||||
fn changing_floating_origin_updates_global_transform() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let first = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_translation(Vec3::new(150.0, 0.0, 0.0)),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
GridCell::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
@ -19,12 +19,12 @@ fn changing_floating_origin_updates_global_transform() {
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
GridCell::new(0, -15, 0),
|
||||
))
|
||||
.id();
|
||||
|
||||
app.world_mut()
|
||||
.spawn(BigSpaceRootBundle::<i32>::default())
|
||||
.spawn(BigSpaceRootBundle::default())
|
||||
.add_children(&[first, second]);
|
||||
|
||||
app.update();
|
||||
@ -45,13 +45,13 @@ fn changing_floating_origin_updates_global_transform() {
|
||||
#[test]
|
||||
fn child_global_transforms_are_updated_when_floating_origin_changes() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::default());
|
||||
|
||||
let first = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_translation(Vec3::new(150.0, 0.0, 0.0)),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
GridCell::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
@ -60,13 +60,13 @@ fn child_global_transforms_are_updated_when_floating_origin_changes() {
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
GridCell::new(0, -15, 0),
|
||||
))
|
||||
.with_child(Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)))
|
||||
.id();
|
||||
|
||||
app.world_mut()
|
||||
.spawn(BigSpaceRootBundle::<i32>::default())
|
||||
.spawn(BigSpaceRootBundle::default())
|
||||
.add_children(&[first, second]);
|
||||
|
||||
app.update();
|
||||
|
||||
@ -1,14 +1,11 @@
|
||||
//! Tools for validating high-precision transform hierarchies
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
|
||||
use crate::{grid::Grid, precision::GridPrecision, BigSpace, FloatingOrigin, GridCell};
|
||||
use crate::{grid::Grid, BigSpace, FloatingOrigin, GridCell};
|
||||
|
||||
struct ValidationStackEntry {
|
||||
parent_node: Box<dyn ValidHierarchyNode>,
|
||||
@ -110,7 +107,7 @@ Because it is a child of a {:#?}, the entity must be one of the following:
|
||||
However, the entity has the following components, which does not match any of the allowed archetypes listed above:
|
||||
{}
|
||||
Common errors include:
|
||||
- Using mismatched GridPrecisions, like GridCell<i32> and GridCell<i64>
|
||||
- Using mismatched GridPrecisions, like GridCell and GridCell<i64>
|
||||
- Spawning an entity with a GridCell as a child of an entity without a Grid.
|
||||
|
||||
If possible, use commands.spawn_big_space(), which prevents these errors, instead of manually assembling a hierarchy. See {} for details.", entity, stack_entry.parent_node.name(), stack_entry.parent_node.name(), possibilities, inspect, file!());
|
||||
@ -166,9 +163,9 @@ mod sealed {
|
||||
|
||||
/// The root hierarchy validation struct, used as a generic parameter in [`crate::validation`].
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SpatialHierarchyRoot<P: GridPrecision>(PhantomData<P>);
|
||||
pub struct SpatialHierarchyRoot;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for SpatialHierarchyRoot<P> {
|
||||
impl ValidHierarchyNode for SpatialHierarchyRoot {
|
||||
fn name(&self) -> &'static str {
|
||||
"Root"
|
||||
}
|
||||
@ -177,40 +174,40 @@ impl<P: GridPrecision> ValidHierarchyNode for SpatialHierarchyRoot<P> {
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<RootFrame<P>>::default(),
|
||||
Box::<RootSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<RootFrame>::default(),
|
||||
Box::<RootSpatialLowPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct AnyNonSpatial<P: GridPrecision>(PhantomData<P>);
|
||||
struct AnyNonSpatial;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for AnyNonSpatial<P> {
|
||||
impl ValidHierarchyNode for AnyNonSpatial {
|
||||
fn name(&self) -> &'static str {
|
||||
"Any non-spatial entity"
|
||||
}
|
||||
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.without::<GridCellAny>()
|
||||
.without::<GridCell>()
|
||||
.without::<Transform>()
|
||||
.without::<GlobalTransform>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid<P>>()
|
||||
.without::<Grid>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![Box::<AnyNonSpatial<P>>::default()]
|
||||
vec![Box::<AnyNonSpatial>::default()]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct RootFrame<P: GridPrecision>(PhantomData<P>);
|
||||
struct RootFrame;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
|
||||
impl ValidHierarchyNode for RootFrame {
|
||||
fn name(&self) -> &'static str {
|
||||
"Root of a BigSpace"
|
||||
}
|
||||
@ -218,9 +215,9 @@ impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<BigSpace>()
|
||||
.with::<Grid<P>>()
|
||||
.with::<Grid>()
|
||||
.with::<GlobalTransform>()
|
||||
.without::<GridCellAny>()
|
||||
.without::<GridCell>()
|
||||
.without::<Transform>()
|
||||
.without::<Parent>()
|
||||
.without::<FloatingOrigin>();
|
||||
@ -228,18 +225,18 @@ impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildFrame<P>>::default(),
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<ChildSpatialHighPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildFrame>::default(),
|
||||
Box::<ChildSpatialLowPrecision>::default(),
|
||||
Box::<ChildSpatialHighPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct RootSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
struct RootSpatialLowPrecision;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for RootSpatialLowPrecision<P> {
|
||||
impl ValidHierarchyNode for RootSpatialLowPrecision {
|
||||
fn name(&self) -> &'static str {
|
||||
"Root of a Transform hierarchy at the root of the tree outside of any BigSpace"
|
||||
}
|
||||
@ -248,33 +245,33 @@ impl<P: GridPrecision> ValidHierarchyNode for RootSpatialLowPrecision<P> {
|
||||
query
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.without::<GridCellAny>()
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid<P>>()
|
||||
.without::<Grid>()
|
||||
.without::<Parent>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildSpatialLowPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildFrame<P: GridPrecision>(PhantomData<P>);
|
||||
struct ChildFrame;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildFrame<P> {
|
||||
impl ValidHierarchyNode for ChildFrame {
|
||||
fn name(&self) -> &'static str {
|
||||
"Non-root Grid"
|
||||
}
|
||||
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<Grid<P>>()
|
||||
.with::<GridCell<P>>()
|
||||
.with::<Grid>()
|
||||
.with::<GridCell>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
@ -283,18 +280,18 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildFrame<P> {
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildFrame<P>>::default(),
|
||||
Box::<ChildRootSpatialLowPrecision<P>>::default(),
|
||||
Box::<ChildSpatialHighPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildFrame>::default(),
|
||||
Box::<ChildRootSpatialLowPrecision>::default(),
|
||||
Box::<ChildSpatialHighPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildRootSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
struct ChildRootSpatialLowPrecision;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildRootSpatialLowPrecision<P> {
|
||||
impl ValidHierarchyNode for ChildRootSpatialLowPrecision {
|
||||
fn name(&self) -> &'static str {
|
||||
"Root of a low-precision Transform hierarchy, within a BigSpace"
|
||||
}
|
||||
@ -305,24 +302,24 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildRootSpatialLowPrecision<P> {
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.with::<crate::grid::propagation::LowPrecisionRoot>()
|
||||
.without::<GridCellAny>()
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid<P>>()
|
||||
.without::<Grid>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildSpatialLowPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
struct ChildSpatialLowPrecision;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialLowPrecision<P> {
|
||||
impl ValidHierarchyNode for ChildSpatialLowPrecision {
|
||||
fn name(&self) -> &'static str {
|
||||
"Non-root low-precision spatial entity"
|
||||
}
|
||||
@ -332,42 +329,42 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialLowPrecision<P> {
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.without::<GridCellAny>()
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid<P>>()
|
||||
.without::<Grid>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildSpatialLowPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildSpatialHighPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
struct ChildSpatialHighPrecision;
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialHighPrecision<P> {
|
||||
impl ValidHierarchyNode for ChildSpatialHighPrecision {
|
||||
fn name(&self) -> &'static str {
|
||||
"Non-root high precision spatial entity"
|
||||
}
|
||||
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<GridCell<P>>()
|
||||
.with::<GridCell>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid<P>>();
|
||||
.without::<Grid>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildRootSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
Box::<ChildRootSpatialLowPrecision>::default(),
|
||||
Box::<AnyNonSpatial>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,26 +12,26 @@ use bevy_transform::prelude::*;
|
||||
/// to read from the position, use [`GridTransformReadOnly`] instead, as this will allow the bevy
|
||||
/// ECS to run multiple queries using [`GridTransformReadOnly`] at the same time (just like multiple
|
||||
/// queries with `&Transform` are fine).
|
||||
pub struct GridTransform<P: GridPrecision> {
|
||||
pub struct GridTransform {
|
||||
/// Grid local transform
|
||||
pub transform: &'static mut Transform,
|
||||
/// The grid to which `transform` is relative to.
|
||||
pub cell: &'static mut GridCell<P>,
|
||||
pub cell: &'static mut GridCell,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridTransformItem<'_, P> {
|
||||
impl GridTransformItem<'_> {
|
||||
/// Compute the global position with double precision.
|
||||
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
|
||||
pub fn position_double(&self, grid: &Grid) -> DVec3 {
|
||||
grid.grid_position_double(&self.cell, &self.transform)
|
||||
}
|
||||
|
||||
/// Compute the global position.
|
||||
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
|
||||
pub fn position(&self, grid: &Grid) -> Vec3 {
|
||||
grid.grid_position(&self.cell, &self.transform)
|
||||
}
|
||||
|
||||
/// Get a copy of the fields to work with.
|
||||
pub fn to_owned(&self) -> GridTransformOwned<P> {
|
||||
pub fn to_owned(&self) -> GridTransformOwned {
|
||||
GridTransformOwned {
|
||||
transform: *self.transform,
|
||||
cell: *self.cell,
|
||||
@ -39,19 +39,19 @@ impl<P: GridPrecision> GridTransformItem<'_, P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridTransformReadOnlyItem<'_, P> {
|
||||
impl GridTransformReadOnlyItem<'_> {
|
||||
/// Compute the global position with double precision.
|
||||
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
|
||||
pub fn position_double(&self, grid: &Grid) -> DVec3 {
|
||||
grid.grid_position_double(self.cell, self.transform)
|
||||
}
|
||||
|
||||
/// Compute the global position.
|
||||
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
|
||||
pub fn position(&self, grid: &Grid) -> Vec3 {
|
||||
grid.grid_position(self.cell, self.transform)
|
||||
}
|
||||
|
||||
/// Get a copy of the fields to work with.
|
||||
pub fn to_owned(&self) -> GridTransformOwned<P> {
|
||||
pub fn to_owned(&self) -> GridTransformOwned {
|
||||
GridTransformOwned {
|
||||
transform: *self.transform,
|
||||
cell: *self.cell,
|
||||
@ -61,14 +61,14 @@ impl<P: GridPrecision> GridTransformReadOnlyItem<'_, P> {
|
||||
|
||||
/// A convenience wrapper that allows working with grid and transform easily
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct GridTransformOwned<P: GridPrecision> {
|
||||
pub struct GridTransformOwned {
|
||||
/// Grid local transform
|
||||
pub transform: Transform,
|
||||
/// The grid to which `transform` is relative to.
|
||||
pub cell: GridCell<P>,
|
||||
pub cell: GridCell,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub for GridTransformOwned<P> {
|
||||
impl std::ops::Sub for GridTransformOwned {
|
||||
type Output = Self;
|
||||
|
||||
/// Compute a new transform that maps from `source` to `self`.
|
||||
@ -81,7 +81,7 @@ impl<P: GridPrecision> std::ops::Sub for GridTransformOwned<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add for GridTransformOwned<P> {
|
||||
impl std::ops::Add for GridTransformOwned {
|
||||
type Output = Self;
|
||||
|
||||
/// Compute a new transform that shifts, scales and rotates `self` by `diff`.
|
||||
@ -94,14 +94,14 @@ impl<P: GridPrecision> std::ops::Add for GridTransformOwned<P> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> GridTransformOwned<P> {
|
||||
impl GridTransformOwned {
|
||||
/// Compute the global position with double precision.
|
||||
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
|
||||
pub fn position_double(&self, grid: &Grid) -> DVec3 {
|
||||
grid.grid_position_double(&self.cell, &self.transform)
|
||||
}
|
||||
|
||||
/// Compute the global position.
|
||||
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
|
||||
pub fn position(&self, grid: &Grid) -> Vec3 {
|
||||
grid.grid_position(&self.cell, &self.transform)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user