mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-09 22:38:27 +00:00
Bevy 0.16 (#46)
# Objective - Working branch to target all fixes for bevy 0.16 Co-authored-by: Zachary Harrold <zac@harrold.com.au>
This commit is contained in:
parent
9bae63e4b4
commit
44ff1f32de
12
.github/workflows/rust.yml
vendored
12
.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 --features=all --all-targets
|
||||
- run: cargo check --all-features --all-targets
|
||||
|
||||
check-no-defaults:
|
||||
runs-on: ubuntu-latest
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@v2.7.0
|
||||
- run: cargo check --no-default-features --all-targets
|
||||
- run: cargo check --no-default-features --all-targets --features=libm
|
||||
|
||||
clippy:
|
||||
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 --features=all --all-targets -- -D warnings
|
||||
- run: cargo clippy --all-features --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 --features=all --no-deps
|
||||
- run: cargo doc --all-features --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 --features=all
|
||||
- run: cargo test --all-features
|
||||
|
||||
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 --features=all --doc
|
||||
- run: cargo test --all-features --doc
|
||||
@ -2,6 +2,12 @@
|
||||
|
||||
## UNRELEASED
|
||||
|
||||
### New: `no_std` Support
|
||||
|
||||
Thanks to `bushrat011899`'s efforts upstream and in this crate, it is now possible to use the plugin without the rust standard library. This is particularly useful when targeting embedded or console targets.
|
||||
|
||||
## v0.9.0 - 2024-12-23
|
||||
|
||||
### New: `GridCell` Spatial Hashing
|
||||
|
||||
Spatial hashing makes fast spatial queries and neighbor lookups possible. This release adds the `GridHashMap`, an automatically updated map of the entities in each grid cell. This makes it possible to query things like:
|
||||
@ -37,5 +43,4 @@ The newly added types follow this pattern:
|
||||
- `GridPartition`: Group of adjacent grid cells.
|
||||
- `GridPartitionMap`: A map for finding independent partitions of entities.
|
||||
|
||||
|
||||
It should now be more clear how all of the `Grid` types are related to each other.
|
||||
127
Cargo.toml
127
Cargo.toml
@ -9,42 +9,57 @@ repository = "https://github.com/aevyrie/big_space"
|
||||
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"]
|
||||
default = ["std"]
|
||||
debug = ["std", "bevy_gizmos", "bevy_color"]
|
||||
camera = ["std", "bevy_render", "bevy_time", "bevy_input"]
|
||||
i8 = []
|
||||
i16 = []
|
||||
i32 = []
|
||||
i64 = []
|
||||
i128 = []
|
||||
|
||||
[dependencies]
|
||||
tracing = "0.1" # Less deps than pulling in bevy_log
|
||||
smallvec = "1.13.2" # Already used by bevy in commands
|
||||
bevy_app = { version = "0.15.0", default-features = false }
|
||||
bevy_ecs = { version = "0.15.0", default-features = true }
|
||||
bevy_hierarchy = { version = "0.15.0", default-features = false }
|
||||
bevy_math = { version = "0.15.0", default-features = false }
|
||||
bevy_reflect = { version = "0.15.0", default-features = false }
|
||||
bevy_tasks = { version = "0.15.0", default-features = false }
|
||||
bevy_transform = { version = "0.15.0", default-features = false, features = [
|
||||
"bevy-support",
|
||||
] }
|
||||
bevy_utils = { version = "0.15.0", default-features = false }
|
||||
# Optional
|
||||
bevy_color = { version = "0.15.0", default-features = false, optional = true }
|
||||
bevy_gizmos = { version = "0.15.0", default-features = false, optional = true }
|
||||
bevy_render = { version = "0.15.0", default-features = false, optional = true }
|
||||
bevy_input = { version = "0.15.0", default-features = false, optional = true }
|
||||
bevy_time = { version = "0.15.0", default-features = false, optional = true }
|
||||
std = [
|
||||
"bevy_app/std",
|
||||
"bevy_ecs/std",
|
||||
"bevy_math/std",
|
||||
"bevy_reflect/std",
|
||||
"bevy_tasks/std",
|
||||
"bevy_transform/std",
|
||||
"bevy_utils/std",
|
||||
"bevy_platform_support/std",
|
||||
"bevy_color?/std",
|
||||
"bevy_input?/std",
|
||||
"bevy_time?/std",
|
||||
]
|
||||
libm = ["bevy_math/libm", "dep:libm"]
|
||||
|
||||
[dependencies]
|
||||
tracing = { version = "0.1", default-features = false } # Less deps than pulling in bevy_log
|
||||
smallvec = { version = "1.13.2", default-features = false } # Already used by bevy in commands
|
||||
bevy_app = { version = "0.16.0-rc.3", default-features = false, features = ["bevy_reflect"] }
|
||||
bevy_ecs = { version = "0.16.0-rc.3", default-features = false }
|
||||
bevy_math = { version = "0.16.0-rc.3", default-features = false }
|
||||
bevy_reflect = { version = "0.16.0-rc.3", default-features = false, features = ["glam"] }
|
||||
bevy_tasks = { version = "0.16.0-rc.3", default-features = false }
|
||||
bevy_transform = { version = "0.16.0-rc.3", default-features = false, features = [
|
||||
"bevy-support",
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_utils = { version = "0.16.0-rc.3", default-features = false }
|
||||
bevy_platform_support = { version = "0.16.0-rc.3", default-features = false, features = ["alloc"] }
|
||||
# Optional
|
||||
bevy_color = { version = "0.16.0-rc.3", default-features = false, optional = true }
|
||||
bevy_gizmos = { version = "0.16.0-rc.3", default-features = false, optional = true }
|
||||
bevy_render = { version = "0.16.0-rc.3", default-features = false, optional = true }
|
||||
bevy_input = { version = "0.16.0-rc.3", default-features = false, optional = true }
|
||||
bevy_time = { version = "0.16.0-rc.3", default-features = false, optional = true }
|
||||
libm = { version = "0.2", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
big_space = { path = "", features = ["debug", "camera"] }
|
||||
bevy = { version = "0.15.0", default-features = false, features = [
|
||||
bevy = { version = "0.16.0-rc.3", default-features = false, features = [
|
||||
"bevy_scene",
|
||||
"bevy_asset",
|
||||
"bevy_color",
|
||||
"bevy_gltf",
|
||||
"bevy_winit",
|
||||
"default_font",
|
||||
@ -61,7 +76,36 @@ noise = "0.9"
|
||||
turborand = "0.10"
|
||||
criterion = "0.5"
|
||||
bytemuck = "1.20"
|
||||
bevy_hanabi = "0.14"
|
||||
# bevy_hanabi = "0.14" # TODO: Update
|
||||
|
||||
[lints.clippy]
|
||||
doc_markdown = "warn"
|
||||
manual_let_else = "warn"
|
||||
match_same_arms = "warn"
|
||||
redundant_closure_for_method_calls = "warn"
|
||||
redundant_else = "warn"
|
||||
semicolon_if_nothing_returned = "warn"
|
||||
type_complexity = "allow"
|
||||
undocumented_unsafe_blocks = "warn"
|
||||
unwrap_or_default = "warn"
|
||||
|
||||
ptr_as_ptr = "warn"
|
||||
ptr_cast_constness = "warn"
|
||||
ref_as_ptr = "warn"
|
||||
|
||||
# see: https://github.com/bevyengine/bevy/pull/15375#issuecomment-2366966219
|
||||
too_long_first_doc_paragraph = "allow"
|
||||
|
||||
std_instead_of_core = "warn"
|
||||
std_instead_of_alloc = "warn"
|
||||
alloc_instead_of_core = "warn"
|
||||
|
||||
[lints.rust]
|
||||
missing_docs = "warn"
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(docsrs_dep)'] }
|
||||
unsafe_code = "deny"
|
||||
unsafe_op_in_unsafe_fn = "warn"
|
||||
unused_qualifications = "warn"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmarks"
|
||||
@ -70,18 +114,19 @@ harness = false
|
||||
[[example]]
|
||||
name = "debug"
|
||||
path = "examples/debug.rs"
|
||||
required-features = ["debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "demo"
|
||||
path = "examples/demo.rs"
|
||||
required-features = ["i128"]
|
||||
required-features = ["i128", "camera", "debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
|
||||
[[example]]
|
||||
name = "error_child"
|
||||
path = "examples/error_child.rs"
|
||||
required-features = ["camera", "debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
@ -92,25 +137,41 @@ doc-scrape-examples = false
|
||||
[[example]]
|
||||
name = "infinite"
|
||||
path = "examples/infinite.rs"
|
||||
required-features = ["i8"]
|
||||
required-features = ["i8", "camera", "debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "minimal"
|
||||
path = "examples/minimal.rs"
|
||||
required-features = ["camera"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "particles"
|
||||
path = "examples/particles.rs"
|
||||
doc-scrape-examples = false
|
||||
# TODO: Uncomment once bevy_hanabi is updated
|
||||
# [[example]]
|
||||
# name = "particles"
|
||||
# path = "examples/particles.rs"
|
||||
# doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "planets"
|
||||
path = "examples/planets.rs"
|
||||
required-features = ["camera"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "small_scale"
|
||||
path = "examples/small_scale.rs"
|
||||
required-features = ["camera", "debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "spatial_hash"
|
||||
path = "examples/spatial_hash.rs"
|
||||
required-features = ["camera"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
[[example]]
|
||||
name = "split_screen"
|
||||
path = "examples/split_screen.rs"
|
||||
required-features = ["camera", "debug"]
|
||||
doc-scrape-examples = false
|
||||
|
||||
12
README.md
12
README.md
@ -5,7 +5,7 @@
|
||||
<img src="https://raw.githubusercontent.com/aevyrie/big_space/refs/heads/main/assets/bigspacebanner.svg" width="80%">
|
||||
|
||||
Huge worlds, high performance, no dependencies, ecosystem compatibility. [Read the docs](https://docs.rs/big_space)
|
||||
|
||||
|
||||
[](https://crates.io/crates/big_space)
|
||||
[](https://docs.rs/big_space)
|
||||
[](https://github.com/aevyrie/big_space/actions/workflows/rust.yml)
|
||||
@ -18,11 +18,10 @@ Huge worlds, high performance, no dependencies, ecosystem compatibility. [Read t
|
||||
- Uses `Transform`, making it compatible with most of the Bevy ecosystem.
|
||||
- No added dependencies.
|
||||
- Absolute coordinates without drift, unlike camera-relative or periodic recentering solutions.
|
||||
- Chunks the world into integer grids, from `i8` up to `i128`.
|
||||
- Grids can be nested.
|
||||
- Chunks the world into nestable integer grids, from `i8` up to `i128`.
|
||||
- Spatial hashing for fast grid cell lookups and neighbor search.
|
||||
- Spatial partitioning to group sets of disconnected entities.
|
||||
- 3-5x faster than Bevy's transform propagation for wide hierarchies.
|
||||
- Spatial partitioning to group sets of connected cells.
|
||||
- Great performance scaling and parallelism with massive entity counts.
|
||||
- 👉 [Extensive documentation you should read.](https://docs.rs/big_space)
|
||||
|
||||

|
||||
@ -40,7 +39,8 @@ https://github.com/user-attachments/assets/9ce5283f-7d48-47dc-beef-9a7626858ed4
|
||||
## Bevy Version Support
|
||||
|
||||
| bevy | big_space |
|
||||
| ---- | --------- |
|
||||
|------|-----------|
|
||||
| 0.16 | 0.10 |
|
||||
| 0.15 | 0.8, 0.9 |
|
||||
| 0.14 | 0.7 |
|
||||
| 0.13 | 0.5, 0.6 |
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
//! `big_space` benchmarks.
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use big_space::prelude::*;
|
||||
use core::{iter::repeat_with, ops::Neg};
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use std::{iter::repeat_with, ops::Neg};
|
||||
use turborand::prelude::*;
|
||||
|
||||
criterion_group!(
|
||||
@ -52,7 +54,7 @@ fn deep_hierarchy(c: &mut Criterion) {
|
||||
fn translate(mut transforms: Query<&mut Transform>) {
|
||||
transforms.iter_mut().for_each(|mut transform| {
|
||||
transform.translation += Vec3::ONE;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let mut app = App::new();
|
||||
@ -91,7 +93,7 @@ fn wide_hierarchy(c: &mut Criterion) {
|
||||
fn translate(mut transforms: Query<&mut Transform>) {
|
||||
transforms.iter_mut().for_each(|mut transform| {
|
||||
transform.translation += Vec3::ONE;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
let mut app = App::new();
|
||||
@ -126,9 +128,9 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
let rng = Rng::with_seed(342525);
|
||||
let values: Vec<_> = repeat_with(|| {
|
||||
[
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
]
|
||||
})
|
||||
.take(N_SPAWN)
|
||||
@ -143,7 +145,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
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();
|
||||
@ -204,7 +206,7 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
// });
|
||||
// });
|
||||
|
||||
fn setup_uniform<const HALF_EXTENT: i64>(mut commands: Commands) {
|
||||
fn setup_uniform<const HALF_EXTENT: GridPrecision>(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 {
|
||||
@ -226,7 +228,8 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
let parent = app
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<BigSpace>>()
|
||||
.single(app.world());
|
||||
.single(app.world())
|
||||
.unwrap();
|
||||
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();
|
||||
@ -254,7 +257,8 @@ fn spatial_hashing(c: &mut Criterion) {
|
||||
let parent = app
|
||||
.world_mut()
|
||||
.query_filtered::<Entity, With<BigSpace>>()
|
||||
.single(app.world());
|
||||
.single(app.world())
|
||||
.unwrap();
|
||||
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();
|
||||
@ -288,9 +292,9 @@ fn hash_filtering(c: &mut Criterion) {
|
||||
let rng = Rng::with_seed(342525);
|
||||
let values: Vec<_> = repeat_with(|| {
|
||||
[
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
rng.i64(-HALF_WIDTH..=HALF_WIDTH) as GridPrecision,
|
||||
]
|
||||
})
|
||||
.take(N_ENTITIES)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
//! Demonstrates debugging visualization for `big_space` components.
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use bevy::{color::palettes, prelude::*};
|
||||
@ -6,9 +7,9 @@ use big_space::prelude::*;
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, (movement, rotation))
|
||||
@ -26,7 +27,7 @@ fn movement(
|
||||
Query<&mut Transform, With<Mover<3>>>,
|
||||
Query<&mut Transform, With<Mover<4>>>,
|
||||
)>,
|
||||
) {
|
||||
) -> Result {
|
||||
let delta_translation = |offset: f32, scale: f32| -> Vec3 {
|
||||
let t_1 = time.elapsed_secs() * 0.1 + offset;
|
||||
let dt = time.delta_secs() * 0.1;
|
||||
@ -38,10 +39,12 @@ fn movement(
|
||||
p1 - p0
|
||||
};
|
||||
|
||||
q.p0().single_mut().translation += delta_translation(20.0, 1.0);
|
||||
q.p1().single_mut().translation += delta_translation(251.0, 1.0);
|
||||
q.p2().single_mut().translation += delta_translation(812.0, 1.0);
|
||||
q.p3().single_mut().translation += delta_translation(863.0, 0.4);
|
||||
q.p0().single_mut()?.translation += delta_translation(20.0, 1.0);
|
||||
q.p1().single_mut()?.translation += delta_translation(251.0, 1.0);
|
||||
q.p2().single_mut()?.translation += delta_translation(812.0, 1.0);
|
||||
q.p3().single_mut()?.translation += delta_translation(863.0, 0.4);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
//! Demonstrates using the plugin over a wide range of scales, from protons to the universe.
|
||||
|
||||
use bevy::{
|
||||
color::palettes,
|
||||
prelude::*,
|
||||
@ -13,10 +15,10 @@ use big_space::{
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.add_systems(Startup, (setup, ui_setup))
|
||||
@ -79,10 +81,10 @@ fn setup(
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct BigSpaceDebugText;
|
||||
struct BigSpaceDebugText;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct FunFactText;
|
||||
struct FunFactText;
|
||||
|
||||
fn ui_setup(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
@ -125,13 +127,11 @@ fn highlight_nearest_sphere(
|
||||
cameras: Query<&CameraController>,
|
||||
objects: Query<&GlobalTransform>,
|
||||
mut gizmos: Gizmos,
|
||||
) {
|
||||
let Some((entity, _)) = cameras.single().nearest_object() else {
|
||||
return;
|
||||
};
|
||||
let Ok(transform) = objects.get(entity) else {
|
||||
return;
|
||||
) -> Result {
|
||||
let Some((entity, _)) = cameras.single()?.nearest_object() else {
|
||||
return Ok(());
|
||||
};
|
||||
let transform = objects.get(entity)?;
|
||||
// Ignore rotation due to panicking in gizmos, as of bevy 0.13
|
||||
let (scale, _, translation) = transform.to_scale_rotation_translation();
|
||||
gizmos
|
||||
@ -141,6 +141,7 @@ fn highlight_nearest_sphere(
|
||||
Color::Srgba(palettes::basic::RED),
|
||||
)
|
||||
.resolution(128);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
@ -155,8 +156,8 @@ fn ui_text_system(
|
||||
origin: Query<(Entity, GridTransformReadOnly), With<FloatingOrigin>>,
|
||||
camera: Query<&CameraController>,
|
||||
objects: Query<&Transform, With<Mesh3d>>,
|
||||
) {
|
||||
let (origin_entity, origin_pos) = origin.single();
|
||||
) -> Result {
|
||||
let (origin_entity, origin_pos) = origin.single()?;
|
||||
let translation = origin_pos.transform.translation;
|
||||
|
||||
let grid_text = format!(
|
||||
@ -170,7 +171,7 @@ fn ui_text_system(
|
||||
);
|
||||
|
||||
let Some(grid) = grids.parent_grid(origin_entity) else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let real_position = grid.grid_position_double(origin_pos.cell, origin_pos.transform);
|
||||
@ -183,7 +184,7 @@ fn ui_text_system(
|
||||
real_position.x as f32, real_position.y as f32, real_position.z as f32
|
||||
);
|
||||
|
||||
let velocity = camera.single().velocity();
|
||||
let velocity = camera.single()?.velocity();
|
||||
let speed = velocity.0.length() / time.delta_secs_f64();
|
||||
let camera_text = if speed > 3.0e8 {
|
||||
format!("Speed: {:.0e} * speed of light", speed / 3.0e8)
|
||||
@ -191,8 +192,8 @@ fn ui_text_system(
|
||||
format!("Speed: {:.2e} m/s", speed)
|
||||
};
|
||||
|
||||
let (nearest_text, fact_text) = if let Some(nearest) = camera.single().nearest_object() {
|
||||
let dia = objects.get(nearest.0).unwrap().scale.max_element();
|
||||
let (nearest_text, fact_text) = if let Some(nearest) = camera.single()?.nearest_object() {
|
||||
let dia = objects.get(nearest.0)?.scale.max_element();
|
||||
let (fact_dia, fact) = closest(dia);
|
||||
let dist = nearest.1;
|
||||
let multiple = dia / fact_dia;
|
||||
@ -206,13 +207,15 @@ fn ui_text_system(
|
||||
("".into(), "".into())
|
||||
};
|
||||
|
||||
let mut debug_text = debug_text.single_mut();
|
||||
let mut debug_text = debug_text.single_mut()?;
|
||||
|
||||
debug_text.0.0 = format!(
|
||||
"{grid_text}\n{translation_text}\n\n{real_position_f64_text}\n{real_position_f32_text}\n\n{camera_text}\n{nearest_text}"
|
||||
);
|
||||
|
||||
fun_text.single_mut().0 = fact_text
|
||||
fun_text.single_mut()?.0 = fact_text;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn closest<'a>(diameter: f32) -> (f32, &'a str) {
|
||||
@ -262,10 +265,8 @@ fn cursor_grab_system(
|
||||
mut cam: ResMut<CameraInput>,
|
||||
btn: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let Some(mut window) = windows.get_single_mut().ok() else {
|
||||
return;
|
||||
};
|
||||
) -> Result {
|
||||
let mut window = windows.single_mut()?;
|
||||
|
||||
if btn.just_pressed(MouseButton::Left) {
|
||||
window.cursor_options.grab_mode = CursorGrabMode::Locked;
|
||||
@ -280,4 +281,6 @@ fn cursor_grab_system(
|
||||
// window.mode = WindowMode::Windowed;
|
||||
cam.defaults_disabled = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -10,7 +10,10 @@ use big_space::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((DefaultPlugins, BigSpacePlugin::default()))
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, (setup_scene, setup_ui))
|
||||
.add_systems(Update, (rotator_system, toggle_plugin))
|
||||
.run();
|
||||
@ -22,8 +25,8 @@ 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 with the default i64 feature, or
|
||||
/// 10_000_000_000_000_000_000_000_000_000_000_000_000 with the i128 feature.
|
||||
/// `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
|
||||
@ -35,14 +38,16 @@ fn toggle_plugin(
|
||||
mut text: Query<&mut Text>,
|
||||
mut disabled: Local<bool>,
|
||||
mut floating_origin: Query<(Entity, &mut GridCell), With<FloatingOrigin>>,
|
||||
) {
|
||||
) -> Result {
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
*disabled = !*disabled;
|
||||
}
|
||||
|
||||
let this_grid = grids.parent_grid(floating_origin.single().0).unwrap();
|
||||
let this_grid = grids
|
||||
.parent_grid(floating_origin.single().unwrap().0)
|
||||
.unwrap();
|
||||
|
||||
let mut origin_cell = floating_origin.single_mut().1;
|
||||
let mut origin_cell = floating_origin.single_mut()?.1;
|
||||
let index_max = DISTANCE / this_grid.cell_edge_length() as GridPrecision;
|
||||
let increment = index_max / 100;
|
||||
|
||||
@ -73,14 +78,16 @@ fn toggle_plugin(
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
.rev()
|
||||
.map(std::str::from_utf8)
|
||||
.map(core::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()
|
||||
.unwrap()
|
||||
.join(",") // separator
|
||||
};
|
||||
|
||||
text.single_mut().0 =
|
||||
format!("Press Spacebar to toggle: {msg}\nCamera distance to floating origin: {}\nMesh distance from origin: {}", thousands(dist), thousands(DISTANCE))
|
||||
text.single_mut()?.0 =
|
||||
format!("Press Spacebar to toggle: {msg}\nCamera distance to floating origin: {}\nMesh distance from origin: {}", thousands(dist), thousands(DISTANCE));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
//! This example demonstrates error accumulating from parent to children in nested grids.
|
||||
use bevy::{math::DVec3, prelude::*};
|
||||
use bevy_color::palettes;
|
||||
use bevy::{color::palettes, math::DVec3, prelude::*};
|
||||
use big_space::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -86,7 +85,7 @@ fn setup_scene(
|
||||
..default()
|
||||
}),
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default() // Built-in camera controller
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
//! Big spaces are infinite, looping back on themselves smoothly.
|
||||
//! Big spaces are infinite, looping back on themselves smoothly. This example requires the use of
|
||||
//! the `i8` feature, because a small world is needed to be able to see the "edge".
|
||||
|
||||
use bevy::prelude::*;
|
||||
use big_space::prelude::*;
|
||||
@ -6,10 +7,10 @@ use big_space::prelude::*;
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -44,7 +45,7 @@ fn setup_scene(
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(0.0, 0.0, 10.0),
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default()
|
||||
CameraController::default()
|
||||
.with_speed(10.)
|
||||
.with_smoothness(0.99, 0.95),
|
||||
));
|
||||
|
||||
@ -10,10 +10,10 @@ const BIG_DISTANCE: f64 = 1_000_000_000_000_000_000.0;
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
CameraControllerPlugin::default(), // Compatible controller
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run();
|
||||
@ -67,7 +67,7 @@ fn setup_scene(
|
||||
Transform::from_translation(cell_offset + Vec3::new(0.0, 0.0, 10.0)),
|
||||
grid_cell,
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default(),
|
||||
CameraController::default(),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use std::collections::VecDeque;
|
||||
//! A practical example of a spare ship on a planet, in a solar system, surrounded by stars.
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::collections::VecDeque;
|
||||
|
||||
use bevy::{
|
||||
color::palettes,
|
||||
@ -15,14 +18,15 @@ use turborand::{rng::Rng, TurboRand};
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::new(true),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 200.0,
|
||||
..Default::default()
|
||||
})
|
||||
.add_systems(Startup, spawn_solar_system)
|
||||
.add_systems(
|
||||
@ -69,11 +73,12 @@ fn rotate(mut rotate_query: Query<(&mut Transform, &Rotates)>) {
|
||||
fn lighting(
|
||||
mut light: Query<(&mut Transform, &mut GlobalTransform), With<PrimaryLight>>,
|
||||
sun: Query<&GlobalTransform, (With<Sun>, Without<PrimaryLight>)>,
|
||||
) {
|
||||
let sun_pos = sun.single().translation();
|
||||
let (mut light_tr, mut light_gt) = light.single_mut();
|
||||
) -> Result {
|
||||
let sun_pos = sun.single()?.translation();
|
||||
let (mut light_tr, mut light_gt) = light.single_mut()?;
|
||||
light_tr.look_at(-sun_pos, Vec3::Y);
|
||||
*light_gt = (*light_tr).into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn springy_ship(
|
||||
@ -81,7 +86,7 @@ fn springy_ship(
|
||||
mut ship: Query<&mut Transform, With<Spaceship>>,
|
||||
mut desired_dir: Local<(Vec3, Quat)>,
|
||||
mut smoothed_rot: Local<VecDeque<Vec3>>,
|
||||
) {
|
||||
) -> Result {
|
||||
desired_dir.0 = DVec3::new(cam_input.right, cam_input.up, -cam_input.forward).as_vec3()
|
||||
* (1.0 + cam_input.boost as u8 as f32);
|
||||
|
||||
@ -89,7 +94,7 @@ fn springy_ship(
|
||||
smoothed_rot.push_front(DVec3::new(cam_input.pitch, cam_input.yaw, cam_input.roll).as_vec3());
|
||||
let avg_rot = smoothed_rot.iter().sum::<Vec3>() / smoothed_rot.len() as f32;
|
||||
|
||||
use std::f32::consts::*;
|
||||
use core::f32::consts::*;
|
||||
desired_dir.1 = Quat::IDENTITY.slerp(
|
||||
Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
@ -100,12 +105,14 @@ fn springy_ship(
|
||||
0.2,
|
||||
) * Quat::from_rotation_y(PI);
|
||||
|
||||
ship.single_mut().translation = ship
|
||||
.single_mut()
|
||||
ship.single_mut()?.translation = ship
|
||||
.single_mut()?
|
||||
.translation
|
||||
.lerp(desired_dir.0 * Vec3::new(0.5, 0.5, -2.0), 0.02);
|
||||
|
||||
ship.single_mut().rotation = ship.single_mut().rotation.slerp(desired_dir.1, 0.02);
|
||||
ship.single_mut()?.rotation = ship.single_mut()?.rotation.slerp(desired_dir.1, 0.02);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_solar_system(
|
||||
@ -218,7 +225,7 @@ fn spawn_solar_system(
|
||||
camera.insert((
|
||||
FloatingOrigin,
|
||||
Transform::from_translation(cam_pos).looking_to(Vec3::NEG_Z, Vec3::X),
|
||||
big_space::camera::CameraController::default() // Built-in camera controller
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([0.1, 10e35])
|
||||
.with_smoothness(0.98, 0.98)
|
||||
.with_speed(1.0),
|
||||
@ -244,7 +251,7 @@ fn spawn_solar_system(
|
||||
camera.with_child((
|
||||
Spaceship,
|
||||
SceneRoot(asset_server.load("models/low_poly_spaceship/scene.gltf#Scene0")),
|
||||
Transform::from_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
||||
Transform::from_rotation(Quat::from_rotation_y(core::f32::consts::PI)),
|
||||
));
|
||||
});
|
||||
});
|
||||
@ -276,10 +283,8 @@ fn cursor_grab_system(
|
||||
mut cam: ResMut<big_space::camera::CameraInput>,
|
||||
btn: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let Some(mut window) = windows.get_single_mut().ok() else {
|
||||
return;
|
||||
};
|
||||
) -> Result<()> {
|
||||
let mut window = windows.single_mut()?;
|
||||
|
||||
if btn.just_pressed(MouseButton::Right) {
|
||||
window.cursor_options.grab_mode = bevy::window::CursorGrabMode::Locked;
|
||||
@ -294,4 +299,6 @@ fn cursor_grab_system(
|
||||
// window.mode = WindowMode::Windowed;
|
||||
cam.defaults_disabled = true;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@
|
||||
use bevy::prelude::*;
|
||||
use bevy_math::DVec3;
|
||||
use big_space::prelude::*;
|
||||
use tracing::info;
|
||||
|
||||
const UNIVERSE_DIA: f64 = 8.8e26; // Diameter of the observable universe
|
||||
const PROTON_DIA: f32 = 1.68e-15; // Diameter of a proton
|
||||
@ -16,10 +17,10 @@ const PROTON_DIA: f32 = 1.68e-15; // Diameter of a proton
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
|
||||
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
|
||||
CameraControllerPlugin::default(), // Compatible controller
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.add_systems(Update, (bounce_atoms, toggle_cam_pos))
|
||||
@ -75,14 +76,14 @@ fn setup_scene(
|
||||
Transform::from_xyz(0.0, 0.0, PROTON_DIA * 2.0),
|
||||
grid_cell,
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default(),
|
||||
CameraController::default(),
|
||||
));
|
||||
|
||||
// A space ship
|
||||
root_grid.spawn_spatial((
|
||||
SceneRoot(asset_server.load("models/low_poly_spaceship/scene.gltf#Scene0")),
|
||||
Transform::from_xyz(0.0, 0.0, 2.5)
|
||||
.with_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
|
||||
.with_rotation(Quat::from_rotation_y(core::f32::consts::PI)),
|
||||
grid_cell,
|
||||
));
|
||||
});
|
||||
@ -104,12 +105,15 @@ fn toggle_cam_pos(
|
||||
grid: Query<&Grid>,
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
protons: Query<&GlobalTransform, With<Proton>>,
|
||||
) {
|
||||
) -> Result {
|
||||
if !keyboard.just_pressed(KeyCode::KeyT) {
|
||||
return;
|
||||
return Ok(());
|
||||
}
|
||||
*cam.single_mut() = if *toggle {
|
||||
grid.single().translation_to_grid(DVec3::X * UNIVERSE_DIA).0
|
||||
*cam.single_mut()? = if *toggle {
|
||||
grid.single()
|
||||
.unwrap()
|
||||
.translation_to_grid(DVec3::X * UNIVERSE_DIA)
|
||||
.0
|
||||
} else {
|
||||
GridCell::ZERO
|
||||
};
|
||||
@ -120,4 +124,5 @@ fn toggle_cam_pos(
|
||||
for proton in &protons {
|
||||
info!("Proton x coord: {}", proton.translation().x);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
use std::hash::Hasher;
|
||||
//! Demonstrates the included optional spatial hashing and partitioning of grid cells.
|
||||
|
||||
use bevy::{
|
||||
core_pipeline::{bloom::Bloom, fxaa::Fxaa, tonemapping::Tonemapping},
|
||||
core_pipeline::{bloom::Bloom, tonemapping::Tonemapping},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_ecs::entity::EntityHasher;
|
||||
use bevy_ecs::{entity::EntityHasher, relationship::Relationship};
|
||||
use bevy_math::DVec3;
|
||||
use big_space::prelude::*;
|
||||
use core::hash::Hasher;
|
||||
use noise::{NoiseFn, Simplex};
|
||||
use smallvec::SmallVec;
|
||||
use turborand::prelude::*;
|
||||
|
||||
// Try bumping this up to really stress test. I'm able to push a million entities with an M3 Max.
|
||||
@ -21,11 +21,11 @@ const PERCENT_STATIC: f32 = 0.99;
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
GridHashPlugin::<()>::default(),
|
||||
GridPartitionPlugin::<()>::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, (spawn, setup_ui))
|
||||
.add_systems(
|
||||
@ -69,7 +69,7 @@ impl FromWorld for MaterialPresets {
|
||||
|
||||
let mut meshes = world.resource_mut::<Assets<Mesh>>();
|
||||
let sphere = meshes.add(
|
||||
Sphere::new(HALF_WIDTH / (1_000_000_f32).powf(0.33) * 0.5)
|
||||
Sphere::new(HALF_WIDTH / 1_000_000_f32.powf(0.33) * 0.5)
|
||||
.mesh()
|
||||
.ico(0)
|
||||
.unwrap(),
|
||||
@ -89,10 +89,12 @@ fn draw_partitions(
|
||||
partitions: Res<GridPartitionMap>,
|
||||
grids: Query<(&GlobalTransform, &Grid)>,
|
||||
camera: Query<&GridHash, With<Camera>>,
|
||||
) {
|
||||
) -> Result {
|
||||
let camera = camera.single()?;
|
||||
|
||||
for (id, p) in partitions.iter().take(10_000) {
|
||||
let Ok((transform, grid)) = grids.get(p.grid()) else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
let l = grid.cell_edge_length();
|
||||
|
||||
@ -102,7 +104,7 @@ fn draw_partitions(
|
||||
let hue = (f % 360) as f32;
|
||||
|
||||
p.iter()
|
||||
.filter(|hash| *hash != camera.single())
|
||||
.filter(|hash| *hash != camera)
|
||||
.take(1_000)
|
||||
.for_each(|h| {
|
||||
let center = [h.cell().x as i32, h.cell().y as i32, h.cell().z as i32];
|
||||
@ -110,7 +112,7 @@ fn draw_partitions(
|
||||
.with_scale(Vec3::splat(l));
|
||||
gizmos.cuboid(
|
||||
transform.mul_transform(local_trans),
|
||||
Hsla::new(hue, 1.0, 0.5, 0.05),
|
||||
Hsla::new(hue, 1.0, 0.5, 0.2),
|
||||
);
|
||||
});
|
||||
|
||||
@ -123,19 +125,20 @@ fn draw_partitions(
|
||||
|
||||
gizmos.cuboid(
|
||||
transform.mul_transform(local_trans),
|
||||
Hsla::new(hue, 1.0, 0.5, 0.2),
|
||||
Hsla::new(hue, 1.0, 0.5, 0.9),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn move_player(
|
||||
time: Res<Time>,
|
||||
mut _gizmos: Gizmos,
|
||||
mut player: Query<(&mut Transform, &mut GridCell, &Parent, &GridHash), With<Player>>,
|
||||
mut player: Query<(&mut Transform, &mut GridCell, &ChildOf, &GridHash), With<Player>>,
|
||||
mut non_player: Query<
|
||||
(&mut Transform, &mut GridCell, &Parent),
|
||||
(&mut Transform, &mut GridCell, &ChildOf),
|
||||
(Without<Player>, With<NonPlayer>),
|
||||
>,
|
||||
mut materials: Query<&mut MeshMaterial3d<StandardMaterial>, Without<Player>>,
|
||||
@ -146,11 +149,11 @@ fn move_player(
|
||||
mut text: Query<&mut Text>,
|
||||
hash_stats: Res<big_space::timing::SmoothedStat<big_space::timing::GridHashStats>>,
|
||||
prop_stats: Res<big_space::timing::SmoothedStat<big_space::timing::PropagationStats>>,
|
||||
) {
|
||||
) -> Result {
|
||||
let n_entities = non_player.iter().len();
|
||||
for neighbor in neighbors.iter() {
|
||||
if let Ok(mut material) = materials.get_mut(*neighbor) {
|
||||
**material = material_presets.default.clone_weak();
|
||||
material.set_if_neq(material_presets.default.clone_weak().into());
|
||||
}
|
||||
}
|
||||
|
||||
@ -170,14 +173,13 @@ fn move_player(
|
||||
}
|
||||
|
||||
let t = time.elapsed_secs() * 0.01;
|
||||
let (mut transform, mut cell, parent, hash) = player.single_mut();
|
||||
let (mut transform, mut cell, parent, hash) = player.single_mut()?;
|
||||
let absolute_pos = HALF_WIDTH
|
||||
* CELL_WIDTH
|
||||
* 0.8
|
||||
* Vec3::new((5.0 * t).sin(), (7.0 * t).cos(), (20.0 * t).sin());
|
||||
(*cell, transform.translation) = grids
|
||||
.get(parent.get())
|
||||
.unwrap()
|
||||
.get(parent.get())?
|
||||
.imprecise_translation_to_grid(absolute_pos);
|
||||
|
||||
neighbors.clear();
|
||||
@ -185,15 +187,8 @@ fn move_player(
|
||||
hash_grid.flood(hash, None).entities().for_each(|entity| {
|
||||
neighbors.push(entity);
|
||||
if let Ok(mut material) = materials.get_mut(entity) {
|
||||
**material = material_presets.flood.clone_weak();
|
||||
material.set_if_neq(material_presets.flood.clone_weak().into());
|
||||
}
|
||||
|
||||
// let grid = grid.get(entry.grid).unwrap();
|
||||
// let transform = grid.global_transform(
|
||||
// &entry.cell,
|
||||
// &Transform::from_scale(Vec3::splat(grid.cell_edge_length() * 0.99)),
|
||||
// );
|
||||
// gizmos.cuboid(transform, Color::linear_rgba(1.0, 1.0, 1.0, 0.2));
|
||||
});
|
||||
|
||||
hash_grid
|
||||
@ -204,11 +199,11 @@ fn move_player(
|
||||
.for_each(|entity| {
|
||||
neighbors.push(entity);
|
||||
if let Ok(mut material) = materials.get_mut(entity) {
|
||||
**material = material_presets.highlight.clone_weak();
|
||||
material.set_if_neq(material_presets.highlight.clone_weak().into());
|
||||
}
|
||||
});
|
||||
|
||||
let mut text = text.single_mut();
|
||||
let mut text = text.single_mut()?;
|
||||
text.0 = format!(
|
||||
"\
|
||||
Controls:
|
||||
@ -236,9 +231,8 @@ Total: {: >22.1?}",
|
||||
.as_bytes()
|
||||
.rchunks(3)
|
||||
.rev()
|
||||
.map(std::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()
|
||||
.unwrap()
|
||||
.map(core::str::from_utf8)
|
||||
.collect::<Result<Vec<&str>, _>>()?
|
||||
.join(","),
|
||||
//
|
||||
prop_stats.avg().grid_recentering(),
|
||||
@ -254,6 +248,8 @@ Total: {: >22.1?}",
|
||||
//
|
||||
prop_stats.avg().total() + hash_stats.avg().total(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn(mut commands: Commands) {
|
||||
@ -267,11 +263,10 @@ fn spawn(mut commands: Commands) {
|
||||
},
|
||||
Tonemapping::AcesFitted,
|
||||
Transform::from_xyz(0.0, 0.0, HALF_WIDTH * CELL_WIDTH * 2.0),
|
||||
big_space::camera::CameraController::default()
|
||||
CameraController::default()
|
||||
.with_smoothness(0.98, 0.93)
|
||||
.with_slowing(false)
|
||||
.with_speed(15.0),
|
||||
Fxaa::default(),
|
||||
Bloom::default(),
|
||||
GridCell::new(0, 0, HALF_WIDTH as GridPrecision / 2),
|
||||
))
|
||||
@ -287,54 +282,41 @@ fn spawn_spheres(
|
||||
mut commands: Commands,
|
||||
input: Res<ButtonInput<KeyCode>>,
|
||||
material_presets: Res<MaterialPresets>,
|
||||
mut grid: Query<(Entity, &Grid, &mut Children)>,
|
||||
grid: Query<Entity, With<Grid>>,
|
||||
non_players: Query<(), With<NonPlayer>>,
|
||||
) {
|
||||
) -> Result {
|
||||
let n_entities = non_players.iter().len().max(1);
|
||||
let n_spawn = if input.pressed(KeyCode::KeyG) {
|
||||
n_entities
|
||||
} else if input.pressed(KeyCode::KeyF) {
|
||||
1_000
|
||||
} else {
|
||||
return;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let (entity, _grid, mut children) = grid.single_mut();
|
||||
let mut dyn_parent = bevy_reflect::DynamicTupleStruct::default();
|
||||
dyn_parent.insert(entity);
|
||||
let dyn_parent = dyn_parent.as_partial_reflect();
|
||||
|
||||
let new_children = sample_noise(n_spawn, &Simplex::new(345612), &Rng::new())
|
||||
.map(|value| {
|
||||
let entity = grid.single()?;
|
||||
commands.entity(entity).with_children(|builder| {
|
||||
for value in sample_noise(n_spawn, &Simplex::new(345612), &Rng::new()) {
|
||||
let hash = GridHash::__new_manual(entity, &GridCell::default());
|
||||
commands
|
||||
.spawn((
|
||||
Transform::from_xyz(value.x, value.y, value.z),
|
||||
GlobalTransform::default(),
|
||||
GridCell::default(),
|
||||
FastGridHash::from(hash),
|
||||
hash,
|
||||
NonPlayer,
|
||||
Parent::from_reflect(dyn_parent).unwrap(),
|
||||
Mesh3d(material_presets.sphere.clone_weak()),
|
||||
MeshMaterial3d(material_presets.default.clone_weak()),
|
||||
bevy_render::view::VisibilityRange {
|
||||
start_margin: 1.0..5.0,
|
||||
end_margin: HALF_WIDTH * CELL_WIDTH * 0.5..HALF_WIDTH * CELL_WIDTH * 0.8,
|
||||
use_aabb: false,
|
||||
},
|
||||
bevy_render::view::NoFrustumCulling,
|
||||
))
|
||||
.id()
|
||||
})
|
||||
.chain(children.iter().copied())
|
||||
.collect::<SmallVec<[Entity; 8]>>();
|
||||
|
||||
let mut dyn_children = bevy_reflect::DynamicTupleStruct::default();
|
||||
dyn_children.insert(new_children);
|
||||
let dyn_children = dyn_children.as_partial_reflect();
|
||||
|
||||
*children = Children::from_reflect(dyn_children).unwrap();
|
||||
builder.spawn((
|
||||
Transform::from_xyz(value.x, value.y, value.z),
|
||||
GlobalTransform::default(),
|
||||
GridCell::default(),
|
||||
FastGridHash::from(hash),
|
||||
hash,
|
||||
NonPlayer,
|
||||
Mesh3d(material_presets.sphere.clone_weak()),
|
||||
MeshMaterial3d(material_presets.default.clone_weak()),
|
||||
bevy_render::view::VisibilityRange {
|
||||
start_margin: 1.0..5.0,
|
||||
end_margin: HALF_WIDTH * CELL_WIDTH * 0.5..HALF_WIDTH * CELL_WIDTH * 0.8,
|
||||
use_aabb: false,
|
||||
},
|
||||
bevy_render::view::NoFrustumCulling,
|
||||
));
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@ -343,7 +325,7 @@ fn sample_noise<'a, T: NoiseFn<f64, 3>>(
|
||||
noise: &'a T,
|
||||
rng: &'a Rng,
|
||||
) -> impl Iterator<Item = Vec3> + use<'a, T> {
|
||||
std::iter::repeat_with(
|
||||
core::iter::repeat_with(
|
||||
|| loop {
|
||||
let noise_scale = 0.05 * HALF_WIDTH as f64;
|
||||
let threshold = 0.50;
|
||||
@ -390,8 +372,8 @@ fn cursor_grab(
|
||||
keyboard: Res<ButtonInput<KeyCode>>,
|
||||
mouse: Res<ButtonInput<MouseButton>>,
|
||||
mut windows: Query<&mut Window, With<bevy::window::PrimaryWindow>>,
|
||||
) {
|
||||
let mut primary_window = windows.single_mut();
|
||||
) -> Result {
|
||||
let mut primary_window = windows.single_mut()?;
|
||||
if mouse.just_pressed(MouseButton::Left) {
|
||||
primary_window.cursor_options.grab_mode = bevy::window::CursorGrabMode::Locked;
|
||||
primary_window.cursor_options.visible = false;
|
||||
@ -400,4 +382,5 @@ fn cursor_grab(
|
||||
primary_window.cursor_options.grab_mode = bevy::window::CursorGrabMode::None;
|
||||
primary_window.cursor_options.visible = true;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
//! Demonstrates how a single bevy world can contain multiple big_space hierarchies, each rendered
|
||||
//! Demonstrates how a single bevy world can contain multiple `big_space` hierarchies, each rendered
|
||||
//! relative to a floating origin inside that big space.
|
||||
//!
|
||||
//! This takes the simplest approach, of simply duplicating the worlds and players for each split
|
||||
//! screen, and synchronizing the player locations between both.
|
||||
|
||||
use bevy::{
|
||||
color::palettes,
|
||||
prelude::*,
|
||||
render::{camera::Viewport, view::RenderLayers},
|
||||
transform::TransformSystem,
|
||||
};
|
||||
use bevy_color::palettes;
|
||||
use big_space::prelude::*;
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins,
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::default(),
|
||||
FloatingOriginDebugPlugin::default(),
|
||||
big_space::camera::CameraControllerPlugin::default(),
|
||||
CameraControllerPlugin::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, set_camera_viewports)
|
||||
@ -60,7 +60,7 @@ fn setup(
|
||||
Camera3d::default(),
|
||||
Transform::from_xyz(1_000_000.0 - 10.0, 100_005.0, 0.0)
|
||||
.looking_to(Vec3::NEG_X, Vec3::Y),
|
||||
big_space::camera::CameraController::default().with_smoothness(0.8, 0.8),
|
||||
CameraController::default().with_smoothness(0.8, 0.8),
|
||||
RenderLayers::layer(2),
|
||||
LeftCamera,
|
||||
FloatingOrigin,
|
||||
@ -182,12 +182,14 @@ fn update_cameras(
|
||||
Without<RightCamera>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
*left_rep.single_mut().cell = *left.single().cell;
|
||||
*left_rep.single_mut().transform = *left.single().transform;
|
||||
) -> Result {
|
||||
*left_rep.single_mut()?.cell = *left.single()?.cell;
|
||||
*left_rep.single_mut()?.transform = *left.single()?.transform;
|
||||
|
||||
*right_rep.single_mut().cell = *right.single().cell;
|
||||
*right_rep.single_mut().transform = *right.single().transform;
|
||||
*right_rep.single_mut()?.cell = *right.single()?.cell;
|
||||
*right_rep.single_mut()?.transform = *right.single()?.transform;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_camera_viewports(
|
||||
@ -195,13 +197,13 @@ fn set_camera_viewports(
|
||||
mut resize_events: EventReader<bevy::window::WindowResized>,
|
||||
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
|
||||
mut right_camera: Query<&mut Camera, With<RightCamera>>,
|
||||
) {
|
||||
) -> Result {
|
||||
// We need to dynamically resize the camera's viewports whenever the window size changes
|
||||
// so then each camera always takes up half the screen.
|
||||
// A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup.
|
||||
for resize_event in resize_events.read() {
|
||||
let window = windows.get(resize_event.window).unwrap();
|
||||
let mut left_camera = left_camera.single_mut();
|
||||
let window = windows.get(resize_event.window)?;
|
||||
let mut left_camera = left_camera.single_mut()?;
|
||||
left_camera.viewport = Some(Viewport {
|
||||
physical_position: UVec2::new(0, 0),
|
||||
physical_size: UVec2::new(
|
||||
@ -211,7 +213,7 @@ fn set_camera_viewports(
|
||||
..default()
|
||||
});
|
||||
|
||||
let mut right_camera = right_camera.single_mut();
|
||||
let mut right_camera = right_camera.single_mut()?;
|
||||
right_camera.viewport = Some(Viewport {
|
||||
physical_position: UVec2::new(window.resolution.physical_width() / 2, 0),
|
||||
physical_size: UVec2::new(
|
||||
@ -221,4 +223,6 @@ fn set_camera_viewports(
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! Component bundles for big_space.
|
||||
//! Component bundles for `big_space`.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
@ -3,9 +3,10 @@
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_input::{mouse::MouseMotion, prelude::*};
|
||||
use bevy_math::{prelude::*, DQuat, DVec3};
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_platform_support::prelude::*;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_render::{
|
||||
primitives::Aabb,
|
||||
@ -13,7 +14,6 @@ use bevy_render::{
|
||||
};
|
||||
use bevy_time::prelude::*;
|
||||
use bevy_transform::{prelude::*, TransformSystem};
|
||||
use bevy_utils::HashSet;
|
||||
|
||||
/// Adds the `big_space` camera controller
|
||||
#[derive(Default)]
|
||||
@ -131,7 +131,7 @@ impl Default for CameraController {
|
||||
}
|
||||
}
|
||||
|
||||
/// ButtonInput state used to command camera motion. Reset every time the values are read to update
|
||||
/// `ButtonInput` state used to command camera motion. Reset every time the values are read to update
|
||||
/// the camera. Allows you to map any input to camera motions. Uses aircraft principle axes
|
||||
/// conventions.
|
||||
#[derive(Clone, Debug, Default, Reflect, Resource)]
|
||||
@ -226,7 +226,7 @@ pub fn nearest_objects_in_grid(
|
||||
)>,
|
||||
children: Query<&Children>,
|
||||
) {
|
||||
let Ok((cam_entity, mut camera, cam_pos, cam_layer)) = camera.get_single_mut() else {
|
||||
let Ok((cam_entity, mut camera, cam_pos, cam_layer)) = camera.single_mut() else {
|
||||
return;
|
||||
};
|
||||
if !camera.slow_near_objects {
|
||||
@ -260,7 +260,7 @@ pub fn nearest_objects_in_grid(
|
||||
/// Uses [`CameraInput`] state to update the camera position.
|
||||
pub fn camera_controller(
|
||||
time: Res<Time>,
|
||||
grids: crate::grid::local_origin::Grids,
|
||||
grids: Grids,
|
||||
mut input: ResMut<CameraInput>,
|
||||
mut camera: Query<(Entity, &mut GridCell, &mut Transform, &mut CameraController)>,
|
||||
) {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
//! Adds `big_space`-specific commands to bevy's `Commands`.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_ecs::{prelude::*, relationship::RelatedSpawnerCommands};
|
||||
use bevy_transform::prelude::*;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
@ -177,7 +176,7 @@ impl Drop for GridCommands<'_> {
|
||||
let entity = self.entity;
|
||||
self.commands
|
||||
.entity(entity)
|
||||
.insert(std::mem::take(&mut self.grid))
|
||||
.insert(core::mem::take(&mut self.grid))
|
||||
.add_children(&self.children);
|
||||
}
|
||||
}
|
||||
@ -189,13 +188,13 @@ pub struct SpatialEntityCommands<'a> {
|
||||
}
|
||||
|
||||
impl<'a> SpatialEntityCommands<'a> {
|
||||
/// Insert a component on this grid
|
||||
/// Insert a component into this grid.
|
||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.entity(self.entity).insert(bundle);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a `Bundle`` of components from the entity.
|
||||
/// Removes a `Bundle` of components from the entity.
|
||||
pub fn remove<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Bundle,
|
||||
@ -204,8 +203,11 @@ impl<'a> SpatialEntityCommands<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Takes a closure which provides a [`ChildBuilder`].
|
||||
pub fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self {
|
||||
/// Spawns children of this entity (with a [`ChildOf`] relationship) by taking a function that operates on a [`ChildSpawner`].
|
||||
pub fn with_children(
|
||||
&mut self,
|
||||
spawn_children: impl FnOnce(&mut RelatedSpawnerCommands<'_, ChildOf>),
|
||||
) -> &mut Self {
|
||||
self.commands
|
||||
.entity(self.entity)
|
||||
.with_children(|child_builder| spawn_children(child_builder));
|
||||
@ -213,12 +215,16 @@ impl<'a> SpatialEntityCommands<'a> {
|
||||
}
|
||||
|
||||
/// Spawns the passed bundle and adds it to this entity as a child.
|
||||
///
|
||||
/// For efficient spawning of multiple children, use [`with_children`].
|
||||
///
|
||||
/// [`with_children`]: SpatialEntityCommands::with_children
|
||||
pub fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
|
||||
self.commands.entity(self.entity).with_child(bundle);
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`Entity``] id of the entity.
|
||||
/// Returns the [`Entity`] id of the entity.
|
||||
pub fn id(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
10
src/debug.rs
10
src/debug.rs
@ -20,16 +20,16 @@ impl Plugin for FloatingOriginDebugPlugin {
|
||||
PostUpdate,
|
||||
(update_debug_bounds, update_grid_axes)
|
||||
.chain()
|
||||
.after(bevy_transform::TransformSystem::TransformPropagate),
|
||||
.after(TransformSystem::TransformPropagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_gizmos(mut store: ResMut<GizmoConfigStore>) {
|
||||
let (config, _) = store.config_mut::<BigSpaceGizmoConfig>();
|
||||
config.line_perspective = false;
|
||||
config.line_joints = GizmoLineJoint::Round(4);
|
||||
config.line_width = 1.0;
|
||||
config.line.perspective = false;
|
||||
config.line.joints = GizmoLineJoint::Round(4);
|
||||
config.line.width = 1.0;
|
||||
}
|
||||
|
||||
/// Update the rendered debug bounds to only highlight occupied [`GridCell`]s.
|
||||
@ -47,7 +47,7 @@ fn update_debug_bounds(
|
||||
&Transform::from_scale(Vec3::splat(grid.cell_edge_length() * 0.999)),
|
||||
);
|
||||
if origin.is_none() {
|
||||
gizmos.cuboid(transform, Color::linear_rgb(0.0, 1.0, 0.0))
|
||||
gizmos.cuboid(transform, Color::linear_rgb(0.0, 1.0, 0.0));
|
||||
} else {
|
||||
// gizmos.cuboid(transform, Color::rgba(0.0, 0.0, 1.0, 0.5))
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
//! A floating origin for camera-relative rendering, to maximize precision when converting to f32.
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_utils::HashMap;
|
||||
|
||||
/// Marks the entity to use as the floating origin.
|
||||
///
|
||||
@ -45,13 +44,13 @@ pub struct BigSpace {
|
||||
}
|
||||
|
||||
impl BigSpace {
|
||||
/// Return the this grid's floating origin if it exists and is a descendent of this root.
|
||||
/// Return this grid's floating origin if it exists and is a descendant of this root.
|
||||
///
|
||||
/// `this_entity`: the entity this component belongs to.
|
||||
pub(crate) fn validate_floating_origin(
|
||||
&self,
|
||||
this_entity: Entity,
|
||||
parents: &Query<&Parent>,
|
||||
parents: &Query<&ChildOf>,
|
||||
) -> Option<Entity> {
|
||||
let floating_origin = self.floating_origin?;
|
||||
let origin_root_entity = parents.iter_ancestors(floating_origin).last()?;
|
||||
@ -63,10 +62,10 @@ impl BigSpace {
|
||||
/// `BigSpace` hierarchy.
|
||||
pub fn find_floating_origin(
|
||||
floating_origins: Query<Entity, With<FloatingOrigin>>,
|
||||
parent_query: Query<&Parent>,
|
||||
parent_query: Query<&ChildOf>,
|
||||
mut big_spaces: Query<(Entity, &mut BigSpace)>,
|
||||
) {
|
||||
let mut spaces_set = HashMap::new();
|
||||
let mut spaces_set = HashMap::<_, _>::default();
|
||||
// Reset all floating origin fields, so we know if any are missing.
|
||||
for (entity, mut space) in &mut big_spaces {
|
||||
space.floating_origin = None;
|
||||
@ -86,7 +85,7 @@ impl BigSpace {
|
||||
tracing::error!(
|
||||
"BigSpace {root:#?} has multiple floating origins. There must be exactly one. Resetting this big space and disabling the floating origin to avoid unexpected propagation behavior.",
|
||||
);
|
||||
space.floating_origin = None
|
||||
space.floating_origin = None;
|
||||
} else {
|
||||
space.floating_origin = Some(origin);
|
||||
}
|
||||
@ -99,7 +98,7 @@ impl BigSpace {
|
||||
.filter(|(_k, v)| **v == 0)
|
||||
.map(|(k, _v)| k)
|
||||
{
|
||||
tracing::error!("BigSpace {space:#} has no floating origins. There must be exactly one. Transform propagation will not work until there is a FloatingOrigin in the hierarchy.",)
|
||||
tracing::error!("BigSpace {space:#} has no floating origins. There must be exactly one. Transform propagation will not work until there is a FloatingOrigin in the hierarchy.",);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
//! Contains the grid cell implementation
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_ecs::{prelude::*, relationship::Relationship};
|
||||
use bevy_math::{DVec3, IVec3};
|
||||
use bevy_platform_support::time::Instant;
|
||||
use bevy_reflect::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::Instant;
|
||||
|
||||
/// 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.
|
||||
@ -84,7 +83,7 @@ impl GridCell {
|
||||
pub fn recenter_large_transforms(
|
||||
mut stats: ResMut<crate::timing::PropagationStats>,
|
||||
grids: Query<&Grid>,
|
||||
mut changed_transform: Query<(&mut Self, &mut Transform, &Parent), Changed<Transform>>,
|
||||
mut changed_transform: Query<(&mut Self, &mut Transform, &ChildOf), Changed<Transform>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
changed_transform
|
||||
@ -111,7 +110,7 @@ impl GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for GridCell {
|
||||
impl core::ops::Add for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
@ -123,7 +122,7 @@ impl std::ops::Add for GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<IVec3> for GridCell {
|
||||
impl core::ops::Add<IVec3> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: IVec3) -> Self::Output {
|
||||
@ -135,7 +134,7 @@ impl std::ops::Add<IVec3> for GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for GridCell {
|
||||
impl core::ops::Sub for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
@ -147,7 +146,7 @@ impl std::ops::Sub for GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<IVec3> for GridCell {
|
||||
impl core::ops::Sub<IVec3> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: IVec3) -> Self::Output {
|
||||
@ -159,7 +158,7 @@ impl std::ops::Sub<IVec3> for GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for &GridCell {
|
||||
impl core::ops::Add for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
@ -167,7 +166,7 @@ impl std::ops::Add for &GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add<IVec3> for &GridCell {
|
||||
impl core::ops::Add<IVec3> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn add(self, rhs: IVec3) -> Self::Output {
|
||||
@ -175,7 +174,7 @@ impl std::ops::Add<IVec3> for &GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub for &GridCell {
|
||||
impl core::ops::Sub for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
@ -183,7 +182,7 @@ impl std::ops::Sub for &GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Sub<IVec3> for &GridCell {
|
||||
impl core::ops::Sub<IVec3> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn sub(self, rhs: IVec3) -> Self::Output {
|
||||
@ -191,35 +190,35 @@ impl std::ops::Sub<IVec3> for &GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign for GridCell {
|
||||
impl core::ops::AddAssign for GridCell {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
use std::ops::Add;
|
||||
use core::ops::Add;
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::AddAssign<IVec3> for GridCell {
|
||||
impl core::ops::AddAssign<IVec3> for GridCell {
|
||||
fn add_assign(&mut self, rhs: IVec3) {
|
||||
use std::ops::Add;
|
||||
use core::ops::Add;
|
||||
*self = self.add(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign for GridCell {
|
||||
impl core::ops::SubAssign for GridCell {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
use std::ops::Sub;
|
||||
use core::ops::Sub;
|
||||
*self = self.sub(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::SubAssign<IVec3> for GridCell {
|
||||
impl core::ops::SubAssign<IVec3> for GridCell {
|
||||
fn sub_assign(&mut self, rhs: IVec3) {
|
||||
use std::ops::Sub;
|
||||
use core::ops::Sub;
|
||||
*self = self.sub(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<GridPrecision> for GridCell {
|
||||
impl core::ops::Mul<GridPrecision> for GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn mul(self, rhs: GridPrecision) -> Self::Output {
|
||||
@ -231,7 +230,7 @@ impl std::ops::Mul<GridPrecision> for GridCell {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Mul<GridPrecision> for &GridCell {
|
||||
impl core::ops::Mul<GridPrecision> for &GridCell {
|
||||
type Output = GridCell;
|
||||
|
||||
fn mul(self, rhs: GridPrecision) -> Self::Output {
|
||||
|
||||
@ -5,13 +5,14 @@
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::{
|
||||
prelude::*,
|
||||
relationship::Relationship,
|
||||
system::{
|
||||
lifetimeless::{Read, Write},
|
||||
SystemParam,
|
||||
},
|
||||
};
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_math::{prelude::*, DAffine3, DQuat};
|
||||
use bevy_platform_support::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
|
||||
pub use inner::LocalFloatingOrigin;
|
||||
@ -75,7 +76,7 @@ mod inner {
|
||||
|
||||
impl LocalFloatingOrigin {
|
||||
/// The grid transform from the local grid, to the floating origin's grid. See
|
||||
/// [Self::grid_transform].
|
||||
/// [`Self::grid_transform`].
|
||||
#[inline]
|
||||
pub fn grid_transform(&self) -> DAffine3 {
|
||||
self.grid_transform
|
||||
@ -236,14 +237,14 @@ fn propagate_origin_to_child(
|
||||
child_origin_translation_float,
|
||||
origin_child_rotation,
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
/// A system param for more easily navigating a hierarchy of [`Grid`]s.
|
||||
#[derive(SystemParam)]
|
||||
pub struct Grids<'w, 's> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
grid_query: Query<'w, 's, (Entity, Read<Grid>, Option<Read<Parent>>)>,
|
||||
parent: Query<'w, 's, Read<ChildOf>>,
|
||||
grid_query: Query<'w, 's, (Entity, Read<Grid>, Option<Read<ChildOf>>)>,
|
||||
}
|
||||
|
||||
impl Grids<'_, '_> {
|
||||
@ -266,7 +267,7 @@ impl Grids<'_, '_> {
|
||||
/// Get the ID of the grid that `this` `Entity` is a child of, if it exists.
|
||||
#[inline]
|
||||
pub fn parent_grid_entity(&self, this: Entity) -> Option<Entity> {
|
||||
match self.parent.get(this).map(|parent| **parent) {
|
||||
match self.parent.get(this).map(Relationship::get) {
|
||||
Err(_) => None,
|
||||
Ok(parent) => match self.grid_query.contains(parent) {
|
||||
true => Some(parent),
|
||||
@ -291,7 +292,7 @@ impl Grids<'_, '_> {
|
||||
.iter()
|
||||
.filter_map(move |(entity, _, parent)| {
|
||||
parent
|
||||
.map(|p| p.get())
|
||||
.map(Relationship::get)
|
||||
.filter(|parent| *parent == this)
|
||||
.map(|_| entity)
|
||||
})
|
||||
@ -317,9 +318,9 @@ impl Grids<'_, '_> {
|
||||
/// A system param for more easily navigating a hierarchy of grids mutably.
|
||||
#[derive(SystemParam)]
|
||||
pub struct GridsMut<'w, 's> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
parent: Query<'w, 's, Read<ChildOf>>,
|
||||
position: Query<'w, 's, (Read<GridCell>, Read<Transform>), With<Grid>>,
|
||||
grid_query: Query<'w, 's, (Entity, Write<Grid>, Option<Read<Parent>>)>,
|
||||
grid_query: Query<'w, 's, (Entity, Write<Grid>, Option<Read<ChildOf>>)>,
|
||||
}
|
||||
|
||||
impl GridsMut<'_, '_> {
|
||||
@ -374,7 +375,7 @@ impl GridsMut<'_, '_> {
|
||||
/// Get the ID of the grid that `this` `Entity` is a child of, if it exists.
|
||||
#[inline]
|
||||
pub fn parent_grid_entity(&self, this: Entity) -> Option<Entity> {
|
||||
match self.parent.get(this).map(|parent| **parent) {
|
||||
match self.parent.get(this).map(Relationship::get) {
|
||||
Err(_) => None,
|
||||
Ok(parent) => match self.grid_query.contains(parent) {
|
||||
true => Some(parent),
|
||||
@ -399,7 +400,7 @@ impl GridsMut<'_, '_> {
|
||||
.iter()
|
||||
.filter_map(move |(entity, _, parent)| {
|
||||
parent
|
||||
.map(|p| p.get())
|
||||
.map(Relationship::get)
|
||||
.filter(|parent| *parent == this)
|
||||
.map(|_| entity)
|
||||
})
|
||||
@ -435,9 +436,9 @@ impl LocalFloatingOrigin {
|
||||
mut scratch_buffer: Local<Vec<Entity>>,
|
||||
cells: Query<(Entity, Ref<GridCell>)>,
|
||||
roots: Query<(Entity, &BigSpace)>,
|
||||
parents: Query<&Parent>,
|
||||
parents: Query<&ChildOf>,
|
||||
) {
|
||||
let start = bevy_utils::Instant::now();
|
||||
let start = bevy_platform_support::time::Instant::now();
|
||||
|
||||
/// The maximum grid tree depth, defensively prevents infinite looping in case there is a
|
||||
/// degenerate hierarchy. It might take a while, but at least it's not forever?
|
||||
@ -493,7 +494,7 @@ impl LocalFloatingOrigin {
|
||||
// child, these do no alias.
|
||||
for child_grid in scratch_buffer.drain(..) {
|
||||
propagate_origin_to_child(this_grid, &mut grids, child_grid);
|
||||
grid_stack.push(child_grid) // Push processed child onto the stack
|
||||
grid_stack.push(child_grid); // Push processed child onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
@ -507,7 +508,7 @@ impl LocalFloatingOrigin {
|
||||
}
|
||||
}
|
||||
|
||||
tracing::error!("Reached the maximum grid depth ({MAX_REFERENCE_FRAME_DEPTH}), and exited early to prevent an infinite loop. This might be caused by a degenerate hierarchy.")
|
||||
tracing::error!("Reached the maximum grid depth ({MAX_REFERENCE_FRAME_DEPTH}), and exited early to prevent an infinite loop. This might be caused by a degenerate hierarchy.");
|
||||
}
|
||||
|
||||
stats.local_origin_propagation += start.elapsed();
|
||||
@ -550,7 +551,7 @@ mod tests {
|
||||
let result = grids.child_grids(child_1).collect::<Vec<_>>();
|
||||
assert_eq!(result, Vec::new());
|
||||
|
||||
// Parent
|
||||
// ChildOf
|
||||
let result = grids.parent_grid_entity(root);
|
||||
assert_eq!(result, None);
|
||||
let result = grids.parent_grid_entity(parent);
|
||||
@ -575,7 +576,7 @@ mod tests {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::new(1_000_000, -1, -1),
|
||||
Vec3::ZERO,
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
DQuat::from_rotation_z(-core::f64::consts::FRAC_PI_2),
|
||||
),
|
||||
..default()
|
||||
};
|
||||
@ -587,7 +588,7 @@ mod tests {
|
||||
let child = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
|
||||
Transform::from_rotation(Quat::from_rotation_z(core::f32::consts::FRAC_PI_2))
|
||||
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
|
||||
GridCell::new(1_000_000, 0, 0),
|
||||
Grid::default(),
|
||||
@ -609,7 +610,7 @@ mod tests {
|
||||
assert_eq!(computed_grid, correct_grid);
|
||||
|
||||
let computed_rot = child_grid.local_floating_origin.rotation();
|
||||
let correct_rot = DQuat::from_rotation_z(std::f64::consts::PI);
|
||||
let correct_rot = DQuat::from_rotation_z(core::f64::consts::PI);
|
||||
let rot_error = computed_rot.angle_between(correct_rot);
|
||||
assert!(rot_error < 1e-10);
|
||||
|
||||
@ -637,14 +638,14 @@ mod tests {
|
||||
let child = app
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
|
||||
Transform::from_rotation(Quat::from_rotation_z(core::f32::consts::FRAC_PI_2))
|
||||
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
|
||||
GridCell::new(150_000_003_000, 0, 0), // roughly radius of earth orbit
|
||||
Grid {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::new(0, 3_000, 0),
|
||||
Vec3::new(5.0, 5.0, 0.0),
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
DQuat::from_rotation_z(-core::f64::consts::FRAC_PI_2),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
@ -708,7 +709,7 @@ mod tests {
|
||||
.world_mut()
|
||||
.spawn((
|
||||
Transform::default()
|
||||
.with_rotation(Quat::from_rotation_z(-std::f32::consts::FRAC_PI_2))
|
||||
.with_rotation(Quat::from_rotation_z(-core::f32::consts::FRAC_PI_2))
|
||||
.with_translation(Vec3::new(3.0, 3.0, 0.0)),
|
||||
GridCell::new(0, 0, 0),
|
||||
Grid::default(),
|
||||
@ -729,7 +730,7 @@ mod tests {
|
||||
let computed_pos = computed_transform.transform_point3(child_local_point);
|
||||
|
||||
let correct_transform = DAffine3::from_rotation_translation(
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
DQuat::from_rotation_z(-core::f64::consts::FRAC_PI_2),
|
||||
DVec3::new(2.0, 2.0, 0.0),
|
||||
);
|
||||
let correct_pos = correct_transform.transform_point3(child_local_point);
|
||||
|
||||
@ -115,9 +115,9 @@ impl Grid {
|
||||
return (GridCell::default(), input.as_vec3());
|
||||
}
|
||||
|
||||
let x_r = (x / l).round();
|
||||
let y_r = (y / l).round();
|
||||
let z_r = (z / l).round();
|
||||
let x_r = round(x / l);
|
||||
let y_r = round(y / l);
|
||||
let z_r = round(z / l);
|
||||
let t_x = x - x_r * l;
|
||||
let t_y = y - y_r * l;
|
||||
let t_z = z - z_r * l;
|
||||
@ -165,3 +165,21 @@ impl Grid {
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn round(x: f64) -> f64 {
|
||||
#[cfg(feature = "libm")]
|
||||
{
|
||||
libm::round(x)
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "libm"), feature = "std"))]
|
||||
{
|
||||
x.round()
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "libm"), not(feature = "std")))]
|
||||
{
|
||||
compile_error!("Must enable the `libm` and/or `std` feature.");
|
||||
f64::NAN
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
//! Logic for propagating transforms through the hierarchy of grids.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_ecs::{prelude::*, relationship::Relationship};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_transform::prelude::*;
|
||||
|
||||
@ -26,13 +25,13 @@ impl Grid {
|
||||
Query<(
|
||||
Ref<GridCell>,
|
||||
Ref<Transform>,
|
||||
Ref<Parent>,
|
||||
Ref<ChildOf>,
|
||||
&mut GlobalTransform,
|
||||
)>,
|
||||
Query<(&Grid, &mut GlobalTransform), With<BigSpace>>,
|
||||
)>,
|
||||
) {
|
||||
let start = bevy_utils::Instant::now();
|
||||
let start = bevy_platform_support::time::Instant::now();
|
||||
|
||||
// Performance note: I've also tried to iterate over each grid's children at once, to avoid
|
||||
// the grid and parent lookup, but that made things worse because it prevented dumb
|
||||
@ -95,13 +94,13 @@ impl Grid {
|
||||
mut commands: Commands,
|
||||
valid_parent: Query<(), (With<GridCell>, With<GlobalTransform>, With<Children>)>,
|
||||
unmarked: Query<
|
||||
(Entity, &Parent),
|
||||
(Entity, &ChildOf),
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
Without<GridCell>,
|
||||
Without<LowPrecisionRoot>,
|
||||
Or<(Changed<Parent>, Added<Transform>)>,
|
||||
Or<(Changed<ChildOf>, Added<Transform>)>,
|
||||
),
|
||||
>,
|
||||
invalidated: Query<
|
||||
@ -112,13 +111,13 @@ impl Grid {
|
||||
Without<Transform>,
|
||||
Without<GlobalTransform>,
|
||||
With<GridCell>,
|
||||
Without<Parent>,
|
||||
Without<ChildOf>,
|
||||
)>,
|
||||
),
|
||||
>,
|
||||
has_possibly_invalid_parent: Query<(Entity, &Parent), With<LowPrecisionRoot>>,
|
||||
has_possibly_invalid_parent: Query<(Entity, &ChildOf), With<LowPrecisionRoot>>,
|
||||
) {
|
||||
let start = bevy_utils::Instant::now();
|
||||
let start = bevy_platform_support::time::Instant::now();
|
||||
for (entity, parent) in unmarked.iter() {
|
||||
if valid_parent.contains(parent.get()) {
|
||||
commands.entity(entity).insert(LowPrecisionRoot);
|
||||
@ -150,17 +149,17 @@ impl Grid {
|
||||
Or<(With<Grid>, With<GridCell>)>,
|
||||
),
|
||||
>,
|
||||
roots: Query<(Entity, &Parent), With<LowPrecisionRoot>>,
|
||||
roots: Query<(Entity, &ChildOf), With<LowPrecisionRoot>>,
|
||||
transform_query: Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
With<ChildOf>,
|
||||
Without<GridCell>, // Used to prove access to GlobalTransform is disjoint
|
||||
Without<Grid>,
|
||||
),
|
||||
>,
|
||||
parent_query: Query<
|
||||
(Entity, Ref<Parent>),
|
||||
(Entity, Ref<ChildOf>),
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
@ -169,7 +168,7 @@ impl Grid {
|
||||
),
|
||||
>,
|
||||
) {
|
||||
let start = bevy_utils::Instant::now();
|
||||
let start = bevy_platform_support::time::Instant::now();
|
||||
let update_transforms = |low_precision_root, parent_transform: Ref<GlobalTransform>| {
|
||||
// High precision global transforms are change-detected, and are only updated if that
|
||||
// entity has moved relative to the floating origin's grid cell.
|
||||
@ -186,6 +185,10 @@ impl Grid {
|
||||
// other root entities' `propagate_recursive` calls will not conflict with this one.
|
||||
// - Since this is the only place where `transform_query` gets used, there will be no
|
||||
// conflicting fetches elsewhere.
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`."
|
||||
)]
|
||||
unsafe {
|
||||
Self::propagate_recursive(
|
||||
&parent_transform,
|
||||
@ -206,8 +209,6 @@ impl Grid {
|
||||
stats.low_precision_propagation += start.elapsed();
|
||||
}
|
||||
|
||||
/// COPIED FROM BEVY
|
||||
///
|
||||
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
||||
///
|
||||
/// # Panics
|
||||
@ -221,18 +222,22 @@ impl Grid {
|
||||
/// nor any of its descendants.
|
||||
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
|
||||
/// remain as a tree or a forest. Each entity must have at most one parent.
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
|
||||
)]
|
||||
unsafe fn propagate_recursive(
|
||||
parent: &GlobalTransform,
|
||||
transform_query: &Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
With<ChildOf>,
|
||||
Without<GridCell>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<Grid>, // ***ADDED*** Only recurse low-precision entities
|
||||
),
|
||||
>,
|
||||
parent_query: &Query<
|
||||
(Entity, Ref<Parent>),
|
||||
(Entity, Ref<ChildOf>),
|
||||
(
|
||||
With<Transform>,
|
||||
With<GlobalTransform>,
|
||||
@ -245,28 +250,34 @@ impl Grid {
|
||||
) {
|
||||
let (global_matrix, children) = {
|
||||
let Ok((transform, mut global_transform, children)) =
|
||||
// SAFETY: This call cannot create aliased mutable references.
|
||||
// - The top level iteration parallelizes on the roots of the hierarchy.
|
||||
// - The caller ensures that each child has one and only one unique parent throughout
|
||||
// the entire hierarchy.
|
||||
//
|
||||
// For example, consider the following malformed hierarchy:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C \ / D
|
||||
//
|
||||
// D has two parents, B and C. If the propagation passes through C, but the Parent
|
||||
// component on D points to B, the above check will panic as the origin parent does
|
||||
// match the recorded parent.
|
||||
//
|
||||
// Also consider the following case, where A and B are roots:
|
||||
//
|
||||
// A B \ / C D \ / E
|
||||
//
|
||||
// Even if these A and B start two separate tasks running in parallel, one of them will
|
||||
// panic before attempting to mutably access E.
|
||||
(unsafe { transform_query.get_unchecked(entity) }) else {
|
||||
// SAFETY: This call cannot create aliased mutable references.
|
||||
// - The top level iteration parallelizes on the roots of the hierarchy.
|
||||
// - The caller ensures that each child has one and only one unique parent
|
||||
// throughout the entire hierarchy.
|
||||
//
|
||||
// For example, consider the following malformed hierarchy:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C
|
||||
// \ /
|
||||
// D
|
||||
//
|
||||
// D has two parents, B and C. If the propagation passes through C, but the ChildOf
|
||||
// component on D points to B, the above check will panic as the origin parent does
|
||||
// match the recorded parent.
|
||||
//
|
||||
// Also consider the following case, where A and B are roots:
|
||||
//
|
||||
// A B
|
||||
// \ /
|
||||
// C D
|
||||
// \ /
|
||||
// E
|
||||
//
|
||||
// Even if these A and B start two separate tasks running in parallel, one of them
|
||||
// will panic before attempting to mutably access E.
|
||||
(unsafe { transform_query.get_unchecked(entity) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
@ -278,11 +289,11 @@ impl Grid {
|
||||
};
|
||||
|
||||
let Some(children) = children else { return };
|
||||
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||
for (child, child_of) in parent_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
actual_parent.get(), entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
child_of.parent, entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY: The caller guarantees that `transform_query` will not be fetched for any
|
||||
// descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
|
||||
//
|
||||
@ -294,7 +305,7 @@ impl Grid {
|
||||
transform_query,
|
||||
parent_query,
|
||||
child,
|
||||
changed || actual_parent.is_changed(),
|
||||
changed || child_of.is_changed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -337,10 +348,10 @@ mod tests {
|
||||
let mut q = app
|
||||
.world_mut()
|
||||
.query_filtered::<&GlobalTransform, With<Test>>();
|
||||
let actual_transform = *q.single(app.world());
|
||||
let actual_transform = *q.single(app.world()).unwrap();
|
||||
assert_eq!(
|
||||
actual_transform,
|
||||
GlobalTransform::from_xyz(2004.0, 2005.0, 2006.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,18 @@
|
||||
//! Components for spatial hashing.
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use alloc::vec::Vec;
|
||||
use core::hash::{BuildHasher, Hash, Hasher};
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::Parent;
|
||||
use bevy_ecs::{prelude::*, relationship::Relationship};
|
||||
use bevy_math::IVec3;
|
||||
use bevy_platform_support::{hash::FixedHasher, time::Instant};
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_utils::{AHasher, Instant, Parallel};
|
||||
|
||||
use super::{ChangedGridHashes, GridHashMapFilter};
|
||||
|
||||
use crate::portable_par::PortableParallel;
|
||||
|
||||
/// A fast but lossy version of [`GridHash`]. Use this component when you don't care about false
|
||||
/// positives (hash collisions). See the docs on [`GridHash::fast_eq`] for more details on fast but
|
||||
/// lossy equality checks.
|
||||
@ -47,7 +49,7 @@ impl From<GridHash> for FastGridHash {
|
||||
/// [`GridHashMap`] resource.
|
||||
///
|
||||
/// Due to grids and multiple big spaces in a single world, this must use both the [`GridCell`] and
|
||||
/// the [`Parent`] of the entity to uniquely identify its position. These two values are then hashed
|
||||
/// the [`ChildOf`] 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 {
|
||||
@ -89,15 +91,15 @@ impl GridHash {
|
||||
/// 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) -> Self {
|
||||
pub(super) fn new(parent: &ChildOf, cell: &GridCell) -> Self {
|
||||
Self::from_parent(parent.get(), cell)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn from_parent(parent: Entity, cell: &GridCell) -> Self {
|
||||
let hasher = &mut AHasher::default();
|
||||
let mut hasher = FixedHasher.build_hasher();
|
||||
hasher.write_u64(parent.to_bits());
|
||||
cell.hash(hasher);
|
||||
cell.hash(&mut hasher);
|
||||
|
||||
GridHash {
|
||||
cell: *cell,
|
||||
@ -131,7 +133,7 @@ impl GridHash {
|
||||
/// errors.
|
||||
///
|
||||
/// In other words, this should only be used for acceleration, when you want to quickly cull
|
||||
/// non-overlapping cells, and you will be double checking for false positives later.
|
||||
/// non-overlapping cells, and you will be double-checking for false positives later.
|
||||
#[inline]
|
||||
pub fn fast_eq(&self, other: &Self) -> bool {
|
||||
self.pre_hash == other.pre_hash
|
||||
@ -160,13 +162,19 @@ impl GridHash {
|
||||
mut commands: Commands,
|
||||
mut changed_hashes: ResMut<ChangedGridHashes<F>>,
|
||||
mut spatial_entities: Query<
|
||||
(Entity, &Parent, &GridCell, &mut GridHash, &mut FastGridHash),
|
||||
(F, Or<(Changed<Parent>, Changed<GridCell>)>),
|
||||
(
|
||||
Entity,
|
||||
&ChildOf,
|
||||
&GridCell,
|
||||
&mut GridHash,
|
||||
&mut FastGridHash,
|
||||
),
|
||||
(F, Or<(Changed<ChildOf>, Changed<GridCell>)>),
|
||||
>,
|
||||
added_entities: Query<(Entity, &Parent, &GridCell), (F, Without<GridHash>)>,
|
||||
added_entities: Query<(Entity, &ChildOf, &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, FastGridHash)>>>,
|
||||
mut thread_updated_hashes: Local<PortableParallel<Vec<Entity>>>,
|
||||
mut thread_commands: Local<PortableParallel<Vec<(Entity, GridHash, FastGridHash)>>>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
|
||||
@ -206,7 +214,7 @@ impl GridHash {
|
||||
self.cell
|
||||
}
|
||||
|
||||
/// The [`Parent`] [`Grid`] of this spatial hash.
|
||||
/// The [`ChildOf`] [`Grid`] of this spatial hash.
|
||||
pub fn grid(&self) -> Entity {
|
||||
self.grid
|
||||
}
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
//! The [`GridHashMap`] that contains mappings between entities and their spatial hash.
|
||||
|
||||
use std::{collections::VecDeque, marker::PhantomData, time::Instant};
|
||||
use alloc::collections::VecDeque;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use super::GridHashMapFilter;
|
||||
use crate::prelude::*;
|
||||
use bevy_ecs::{entity::EntityHash, prelude::*};
|
||||
use bevy_utils::{
|
||||
hashbrown::{HashMap, HashSet},
|
||||
PassHash,
|
||||
use bevy_platform_support::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::PassHash,
|
||||
prelude::*,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
/// An entry in a [`GridHashMap`], accessed with a [`GridHash`].
|
||||
@ -51,10 +54,10 @@ pub trait SpatialEntryToEntities<'a> {
|
||||
impl<'a, T, I> SpatialEntryToEntities<'a> for T
|
||||
where
|
||||
T: Iterator<Item = I> + 'a,
|
||||
I: SpatialEntryToEntities<'a>,
|
||||
I: SpatialEntryToEntities<'a> + 'a,
|
||||
{
|
||||
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
|
||||
self.flat_map(|entry| entry.entities())
|
||||
self.flat_map(SpatialEntryToEntities::entities)
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,11 +90,11 @@ where
|
||||
spooky: PhantomData<F>,
|
||||
}
|
||||
|
||||
impl<F> std::fmt::Debug for GridHashMap<F>
|
||||
impl<F> core::fmt::Debug for GridHashMap<F>
|
||||
where
|
||||
F: GridHashMapFilter,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("GridHashMap")
|
||||
.field("map", &self.map)
|
||||
.field("reverse_map", &self.reverse_map)
|
||||
@ -150,9 +153,9 @@ where
|
||||
&'a self,
|
||||
entry: &'a GridHashEntry,
|
||||
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
|
||||
// Use `std::iter::once` to avoid returning a function-local variable.
|
||||
// Use `core::iter::once` to avoid returning a function-local variable.
|
||||
Iterator::chain(
|
||||
std::iter::once(entry),
|
||||
core::iter::once(entry),
|
||||
entry.occupied_neighbors.iter().map(|neighbor_hash| {
|
||||
self.get(neighbor_hash)
|
||||
.expect("occupied_neighbors should be occupied")
|
||||
@ -177,8 +180,8 @@ where
|
||||
center: &'a GridHash,
|
||||
radius: u8,
|
||||
) -> 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))
|
||||
// Use `core::iter::once` to avoid returning a function-local variable.
|
||||
Iterator::chain(core::iter::once(*center), center.adjacent(radius))
|
||||
.filter_map(|hash| self.get(&hash))
|
||||
}
|
||||
|
||||
@ -194,7 +197,7 @@ where
|
||||
///
|
||||
/// Consider the case of a long thin U-shaped set of connected cells. While iterating from one
|
||||
/// end of the "U" to the other with this flood fill, if any of the cells near the base of the
|
||||
/// "U" exceed the max_depth (radius), iteration will stop. Even if the "U" loops back within
|
||||
/// "U" exceed the `max_depth` (radius), iteration will stop. Even if the "U" loops back within
|
||||
/// the radius, those cells will never be visited.
|
||||
///
|
||||
/// Also note that the `max_depth` (radius) is a chebyshev distance, not a euclidean distance.
|
||||
@ -261,7 +264,7 @@ where
|
||||
spatial_map.map.just_removed.clear();
|
||||
|
||||
for entity in removed.read() {
|
||||
spatial_map.remove(entity)
|
||||
spatial_map.remove(entity);
|
||||
}
|
||||
|
||||
if let Some(ref mut stats) = stats {
|
||||
@ -309,7 +312,7 @@ where
|
||||
#[inline]
|
||||
fn remove(&mut self, entity: Entity) {
|
||||
if let Some(old_hash) = self.reverse_map.remove(&entity) {
|
||||
self.map.remove(entity, old_hash)
|
||||
self.map.remove(entity, old_hash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
//! Spatial hashing acceleration structure. See [`GridHashPlugin`].
|
||||
|
||||
use std::marker::PhantomData;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::{prelude::*, query::QueryFilter};
|
||||
use bevy_platform_support::prelude::*;
|
||||
|
||||
pub mod component;
|
||||
pub mod map;
|
||||
@ -110,10 +111,8 @@ impl<F: GridHashMapFilter> Default for ChangedGridHashes<F> {
|
||||
// hash ever recomputed? Is it removed? Is the spatial map updated?
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use crate::{hash::map::SpatialEntryToEntities, prelude::*};
|
||||
use bevy_utils::hashbrown::HashSet;
|
||||
use bevy_platform_support::{collections::HashSet, sync::OnceLock};
|
||||
|
||||
#[test]
|
||||
fn entity_despawn() {
|
||||
@ -269,7 +268,7 @@ mod tests {
|
||||
let entities = app.world().resource::<Entities>().clone();
|
||||
let parent = app
|
||||
.world_mut()
|
||||
.query::<&Parent>()
|
||||
.query::<&ChildOf>()
|
||||
.get(app.world(), entities.a)
|
||||
.unwrap();
|
||||
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
//! Detect and update groups of nearby occupied cells.
|
||||
|
||||
use std::{hash::Hash, marker::PhantomData, ops::Deref};
|
||||
use core::{hash::Hash, marker::PhantomData, ops::Deref};
|
||||
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_tasks::{ComputeTaskPool, ParallelSliceMut};
|
||||
use bevy_utils::{
|
||||
hashbrown::{HashMap, HashSet},
|
||||
Instant, PassHash,
|
||||
use bevy_platform_support::prelude::*;
|
||||
use bevy_platform_support::{
|
||||
collections::{HashMap, HashSet},
|
||||
hash::PassHash,
|
||||
time::Instant,
|
||||
};
|
||||
use bevy_tasks::{ComputeTaskPool, ParallelSliceMut};
|
||||
|
||||
use super::{GridCell, GridHash, GridHashMap, GridHashMapFilter, GridHashMapSystem};
|
||||
|
||||
@ -55,7 +57,7 @@ impl GridPartitionId {
|
||||
|
||||
impl Hash for GridPartitionId {
|
||||
#[inline]
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_u64(self.0);
|
||||
}
|
||||
}
|
||||
@ -146,7 +148,7 @@ where
|
||||
#[inline]
|
||||
fn push(&mut self, partition: &GridPartitionId, hash: &GridHash) {
|
||||
if let Some(partition) = self.partitions.get_mut(partition) {
|
||||
partition.insert(*hash)
|
||||
partition.insert(*hash);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -180,11 +182,7 @@ where
|
||||
fn merge(&mut self, partitions: &[GridPartitionId]) {
|
||||
let Some(largest_partition) = partitions
|
||||
.iter()
|
||||
.filter_map(|id| {
|
||||
self.resolve(id)
|
||||
.map(|partition| partition.num_cells())
|
||||
.zip(Some(id))
|
||||
})
|
||||
.filter_map(|id| self.resolve(id).map(GridPartition::num_cells).zip(Some(id)))
|
||||
.reduce(|acc, elem| if elem.0 > acc.0 { elem } else { acc })
|
||||
.map(|(_cells, id)| id)
|
||||
else {
|
||||
@ -307,10 +305,10 @@ where
|
||||
// it means the partition has not been split, and we can continue to
|
||||
// the next partition.
|
||||
return None;
|
||||
} else {
|
||||
new_partitions
|
||||
.push(hash_grid.flood(&this_cell, None).map(|n| n.0).collect());
|
||||
}
|
||||
new_partitions
|
||||
.push(hash_grid.flood(&this_cell, None).map(|n| n.0).collect());
|
||||
|
||||
counter += 1;
|
||||
}
|
||||
|
||||
@ -330,7 +328,7 @@ where
|
||||
{
|
||||
// We want the original partition to retain the most cells to ensure that the smaller
|
||||
// sets are the ones that are assigned a new partition ID.
|
||||
new_partitions.sort_unstable_by_key(|set| set.len());
|
||||
new_partitions.sort_unstable_by_key(HashSet::len);
|
||||
if let Some(largest_partition) = new_partitions.pop() {
|
||||
partition_map.insert(*original_partition_id, largest_partition);
|
||||
}
|
||||
@ -357,7 +355,7 @@ mod private {
|
||||
use super::{GridCell, GridHash};
|
||||
use crate::precision::GridPrecision;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_utils::{hashbrown::HashSet, PassHash};
|
||||
use bevy_platform_support::{collections::HashSet, hash::PassHash, prelude::*};
|
||||
|
||||
/// A group of nearby [`GridCell`](crate::GridCell)s in an island disconnected from all other
|
||||
/// [`GridCell`](crate::GridCell)s.
|
||||
@ -385,7 +383,7 @@ mod private {
|
||||
/// Returns the total number of cells in this partition.
|
||||
#[inline]
|
||||
pub fn num_cells(&self) -> usize {
|
||||
self.tables.iter().map(|t| t.len()).sum()
|
||||
self.tables.iter().map(HashSet::len).sum()
|
||||
}
|
||||
|
||||
/// The grid this partition resides in.
|
||||
@ -462,22 +460,23 @@ mod private {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn extend(&mut self, mut partition: GridPartition) {
|
||||
for mut table in partition.tables.drain(..) {
|
||||
if table.len() < Self::MIN_TABLE_SIZE {
|
||||
pub(crate) fn extend(&mut self, mut other: GridPartition) {
|
||||
assert_eq!(self.grid, other.grid);
|
||||
|
||||
for other_table in other.tables.drain(..) {
|
||||
if other_table.len() < Self::MIN_TABLE_SIZE {
|
||||
if let Some(i) = self.smallest_table() {
|
||||
for hash in table.drain() {
|
||||
self.tables[i].insert_unique_unchecked(hash);
|
||||
}
|
||||
self.tables[i].reserve(other_table.len());
|
||||
self.tables[i].extend(other_table);
|
||||
} else {
|
||||
self.tables.push(table);
|
||||
self.tables.push(other_table);
|
||||
}
|
||||
} else {
|
||||
self.tables.push(table);
|
||||
self.tables.push(other_table);
|
||||
}
|
||||
}
|
||||
self.min = self.min.min(partition.min);
|
||||
self.max = self.max.max(partition.max);
|
||||
self.min = self.min.min(other.min);
|
||||
self.max = self.max.max(other.max);
|
||||
}
|
||||
|
||||
/// Removes a grid hash from the partition. Returns whether the value was present.
|
||||
@ -512,12 +511,8 @@ mod private {
|
||||
/// partition.
|
||||
#[inline]
|
||||
fn compute_min(&mut self) {
|
||||
if let Some(min) = self
|
||||
.iter()
|
||||
.map(|hash| hash.cell())
|
||||
.reduce(|acc, e| acc.min(e))
|
||||
{
|
||||
self.min = min
|
||||
if let Some(min) = self.iter().map(GridHash::cell).reduce(|acc, e| acc.min(e)) {
|
||||
self.min = min;
|
||||
} else {
|
||||
self.min = GridCell::ONE * 1e10f64 as GridPrecision;
|
||||
}
|
||||
@ -527,12 +522,8 @@ mod private {
|
||||
/// partition.
|
||||
#[inline]
|
||||
fn compute_max(&mut self) {
|
||||
if let Some(max) = self
|
||||
.iter()
|
||||
.map(|hash| hash.cell())
|
||||
.reduce(|acc, e| acc.max(e))
|
||||
{
|
||||
self.max = max
|
||||
if let Some(max) = self.iter().map(GridHash::cell).reduce(|acc, e| acc.max(e)) {
|
||||
self.max = max;
|
||||
} else {
|
||||
self.min = GridCell::ONE * -1e10 as GridPrecision;
|
||||
}
|
||||
|
||||
16
src/lib.rs
16
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,
|
||||
@ -197,12 +197,17 @@
|
||||
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![warn(missing_docs)]
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[allow(unused_imports)] // For docs
|
||||
use bevy_transform::prelude::*;
|
||||
#[allow(unused_imports)] // For docs
|
||||
use prelude::*;
|
||||
|
||||
pub(crate) mod portable_par;
|
||||
|
||||
pub mod bundles;
|
||||
pub mod commands;
|
||||
pub mod floating_origins;
|
||||
@ -220,13 +225,11 @@ pub mod debug;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Common big_space imports.
|
||||
/// Common `big_space` imports.
|
||||
pub mod prelude {
|
||||
use crate::*;
|
||||
pub use bundles::{BigGridBundle, BigSpaceRootBundle, BigSpatialBundle};
|
||||
pub use commands::{BigSpaceCommands, GridCommands, SpatialEntityCommands};
|
||||
#[cfg(feature = "debug")]
|
||||
pub use debug::FloatingOriginDebugPlugin;
|
||||
pub use floating_origins::{BigSpace, FloatingOrigin};
|
||||
pub use grid::{
|
||||
cell::GridCell,
|
||||
@ -242,6 +245,11 @@ pub mod prelude {
|
||||
pub use plugin::{BigSpacePlugin, FloatingOriginSystem};
|
||||
pub use precision::GridPrecision;
|
||||
pub use world_query::{GridTransform, GridTransformOwned, GridTransformReadOnly};
|
||||
|
||||
#[cfg(feature = "camera")]
|
||||
pub use camera::{CameraController, CameraControllerPlugin};
|
||||
#[cfg(feature = "debug")]
|
||||
pub use debug::FloatingOriginDebugPlugin;
|
||||
}
|
||||
|
||||
/// Contains the [`GridPrecision`] integer index type, which defines how much precision is available
|
||||
|
||||
159
src/plugin.rs
159
src/plugin.rs
@ -1,6 +1,7 @@
|
||||
//! The bevy plugin for big_space.
|
||||
//! The bevy plugin for `big_space`.
|
||||
|
||||
use crate::prelude::*;
|
||||
use alloc::vec::Vec;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_transform::prelude::*;
|
||||
@ -39,9 +40,6 @@ pub enum FloatingOriginSystem {
|
||||
|
||||
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));
|
||||
|
||||
// Performance timings
|
||||
app.add_plugins(crate::timing::TimingStatsPlugin);
|
||||
|
||||
@ -72,6 +70,7 @@ impl Plugin for BigSpacePlugin {
|
||||
// Reflect
|
||||
.register_type::<Transform>()
|
||||
.register_type::<GlobalTransform>()
|
||||
.register_type::<TransformTreeChanged>()
|
||||
.register_type::<GridCell>()
|
||||
.register_type::<Grid>()
|
||||
.register_type::<BigSpace>()
|
||||
@ -101,18 +100,166 @@ impl Plugin for BigSpacePlugin {
|
||||
.add_systems(
|
||||
PostStartup,
|
||||
(
|
||||
propagate_parent_transforms,
|
||||
bevy_transform::systems::sync_simple_transforms,
|
||||
bevy_transform::systems::propagate_transforms,
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
propagate_parent_transforms,
|
||||
bevy_transform::systems::sync_simple_transforms,
|
||||
bevy_transform::systems::propagate_transforms,
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Copied from bevy. This is the simpler propagation implementation that doesn't use dirty tree
|
||||
/// marking. This is needed because dirty tree marking doesn't start from the root, and will end up
|
||||
/// doing the work for big space hierarchies, which it cannot affect anyway.
|
||||
pub fn propagate_parent_transforms(
|
||||
mut root_query: Query<
|
||||
(Entity, &Children, Ref<Transform>, &mut GlobalTransform),
|
||||
Without<ChildOf>,
|
||||
>,
|
||||
mut orphaned: RemovedComponents<ChildOf>,
|
||||
transform_query: Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
With<ChildOf>,
|
||||
>,
|
||||
child_query: Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
|
||||
mut orphaned_entities: Local<Vec<Entity>>,
|
||||
) {
|
||||
orphaned_entities.clear();
|
||||
orphaned_entities.extend(orphaned.read());
|
||||
orphaned_entities.sort_unstable();
|
||||
root_query.par_iter_mut().for_each(
|
||||
|(entity, children, transform, mut global_transform)| {
|
||||
let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
|
||||
if changed {
|
||||
*global_transform = GlobalTransform::from(*transform);
|
||||
}
|
||||
|
||||
for (child, child_of) in child_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
child_of.parent, entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY:
|
||||
// - `child` must have consistent parentage, or the above assertion would panic.
|
||||
// Since `child` is parented to a root entity, the entire hierarchy leading to it
|
||||
// is consistent.
|
||||
// - We may operate as if all descendants are consistent, since
|
||||
// `propagate_recursive` will panic before continuing to propagate if it
|
||||
// encounters an entity with inconsistent parentage.
|
||||
// - Since each root entity is unique and the hierarchy is consistent and
|
||||
// forest-like, other root entities' `propagate_recursive` calls will not conflict
|
||||
// with this one.
|
||||
// - Since this is the only place where `transform_query` gets used, there will be
|
||||
// no conflicting fetches elsewhere.
|
||||
#[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
|
||||
unsafe {
|
||||
propagate_recursive(
|
||||
&global_transform,
|
||||
&transform_query,
|
||||
&child_query,
|
||||
child,
|
||||
changed || child_of.is_changed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
|
||||
/// propagating the transforms of any malformed entities and their descendants.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - While this function is running, `transform_query` must not have any fetches for `entity`,
|
||||
/// nor any of its descendants.
|
||||
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
|
||||
/// remain as a tree or a forest. Each entity must have at most one parent.
|
||||
#[expect(
|
||||
unsafe_code,
|
||||
reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
|
||||
)]
|
||||
unsafe fn propagate_recursive(
|
||||
parent: &GlobalTransform,
|
||||
transform_query: &Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
With<ChildOf>,
|
||||
>,
|
||||
child_query: &Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
|
||||
entity: Entity,
|
||||
mut changed: bool,
|
||||
) {
|
||||
let (global_matrix, children) = {
|
||||
let Ok((transform, mut global_transform, children)) =
|
||||
// SAFETY: This call cannot create aliased mutable references.
|
||||
// - The top level iteration parallelizes on the roots of the hierarchy.
|
||||
// - The caller ensures that each child has one and only one unique parent throughout
|
||||
// the entire hierarchy.
|
||||
//
|
||||
// For example, consider the following malformed hierarchy:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C
|
||||
// \ /
|
||||
// D
|
||||
//
|
||||
// D has two parents, B and C. If the propagation passes through C, but the ChildOf
|
||||
// component on D points to B, the above check will panic as the origin parent does
|
||||
// match the recorded parent.
|
||||
//
|
||||
// Also consider the following case, where A and B are roots:
|
||||
//
|
||||
// A B
|
||||
// \ /
|
||||
// C D
|
||||
// \ /
|
||||
// E
|
||||
//
|
||||
// Even if these A and B start two separate tasks running in parallel, one of them will
|
||||
// panic before attempting to mutably access E.
|
||||
(unsafe { transform_query.get_unchecked(entity) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
changed |= transform.is_changed() || global_transform.is_added();
|
||||
if changed {
|
||||
*global_transform = parent.mul_transform(*transform);
|
||||
}
|
||||
(global_transform, children)
|
||||
};
|
||||
|
||||
let Some(children) = children else { return };
|
||||
for (child, child_of) in child_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
child_of.parent, entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY: The caller guarantees that `transform_query` will not be fetched for any
|
||||
// descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
|
||||
//
|
||||
// The above assertion ensures that each child has one and only one unique parent
|
||||
// throughout the entire hierarchy.
|
||||
unsafe {
|
||||
propagate_recursive(
|
||||
global_matrix.as_ref(),
|
||||
transform_query,
|
||||
child_query,
|
||||
child,
|
||||
changed || child_of.is_changed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
126
src/portable_par.rs
Normal file
126
src/portable_par.rs
Normal file
@ -0,0 +1,126 @@
|
||||
//! Module for defining a `no_std` compatible `Parallel` thread local.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::ops::DerefMut;
|
||||
|
||||
/// A no_std-compatible version of bevy's `Parallel`.
|
||||
#[derive(Default)]
|
||||
pub struct PortableParallel<T: Send>(
|
||||
#[cfg(feature = "std")] bevy_utils::Parallel<T>,
|
||||
#[cfg(not(feature = "std"))] bevy_platform_support::sync::RwLock<Option<T>>,
|
||||
);
|
||||
|
||||
/// A scope guard of a `Parallel`, when this struct is dropped ,the value will writeback to its `Parallel`
|
||||
impl<T: Send> PortableParallel<T> {
|
||||
/// Gets a mutable iterator over all of the per-thread queues.
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = impl DerefMut<Target = T> + '_> {
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
self.0.iter_mut()
|
||||
}
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
self.0.get_mut().unwrap().iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears all of the stored thread local values.
|
||||
pub fn clear(&mut self) {
|
||||
#[cfg(feature = "std")]
|
||||
self.0.clear();
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
*self.0.write().unwrap() = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Default + Send> PortableParallel<T> {
|
||||
/// Retrieves the thread-local value for the current thread and runs `f` on it.
|
||||
///
|
||||
/// If there is no thread-local value, it will be initialized to its default.
|
||||
pub fn scope<R>(&self, f: impl FnOnce(&mut T) -> R) -> R {
|
||||
#[cfg(feature = "std")]
|
||||
let ret = self.0.scope(f);
|
||||
#[cfg(not(feature = "std"))]
|
||||
let ret = {
|
||||
let mut cell = self.0.write().unwrap();
|
||||
let value = cell.get_or_insert_default();
|
||||
f(value)
|
||||
};
|
||||
ret
|
||||
}
|
||||
|
||||
/// Mutably borrows the thread-local value.
|
||||
///
|
||||
/// If there is no thread-local value, it will be initialized to it's default.
|
||||
pub fn borrow_local_mut(&self) -> impl DerefMut<Target = T> + '_ {
|
||||
#[cfg(feature = "std")]
|
||||
let ret = self.0.borrow_local_mut();
|
||||
#[cfg(not(feature = "std"))]
|
||||
let ret = {
|
||||
if self.0.read().unwrap().is_none() {
|
||||
*self.0.write().unwrap() = Some(Default::default());
|
||||
}
|
||||
let opt = self.0.write().unwrap();
|
||||
no_std_deref::UncheckedDerefMutWrapper(opt)
|
||||
};
|
||||
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
/// Needed until Parallel is portable. This assumes the value is `Some`.
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod no_std_deref {
|
||||
use bevy_platform_support::sync::RwLockWriteGuard;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
pub struct UncheckedDerefMutWrapper<'a, T>(pub(super) RwLockWriteGuard<'a, Option<T>>);
|
||||
|
||||
impl<T> Deref for UncheckedDerefMutWrapper<'_, T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
impl<T> DerefMut for UncheckedDerefMutWrapper<'_, T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.0.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, I> PortableParallel<I>
|
||||
where
|
||||
I: IntoIterator<Item = T> + Default + Send + 'static,
|
||||
{
|
||||
/// Drains all enqueued items from all threads and returns an iterator over them.
|
||||
///
|
||||
/// Unlike [`Vec::drain`], this will piecemeal remove chunks of the data stored.
|
||||
/// If iteration is terminated part way, the rest of the enqueued items in the same
|
||||
/// chunk will be dropped, and the rest of the undrained elements will remain.
|
||||
///
|
||||
/// The ordering is not guaranteed.
|
||||
pub fn drain(&mut self) -> impl Iterator<Item = T> + '_ {
|
||||
#[cfg(feature = "std")]
|
||||
let ret = self.0.drain();
|
||||
#[cfg(not(feature = "std"))]
|
||||
let ret = self.0.get_mut().unwrap().take().into_iter().flatten();
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + 'static> PortableParallel<Vec<T>> {
|
||||
/// Collect all enqueued items from all threads and appends them to the end of a
|
||||
/// single Vec.
|
||||
///
|
||||
/// The ordering is not guaranteed.
|
||||
pub fn drain_into(&mut self, out: &mut Vec<T>) {
|
||||
#[cfg(feature = "std")]
|
||||
self.0.drain_into(out);
|
||||
#[cfg(not(feature = "std"))]
|
||||
out.extend(self.drain());
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
//! Timing statistics for transform propagation
|
||||
|
||||
use std::{collections::VecDeque, iter::Sum, ops::Div, time::Duration};
|
||||
use alloc::collections::VecDeque;
|
||||
use core::{iter::Sum, ops::Div, time::Duration};
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy_app::prelude::*;
|
||||
@ -12,7 +13,7 @@ use bevy_transform::TransformSystem;
|
||||
pub struct TimingStatsPlugin;
|
||||
|
||||
impl Plugin for TimingStatsPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.init_resource::<PropagationStats>()
|
||||
.register_type::<PropagationStats>()
|
||||
.init_resource::<GridHashStats>()
|
||||
@ -108,7 +109,7 @@ impl PropagationStats {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Sum<&'a PropagationStats> for PropagationStats {
|
||||
impl<'a> Sum<&'a PropagationStats> for PropagationStats {
|
||||
fn sum<I: Iterator<Item = &'a PropagationStats>>(iter: I) -> Self {
|
||||
iter.fold(PropagationStats::default(), |mut acc, e| {
|
||||
acc.grid_recentering += e.grid_recentering;
|
||||
@ -122,7 +123,7 @@ impl<'a> std::iter::Sum<&'a PropagationStats> for PropagationStats {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Div<u32> for PropagationStats {
|
||||
impl Div<u32> for PropagationStats {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: u32) -> Self::Output {
|
||||
@ -178,7 +179,7 @@ impl GridHashStats {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::Sum<&'a GridHashStats> for GridHashStats {
|
||||
impl<'a> Sum<&'a GridHashStats> for GridHashStats {
|
||||
fn sum<I: Iterator<Item = &'a GridHashStats>>(iter: I) -> Self {
|
||||
iter.fold(GridHashStats::default(), |mut acc, e| {
|
||||
acc.hash_update_duration += e.hash_update_duration;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
//! Tools for validating high-precision transform hierarchies
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_hierarchy::prelude::*;
|
||||
use bevy_platform_support::{
|
||||
collections::{HashMap, HashSet},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_transform::prelude::*;
|
||||
use bevy_utils::{HashMap, HashSet};
|
||||
|
||||
use crate::{grid::Grid, BigSpace, FloatingOrigin, GridCell};
|
||||
|
||||
@ -16,7 +18,7 @@ struct ValidationStackEntry {
|
||||
struct ValidatorCaches {
|
||||
query_state_cache: HashMap<&'static str, QueryState<(Entity, Option<&'static Children>)>>,
|
||||
validator_cache: HashMap<&'static str, Vec<Box<dyn ValidHierarchyNode>>>,
|
||||
root_query: Option<QueryState<Entity, Without<Parent>>>,
|
||||
root_query: Option<QueryState<Entity, Without<ChildOf>>>,
|
||||
stack: Vec<ValidationStackEntry>,
|
||||
/// Only report errors for an entity one time.
|
||||
error_entities: HashSet<Entity>,
|
||||
@ -29,7 +31,7 @@ pub fn validate_hierarchy<V: 'static + ValidHierarchyNode + Default>(world: &mut
|
||||
|
||||
let root_entities = caches
|
||||
.root_query
|
||||
.get_or_insert(world.query_filtered::<Entity, Without<Parent>>())
|
||||
.get_or_insert(world.query_filtered::<Entity, Without<ChildOf>>())
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
@ -89,11 +91,15 @@ pub fn validate_hierarchy<V: 'static + ValidHierarchyNode + Default>(world: &mut
|
||||
});
|
||||
|
||||
let mut inspect = String::new();
|
||||
world.inspect_entity(*entity).for_each(|info| {
|
||||
inspect.push_str(" - ");
|
||||
inspect.push_str(info.name());
|
||||
inspect.push('\n');
|
||||
});
|
||||
world
|
||||
.inspect_entity(*entity)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.for_each(|info| {
|
||||
inspect.push_str(" - ");
|
||||
inspect.push_str(info.name());
|
||||
inspect.push('\n');
|
||||
});
|
||||
|
||||
tracing::error!("
|
||||
-------------------------------------------
|
||||
@ -134,12 +140,13 @@ pub trait ValidHierarchyNode: sealed::CloneHierarchy + Send + Sync {
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>>;
|
||||
/// A unique identifier of this type
|
||||
fn name(&self) -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
core::any::type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
use super::ValidHierarchyNode;
|
||||
use bevy_platform_support::prelude::*;
|
||||
|
||||
pub trait CloneHierarchy {
|
||||
fn clone_box(&self) -> Box<dyn ValidHierarchyNode>;
|
||||
@ -219,7 +226,7 @@ impl ValidHierarchyNode for RootFrame {
|
||||
.with::<GlobalTransform>()
|
||||
.without::<GridCell>()
|
||||
.without::<Transform>()
|
||||
.without::<Parent>()
|
||||
.without::<ChildOf>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
@ -248,7 +255,7 @@ impl ValidHierarchyNode for RootSpatialLowPrecision {
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid>()
|
||||
.without::<Parent>()
|
||||
.without::<ChildOf>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
@ -274,7 +281,7 @@ impl ValidHierarchyNode for ChildFrame {
|
||||
.with::<GridCell>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.with::<ChildOf>()
|
||||
.without::<BigSpace>();
|
||||
}
|
||||
|
||||
@ -300,7 +307,7 @@ impl ValidHierarchyNode for ChildRootSpatialLowPrecision {
|
||||
query
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.with::<ChildOf>()
|
||||
.with::<crate::grid::propagation::LowPrecisionRoot>()
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
@ -328,7 +335,7 @@ impl ValidHierarchyNode for ChildSpatialLowPrecision {
|
||||
query
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.with::<ChildOf>()
|
||||
.without::<GridCell>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid>()
|
||||
@ -356,7 +363,7 @@ impl ValidHierarchyNode for ChildSpatialHighPrecision {
|
||||
.with::<GridCell>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.with::<ChildOf>()
|
||||
.without::<BigSpace>()
|
||||
.without::<Grid>();
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ pub struct GridTransformOwned {
|
||||
pub cell: GridCell,
|
||||
}
|
||||
|
||||
impl std::ops::Sub for GridTransformOwned {
|
||||
impl core::ops::Sub for GridTransformOwned {
|
||||
type Output = Self;
|
||||
|
||||
/// Compute a new transform that maps from `source` to `self`.
|
||||
@ -81,7 +81,7 @@ impl std::ops::Sub for GridTransformOwned {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Add for GridTransformOwned {
|
||||
impl core::ops::Add for GridTransformOwned {
|
||||
type Output = Self;
|
||||
|
||||
/// Compute a new transform that shifts, scales and rotates `self` by `diff`.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user