Yeet Precision Generics (#40)

This commit is contained in:
Aevyrie 2025-03-04 21:41:31 -08:00 committed by GitHub
parent 2a1cb54e63
commit f6d8bf0649
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 624 additions and 947 deletions

View File

@ -29,7 +29,7 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2.7.0
- run: cargo check --all-features --all-targets
- run: cargo check --features=all --all-targets
check-no-defaults:
runs-on: ubuntu-latest
@ -48,7 +48,7 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2.7.0
- run: rustup component add clippy
- run: cargo clippy --all-features --all-targets -- -D warnings
- run: cargo clippy --features=all --all-targets -- -D warnings
doc:
runs-on: ubuntu-latest
@ -57,7 +57,7 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2.7.0
- run: cargo doc --all-features --no-deps
- run: cargo doc --features=all --no-deps
env:
RUSTDOCFLAGS: -D warnings
@ -68,7 +68,7 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2.7.0
- run: cargo test --all-features
- run: cargo test --features=all
doctest:
runs-on: ubuntu-latest
@ -77,4 +77,4 @@ jobs:
- uses: actions/checkout@v3
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2.7.0
- run: cargo test --all-features --doc
- run: cargo test --features=all --doc

View File

@ -10,8 +10,14 @@ documentation = "https://docs.rs/crate/big_space/latest"
[features]
default = []
all = ["debug", "camera"] # Can't use all-features, integer type features are incompatible.
debug = ["bevy_gizmos", "bevy_color"]
camera = ["bevy_render", "bevy_time", "bevy_input"]
i8 = []
i16 = []
i32 = []
i64 = []
i128 = []
[dependencies]
tracing = "0.1" # Less deps than pulling in bevy_log
@ -69,8 +75,10 @@ doc-scrape-examples = false
[[example]]
name = "demo"
path = "examples/demo.rs"
required-features = ["i128"]
doc-scrape-examples = false
[[example]]
name = "error_child"
path = "examples/error_child.rs"
@ -84,6 +92,7 @@ doc-scrape-examples = false
[[example]]
name = "infinite"
path = "examples/infinite.rs"
required-features = ["i8"]
doc-scrape-examples = false
[[example]]

View File

@ -38,10 +38,10 @@ fn deep_hierarchy(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("deep_hierarchy {N_SPAWN}"));
fn setup(mut commands: Commands) {
commands.spawn_big_space::<i32>(Grid::new(10000.0, 0.0), |root| {
commands.spawn_big_space(Grid::new(10000.0, 0.0), |root| {
let mut parent = root.spawn_grid_default(()).id();
for _ in 0..N_SPAWN {
let child = root.commands().spawn(BigGridBundle::<i32>::default()).id();
let child = root.commands().spawn(BigGridBundle::default()).id();
root.commands().entity(parent).add_child(child);
parent = child;
}
@ -58,8 +58,8 @@ fn deep_hierarchy(c: &mut Criterion) {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
GridHashPlugin::<i32>::default(),
BigSpacePlugin::<i32>::default(),
GridHashPlugin::<()>::default(),
BigSpacePlugin::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, translate)
@ -80,7 +80,7 @@ fn wide_hierarchy(c: &mut Criterion) {
let mut group = c.benchmark_group(format!("wide_hierarchy {N_SPAWN}"));
fn setup(mut commands: Commands) {
commands.spawn_big_space::<i32>(Grid::new(10000.0, 0.0), |root| {
commands.spawn_big_space(Grid::new(10000.0, 0.0), |root| {
for _ in 0..N_SPAWN {
root.spawn_spatial(());
}
@ -97,8 +97,8 @@ fn wide_hierarchy(c: &mut Criterion) {
let mut app = App::new();
app.add_plugins((
MinimalPlugins,
GridHashPlugin::<i32>::default(),
BigSpacePlugin::<i32>::default(),
GridHashPlugin::<()>::default(),
BigSpacePlugin::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, translate)
@ -115,20 +115,20 @@ fn wide_hierarchy(c: &mut Criterion) {
fn spatial_hashing(c: &mut Criterion) {
let mut group = c.benchmark_group("spatial_hashing");
const HALF_WIDTH: i32 = 100;
const HALF_WIDTH: i64 = 100;
/// Total number of entities to spawn
const N_SPAWN: usize = 10_000;
/// Number of entities that move into a different cell each update
const N_MOVE: usize = 1_000;
fn setup(mut commands: Commands) {
commands.spawn_big_space::<i32>(Grid::new(1.0, 0.0), |root| {
commands.spawn_big_space(Grid::new(1.0, 0.0), |root| {
let rng = Rng::with_seed(342525);
let values: Vec<_> = repeat_with(|| {
[
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
]
})
.take(N_SPAWN)
@ -140,14 +140,14 @@ fn spatial_hashing(c: &mut Criterion) {
});
}
fn translate(mut cells: Query<&mut GridCell<i32>>) {
fn translate(mut cells: Query<&mut GridCell>) {
cells.iter_mut().take(N_MOVE).for_each(|mut cell| {
*cell += GridCell::ONE;
})
}
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Startup, setup)
.update();
@ -164,7 +164,7 @@ fn spatial_hashing(c: &mut Criterion) {
});
});
let map = app.world().resource::<GridHashMap<i32>>();
let map = app.world().resource::<GridHashMap>();
let first = map
.all_entries()
.find(|(_, entry)| !entry.entities.is_empty())
@ -185,8 +185,8 @@ fn spatial_hashing(c: &mut Criterion) {
});
});
// let parent = app .world_mut() .query::<&GridHash<i32>>() .get(app.world(), ent)
// .unwrap(); let map = app.world().resource::<GridHashMap<i32>>(); let entry =
// let parent = app .world_mut() .query::<&GridHash>() .get(app.world(), ent)
// .unwrap(); let map = app.world().resource::<GridHashMap>(); let entry =
// map.get(parent).unwrap();
// group.bench_function("Neighbors radius: 4", |b| {
@ -204,8 +204,8 @@ fn spatial_hashing(c: &mut Criterion) {
// });
// });
fn setup_uniform<const HALF_EXTENT: i32>(mut commands: Commands) {
commands.spawn_big_space::<i32>(Grid::new(1.0, 0.0), |root| {
fn setup_uniform<const HALF_EXTENT: i64>(mut commands: Commands) {
commands.spawn_big_space(Grid::new(1.0, 0.0), |root| {
for x in HALF_EXTENT.neg()..HALF_EXTENT {
for y in HALF_EXTENT.neg()..HALF_EXTENT {
for z in HALF_EXTENT.neg()..HALF_EXTENT {
@ -219,7 +219,7 @@ fn spatial_hashing(c: &mut Criterion) {
// Uniform Grid Population 1_000
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Startup, setup_uniform::<5>)
.update();
@ -227,7 +227,7 @@ fn spatial_hashing(c: &mut Criterion) {
.world_mut()
.query_filtered::<Entity, With<BigSpace>>()
.single(app.world());
let spatial_map = app.world().resource::<GridHashMap<i32>>();
let spatial_map = app.world().resource::<GridHashMap>();
let hash = GridHash::__new_manual(parent, &GridCell { x: 0, y: 0, z: 0 });
let entry = spatial_map.get(&hash).unwrap();
@ -247,7 +247,7 @@ fn spatial_hashing(c: &mut Criterion) {
// Uniform Grid Population 1_000_000
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Startup, setup_uniform::<50>)
.update();
@ -255,7 +255,7 @@ fn spatial_hashing(c: &mut Criterion) {
.world_mut()
.query_filtered::<Entity, With<BigSpace>>()
.single(app.world());
let spatial_map = app.world().resource::<GridHashMap<i32>>();
let spatial_map = app.world().resource::<GridHashMap>();
let hash = GridHash::__new_manual(parent, &GridCell { x: 0, y: 0, z: 0 });
let entry = spatial_map.get(&hash).unwrap();
@ -279,7 +279,7 @@ fn hash_filtering(c: &mut Criterion) {
const N_ENTITIES: usize = 100_000;
const N_PLAYERS: usize = 100;
const N_MOVE: usize = 1_000;
const HALF_WIDTH: i32 = 100;
const HALF_WIDTH: i64 = 100;
#[derive(Component)]
struct Player;
@ -288,15 +288,15 @@ fn hash_filtering(c: &mut Criterion) {
let rng = Rng::with_seed(342525);
let values: Vec<_> = repeat_with(|| {
[
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i32(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
rng.i64(-HALF_WIDTH..=HALF_WIDTH),
]
})
.take(N_ENTITIES)
.collect();
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
for (i, pos) in values.iter().enumerate() {
let mut cmd = root.spawn_spatial(GridCell::new(pos[0], pos[1], pos[2]));
if i < N_PLAYERS {
@ -306,7 +306,7 @@ fn hash_filtering(c: &mut Criterion) {
});
}
fn translate(mut cells: Query<&mut GridCell<i32>>) {
fn translate(mut cells: Query<&mut GridCell>) {
cells.iter_mut().take(N_MOVE).for_each(|mut cell| {
*cell += IVec3::ONE;
});
@ -317,7 +317,7 @@ fn hash_filtering(c: &mut Criterion) {
.add_systems(Update, translate)
.update();
app.update();
app.add_plugins((GridHashPlugin::<i32>::default(),));
app.add_plugins((GridHashPlugin::<()>::default(),));
group.bench_function("No Filter Plugin", |b| {
b.iter(|| {
black_box(app.update());
@ -329,7 +329,7 @@ fn hash_filtering(c: &mut Criterion) {
.add_systems(Update, translate)
.update();
app.update();
app.add_plugins((GridHashPlugin::<i32, With<Player>>::default(),));
app.add_plugins((GridHashPlugin::<With<Player>>::default(),));
group.bench_function("With Player Plugin", |b| {
b.iter(|| {
black_box(app.update());
@ -341,7 +341,7 @@ fn hash_filtering(c: &mut Criterion) {
.add_systems(Update, translate)
.update();
app.update();
app.add_plugins((GridHashPlugin::<i32, Without<Player>>::default(),));
app.add_plugins((GridHashPlugin::<Without<Player>>::default(),));
group.bench_function("Without Player Plugin", |b| {
b.iter(|| {
black_box(app.update());
@ -353,9 +353,9 @@ fn hash_filtering(c: &mut Criterion) {
.add_systems(Update, translate)
.update();
app.update();
app.add_plugins((GridHashPlugin::<i32>::default(),))
.add_plugins((GridHashPlugin::<i32, With<Player>>::default(),))
.add_plugins((GridHashPlugin::<i32, Without<Player>>::default(),));
app.add_plugins((GridHashPlugin::<()>::default(),))
.add_plugins((GridHashPlugin::<With<Player>>::default(),))
.add_plugins((GridHashPlugin::<Without<Player>>::default(),));
group.bench_function("All Plugins", |b| {
b.iter(|| {
black_box(app.update());
@ -383,7 +383,7 @@ fn vs_bevy(c: &mut Criterion) {
}
fn setup_big(mut commands: Commands) {
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
for _ in 0..N_ENTITIES {
root.spawn_spatial(());
}
@ -403,7 +403,7 @@ fn vs_bevy(c: &mut Criterion) {
});
let mut app = App::new();
app.add_plugins((MinimalPlugins, BigSpacePlugin::<i32>::default()))
app.add_plugins((MinimalPlugins, BigSpacePlugin::default()))
.add_systems(Startup, setup_big)
.update();
@ -432,7 +432,7 @@ fn vs_bevy(c: &mut Criterion) {
});
let mut app = App::new();
app.add_plugins((MinimalPlugins, BigSpacePlugin::<i32>::default()))
app.add_plugins((MinimalPlugins, BigSpacePlugin::default()))
.add_systems(Startup, setup_big)
.add_systems(Update, translate)
.update();

View File

@ -7,8 +7,8 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i64>::default(),
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
BigSpacePlugin::default(),
big_space::debug::FloatingOriginDebugPlugin::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, (movement, rotation))
@ -64,7 +64,7 @@ fn setup(
..default()
});
commands.spawn_big_space::<i64>(Grid::new(1.0, 0.01), |root| {
commands.spawn_big_space(Grid::new(1.0, 0.01), |root| {
root.spawn_spatial((
Mesh3d(mesh_handle.clone()),
MeshMaterial3d(matl_handle.clone()),

View File

@ -14,9 +14,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i128>::default(),
FloatingOriginDebugPlugin::<i128>::default(),
big_space::camera::CameraControllerPlugin::<i128>::default(),
BigSpacePlugin::default(),
FloatingOriginDebugPlugin::default(),
big_space::camera::CameraControllerPlugin::default(),
))
.insert_resource(ClearColor(Color::BLACK))
.add_systems(Startup, (setup, ui_setup))
@ -33,7 +33,7 @@ fn setup(
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn_big_space_default::<i128>(|root| {
commands.spawn_big_space_default(|root| {
root.spawn_spatial((
Camera3d::default(),
Projection::Perspective(PerspectiveProjection {
@ -150,9 +150,9 @@ fn ui_text_system(
(With<BigSpaceDebugText>, Without<FunFactText>),
>,
mut fun_text: Query<&mut Text, (With<FunFactText>, Without<BigSpaceDebugText>)>,
grids: Grids<i128>,
grids: Grids,
time: Res<Time>,
origin: Query<(Entity, GridTransformReadOnly<i128>), With<FloatingOrigin>>,
origin: Query<(Entity, GridTransformReadOnly), With<FloatingOrigin>>,
camera: Query<&CameraController>,
objects: Query<&Transform, With<Mesh3d>>,
) {

View File

@ -1,8 +1,8 @@
//! This example demonstrates what floating point error in rendering looks like. You can press
//! spacebar to smoothly switch between enabling and disabling the floating origin.
//! space bar to smoothly switch between enabling and disabling the floating origin.
//!
//! Instead of disabling the plugin outright, this example simply moves the floating origin
//! independently from the camera, which is equivalent to what would happen when moving far from the
//! independently of the camera, which is equivalent to what would happen when moving far from the
//! origin when not using this plugin.
use bevy::prelude::*;
@ -10,7 +10,7 @@ use big_space::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, BigSpacePlugin::<i128>::default()))
.add_plugins((DefaultPlugins, BigSpacePlugin::default()))
.add_systems(Startup, (setup_scene, setup_ui))
.add_systems(Update, (rotator_system, toggle_plugin))
.run();
@ -22,18 +22,19 @@ fn main() {
/// floating point error when we disable this plugin.
///
/// This plugin can function much further from the origin without any issues. Try setting this to:
/// 10_000_000_000_000_000_000_000_000_000_000_000_000
const DISTANCE: i128 = 2_000_000;
/// 10_000_000_000_000_000 with the default i64 feature, or
/// 10_000_000_000_000_000_000_000_000_000_000_000_000 with the i128 feature.
const DISTANCE: GridPrecision = 2_000_000;
/// Move the floating origin back to the "true" origin when the user presses the spacebar to emulate
/// disabling the plugin. Normally you would make your active camera the floating origin to avoid
/// this issue.
fn toggle_plugin(
input: Res<ButtonInput<KeyCode>>,
grids: Grids<i128>,
grids: Grids,
mut text: Query<&mut Text>,
mut disabled: Local<bool>,
mut floating_origin: Query<(Entity, &mut GridCell<i128>), With<FloatingOrigin>>,
mut floating_origin: Query<(Entity, &mut GridCell), With<FloatingOrigin>>,
) {
if input.just_pressed(KeyCode::Space) {
*disabled = !*disabled;
@ -42,7 +43,7 @@ fn toggle_plugin(
let this_grid = grids.parent_grid(floating_origin.single().0).unwrap();
let mut origin_cell = floating_origin.single_mut().1;
let index_max = DISTANCE / this_grid.cell_edge_length() as i128;
let index_max = DISTANCE / this_grid.cell_edge_length() as GridPrecision;
let increment = index_max / 100;
let msg = if *disabled {
@ -64,9 +65,10 @@ fn toggle_plugin(
"Floating Origin Enabled"
};
let dist = index_max.saturating_sub(origin_cell.x) * this_grid.cell_edge_length() as i128;
let dist =
index_max.saturating_sub(origin_cell.x) * this_grid.cell_edge_length() as GridPrecision;
let thousands = |num: i128| {
let thousands = |num: GridPrecision| {
num.to_string()
.as_bytes()
.rchunks(3)
@ -107,9 +109,9 @@ fn setup_ui(mut commands: Commands) {
}
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn_big_space_default::<i128>(|root| {
let d = DISTANCE / root.grid().cell_edge_length() as i128;
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
commands.spawn_big_space_default(|root| {
let d = DISTANCE / root.grid().cell_edge_length() as GridPrecision;
let distant_grid_cell = GridCell::new(d, d, d);
// Normally, we would put the floating origin on the camera. However in this example, we
// want to show what happens as the camera is far from the origin, to emulate what

View File

@ -7,9 +7,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i64>::default(),
big_space::camera::CameraControllerPlugin::<i64>::default(),
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
BigSpacePlugin::default(),
big_space::camera::CameraControllerPlugin::default(),
big_space::debug::FloatingOriginDebugPlugin::default(),
))
.add_systems(Startup, setup_scene)
.run();
@ -37,7 +37,7 @@ fn setup_scene(
) {
let mesh_handle = meshes.add(Sphere::new(SPHERE_RADIUS).mesh());
commands.spawn_big_space::<i64>(Grid::new(SPHERE_RADIUS * 100.0, 0.0), |root_grid| {
commands.spawn_big_space(Grid::new(SPHERE_RADIUS * 100.0, 0.0), |root_grid| {
root_grid.spawn_spatial((
Mesh3d(mesh_handle.clone()),
MeshMaterial3d(materials.add(Color::from(palettes::css::BLUE))),

View File

@ -7,9 +7,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i8>::default(),
FloatingOriginDebugPlugin::<i8>::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::<i8>::default(), // Compatible controller
BigSpacePlugin::default(),
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
))
.add_systems(Startup, setup_scene)
.run();
@ -23,7 +23,7 @@ fn setup_scene(
let sphere = Mesh3d(meshes.add(Sphere::default()));
let matl = MeshMaterial3d(materials.add(Color::WHITE));
commands.spawn_big_space::<i8>(Grid::default(), |root_grid| {
commands.spawn_big_space(Grid::default(), |root_grid| {
let width = || -8..8;
for (x, y, z) in width()
.flat_map(|x| width().map(move |y| (x, y)))
@ -32,7 +32,7 @@ fn setup_scene(
root_grid.spawn_spatial((
sphere.clone(),
matl.clone(),
GridCell::<i8> {
GridCell {
x: x * 16,
y: y * 16,
z: z * 16,

View File

@ -11,9 +11,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i64>::default(),
FloatingOriginDebugPlugin::<i64>::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::<i64>::default(), // Compatible controller
BigSpacePlugin::default(),
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
))
.add_systems(Startup, setup_scene)
.run();
@ -32,7 +32,7 @@ fn setup_scene(
// A world can have multiple independent BigSpaces, with their own floating origins. This can
// come in handy if you want to have two cameras very far from each other, rendering at the same
// time like split screen, or portals.
commands.spawn_big_space_default::<i64>(|root_grid| {
commands.spawn_big_space_default(|root_grid| {
// Because BIG_DISTANCE is so large, we want to avoid using bevy's f32 transforms alone and
// experience rounding errors. Instead, we use this helper to convert an f64 position into a
// grid cell and f32 offset.

View File

@ -8,9 +8,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i64>::default(),
big_space::camera::CameraControllerPlugin::<i64>::default(),
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
BigSpacePlugin::default(),
big_space::camera::CameraControllerPlugin::default(),
big_space::debug::FloatingOriginDebugPlugin::default(),
bevy_hanabi::HanabiPlugin, // TODO fix once hanabi updates to bevy 0.15
))
.add_systems(Startup, setup_scene)
@ -28,7 +28,7 @@ fn setup_scene(
mut effects: ResMut<Assets<bevy_hanabi::EffectAsset>>,
) {
let effect = effects.add(particle_effect());
commands.spawn_big_space_default::<i64>(|root| {
commands.spawn_big_space_default(|root| {
root.spawn_spatial(DirectionalLight::default());
root.spawn_spatial((
Mesh3d(meshes.add(Sphere::default())),

View File

@ -16,8 +16,8 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i64>::new(true),
big_space::camera::CameraControllerPlugin::<i64>::default(),
BigSpacePlugin::new(true),
big_space::camera::CameraControllerPlugin::default(),
))
.insert_resource(ClearColor(Color::BLACK))
.insert_resource(AmbientLight {
@ -35,7 +35,7 @@ fn main() {
cursor_grab_system,
springy_ship
.after(big_space::camera::default_camera_inputs)
.before(big_space::camera::camera_controller::<i64>),
.before(big_space::camera::camera_controller),
),
)
.register_type::<Sun>()
@ -138,7 +138,7 @@ fn spawn_solar_system(
.build(),
));
commands.spawn_big_space_default::<i64>(|root_grid| {
commands.spawn_big_space_default(|root_grid| {
root_grid.with_grid_default(|sun| {
sun.insert((Sun, Name::new("Sun")));
sun.spawn_spatial((

View File

@ -17,9 +17,9 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i128>::default(),
FloatingOriginDebugPlugin::<i128>::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::<i128>::default(), // Compatible controller
BigSpacePlugin::default(),
FloatingOriginDebugPlugin::default(), // Draws cell AABBs and grids
big_space::camera::CameraControllerPlugin::default(), // Compatible controller
))
.add_systems(Startup, setup_scene)
.add_systems(Update, (bounce_atoms, toggle_cam_pos))
@ -39,7 +39,7 @@ fn setup_scene(
// Because we are working on such small scales, we need to make the grid very small. This
// ensures that the maximum floating point error is also very small, because no entities can
// ever get farther than `SMALL_SCALE * 500` units from the origin.
let small_grid = Grid::<i128>::new(PROTON_DIA * 5_000.0, 0.0);
let small_grid = Grid::new(PROTON_DIA * 5_000.0, 0.0);
commands.spawn_big_space(small_grid, |root_grid| {
root_grid.spawn_spatial(DirectionalLight::default());
@ -99,9 +99,9 @@ fn bounce_atoms(mut atoms: Query<&mut Transform, With<Proton>>, time: Res<Time>)
}
fn toggle_cam_pos(
mut cam: Query<&mut GridCell<i128>, With<Camera>>,
mut cam: Query<&mut GridCell, With<Camera>>,
mut toggle: Local<bool>,
grid: Query<&Grid<i128>>,
grid: Query<&Grid>,
keyboard: Res<ButtonInput<KeyCode>>,
protons: Query<&GlobalTransform, With<Proton>>,
) {

View File

@ -22,10 +22,10 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i32>::default(),
GridHashPlugin::<i32>::default(),
GridPartitionPlugin::<i32>::default(),
big_space::camera::CameraControllerPlugin::<i32>::default(),
BigSpacePlugin::default(),
GridHashPlugin::<()>::default(),
GridPartitionPlugin::<()>::default(),
big_space::camera::CameraControllerPlugin::default(),
))
.add_systems(Startup, (spawn, setup_ui))
.add_systems(
@ -86,9 +86,9 @@ impl FromWorld for MaterialPresets {
fn draw_partitions(
mut gizmos: Gizmos,
partitions: Res<GridPartitionMap<i32>>,
grids: Query<(&GlobalTransform, &Grid<i32>)>,
camera: Query<&GridHash<i32>, With<Camera>>,
partitions: Res<GridPartitionMap>,
grids: Query<(&GlobalTransform, &Grid)>,
camera: Query<&GridHash, With<Camera>>,
) {
for (id, p) in partitions.iter().take(10_000) {
let Ok((transform, grid)) = grids.get(p.grid()) else {
@ -105,7 +105,7 @@ fn draw_partitions(
.filter(|hash| *hash != camera.single())
.take(1_000)
.for_each(|h| {
let center = [h.cell().x, h.cell().y, h.cell().z];
let center = [h.cell().x as i32, h.cell().y as i32, h.cell().z as i32];
let local_trans = Transform::from_translation(IVec3::from(center).as_vec3() * l)
.with_scale(Vec3::splat(l));
gizmos.cuboid(
@ -114,8 +114,8 @@ fn draw_partitions(
);
});
let min = IVec3::from([p.min().x, p.min().y, p.min().z]).as_vec3() * l;
let max = IVec3::from([p.max().x, p.max().y, p.max().z]).as_vec3() * l;
let min = IVec3::from([p.min().x as i32, p.min().y as i32, p.min().z as i32]).as_vec3() * l;
let max = IVec3::from([p.max().x as i32, p.max().y as i32, p.max().z as i32]).as_vec3() * l;
let size = max - min;
let center = min + (size) * 0.5;
@ -133,15 +133,15 @@ fn draw_partitions(
fn move_player(
time: Res<Time>,
mut _gizmos: Gizmos,
mut player: Query<(&mut Transform, &mut GridCell<i32>, &Parent, &GridHash<i32>), With<Player>>,
mut player: Query<(&mut Transform, &mut GridCell, &Parent, &GridHash), With<Player>>,
mut non_player: Query<
(&mut Transform, &mut GridCell<i32>, &Parent),
(&mut Transform, &mut GridCell, &Parent),
(Without<Player>, With<NonPlayer>),
>,
mut materials: Query<&mut MeshMaterial3d<StandardMaterial>, Without<Player>>,
mut neighbors: Local<Vec<Entity>>,
grids: Query<&Grid<i32>>,
hash_grid: Res<GridHashMap<i32>>,
grids: Query<&Grid>,
hash_grid: Res<GridHashMap>,
material_presets: Res<MaterialPresets>,
mut text: Query<&mut Text>,
hash_stats: Res<big_space::timing::SmoothedStat<big_space::timing::GridHashStats>>,
@ -257,7 +257,7 @@ Total: {: >22.1?}",
}
fn spawn(mut commands: Commands) {
commands.spawn_big_space::<i32>(Grid::new(CELL_WIDTH, 0.0), |root| {
commands.spawn_big_space(Grid::new(CELL_WIDTH, 0.0), |root| {
root.spawn_spatial((
FloatingOrigin,
Camera3d::default(),
@ -273,7 +273,7 @@ fn spawn(mut commands: Commands) {
.with_speed(15.0),
Fxaa::default(),
Bloom::default(),
GridCell::new(0, 0, HALF_WIDTH as i32 / 2),
GridCell::new(0, 0, HALF_WIDTH as GridPrecision / 2),
))
.with_children(|b| {
b.spawn(DirectionalLight::default());
@ -287,7 +287,7 @@ fn spawn_spheres(
mut commands: Commands,
input: Res<ButtonInput<KeyCode>>,
material_presets: Res<MaterialPresets>,
mut grid: Query<(Entity, &Grid<i32>, &mut Children)>,
mut grid: Query<(Entity, &Grid, &mut Children)>,
non_players: Query<(), With<NonPlayer>>,
) {
let n_entities = non_players.iter().len().max(1);
@ -306,12 +306,12 @@ fn spawn_spheres(
let new_children = sample_noise(n_spawn, &Simplex::new(345612), &Rng::new())
.map(|value| {
let hash = GridHash::<i32>::__new_manual(entity, &GridCell::default());
let hash = GridHash::__new_manual(entity, &GridCell::default());
commands
.spawn((
Transform::from_xyz(value.x, value.y, value.z),
GlobalTransform::default(),
GridCell::<i32>::default(),
GridCell::default(),
FastGridHash::from(hash),
hash,
NonPlayer,

View File

@ -16,16 +16,16 @@ fn main() {
App::new()
.add_plugins((
DefaultPlugins,
BigSpacePlugin::<i32>::default(),
FloatingOriginDebugPlugin::<i32>::default(),
big_space::camera::CameraControllerPlugin::<i32>::default(),
BigSpacePlugin::default(),
FloatingOriginDebugPlugin::default(),
big_space::camera::CameraControllerPlugin::default(),
))
.add_systems(Startup, setup)
.add_systems(Update, set_camera_viewports)
.add_systems(
PostUpdate,
update_cameras
.after(big_space::camera::camera_controller::<i32>)
.after(big_space::camera::camera_controller)
.before(TransformSystem::TransformPropagate),
)
.run();
@ -55,7 +55,7 @@ fn setup(
));
// Big Space 1
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
root.spawn_spatial((
Camera3d::default(),
Transform::from_xyz(1_000_000.0 - 10.0, 100_005.0, 0.0)
@ -106,7 +106,7 @@ fn setup(
});
// Big Space 2
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
root.spawn_spatial((
Camera3d::default(),
Camera {
@ -162,9 +162,9 @@ fn setup(
#[allow(clippy::type_complexity)]
fn update_cameras(
left: Query<GridTransformReadOnly<i32>, With<LeftCamera>>,
left: Query<GridTransformReadOnly, With<LeftCamera>>,
mut left_rep: Query<
GridTransform<i32>,
GridTransform,
(
With<LeftCameraReplicated>,
Without<RightCameraReplicated>,
@ -172,9 +172,9 @@ fn update_cameras(
Without<RightCamera>,
),
>,
right: Query<GridTransformReadOnly<i32>, With<RightCamera>>,
right: Query<GridTransformReadOnly, With<RightCamera>>,
mut right_rep: Query<
GridTransform<i32>,
GridTransform,
(
With<RightCameraReplicated>,
Without<LeftCameraReplicated>,

View File

@ -8,7 +8,7 @@ use bevy_transform::prelude::*;
///
/// This is the floating origin equivalent of the `bevy` `SpatialBundle`.
#[derive(Bundle, Default)]
pub struct BigSpatialBundle<P: GridPrecision> {
pub struct BigSpatialBundle {
/// The visibility of the entity.
#[cfg(feature = "bevy_render")]
pub visibility: bevy_render::view::Visibility,
@ -23,7 +23,7 @@ pub struct BigSpatialBundle<P: GridPrecision> {
/// The global transform of the entity.
pub global_transform: GlobalTransform,
/// The grid position of the entity
pub cell: GridCell<P>,
pub cell: GridCell,
}
/// A `SpatialBundle` that also has a grid, allowing other high precision spatial bundles to be
@ -31,7 +31,7 @@ pub struct BigSpatialBundle<P: GridPrecision> {
///
/// This is the floating origin equivalent of the `bevy` `SpatialBundle`.
#[derive(Bundle, Default)]
pub struct BigGridBundle<P: GridPrecision> {
pub struct BigGridBundle {
/// The visibility of the entity.
#[cfg(feature = "bevy_render")]
pub visibility: bevy_render::view::Visibility,
@ -40,19 +40,19 @@ pub struct BigGridBundle<P: GridPrecision> {
/// The global transform of the entity for rendering, computed relative to the floating origin.
pub global_transform: GlobalTransform,
/// The grid position of the grid within its parent grid.
pub cell: GridCell<P>,
pub cell: GridCell,
/// The grid.
pub grid: Grid<P>,
pub grid: Grid,
}
/// The root of any [`BigSpace`] needs these components to function.
#[derive(Bundle, Default)]
pub struct BigSpaceRootBundle<P: GridPrecision> {
pub struct BigSpaceRootBundle {
/// The visibility of the entity.
#[cfg(feature = "bevy_render")]
pub visibility: bevy_render::view::Visibility,
/// The root grid
pub grid: Grid<P>,
pub grid: Grid,
/// The rendered position of the root grid relative to the floating origin.
pub global_transform: GlobalTransform,
/// Tracks the current floating origin.

View File

@ -1,7 +1,5 @@
//! Provides a camera controller compatible with the floating origin plugin.
use std::marker::PhantomData;
use crate::prelude::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
@ -19,17 +17,17 @@ use bevy_utils::HashSet;
/// Adds the `big_space` camera controller
#[derive(Default)]
pub struct CameraControllerPlugin<P: GridPrecision>(PhantomData<P>);
impl<P: GridPrecision> Plugin for CameraControllerPlugin<P> {
pub struct CameraControllerPlugin(());
impl Plugin for CameraControllerPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CameraInput>().add_systems(
PostUpdate,
(
default_camera_inputs
.before(camera_controller::<P>)
.before(camera_controller)
.run_if(|input: Res<CameraInput>| !input.defaults_disabled),
nearest_objects_in_grid::<P>.before(camera_controller::<P>),
camera_controller::<P>.before(TransformSystem::TransformPropagate),
nearest_objects_in_grid.before(camera_controller),
camera_controller.before(TransformSystem::TransformPropagate),
),
);
}
@ -211,7 +209,7 @@ pub fn default_camera_inputs(
}
/// Find the object nearest the camera, within the same grid as the camera.
pub fn nearest_objects_in_grid<P: GridPrecision>(
pub fn nearest_objects_in_grid(
objects: Query<(
Entity,
&Transform,
@ -260,16 +258,11 @@ pub fn nearest_objects_in_grid<P: GridPrecision>(
}
/// Uses [`CameraInput`] state to update the camera position.
pub fn camera_controller<P: GridPrecision>(
pub fn camera_controller(
time: Res<Time>,
grids: crate::grid::local_origin::Grids<P>,
grids: crate::grid::local_origin::Grids,
mut input: ResMut<CameraInput>,
mut camera: Query<(
Entity,
&mut GridCell<P>,
&mut Transform,
&mut CameraController,
)>,
mut camera: Query<(Entity, &mut GridCell, &mut Transform, &mut CameraController)>,
) {
for (camera, mut cell, mut transform, mut controller) in camera.iter_mut() {
let Some(grid) = grids.parent_grid(camera) else {

View File

@ -5,35 +5,23 @@ use bevy_ecs::prelude::*;
use bevy_hierarchy::prelude::*;
use bevy_transform::prelude::*;
use smallvec::SmallVec;
use std::marker::PhantomData;
/// Adds `big_space` commands to bevy's `Commands`.
pub trait BigSpaceCommands {
/// Spawn a root [`BigSpace`] [`Grid`].
fn spawn_big_space<P: GridPrecision>(
&mut self,
root_grid: Grid<P>,
child_builder: impl FnOnce(&mut GridCommands<P>),
);
fn spawn_big_space(&mut self, root_grid: Grid, child_builder: impl FnOnce(&mut GridCommands));
/// Spawn a root [`BigSpace`] with default [`Grid`] settings.
fn spawn_big_space_default<P: GridPrecision>(
&mut self,
child_builder: impl FnOnce(&mut GridCommands<P>),
);
fn spawn_big_space_default(&mut self, child_builder: impl FnOnce(&mut GridCommands));
/// Access the [`GridCommands`] of an entity by passing in the [`Entity`] and [`Grid`]. Note
/// that the value of `grid` will be inserted in this entity when the command is applied.
fn grid<P: GridPrecision>(&mut self, entity: Entity, grid: Grid<P>) -> GridCommands<P>;
fn grid(&mut self, entity: Entity, grid: Grid) -> GridCommands;
}
impl BigSpaceCommands for Commands<'_, '_> {
fn spawn_big_space<P: GridPrecision>(
&mut self,
grid: Grid<P>,
root_grid: impl FnOnce(&mut GridCommands<P>),
) {
let mut entity_commands = self.spawn(BigSpaceRootBundle::<P>::default());
fn spawn_big_space(&mut self, grid: Grid, root_grid: impl FnOnce(&mut GridCommands)) {
let mut entity_commands = self.spawn(BigSpaceRootBundle::default());
let mut cmd = GridCommands {
entity: entity_commands.id(),
commands: entity_commands.commands(),
@ -43,14 +31,11 @@ impl BigSpaceCommands for Commands<'_, '_> {
root_grid(&mut cmd);
}
fn spawn_big_space_default<P: GridPrecision>(
&mut self,
child_builder: impl FnOnce(&mut GridCommands<P>),
) {
fn spawn_big_space_default(&mut self, child_builder: impl FnOnce(&mut GridCommands)) {
self.spawn_big_space(Grid::default(), child_builder);
}
fn grid<P: GridPrecision>(&mut self, entity: Entity, grid: Grid<P>) -> GridCommands<P> {
fn grid(&mut self, entity: Entity, grid: Grid) -> GridCommands {
GridCommands {
entity,
commands: self.reborrow(),
@ -61,16 +46,16 @@ impl BigSpaceCommands for Commands<'_, '_> {
}
/// Build [`big_space`](crate) hierarchies more easily, with access to grids.
pub struct GridCommands<'a, P: GridPrecision> {
pub struct GridCommands<'a> {
entity: Entity,
commands: Commands<'a, 'a>,
grid: Grid<P>,
grid: Grid,
children: SmallVec<[Entity; 8]>,
}
impl<'a, P: GridPrecision> GridCommands<'a, P> {
impl<'a> GridCommands<'a> {
/// Get a reference to the current grid.
pub fn grid(&mut self) -> &Grid<P> {
pub fn grid(&mut self) -> &Grid {
&self.grid
}
@ -82,26 +67,25 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
/// Spawn an entity in this grid.
#[inline]
pub fn spawn(&mut self, bundle: impl Bundle) -> SpatialEntityCommands<P> {
pub fn spawn(&mut self, bundle: impl Bundle) -> SpatialEntityCommands {
let entity = self.commands.spawn(bundle).id();
self.children.push(entity);
SpatialEntityCommands {
entity,
commands: self.commands.reborrow(),
phantom: PhantomData,
}
}
/// Add a high-precision spatial entity ([`GridCell`]) to this grid, and insert the provided
/// bundle.
#[inline]
pub fn spawn_spatial(&mut self, bundle: impl Bundle) -> SpatialEntityCommands<P> {
pub fn spawn_spatial(&mut self, bundle: impl Bundle) -> SpatialEntityCommands {
let entity = self
.spawn((
#[cfg(feature = "bevy_render")]
bevy_render::view::Visibility::default(),
Transform::default(),
GridCell::<P>::default(),
GridCell::default(),
))
.insert(bundle)
.id();
@ -109,7 +93,6 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
SpatialEntityCommands {
entity,
commands: self.commands.reborrow(),
phantom: PhantomData,
}
}
@ -123,10 +106,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
/// to it via the closure. This allows you to insert bundles on this new spatial entities, and
/// add more children to it.
#[inline]
pub fn with_spatial(
&mut self,
spatial: impl FnOnce(&mut SpatialEntityCommands<P>),
) -> &mut Self {
pub fn with_spatial(&mut self, spatial: impl FnOnce(&mut SpatialEntityCommands)) -> &mut Self {
spatial(&mut self.spawn_spatial(()));
self
}
@ -137,8 +117,8 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
#[inline]
pub fn with_grid(
&mut self,
new_grid: Grid<P>,
builder: impl FnOnce(&mut GridCommands<P>),
new_grid: Grid,
builder: impl FnOnce(&mut GridCommands),
) -> &mut Self {
builder(&mut self.spawn_grid(new_grid, ()));
self
@ -146,20 +126,20 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
/// Same as [`Self::with_grid`], but using the default [`Grid`] value.
#[inline]
pub fn with_grid_default(&mut self, builder: impl FnOnce(&mut GridCommands<P>)) -> &mut Self {
pub fn with_grid_default(&mut self, builder: impl FnOnce(&mut GridCommands)) -> &mut Self {
self.with_grid(Grid::default(), builder)
}
/// Spawn a grid as a child of the current grid.
#[inline]
pub fn spawn_grid(&mut self, new_grid: Grid<P>, bundle: impl Bundle) -> GridCommands<P> {
pub fn spawn_grid(&mut self, new_grid: Grid, bundle: impl Bundle) -> GridCommands {
let entity = self
.spawn((
#[cfg(feature = "bevy_render")]
bevy_render::view::Visibility::default(),
Transform::default(),
GridCell::<P>::default(),
Grid::<P>::default(),
GridCell::default(),
Grid::default(),
))
.insert(bundle)
.id();
@ -173,7 +153,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
}
/// Spawn a grid as a child of the current grid.
pub fn spawn_grid_default(&mut self, bundle: impl Bundle) -> GridCommands<P> {
pub fn spawn_grid_default(&mut self, bundle: impl Bundle) -> GridCommands {
self.spawn_grid(Grid::default(), bundle)
}
@ -192,7 +172,7 @@ impl<'a, P: GridPrecision> GridCommands<'a, P> {
}
/// Insert the grid on drop.
impl<P: GridPrecision> Drop for GridCommands<'_, P> {
impl Drop for GridCommands<'_> {
fn drop(&mut self) {
let entity = self.entity;
self.commands
@ -203,13 +183,12 @@ impl<P: GridPrecision> Drop for GridCommands<'_, P> {
}
/// Build [`big_space`](crate) hierarchies more easily, with access to grids.
pub struct SpatialEntityCommands<'a, P: GridPrecision> {
pub struct SpatialEntityCommands<'a> {
entity: Entity,
commands: Commands<'a, 'a>,
phantom: PhantomData<P>,
}
impl<'a, P: GridPrecision> SpatialEntityCommands<'a, P> {
impl<'a> SpatialEntityCommands<'a> {
/// Insert a component on this grid
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
self.commands.entity(self.entity).insert(bundle);

View File

@ -1,7 +1,5 @@
//! Contains tools for debugging the floating origin.
use std::marker::PhantomData;
use crate::prelude::*;
use bevy_app::prelude::*;
use bevy_color::prelude::*;
@ -13,14 +11,14 @@ use bevy_transform::prelude::*;
/// This plugin will render the bounds of occupied grid cells.
#[derive(Default)]
pub struct FloatingOriginDebugPlugin<P: GridPrecision>(PhantomData<P>);
impl<P: GridPrecision> Plugin for FloatingOriginDebugPlugin<P> {
pub struct FloatingOriginDebugPlugin(());
impl Plugin for FloatingOriginDebugPlugin {
fn build(&self, app: &mut App) {
app.init_gizmo_group::<BigSpaceGizmoConfig>()
.add_systems(Startup, setup_gizmos)
.add_systems(
PostUpdate,
(update_debug_bounds::<P>, update_grid_axes::<P>)
(update_debug_bounds, update_grid_axes)
.chain()
.after(bevy_transform::TransformSystem::TransformPropagate),
);
@ -35,10 +33,10 @@ fn setup_gizmos(mut store: ResMut<GizmoConfigStore>) {
}
/// Update the rendered debug bounds to only highlight occupied [`GridCell`]s.
fn update_debug_bounds<P: GridPrecision>(
fn update_debug_bounds(
mut gizmos: Gizmos<BigSpaceGizmoConfig>,
grids: Grids<P>,
occupied_cells: Query<(Entity, &GridCell<P>, Option<&FloatingOrigin>)>,
grids: Grids,
occupied_cells: Query<(Entity, &GridCell, Option<&FloatingOrigin>)>,
) {
for (cell_entity, cell, origin) in occupied_cells.iter() {
let Some(grid) = grids.parent_grid(cell_entity) else {
@ -62,9 +60,9 @@ struct BigSpaceGizmoConfig;
impl GizmoConfigGroup for BigSpaceGizmoConfig {}
/// Draw axes for grids.
fn update_grid_axes<P: GridPrecision>(
fn update_grid_axes(
mut gizmos: Gizmos<BigSpaceGizmoConfig>,
grids: Query<(&GlobalTransform, &Grid<P>)>,
grids: Query<(&GlobalTransform, &Grid)>,
) {
for (transform, grid) in grids.iter() {
let start = transform.translation();

View File

@ -1,25 +1,13 @@
//! Contains the grid cell implementation
use crate::prelude::*;
use bevy_ecs::{component::ComponentId, prelude::*, world::DeferredWorld};
use bevy_ecs::prelude::*;
use bevy_hierarchy::prelude::*;
use bevy_math::{DVec3, IVec3};
use bevy_reflect::prelude::*;
use bevy_transform::prelude::*;
use bevy_utils::Instant;
/// Marks entities with any generic [`GridCell`] component. Allows you to query for high precision
/// spatial entities of any [`GridPrecision`].
///
/// Also useful for filtering. You might want to run queries on things without a grid cell, however
/// there could by many generic types of grid cell. `Without<GridCellAny>` will cover all of these
/// cases.
///
/// This is automatically added and removed by the component lifecycle hooks on [`GridCell`].
#[derive(Component, Default, Debug, Clone, Copy, Reflect)]
#[reflect(Component, Default)]
pub struct GridCellAny;
/// Locates an entity in a cell within its parent's [`Grid`]. The [`Transform`] of an entity with
/// this component is a transformation from the center of this cell.
///
@ -39,51 +27,33 @@ pub struct GridCellAny;
#[derive(Component, Default, Debug, PartialEq, Eq, Clone, Copy, Hash, Reflect)]
#[reflect(Component, Default, PartialEq)]
#[require(Transform, GlobalTransform)]
#[component(storage = "Table", on_add = Self::on_add, on_remove = Self::on_remove)]
pub struct GridCell<P: GridPrecision> {
pub struct GridCell {
/// The x-index of the cell.
pub x: P,
pub x: GridPrecision,
/// The y-index of the cell.
pub y: P,
pub y: GridPrecision,
/// The z-index of the cell.
pub z: P,
pub z: GridPrecision,
}
impl<P: GridPrecision> GridCell<P> {
fn on_add(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
assert!(world.get::<GridCellAny>(entity).is_none(), "Adding multiple GridCell<P>s with different generic values on the same entity is not supported");
world.commands().entity(entity).insert(GridCellAny);
}
fn on_remove(mut world: DeferredWorld, entity: Entity, _: ComponentId) {
world.commands().entity(entity).remove::<GridCellAny>();
}
impl GridCell {
/// Construct a new [`GridCell`].
pub fn new(x: P, y: P, z: P) -> Self {
pub fn new(x: GridPrecision, y: GridPrecision, z: GridPrecision) -> Self {
Self { x, y, z }
}
/// The origin [`GridCell`].
pub const ZERO: Self = GridCell {
x: P::ZERO,
y: P::ZERO,
z: P::ZERO,
};
pub const ZERO: Self = GridCell { x: 0, y: 0, z: 0 };
/// A unit value [`GridCell`]. Useful for offsets.
pub const ONE: Self = GridCell {
x: P::ONE,
y: P::ONE,
z: P::ONE,
};
pub const ONE: Self = GridCell { x: 1, y: 1, z: 1 };
/// Convert this grid cell to a floating point translation within this `grid`.
pub fn as_dvec3(&self, grid: &Grid<P>) -> DVec3 {
pub fn as_dvec3(&self, grid: &Grid) -> DVec3 {
DVec3 {
x: self.x.as_f64() * grid.cell_edge_length() as f64,
y: self.y.as_f64() * grid.cell_edge_length() as f64,
z: self.z.as_f64() * grid.cell_edge_length() as f64,
x: self.x as f64 * grid.cell_edge_length() as f64,
y: self.y as f64 * grid.cell_edge_length() as f64,
z: self.z as f64 * grid.cell_edge_length() as f64,
}
}
@ -113,7 +83,7 @@ impl<P: GridPrecision> GridCell<P> {
/// [`Grid`], it will be relocated to the nearest grid cell to reduce the size of the transform.
pub fn recenter_large_transforms(
mut stats: ResMut<crate::timing::PropagationStats>,
grids: Query<&Grid<P>>,
grids: Query<&Grid>,
mut changed_transform: Query<(&mut Self, &mut Transform, &Parent), Changed<Transform>>,
) {
let start = Instant::now();
@ -141,8 +111,8 @@ impl<P: GridPrecision> GridCell<P> {
}
}
impl<P: GridPrecision> std::ops::Add for GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Add for GridCell {
type Output = GridCell;
fn add(self, rhs: Self) -> Self::Output {
GridCell {
@ -153,20 +123,20 @@ impl<P: GridPrecision> std::ops::Add for GridCell<P> {
}
}
impl<P: GridPrecision> std::ops::Add<IVec3> for GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Add<IVec3> for GridCell {
type Output = GridCell;
fn add(self, rhs: IVec3) -> Self::Output {
GridCell {
x: self.x.wrapping_add_i32(rhs.x),
y: self.y.wrapping_add_i32(rhs.y),
z: self.z.wrapping_add_i32(rhs.z),
x: self.x.wrapping_add(rhs.x as GridPrecision),
y: self.y.wrapping_add(rhs.y as GridPrecision),
z: self.z.wrapping_add(rhs.z as GridPrecision),
}
}
}
impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Sub for GridCell {
type Output = GridCell;
fn sub(self, rhs: Self) -> Self::Output {
GridCell {
@ -177,114 +147,94 @@ impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
}
}
impl<P: GridPrecision> std::ops::Sub<IVec3> for GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Sub<IVec3> for GridCell {
type Output = GridCell;
fn sub(self, rhs: IVec3) -> Self::Output {
GridCell {
x: self.x.wrapping_add_i32(-rhs.x),
y: self.y.wrapping_add_i32(-rhs.y),
z: self.z.wrapping_add_i32(-rhs.z),
x: self.x.wrapping_add(-rhs.x as GridPrecision),
y: self.y.wrapping_add(-rhs.y as GridPrecision),
z: self.z.wrapping_add(-rhs.z as GridPrecision),
}
}
}
impl<P: GridPrecision> std::ops::Add for &GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Add for &GridCell {
type Output = GridCell;
fn add(self, rhs: Self) -> Self::Output {
(*self).add(*rhs)
}
}
impl<P: GridPrecision> std::ops::Add<IVec3> for &GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Add<IVec3> for &GridCell {
type Output = GridCell;
fn add(self, rhs: IVec3) -> Self::Output {
(*self).add(rhs)
}
}
impl<P: GridPrecision> std::ops::Sub for &GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Sub for &GridCell {
type Output = GridCell;
fn sub(self, rhs: Self) -> Self::Output {
(*self).sub(*rhs)
}
}
impl<P: GridPrecision> std::ops::Sub<IVec3> for &GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Sub<IVec3> for &GridCell {
type Output = GridCell;
fn sub(self, rhs: IVec3) -> Self::Output {
(*self).sub(rhs)
}
}
impl<P: GridPrecision> std::ops::AddAssign for GridCell<P> {
impl std::ops::AddAssign for GridCell {
fn add_assign(&mut self, rhs: Self) {
use std::ops::Add;
*self = self.add(rhs);
}
}
impl<P: GridPrecision> std::ops::AddAssign<IVec3> for GridCell<P> {
impl std::ops::AddAssign<IVec3> for GridCell {
fn add_assign(&mut self, rhs: IVec3) {
use std::ops::Add;
*self = self.add(rhs);
}
}
impl<P: GridPrecision> std::ops::SubAssign for GridCell<P> {
impl std::ops::SubAssign for GridCell {
fn sub_assign(&mut self, rhs: Self) {
use std::ops::Sub;
*self = self.sub(rhs);
}
}
impl<P: GridPrecision> std::ops::SubAssign<IVec3> for GridCell<P> {
impl std::ops::SubAssign<IVec3> for GridCell {
fn sub_assign(&mut self, rhs: IVec3) {
use std::ops::Sub;
*self = self.sub(rhs);
}
}
impl<P: GridPrecision> std::ops::Mul<P> for GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Mul<GridPrecision> for GridCell {
type Output = GridCell;
fn mul(self, rhs: P) -> Self::Output {
fn mul(self, rhs: GridPrecision) -> Self::Output {
GridCell {
x: GridPrecision::mul(self.x, rhs),
y: GridPrecision::mul(self.y, rhs),
z: GridPrecision::mul(self.z, rhs),
x: self.x * rhs,
y: self.y * rhs,
z: self.z * rhs,
}
}
}
impl<P: GridPrecision> std::ops::Mul<P> for &GridCell<P> {
type Output = GridCell<P>;
impl std::ops::Mul<GridPrecision> for &GridCell {
type Output = GridCell;
fn mul(self, rhs: P) -> Self::Output {
fn mul(self, rhs: GridPrecision) -> Self::Output {
(*self).mul(rhs)
}
}
#[cfg(test)]
mod tests {
use bevy::prelude::*;
#[test]
#[should_panic(
expected = "Adding multiple GridCell<P>s with different generic values on the same entity is not supported"
)]
fn disallow_multiple_grid_cells_on_same_entity() {
App::new()
.add_systems(Startup, |mut commands: Commands| {
commands
.spawn_empty()
.insert(super::GridCell::<i8>::default())
.insert(super::GridCell::<i16>::default());
})
.run();
}
}

View File

@ -38,9 +38,9 @@ mod inner {
/// other - consider the grid of a planet, spinning about its axis and orbiting about a star, it
/// will not align with the grid of the star system!
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
pub struct LocalFloatingOrigin<P: GridPrecision> {
pub struct LocalFloatingOrigin {
/// The local cell that the floating origin's grid cell origin falls into.
cell: GridCell<P>,
cell: GridCell,
/// The translation of the floating origin's grid cell relative to the origin of
/// [`LocalFloatingOrigin::cell`].
translation: Vec3,
@ -73,7 +73,7 @@ mod inner {
is_local_origin_unchanged: bool,
}
impl<P: GridPrecision> LocalFloatingOrigin<P> {
impl LocalFloatingOrigin {
/// The grid transform from the local grid, to the floating origin's grid. See
/// [Self::grid_transform].
#[inline]
@ -83,7 +83,7 @@ mod inner {
/// Gets [`Self::cell`].
#[inline]
pub fn cell(&self) -> GridCell<P> {
pub fn cell(&self) -> GridCell {
self.cell
}
@ -102,7 +102,7 @@ mod inner {
/// Update this local floating origin, and compute the new inverse transform.
pub fn set(
&mut self,
translation_grid: GridCell<P>,
translation_grid: GridCell,
translation_float: Vec3,
rotation_float: DQuat,
) {
@ -120,7 +120,7 @@ mod inner {
}
/// Create a new [`LocalFloatingOrigin`].
pub fn new(cell: GridCell<P>, translation: Vec3, rotation: DQuat) -> Self {
pub fn new(cell: GridCell, translation: Vec3, rotation: DQuat) -> Self {
let grid_transform = DAffine3 {
matrix3: DMat3::from_quat(rotation),
translation: translation.as_dvec3(),
@ -144,9 +144,9 @@ mod inner {
}
}
fn propagate_origin_to_parent<P: GridPrecision>(
fn propagate_origin_to_parent(
this_grid_entity: Entity,
grids: &mut GridsMut<P>,
grids: &mut GridsMut,
parent_grid_entity: Entity,
) {
let (this_grid, this_cell, this_transform) = grids.get(this_grid_entity);
@ -190,9 +190,9 @@ fn propagate_origin_to_parent<P: GridPrecision>(
});
}
fn propagate_origin_to_child<P: GridPrecision>(
fn propagate_origin_to_child(
this_grid_entity: Entity,
grids: &mut GridsMut<P>,
grids: &mut GridsMut,
child_grid_entity: Entity,
) {
let (this_grid, _this_cell, _this_transform) = grids.get(this_grid_entity);
@ -241,14 +241,14 @@ fn propagate_origin_to_child<P: GridPrecision>(
/// A system param for more easily navigating a hierarchy of [`Grid`]s.
#[derive(SystemParam)]
pub struct Grids<'w, 's, P: GridPrecision> {
pub struct Grids<'w, 's> {
parent: Query<'w, 's, Read<Parent>>,
grid_query: Query<'w, 's, (Entity, Read<Grid<P>>, Option<Read<Parent>>)>,
grid_query: Query<'w, 's, (Entity, Read<Grid>, Option<Read<Parent>>)>,
}
impl<P: GridPrecision> Grids<'_, '_, P> {
impl Grids<'_, '_> {
/// Get a [`Grid`] from its `Entity`.
pub fn get(&self, grid_entity: Entity) -> &Grid<P> {
pub fn get(&self, grid_entity: Entity) -> &Grid {
self.grid_query
.get(grid_entity)
.map(|(_entity, grid, _parent)| grid)
@ -258,7 +258,7 @@ impl<P: GridPrecision> Grids<'_, '_, P> {
}
/// Get the [`Grid`] that `this` `Entity` is a child of, if it exists.
pub fn parent_grid(&self, this: Entity) -> Option<&Grid<P>> {
pub fn parent_grid(&self, this: Entity) -> Option<&Grid> {
self.parent_grid_entity(this)
.map(|grid_entity| self.get(grid_entity))
}
@ -316,13 +316,13 @@ impl<P: GridPrecision> Grids<'_, '_, P> {
/// A system param for more easily navigating a hierarchy of grids mutably.
#[derive(SystemParam)]
pub struct GridsMut<'w, 's, P: GridPrecision> {
pub struct GridsMut<'w, 's> {
parent: Query<'w, 's, Read<Parent>>,
position: Query<'w, 's, (Read<GridCell<P>>, Read<Transform>), With<Grid<P>>>,
grid_query: Query<'w, 's, (Entity, Write<Grid<P>>, Option<Read<Parent>>)>,
position: Query<'w, 's, (Read<GridCell>, Read<Transform>), With<Grid>>,
grid_query: Query<'w, 's, (Entity, Write<Grid>, Option<Read<Parent>>)>,
}
impl<P: GridPrecision> GridsMut<'_, '_, P> {
impl GridsMut<'_, '_> {
/// Get mutable access to the [`Grid`], and run the provided function or closure, optionally
/// returning data.
///
@ -332,7 +332,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
pub fn update<T>(
&mut self,
grid_entity: Entity,
mut func: impl FnMut(&mut Grid<P>, &GridCell<P>, &Transform) -> T,
mut func: impl FnMut(&mut Grid, &GridCell, &Transform) -> T,
) -> T {
let (cell, transform) = self.position(grid_entity);
self.grid_query
@ -342,7 +342,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
}
/// Get the grid and the position of the grid from its `Entity`.
pub fn get(&self, grid_entity: Entity) -> (&Grid<P>, GridCell<P>, Transform) {
pub fn get(&self, grid_entity: Entity) -> (&Grid, GridCell, Transform) {
let (cell, transform) = self.position(grid_entity);
self.grid_query
.get(grid_entity)
@ -356,7 +356,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
/// they are missing.
///
/// Needed because the root grid should not have a grid cell or transform.
pub fn position(&self, grid_entity: Entity) -> (GridCell<P>, Transform) {
pub fn position(&self, grid_entity: Entity) -> (GridCell, Transform) {
let (cell, transform) = (GridCell::default(), Transform::default());
let (cell, transform) = self.position.get(grid_entity).unwrap_or_else(|_| {
assert!(self.parent.get(grid_entity).is_err(), "Grid entity {grid_entity:?} is missing a GridCell and Transform. This is valid only if this is a root grid, but this is not.");
@ -366,7 +366,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
}
/// Get the [`Grid`] that `this` `Entity` is a child of, if it exists.
pub fn parent_grid(&self, this: Entity) -> Option<(&Grid<P>, GridCell<P>, Transform)> {
pub fn parent_grid(&self, this: Entity) -> Option<(&Grid, GridCell, Transform)> {
self.parent_grid_entity(this)
.map(|grid_entity| self.get(grid_entity))
}
@ -421,7 +421,7 @@ impl<P: GridPrecision> GridsMut<'_, '_, P> {
}
}
impl<P: GridPrecision> LocalFloatingOrigin<P> {
impl LocalFloatingOrigin {
/// Update the [`LocalFloatingOrigin`] of every [`Grid`] in the world. This does not update any
/// entity transforms, instead this is a preceding step that updates every reference grid, so it
/// knows where the floating origin is located with respect to that reference grid. This is all
@ -430,10 +430,10 @@ impl<P: GridPrecision> LocalFloatingOrigin<P> {
/// source of truth and never mutated.
pub fn compute_all(
mut stats: ResMut<crate::timing::PropagationStats>,
mut grids: GridsMut<P>,
mut grids: GridsMut,
mut grid_stack: Local<Vec<Entity>>,
mut scratch_buffer: Local<Vec<Entity>>,
cells: Query<(Entity, Ref<GridCell<P>>)>,
cells: Query<(Entity, Ref<GridCell>)>,
roots: Query<(Entity, &BigSpace)>,
parents: Query<&Parent>,
) {
@ -524,13 +524,9 @@ mod tests {
#[test]
fn grid_hierarchy_getters() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default());
app.add_plugins(BigSpacePlugin::default());
let grid_bundle = (
Transform::default(),
GridCell::<i32>::default(),
Grid::<i32>::default(),
);
let grid_bundle = (Transform::default(), GridCell::default(), Grid::default());
let child_1 = app.world_mut().spawn(grid_bundle.clone()).id();
let child_2 = app.world_mut().spawn(grid_bundle.clone()).id();
@ -542,7 +538,7 @@ mod tests {
.entity_mut(parent)
.add_children(&[child_1, child_2]);
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
let mut state = SystemState::<GridsMut>::new(app.world_mut());
let mut grids = state.get_mut(app.world_mut());
// Children
@ -573,11 +569,11 @@ mod tests {
#[test]
fn child_propagation() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default());
app.add_plugins(BigSpacePlugin::default());
let root_grid = Grid {
local_floating_origin: LocalFloatingOrigin::new(
GridCell::<i32>::new(1_000_000, -1, -1),
GridCell::new(1_000_000, -1, -1),
Vec3::ZERO,
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
),
@ -585,7 +581,7 @@ mod tests {
};
let root = app
.world_mut()
.spawn((Transform::default(), GridCell::<i32>::default(), root_grid))
.spawn((Transform::default(), GridCell::default(), root_grid))
.id();
let child = app
@ -593,14 +589,14 @@ mod tests {
.spawn((
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
GridCell::<i32>::new(1_000_000, 0, 0),
Grid::<i32>::default(),
GridCell::new(1_000_000, 0, 0),
Grid::default(),
))
.id();
app.world_mut().entity_mut(root).add_child(child);
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
let mut state = SystemState::<GridsMut>::new(app.world_mut());
let mut grids = state.get_mut(app.world_mut());
// The function we are testing
@ -633,13 +629,9 @@ mod tests {
#[test]
fn parent_propagation() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i64>::default());
app.add_plugins(BigSpacePlugin::default());
let grid_bundle = (
Transform::default(),
GridCell::<i64>::default(),
Grid::<i64>::default(),
);
let grid_bundle = (Transform::default(), GridCell::default(), Grid::default());
let root = app.world_mut().spawn(grid_bundle.clone()).id();
let child = app
@ -647,10 +639,10 @@ mod tests {
.spawn((
Transform::from_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2))
.with_translation(Vec3::new(1.0, 1.0, 0.0)),
GridCell::<i64>::new(150_000_003_000, 0, 0), // roughly radius of earth orbit
GridCell::new(150_000_003_000, 0, 0), // roughly radius of earth orbit
Grid {
local_floating_origin: LocalFloatingOrigin::new(
GridCell::<i64>::new(0, 3_000, 0),
GridCell::new(0, 3_000, 0),
Vec3::new(5.0, 5.0, 0.0),
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
),
@ -661,7 +653,7 @@ mod tests {
app.world_mut().entity_mut(root).add_child(child);
let mut state = SystemState::<GridsMut<i64>>::new(app.world_mut());
let mut state = SystemState::<GridsMut>::new(app.world_mut());
let mut grids = state.get_mut(app.world_mut());
// The function we are testing
@ -694,16 +686,16 @@ mod tests {
#[test]
fn origin_transform() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default());
app.add_plugins(BigSpacePlugin::default());
let root = app
.world_mut()
.spawn((
Transform::default(),
GridCell::<i32>::default(),
GridCell::default(),
Grid {
local_floating_origin: LocalFloatingOrigin::new(
GridCell::<i32>::new(0, 0, 0),
GridCell::new(0, 0, 0),
Vec3::new(1.0, 1.0, 0.0),
DQuat::from_rotation_z(0.0),
),
@ -718,14 +710,14 @@ mod tests {
Transform::default()
.with_rotation(Quat::from_rotation_z(-std::f32::consts::FRAC_PI_2))
.with_translation(Vec3::new(3.0, 3.0, 0.0)),
GridCell::<i32>::new(0, 0, 0),
Grid::<i32>::default(),
GridCell::new(0, 0, 0),
Grid::default(),
))
.id();
app.world_mut().entity_mut(root).add_child(child);
let mut state = SystemState::<GridsMut<i32>>::new(app.world_mut());
let mut state = SystemState::<GridsMut>::new(app.world_mut());
let mut grids = state.get_mut(app.world_mut());
propagate_origin_to_child(root, &mut grids, child);

View File

@ -30,22 +30,22 @@ pub mod propagation;
#[reflect(Component)]
// We do not require the Transform, GlobalTransform, or GridCell, because these are not required in
// all cases: e.g. BigSpace should not have a Transform or GridCell.
pub struct Grid<P: GridPrecision> {
pub struct Grid {
/// The high-precision position of the floating origin's current grid cell local to this grid.
local_floating_origin: LocalFloatingOrigin<P>,
local_floating_origin: LocalFloatingOrigin,
/// Defines the uniform scale of the grid by the length of the edge of a grid cell.
cell_edge_length: f32,
/// How far an entity can move from the origin before its grid cell is recomputed.
maximum_distance_from_origin: f32,
}
impl<P: GridPrecision> Default for Grid<P> {
impl Default for Grid {
fn default() -> Self {
Self::new(2_000f32, 100f32)
}
}
impl<P: GridPrecision> Grid<P> {
impl Grid {
/// Construct a new [`Grid`]. The properties of a grid cannot be changed after construction.
pub fn new(cell_edge_length: f32, switching_threshold: f32) -> Self {
Self {
@ -57,7 +57,7 @@ impl<P: GridPrecision> Grid<P> {
/// Get the position of the floating origin relative to the current grid.
#[inline]
pub fn local_floating_origin(&self) -> &LocalFloatingOrigin<P> {
pub fn local_floating_origin(&self) -> &LocalFloatingOrigin {
&self.local_floating_origin
}
@ -76,37 +76,37 @@ impl<P: GridPrecision> Grid<P> {
/// Compute the double precision position of an entity's [`Transform`] with respect to the given
/// [`GridCell`] within this grid.
#[inline]
pub fn grid_position_double(&self, pos: &GridCell<P>, transform: &Transform) -> DVec3 {
pub fn grid_position_double(&self, pos: &GridCell, transform: &Transform) -> DVec3 {
DVec3 {
x: pos.x.as_f64() * self.cell_edge_length as f64 + transform.translation.x as f64,
y: pos.y.as_f64() * self.cell_edge_length as f64 + transform.translation.y as f64,
z: pos.z.as_f64() * self.cell_edge_length as f64 + transform.translation.z as f64,
x: pos.x as f64 * self.cell_edge_length as f64 + transform.translation.x as f64,
y: pos.y as f64 * self.cell_edge_length as f64 + transform.translation.y as f64,
z: pos.z as f64 * self.cell_edge_length as f64 + transform.translation.z as f64,
}
}
/// Compute the single precision position of an entity's [`Transform`] with respect to the given
/// [`GridCell`].
#[inline]
pub fn grid_position(&self, pos: &GridCell<P>, transform: &Transform) -> Vec3 {
pub fn grid_position(&self, pos: &GridCell, transform: &Transform) -> Vec3 {
Vec3 {
x: pos.x.as_f64() as f32 * self.cell_edge_length + transform.translation.x,
y: pos.y.as_f64() as f32 * self.cell_edge_length + transform.translation.y,
z: pos.z.as_f64() as f32 * self.cell_edge_length + transform.translation.z,
x: pos.x as f64 as f32 * self.cell_edge_length + transform.translation.x,
y: pos.y as f64 as f32 * self.cell_edge_length + transform.translation.y,
z: pos.z as f64 as f32 * self.cell_edge_length + transform.translation.z,
}
}
/// Returns the floating point position of a [`GridCell`].
pub fn cell_to_float(&self, pos: &GridCell<P>) -> DVec3 {
pub fn cell_to_float(&self, pos: &GridCell) -> DVec3 {
DVec3 {
x: pos.x.as_f64(),
y: pos.y.as_f64(),
z: pos.z.as_f64(),
x: pos.x as f64,
y: pos.y as f64,
z: pos.z as f64,
} * self.cell_edge_length as f64
}
/// Convert a large translation into a small translation relative to a grid cell.
#[inline]
pub fn translation_to_grid(&self, input: impl Into<DVec3>) -> (GridCell<P>, Vec3) {
pub fn translation_to_grid(&self, input: impl Into<DVec3>) -> (GridCell, Vec3) {
let l = self.cell_edge_length as f64;
let input = input.into();
let DVec3 { x, y, z } = input;
@ -124,9 +124,9 @@ impl<P: GridPrecision> Grid<P> {
(
GridCell {
x: P::from_f64(x_r),
y: P::from_f64(y_r),
z: P::from_f64(z_r),
x: x_r as GridPrecision,
y: y_r as GridPrecision,
z: z_r as GridPrecision,
},
Vec3::new(t_x as f32, t_y as f32, t_z as f32),
)
@ -134,7 +134,7 @@ impl<P: GridPrecision> Grid<P> {
/// Convert a large translation into a small translation relative to a grid cell.
#[inline]
pub fn imprecise_translation_to_grid(&self, input: Vec3) -> (GridCell<P>, Vec3) {
pub fn imprecise_translation_to_grid(&self, input: Vec3) -> (GridCell, Vec3) {
self.translation_to_grid(input.as_dvec3())
}
@ -142,7 +142,7 @@ impl<P: GridPrecision> Grid<P> {
#[inline]
pub fn global_transform(
&self,
local_cell: &GridCell<P>,
local_cell: &GridCell,
local_transform: &Transform,
) -> GlobalTransform {
// The grid transform from the floating origin's grid, to the local grid.

View File

@ -16,20 +16,20 @@ use bevy_transform::prelude::*;
#[derive(Component, Default, Reflect)]
pub struct LowPrecisionRoot;
impl<P: GridPrecision> Grid<P> {
impl Grid {
/// Update the `GlobalTransform` of entities with a [`GridCell`], using the [`Grid`] the entity
/// belongs to.
pub fn propagate_high_precision(
mut stats: ResMut<crate::timing::PropagationStats>,
grids: Query<&Grid<P>>,
grids: Query<&Grid>,
mut entities: ParamSet<(
Query<(
Ref<GridCell<P>>,
Ref<GridCell>,
Ref<Transform>,
Ref<Parent>,
&mut GlobalTransform,
)>,
Query<(&Grid<P>, &mut GlobalTransform), With<BigSpace>>,
Query<(&Grid, &mut GlobalTransform), With<BigSpace>>,
)>,
) {
let start = bevy_utils::Instant::now();
@ -93,13 +93,13 @@ impl<P: GridPrecision> Grid<P> {
pub fn tag_low_precision_roots(
mut stats: ResMut<crate::timing::PropagationStats>,
mut commands: Commands,
valid_parent: Query<(), (With<GridCell<P>>, With<GlobalTransform>, With<Children>)>,
valid_parent: Query<(), (With<GridCell>, With<GlobalTransform>, With<Children>)>,
unmarked: Query<
(Entity, &Parent),
(
With<Transform>,
With<GlobalTransform>,
Without<GridCellAny>,
Without<GridCell>,
Without<LowPrecisionRoot>,
Or<(Changed<Parent>, Added<Transform>)>,
),
@ -111,7 +111,7 @@ impl<P: GridPrecision> Grid<P> {
Or<(
Without<Transform>,
Without<GlobalTransform>,
With<GridCell<P>>,
With<GridCell>,
Without<Parent>,
)>,
),
@ -147,7 +147,7 @@ impl<P: GridPrecision> Grid<P> {
(
// A root big space does not have a grid cell, and not all high precision entities
// have a grid
Or<(With<Grid<P>>, With<GridCell<P>>)>,
Or<(With<Grid>, With<GridCell>)>,
),
>,
roots: Query<(Entity, &Parent), With<LowPrecisionRoot>>,
@ -155,9 +155,8 @@ impl<P: GridPrecision> Grid<P> {
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
(
With<Parent>,
Without<GridCellAny>,
Without<GridCell<P>>, // Used to prove access to GlobalTransform is disjoint
Without<Grid<P>>,
Without<GridCell>, // Used to prove access to GlobalTransform is disjoint
Without<Grid>,
),
>,
parent_query: Query<
@ -165,8 +164,8 @@ impl<P: GridPrecision> Grid<P> {
(
With<Transform>,
With<GlobalTransform>,
Without<GridCellAny>,
Without<Grid<P>>,
Without<GridCell>,
Without<Grid>,
),
>,
) {
@ -228,9 +227,8 @@ impl<P: GridPrecision> Grid<P> {
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
(
With<Parent>,
Without<GridCellAny>, // ***ADDED*** Only recurse low-precision entities
Without<GridCell<P>>, // ***ADDED*** Only recurse low-precision entities
Without<Grid<P>>, // ***ADDED*** Only recurse low-precision entities
Without<GridCell>, // ***ADDED*** Only recurse low-precision entities
Without<Grid>, // ***ADDED*** Only recurse low-precision entities
),
>,
parent_query: &Query<
@ -238,8 +236,8 @@ impl<P: GridPrecision> Grid<P> {
(
With<Transform>,
With<GlobalTransform>,
Without<GridCellAny>,
Without<Grid<P>>,
Without<GridCell>,
Without<Grid>,
),
>,
entity: Entity,
@ -314,9 +312,10 @@ mod tests {
struct Test;
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default())
.add_systems(Startup, |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
app.add_plugins(BigSpacePlugin::default()).add_systems(
Startup,
|mut commands: Commands| {
commands.spawn_big_space_default(|root| {
root.spawn_spatial(FloatingOrigin);
root.spawn_spatial((
Transform::from_xyz(3.0, 3.0, 3.0),
@ -330,7 +329,8 @@ mod tests {
));
});
});
});
},
);
app.update();

View File

@ -28,14 +28,14 @@ impl Hash for FastGridHash {
}
}
impl<P: GridPrecision> PartialEq<GridHash<P>> for FastGridHash {
fn eq(&self, other: &GridHash<P>) -> bool {
impl PartialEq<GridHash> for FastGridHash {
fn eq(&self, other: &GridHash) -> bool {
self.0 == other.pre_hash
}
}
impl<P: GridPrecision> From<GridHash<P>> for FastGridHash {
fn from(value: GridHash<P>) -> Self {
impl From<GridHash> for FastGridHash {
fn from(value: GridHash) -> Self {
Self(value.pre_hash)
}
}
@ -50,9 +50,9 @@ impl<P: GridPrecision> From<GridHash<P>> for FastGridHash {
/// the [`Parent`] of the entity to uniquely identify its position. These two values are then hashed
/// and stored in this spatial hash component.
#[derive(Component, Clone, Copy, Debug, Reflect)]
pub struct GridHash<P: GridPrecision> {
pub struct GridHash {
// Needed for equality checks
cell: GridCell<P>,
cell: GridCell,
// Needed for equality checks
grid: Entity,
// The hashed value of the `cell` and `grid` fields. Hash collisions are possible, especially
@ -62,7 +62,7 @@ pub struct GridHash<P: GridPrecision> {
pre_hash: u64,
}
impl<P: GridPrecision> PartialEq for GridHash<P> {
impl PartialEq for GridHash {
#[inline]
fn eq(&self, other: &Self) -> bool {
// Comparing the hash is redundant.
@ -74,27 +74,27 @@ impl<P: GridPrecision> PartialEq for GridHash<P> {
}
}
impl<P: GridPrecision> Eq for GridHash<P> {}
impl Eq for GridHash {}
impl<P: GridPrecision> Hash for GridHash<P> {
impl Hash for GridHash {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.pre_hash);
}
}
impl<P: GridPrecision> GridHash<P> {
impl GridHash {
/// Generate a new hash from parts.
///
/// Intentionally left private, so we can ensure the only place these are constructed/mutated is
/// this module. This allows us to optimize change detection using [`ChangedGridHashes`].
#[inline]
pub(super) fn new(parent: &Parent, cell: &GridCell<P>) -> Self {
pub(super) fn new(parent: &Parent, cell: &GridCell) -> Self {
Self::from_parent(parent.get(), cell)
}
#[inline]
pub(super) fn from_parent(parent: Entity, cell: &GridCell<P>) -> Self {
pub(super) fn from_parent(parent: Entity, cell: &GridCell) -> Self {
let hasher = &mut AHasher::default();
hasher.write_u64(parent.to_bits());
cell.hash(hasher);
@ -108,7 +108,7 @@ impl<P: GridPrecision> GridHash<P> {
/// Do not use this to manually construct this component. You've been warned.
#[doc(hidden)]
pub fn __new_manual(parent: Entity, cell: &GridCell<P>) -> Self {
pub fn __new_manual(parent: Entity, cell: &GridCell) -> Self {
Self::from_parent(parent, cell)
}
@ -139,7 +139,7 @@ impl<P: GridPrecision> GridHash<P> {
/// Returns an iterator over all neighboring grid cells and their hashes, within the
/// `cell_radius`. This iterator will not visit `cell`.
pub fn adjacent(&self, cell_radius: u8) -> impl Iterator<Item = GridHash<P>> + '_ {
pub fn adjacent(&self, cell_radius: u8) -> impl Iterator<Item = GridHash> + '_ {
let radius = cell_radius as i32;
let search_width = 1 + 2 * radius;
let search_volume = search_width.pow(3);
@ -158,21 +158,15 @@ impl<P: GridPrecision> GridHash<P> {
/// [`GridHashMapFilter`].
pub(super) fn update<F: GridHashMapFilter>(
mut commands: Commands,
mut changed_hashes: ResMut<ChangedGridHashes<P, F>>,
mut changed_hashes: ResMut<ChangedGridHashes<F>>,
mut spatial_entities: Query<
(
Entity,
&Parent,
&GridCell<P>,
&mut GridHash<P>,
&mut FastGridHash,
),
(F, Or<(Changed<Parent>, Changed<GridCell<P>>)>),
(Entity, &Parent, &GridCell, &mut GridHash, &mut FastGridHash),
(F, Or<(Changed<Parent>, Changed<GridCell>)>),
>,
added_entities: Query<(Entity, &Parent, &GridCell<P>), (F, Without<GridHash<P>>)>,
added_entities: Query<(Entity, &Parent, &GridCell), (F, Without<GridHash>)>,
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
mut thread_updated_hashes: Local<Parallel<Vec<Entity>>>,
mut thread_commands: Local<Parallel<Vec<(Entity, GridHash<P>, FastGridHash)>>>,
mut thread_commands: Local<Parallel<Vec<(Entity, GridHash, FastGridHash)>>>,
) {
let start = Instant::now();
@ -208,7 +202,7 @@ impl<P: GridPrecision> GridHash<P> {
}
/// The [`GridCell`] associated with this spatial hash.
pub fn cell(&self) -> GridCell<P> {
pub fn cell(&self) -> GridCell {
self.cell
}

View File

@ -2,6 +2,7 @@
use std::{collections::VecDeque, marker::PhantomData, time::Instant};
use super::GridHashMapFilter;
use crate::prelude::*;
use bevy_ecs::{entity::EntityHash, prelude::*};
use bevy_utils::{
@ -9,22 +10,20 @@ use bevy_utils::{
PassHash,
};
use super::GridHashMapFilter;
/// An entry in a [`GridHashMap`], accessed with a [`GridHash`].
#[derive(Clone, Debug)]
pub struct GridHashEntry<P: GridPrecision> {
pub struct GridHashEntry {
/// All the entities located in this grid cell.
pub entities: HashSet<Entity, EntityHash>,
/// Precomputed hashes to direct neighbors.
// TODO: computation cheap, heap slow. Can this be replaced with a u32 bitmask of occupied cells
// (only need 26 bits), with the hashes computed based on the neighbor's relative position?
pub occupied_neighbors: Vec<GridHash<P>>,
pub occupied_neighbors: Vec<GridHash>,
}
impl<P: GridPrecision> GridHashEntry<P> {
impl GridHashEntry {
/// Find an occupied neighbor's index in the list.
fn neighbor_index(&self, hash: &GridHash<P>) -> Option<usize> {
fn neighbor_index(&self, hash: &GridHash) -> Option<usize> {
self.occupied_neighbors
.iter()
.enumerate()
@ -37,8 +36,8 @@ impl<P: GridPrecision> GridHashEntry<P> {
/// See [`GridHashMap::nearby`].
pub fn nearby<'a, F: GridHashMapFilter>(
&'a self,
map: &'a GridHashMap<P, F>,
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
map: &'a GridHashMap<F>,
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
map.nearby(self)
}
}
@ -59,14 +58,14 @@ where
}
}
impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for &'a GridHashEntry<P> {
impl<'a> SpatialEntryToEntities<'a> for &'a GridHashEntry {
#[inline]
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
self.entities.iter().copied()
}
}
impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for Neighbor<'a, P> {
impl<'a> SpatialEntryToEntities<'a> for Neighbor<'a> {
#[inline]
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
self.1.entities.iter().copied()
@ -75,23 +74,21 @@ impl<'a, P: GridPrecision> SpatialEntryToEntities<'a> for Neighbor<'a, P> {
/// A global spatial hash map for quickly finding entities in a grid cell.
#[derive(Resource, Clone)]
pub struct GridHashMap<P, F = ()>
pub struct GridHashMap<F = ()>
where
P: GridPrecision,
F: GridHashMapFilter,
{
/// The primary hash map for looking up entities by their [`GridHash`].
map: InnerGridHashMap<P>,
map: InnerGridHashMap,
/// A reverse lookup to find the latest spatial hash associated with an entity that this map is
/// aware of. This is needed to remove or move an entity when its cell changes, because once it
/// changes in the ECS, we need to know its *previous* value when it was inserted in this map.
reverse_map: HashMap<Entity, GridHash<P>, PassHash>,
reverse_map: HashMap<Entity, GridHash, PassHash>,
spooky: PhantomData<F>,
}
impl<P, F> std::fmt::Debug for GridHashMap<P, F>
impl<F> std::fmt::Debug for GridHashMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -102,9 +99,8 @@ where
}
}
impl<P, F> Default for GridHashMap<P, F>
impl<F> Default for GridHashMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn default() -> Self {
@ -116,27 +112,26 @@ where
}
}
impl<P, F> GridHashMap<P, F>
impl<F> GridHashMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
/// Get information about all entities located at this [`GridHash`], as well as its
/// neighbors.
#[inline]
pub fn get(&self, hash: &GridHash<P>) -> Option<&GridHashEntry<P>> {
pub fn get(&self, hash: &GridHash) -> Option<&GridHashEntry> {
self.map.inner.get(hash)
}
/// Returns `true` if this [`GridHash`] is occupied.
#[inline]
pub fn contains(&self, hash: &GridHash<P>) -> bool {
pub fn contains(&self, hash: &GridHash) -> bool {
self.map.inner.contains_key(hash)
}
/// An iterator visiting all spatial hash cells and their contents in arbitrary order.
#[inline]
pub fn all_entries(&self) -> impl Iterator<Item = (&GridHash<P>, &GridHashEntry<P>)> {
pub fn all_entries(&self) -> impl Iterator<Item = (&GridHash, &GridHashEntry)> {
self.map.inner.iter()
}
@ -153,8 +148,8 @@ where
#[inline]
pub fn nearby<'a>(
&'a self,
entry: &'a GridHashEntry<P>,
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
entry: &'a GridHashEntry,
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
// Use `std::iter::once` to avoid returning a function-local variable.
Iterator::chain(
std::iter::once(entry),
@ -179,9 +174,9 @@ where
#[inline]
pub fn within_cube<'a>(
&'a self,
center: &'a GridHash<P>,
center: &'a GridHash,
radius: u8,
) -> impl Iterator<Item = &'a GridHashEntry<P>> + 'a {
) -> impl Iterator<Item = &'a GridHashEntry> + 'a {
// Use `std::iter::once` to avoid returning a function-local variable.
Iterator::chain(std::iter::once(*center), center.adjacent(radius))
.filter_map(|hash| self.get(&hash))
@ -206,9 +201,9 @@ where
#[doc(alias = "bfs")]
pub fn flood<'a>(
&'a self,
seed: &GridHash<P>,
max_depth: Option<P>,
) -> impl Iterator<Item = Neighbor<'a, P>> {
seed: &GridHash,
max_depth: Option<GridPrecision>,
) -> impl Iterator<Item = Neighbor<'a>> {
let starting_cell_cell = seed.cell();
ContiguousNeighborsIter {
initial_hash: Some(*seed),
@ -231,7 +226,7 @@ where
///
/// Useful for incrementally updating data structures that extend the functionality of
/// [`GridHashMap`]. Updated in [`GridHashMapSystem::UpdateMap`].
pub fn just_inserted(&self) -> &HashSet<GridHash<P>, PassHash> {
pub fn just_inserted(&self) -> &HashSet<GridHash, PassHash> {
&self.map.just_inserted
}
@ -241,24 +236,23 @@ where
///
/// Useful for incrementally updating data structures that extend the functionality of
/// [`GridHashMap`]. Updated in [`GridHashMapSystem::UpdateMap`].
pub fn just_removed(&self) -> &HashSet<GridHash<P>, PassHash> {
pub fn just_removed(&self) -> &HashSet<GridHash, PassHash> {
&self.map.just_removed
}
}
/// Private Systems
impl<P, F> GridHashMap<P, F>
impl<F> GridHashMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
/// Update the [`GridHashMap`] with entities that have changed [`GridHash`]es, and meet the
/// optional [`GridHashMapFilter`].
pub(super) fn update(
mut spatial_map: ResMut<Self>,
mut changed_hashes: ResMut<super::ChangedGridHashes<P, F>>,
all_hashes: Query<(Entity, &GridHash<P>), F>,
mut removed: RemovedComponents<GridHash<P>>,
mut changed_hashes: ResMut<super::ChangedGridHashes<F>>,
all_hashes: Query<(Entity, &GridHash), F>,
mut removed: RemovedComponents<GridHash>,
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
) {
let start = Instant::now();
@ -290,14 +284,13 @@ where
}
/// Private Methods
impl<P, F> GridHashMap<P, F>
impl<F> GridHashMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
/// Insert an entity into the [`GridHashMap`], updating any existing entries.
#[inline]
fn insert(&mut self, entity: Entity, hash: GridHash<P>) {
fn insert(&mut self, entity: Entity, hash: GridHash) {
// If this entity is already in the maps, we need to remove and update it.
if let Some(old_hash) = self.reverse_map.get_mut(&entity) {
if hash.eq(old_hash) {
@ -350,24 +343,24 @@ where
// Z-order in *another* collection (BTreeMap?) to improve locality for sequential lookups of
// spatial neighbors. Would ordering cause hitches with insertions?
#[derive(Debug, Clone, Default)]
struct InnerGridHashMap<P: GridPrecision> {
inner: HashMap<GridHash<P>, GridHashEntry<P>, PassHash>,
struct InnerGridHashMap {
inner: HashMap<GridHash, GridHashEntry, PassHash>,
/// Creating and freeing hash sets is expensive. To reduce time spent allocating and running
/// destructors, we save any hash sets that would otherwise be thrown away. The next time we
/// need to construct a new hash set of entities, we can grab one here.
///
/// <https://en.wikipedia.org/wiki/Object_pool_pattern>.
hash_set_pool: Vec<HashSet<Entity, EntityHash>>,
neighbor_pool: Vec<Vec<GridHash<P>>>,
neighbor_pool: Vec<Vec<GridHash>>,
/// Cells that were added because they were empty but now contain entities.
just_inserted: HashSet<GridHash<P>, PassHash>,
just_inserted: HashSet<GridHash, PassHash>,
/// Cells that were removed because all entities vacated the cell.
just_removed: HashSet<GridHash<P>, PassHash>,
just_removed: HashSet<GridHash, PassHash>,
}
impl<P: GridPrecision> InnerGridHashMap<P> {
impl InnerGridHashMap {
#[inline]
fn insert(&mut self, entity: Entity, hash: GridHash<P>) {
fn insert(&mut self, entity: Entity, hash: GridHash) {
if let Some(entry) = self.inner.get_mut(&hash) {
entry.entities.insert(entity);
} else {
@ -378,7 +371,7 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
}
#[inline]
fn insert_entry(&mut self, hash: GridHash<P>, entities: HashSet<Entity, EntityHash>) {
fn insert_entry(&mut self, hash: GridHash, entities: HashSet<Entity, EntityHash>) {
let mut occupied_neighbors = self.neighbor_pool.pop().unwrap_or_default();
occupied_neighbors.extend(hash.adjacent(1).filter(|neighbor| {
self.inner
@ -406,7 +399,7 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
}
#[inline]
fn remove(&mut self, entity: Entity, old_hash: GridHash<P>) {
fn remove(&mut self, entity: Entity, old_hash: GridHash) {
if let Some(entry) = self.inner.get_mut(&old_hash) {
entry.entities.remove(&entity);
if !entry.entities.is_empty() {
@ -443,26 +436,24 @@ impl<P: GridPrecision> InnerGridHashMap<P> {
}
/// An iterator over the neighbors of a cell, breadth-first.
pub struct ContiguousNeighborsIter<'a, P, F>
pub struct ContiguousNeighborsIter<'a, F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
initial_hash: Option<GridHash<P>>,
spatial_map: &'a GridHashMap<P, F>,
stack: VecDeque<Neighbor<'a, P>>,
visited_cells: HashSet<GridHash<P>>,
initial_hash: Option<GridHash>,
spatial_map: &'a GridHashMap<F>,
stack: VecDeque<Neighbor<'a>>,
visited_cells: HashSet<GridHash>,
}
/// Newtype used for adding useful extensions like `.entities()`.
pub struct Neighbor<'a, P: GridPrecision>(pub GridHash<P>, pub &'a GridHashEntry<P>);
pub struct Neighbor<'a>(pub GridHash, pub &'a GridHashEntry);
impl<'a, P, F> Iterator for ContiguousNeighborsIter<'a, P, F>
impl<'a, F> Iterator for ContiguousNeighborsIter<'a, F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
type Item = Neighbor<'a, P>;
type Item = Neighbor<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(hash) = self.initial_hash.take() {

View File

@ -20,27 +20,25 @@ pub mod partition;
/// If you are adding multiple copies of this plugin with different filters, there are optimizations
/// in place to avoid duplicating work. However, you should still take care to avoid excessively
/// overlapping filters.
pub struct GridHashPlugin<P, F = ()>(PhantomData<(P, F)>)
pub struct GridHashPlugin<F = ()>(PhantomData<F>)
where
P: GridPrecision,
F: GridHashMapFilter;
impl<P, F> Plugin for GridHashPlugin<P, F>
impl<F> Plugin for GridHashPlugin<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn build(&self, app: &mut App) {
app.init_resource::<GridHashMap<P, F>>()
.init_resource::<ChangedGridHashes<P, F>>()
.register_type::<GridHash<P>>()
app.init_resource::<GridHashMap<F>>()
.init_resource::<ChangedGridHashes<F>>()
.register_type::<GridHash>()
.add_systems(
PostUpdate,
(
GridHash::<P>::update::<F>
GridHash::update::<F>
.in_set(GridHashMapSystem::UpdateHash)
.after(FloatingOriginSystem::RecenterLargeTransforms),
GridHashMap::<P, F>::update
GridHashMap::<F>::update
.in_set(GridHashMapSystem::UpdateMap)
.after(GridHashMapSystem::UpdateHash),
),
@ -48,7 +46,7 @@ where
}
}
impl<P: GridPrecision, F: GridHashMapFilter> Default for GridHashPlugin<P, F> {
impl<F: GridHashMapFilter> Default for GridHashPlugin<F> {
fn default() -> Self {
Self(PhantomData)
}
@ -91,12 +89,12 @@ impl<T: QueryFilter + Send + Sync + 'static> GridHashMapFilter for T {}
/// It may be possible to remove this if bevy gets archetype change detection, or observers that can
/// react to a component being mutated. For now, this performs well enough.
#[derive(Resource)]
struct ChangedGridHashes<P: GridPrecision, F: GridHashMapFilter> {
struct ChangedGridHashes<F: GridHashMapFilter> {
updated: Vec<Entity>,
spooky: PhantomData<(P, F)>,
spooky: PhantomData<F>,
}
impl<P: GridPrecision, F: GridHashMapFilter> Default for ChangedGridHashes<P, F> {
impl<F: GridHashMapFilter> Default for ChangedGridHashes<F> {
fn default() -> Self {
Self {
updated: Vec::new(),
@ -124,38 +122,30 @@ mod tests {
static ENTITY: OnceLock<Entity> = OnceLock::new();
let setup = |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
let entity = root.spawn_spatial(GridCell::<i32>::ZERO).id();
commands.spawn_big_space_default(|root| {
let entity = root.spawn_spatial(GridCell::ZERO).id();
ENTITY.set(entity).ok();
});
};
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Update, setup)
.update();
let hash = *app
.world()
.entity(*ENTITY.get().unwrap())
.get::<GridHash<i32>>()
.get::<GridHash>()
.unwrap();
assert!(app
.world()
.resource::<GridHashMap<i32>>()
.get(&hash)
.is_some());
assert!(app.world().resource::<GridHashMap>().get(&hash).is_some());
app.world_mut().despawn(*ENTITY.get().unwrap());
app.update();
assert!(app
.world()
.resource::<GridHashMap<i32>>()
.get(&hash)
.is_none());
assert!(app.world().resource::<GridHashMap>().get(&hash).is_none());
}
#[test]
@ -177,7 +167,7 @@ mod tests {
}
let setup = |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
let a = root.spawn_spatial(GridCell::new(0, 1, 2)).id();
let b = root.spawn_spatial(GridCell::new(0, 1, 2)).id();
let c = root.spawn_spatial(GridCell::new(5, 5, 5)).id();
@ -194,12 +184,12 @@ mod tests {
};
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Update, setup);
app.update();
let mut spatial_hashes = app.world_mut().query::<&GridHash<i32>>();
let mut spatial_hashes = app.world_mut().query::<&GridHash>();
let parent = app.world().resource::<ParentSet>().clone();
let child = app.world().resource::<ChildSet>().clone();
@ -236,7 +226,7 @@ mod tests {
let entities = &app
.world()
.resource::<GridHashMap<i32>>()
.resource::<GridHashMap>()
.get(spatial_hashes.get(app.world(), parent.a).unwrap())
.unwrap()
.entities;
@ -261,7 +251,7 @@ mod tests {
}
let setup = |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
let a = root.spawn_spatial(GridCell::new(0, 0, 0)).id();
let b = root.spawn_spatial(GridCell::new(1, 1, 1)).id();
let c = root.spawn_spatial(GridCell::new(2, 2, 2)).id();
@ -271,7 +261,7 @@ mod tests {
};
let mut app = App::new();
app.add_plugins(GridHashPlugin::<i32>::default())
app.add_plugins(GridHashPlugin::<()>::default())
.add_systems(Startup, setup);
app.update();
@ -283,7 +273,7 @@ mod tests {
.get(app.world(), entities.a)
.unwrap();
let map = app.world().resource::<GridHashMap<i32>>();
let map = app.world().resource::<GridHashMap>();
let entry = map.get(&GridHash::new(parent, &GridCell::ZERO)).unwrap();
let neighbors: HashSet<Entity> = map.nearby(entry).entities().collect();
@ -311,40 +301,40 @@ mod tests {
static ROOT: OnceLock<Entity> = OnceLock::new();
let setup = |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
root.spawn_spatial((GridCell::<i32>::ZERO, Player));
root.spawn_spatial(GridCell::<i32>::ZERO);
root.spawn_spatial(GridCell::<i32>::ZERO);
commands.spawn_big_space_default(|root| {
root.spawn_spatial((GridCell::ZERO, Player));
root.spawn_spatial(GridCell::ZERO);
root.spawn_spatial(GridCell::ZERO);
ROOT.set(root.id()).ok();
});
};
let mut app = App::new();
app.add_plugins((
GridHashPlugin::<i32>::default(),
GridHashPlugin::<i32, With<Player>>::default(),
GridHashPlugin::<i32, Without<Player>>::default(),
GridHashPlugin::<()>::default(),
GridHashPlugin::<With<Player>>::default(),
GridHashPlugin::<Without<Player>>::default(),
))
.add_systems(Startup, setup)
.update();
let zero_hash = GridHash::from_parent(*ROOT.get().unwrap(), &GridCell::ZERO);
let map = app.world().resource::<GridHashMap<i32>>();
let map = app.world().resource::<GridHashMap>();
assert_eq!(
map.get(&zero_hash).unwrap().entities.iter().count(),
3,
"There are a total of 3 spatial entities"
);
let map = app.world().resource::<GridHashMap<i32, With<Player>>>();
let map = app.world().resource::<GridHashMap<With<Player>>>();
assert_eq!(
map.get(&zero_hash).unwrap().entities.iter().count(),
1,
"There is only one entity with the Player component"
);
let map = app.world().resource::<GridHashMap<i32, Without<Player>>>();
let map = app.world().resource::<GridHashMap<Without<Player>>>();
assert_eq!(
map.get(&zero_hash).unwrap().entities.iter().count(),
2,
@ -366,7 +356,7 @@ mod tests {
}
let setup = |mut commands: Commands| {
commands.spawn_big_space_default::<i32>(|root| {
commands.spawn_big_space_default(|root| {
let a = root.spawn_spatial(GridCell::new(0, 0, 0)).id();
let b = root.spawn_spatial(GridCell::new(1, 1, 1)).id();
let c = root.spawn_spatial(GridCell::new(2, 2, 2)).id();
@ -376,18 +366,15 @@ mod tests {
};
let mut app = App::new();
app.add_plugins((
BigSpacePlugin::<i32>::default(),
GridHashPlugin::<i32>::default(),
))
.add_systems(Startup, setup);
app.add_plugins((BigSpacePlugin::default(), GridHashPlugin::<()>::default()))
.add_systems(Startup, setup);
app.update();
let entities = app.world().resource::<Entities>().clone();
let get_hash = |app: &mut App, entity| {
*app.world_mut()
.query::<&GridHash<i32>>()
.query::<&GridHash>()
.get(app.world(), entity)
.unwrap()
};
@ -395,7 +382,7 @@ mod tests {
let a_hash_t0 = get_hash(&mut app, entities.a);
let b_hash_t0 = get_hash(&mut app, entities.b);
let c_hash_t0 = get_hash(&mut app, entities.c);
let map = app.world().resource::<GridHashMap<i32>>();
let map = app.world().resource::<GridHashMap>();
assert!(map.just_inserted().contains(&a_hash_t0));
assert!(map.just_inserted().contains(&b_hash_t0));
assert!(map.just_inserted().contains(&c_hash_t0));
@ -403,7 +390,7 @@ mod tests {
// Move entities and run an update
app.world_mut()
.entity_mut(entities.a)
.get_mut::<GridCell<i32>>()
.get_mut::<GridCell>()
.unwrap()
.z += 1;
app.world_mut()
@ -417,7 +404,7 @@ mod tests {
let a_hash_t1 = get_hash(&mut app, entities.a);
let b_hash_t1 = get_hash(&mut app, entities.b);
let c_hash_t1 = get_hash(&mut app, entities.c);
let map = app.world().resource::<GridHashMap<i32>>();
let map = app.world().resource::<GridHashMap>();
// Last grid
assert!(map.just_removed().contains(&a_hash_t0)); // Moved cell

View File

@ -10,19 +10,17 @@ use bevy_utils::{
Instant, PassHash,
};
use super::{GridCell, GridHash, GridHashMap, GridHashMapFilter, GridHashMapSystem, GridPrecision};
use super::{GridCell, GridHash, GridHashMap, GridHashMapFilter, GridHashMapSystem};
pub use private::GridPartition;
/// Adds support for spatial partitioning. Requires [`GridHashPlugin`](super::GridHashPlugin).
pub struct GridPartitionPlugin<P, F = ()>(PhantomData<(P, F)>)
pub struct GridPartitionPlugin<F = ()>(PhantomData<F>)
where
P: GridPrecision,
F: GridHashMapFilter;
impl<P, F> Default for GridPartitionPlugin<P, F>
impl<F> Default for GridPartitionPlugin<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn default() -> Self {
@ -30,15 +28,14 @@ where
}
}
impl<P, F> Plugin for GridPartitionPlugin<P, F>
impl<F> Plugin for GridPartitionPlugin<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn build(&self, app: &mut App) {
app.init_resource::<GridPartitionMap<P, F>>().add_systems(
app.init_resource::<GridPartitionMap<F>>().add_systems(
PostUpdate,
GridPartitionMap::<P, F>::update
GridPartitionMap::<F>::update
.in_set(GridHashMapSystem::UpdatePartition)
.after(GridHashMapSystem::UpdateMap),
);
@ -68,22 +65,20 @@ impl Hash for GridPartitionId {
/// Partitions divide space into independent groups of cells.
///
/// The map depends on and is built from a corresponding [`GridHashMap`] with the same
/// `P:`[`GridPrecision`] and `F:`[`GridHashMapFilter`].
/// `F:`[`GridHashMapFilter`].
#[derive(Resource)]
pub struct GridPartitionMap<P, F = ()>
pub struct GridPartitionMap<F = ()>
where
P: GridPrecision,
F: GridHashMapFilter,
{
partitions: HashMap<GridPartitionId, GridPartition<P>>,
reverse_map: HashMap<GridHash<P>, GridPartitionId, PassHash>,
partitions: HashMap<GridPartitionId, GridPartition>,
reverse_map: HashMap<GridHash, GridPartitionId, PassHash>,
next_partition: u64,
spooky: PhantomData<F>,
}
impl<P, F> Default for GridPartitionMap<P, F>
impl<F> Default for GridPartitionMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
fn default() -> Self {
@ -96,44 +91,42 @@ where
}
}
impl<P, F> Deref for GridPartitionMap<P, F>
impl<F> Deref for GridPartitionMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
type Target = HashMap<GridPartitionId, GridPartition<P>>;
type Target = HashMap<GridPartitionId, GridPartition>;
fn deref(&self) -> &Self::Target {
&self.partitions
}
}
impl<P, F> GridPartitionMap<P, F>
impl<F> GridPartitionMap<F>
where
P: GridPrecision,
F: GridHashMapFilter,
{
/// Returns a reference to the [`GridPartition`], if it exists.
#[inline]
pub fn resolve(&self, id: &GridPartitionId) -> Option<&GridPartition<P>> {
pub fn resolve(&self, id: &GridPartitionId) -> Option<&GridPartition> {
self.partitions.get(id)
}
/// Searches for the [`GridPartition`] that contains this `hash`, returning the partition's
/// [`GridPartitionId`] if the hash is found in any partition.
#[inline]
pub fn get(&self, hash: &GridHash<P>) -> Option<&GridPartitionId> {
pub fn get(&self, hash: &GridHash) -> Option<&GridPartitionId> {
self.reverse_map.get(hash)
}
/// Iterates over all [`GridPartition`]s.
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&GridPartitionId, &GridPartition<P>)> {
pub fn iter(&self) -> impl Iterator<Item = (&GridPartitionId, &GridPartition)> {
self.partitions.iter()
}
#[inline]
fn insert(&mut self, partition: GridPartitionId, set: HashSet<GridHash<P>, PassHash>) {
fn insert(&mut self, partition: GridPartitionId, set: HashSet<GridHash, PassHash>) {
let Some(hash) = set.iter().next() else {
return;
};
@ -151,7 +144,7 @@ where
}
#[inline]
fn push(&mut self, partition: &GridPartitionId, hash: &GridHash<P>) {
fn push(&mut self, partition: &GridPartitionId, hash: &GridHash) {
if let Some(partition) = self.partitions.get_mut(partition) {
partition.insert(*hash)
} else {
@ -161,7 +154,7 @@ where
}
#[inline]
fn remove(&mut self, hash: &GridHash<P>) {
fn remove(&mut self, hash: &GridHash) {
let Some(old_id) = self.reverse_map.remove(hash) else {
return;
};
@ -217,12 +210,12 @@ where
fn update(
mut partition_map: ResMut<Self>,
mut timing: ResMut<crate::timing::GridHashStats>,
hash_grid: Res<GridHashMap<P, F>>,
hash_grid: Res<GridHashMap<F>>,
// Scratch space allocations
mut added_neighbors: Local<Vec<GridPartitionId>>,
mut adjacent_to_removals: Local<HashMap<GridPartitionId, HashSet<GridHash<P>, PassHash>>>,
mut split_candidates: Local<Vec<(GridPartitionId, HashSet<GridHash<P>, PassHash>)>>,
mut split_results: Local<Vec<Vec<SplitResult<P>>>>,
mut adjacent_to_removals: Local<HashMap<GridPartitionId, HashSet<GridHash, PassHash>>>,
mut split_candidates: Local<Vec<(GridPartitionId, HashSet<GridHash, PassHash>)>>,
mut split_results: Local<Vec<Vec<SplitResult>>>,
) {
let start = Instant::now();
for added_hash in hash_grid.just_inserted().iter() {
@ -353,37 +346,39 @@ where
}
}
struct SplitResult<P: GridPrecision> {
struct SplitResult {
original_partition_id: GridPartitionId,
new_partitions: Vec<HashSet<GridHash<P>, PassHash>>,
new_partitions: Vec<HashSet<GridHash, PassHash>>,
}
/// A private module to ensure the internal fields of the partition are not accessed directly.
/// Needed to ensure invariants are upheld.
mod private {
use super::{GridCell, GridHash, GridPrecision};
use super::{GridCell, GridHash};
use crate::precision::GridPrecision;
use bevy_ecs::prelude::*;
use bevy_utils::{hashbrown::HashSet, PassHash};
/// A group of nearby [`GridCell`](crate::GridCell)s in an island disconnected from all other
/// [`GridCell`](crate::GridCell)s.
#[derive(Debug)]
pub struct GridPartition<P: GridPrecision> {
pub struct GridPartition {
grid: Entity,
tables: Vec<HashSet<GridHash<P>, PassHash>>,
min: GridCell<P>,
max: GridCell<P>,
tables: Vec<HashSet<GridHash, PassHash>>,
min: GridCell,
max: GridCell,
}
impl<P: GridPrecision> GridPartition<P> {
impl GridPartition {
/// Returns `true` if the `hash` is in this partition.
#[inline]
pub fn contains(&self, hash: &GridHash<P>) -> bool {
pub fn contains(&self, hash: &GridHash) -> bool {
self.tables.iter().any(|table| table.contains(hash))
}
/// Iterates over all [`GridHash`]s in this partition.
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &GridHash<P>> {
pub fn iter(&self) -> impl Iterator<Item = &GridHash> {
self.tables.iter().flat_map(|table| table.iter())
}
@ -400,12 +395,12 @@ mod private {
}
/// The maximum grid cell extent of the partition.
pub fn max(&self) -> GridCell<P> {
pub fn max(&self) -> GridCell {
self.max
}
/// The minimum grid cell extent of the partition.
pub fn min(&self) -> GridCell<P> {
pub fn min(&self) -> GridCell {
self.min
}
@ -416,12 +411,12 @@ mod private {
}
/// Private internal methods
impl<P: GridPrecision> GridPartition<P> {
impl GridPartition {
pub(crate) fn new(
grid: Entity,
tables: Vec<HashSet<GridHash<P>, PassHash>>,
min: GridCell<P>,
max: GridCell<P>,
tables: Vec<HashSet<GridHash, PassHash>>,
min: GridCell,
max: GridCell,
) -> Self {
Self {
grid,
@ -441,7 +436,7 @@ mod private {
const MIN_TABLE_SIZE: usize = 20_000;
#[inline]
pub(crate) fn insert(&mut self, cell: GridHash<P>) {
pub(crate) fn insert(&mut self, cell: GridHash) {
if self.contains(&cell) {
return;
}
@ -467,7 +462,7 @@ mod private {
}
#[inline]
pub(crate) fn extend(&mut self, mut partition: GridPartition<P>) {
pub(crate) fn extend(&mut self, mut partition: GridPartition) {
for mut table in partition.tables.drain(..) {
if table.len() < Self::MIN_TABLE_SIZE {
if let Some(i) = self.smallest_table() {
@ -487,7 +482,7 @@ mod private {
/// Removes a grid hash from the partition. Returns whether the value was present.
#[inline]
pub(crate) fn remove(&mut self, hash: &GridHash<P>) -> bool {
pub(crate) fn remove(&mut self, hash: &GridHash) -> bool {
let Some(i_table) = self
.tables
.iter_mut()
@ -524,7 +519,7 @@ mod private {
{
self.min = min
} else {
self.min = GridCell::ONE * P::from_f64(1e10);
self.min = GridCell::ONE * 1e10f64 as GridPrecision;
}
}
@ -539,7 +534,7 @@ mod private {
{
self.max = max
} else {
self.min = GridCell::ONE * P::from_f64(-1e10);
self.min = GridCell::ONE * -1e10 as GridPrecision;
}
}
}

View File

@ -106,7 +106,7 @@
//! grid, in high precision.
//!
//! Entities at the root of bevy's entity hierarchy are not in a grid. This allows plugins from the
//! rest of the ecosystem to operate normally, such as bevy_ui, which relies on the built in
//! rest of the ecosystem to operate normally, such as bevy_ui, which relies on the built-in
//! transform propagation system. This also means that if you don't need to place entities in a
//! high-precision grid, you don't have to, as the process is opt-in. The high-precision
//! hierarchical grids are explicit. Each high-precision tree must have a [`BigSpace`] at the root,
@ -209,7 +209,6 @@ pub mod floating_origins;
pub mod grid;
pub mod hash;
pub mod plugin;
pub mod precision;
pub mod timing;
pub mod validation;
pub mod world_query;
@ -230,7 +229,7 @@ pub mod prelude {
pub use debug::FloatingOriginDebugPlugin;
pub use floating_origins::{BigSpace, FloatingOrigin};
pub use grid::{
cell::{GridCell, GridCellAny},
cell::GridCell,
local_origin::{Grids, GridsMut, LocalFloatingOrigin},
Grid,
};
@ -244,3 +243,57 @@ pub mod prelude {
pub use precision::GridPrecision;
pub use world_query::{GridTransform, GridTransformOwned, GridTransformReadOnly};
}
/// Contains [`GridPrecision`], allowing `big_space` to work with many grid integer sizes.
///
/// The integer type used is controlled with compile time feature flags like `i8`. The crate
/// defaults to `i64` grids if none is specified.
///
/// Larger grids result in a larger usable volume, at the cost of increased memory usage. In
/// addition, some platforms may be unable to use larger numeric types (e.g. [`i128`]).
///
/// [`big_space`](crate) is generic over a few integer types to allow you to select the grid size
/// you need. Assuming you are using a grid cell edge length of 10,000 meters, and `1.0` == 1 meter,
/// these correspond to a total usable volume of a cube with the following edge lengths:
///
/// - `i8`: 2,560 km = 74% of the diameter of the Moon
/// - `i16`: 655,350 km = 85% of the diameter of the Moon's orbit around Earth
/// - `i32`: 0.0045 light years = ~4 times the width of the solar system
/// - `i64`: 19.5 million light years = ~100 times the width of the milky way galaxy
/// - `i128`: 3.6e+26 light years = ~3.9e+15 times the width of the observable universe
///
/// where `usable_edge_length = 2^(integer_bits) * cell_edge_length`, resulting in a worst case
/// precision of 0.5mm in any of these cases.
///
/// This can also be used for small scales. With a cell edge length of `1e-11`, and using `i128`,
/// there is enough precision to render objects the size of protons anywhere in the observable
/// universe.
pub mod precision {
#[allow(unused_imports)] // Docs
use super::*;
#[cfg(feature = "i8")]
/// Adds 8 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i8;
#[cfg(feature = "i16")]
/// Adds 16 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i16;
#[cfg(feature = "i32")]
/// Adds 32 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i32;
#[cfg(feature = "i64")]
/// Adds 64 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i64;
#[cfg(feature = "i128")]
/// Adds 128 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i128;
#[cfg(not(any(
feature = "i8",
feature = "i16",
feature = "i32",
feature = "i64",
feature = "i128"
)))]
/// Adds 64 bits of precision to bevy's [`Transform`]. See [`precision`].
pub type GridPrecision = i64;
}

View File

@ -3,30 +3,25 @@
use crate::prelude::*;
use bevy_app::prelude::*;
use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_transform::prelude::*;
use std::marker::PhantomData;
/// Add this plugin to your [`App`] for floating origin functionality.
pub struct BigSpacePlugin<P: GridPrecision> {
phantom: PhantomData<P>,
pub struct BigSpacePlugin {
validate_hierarchies: bool,
}
impl<P: GridPrecision> BigSpacePlugin<P> {
impl BigSpacePlugin {
/// Create a big space plugin, and specify whether hierarchy validation should be enabled.
pub fn new(validate_hierarchies: bool) -> Self {
Self {
phantom: PhantomData::<P>,
validate_hierarchies,
}
}
}
impl<P: GridPrecision> Default for BigSpacePlugin<P> {
impl Default for BigSpacePlugin {
fn default() -> Self {
Self {
phantom: PhantomData,
validate_hierarchies: cfg!(debug_assertions),
}
}
@ -42,9 +37,7 @@ pub enum FloatingOriginSystem {
PropagateLowPrecision,
}
impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetTypeRegistration> Plugin
for BigSpacePlugin<P>
{
impl Plugin for BigSpacePlugin {
fn build(&self, app: &mut App) {
// Silence bevy's built-in error spam about GlobalTransforms in the hierarchy
app.insert_resource(bevy_hierarchy::ReportHierarchyIssue::<GlobalTransform>::new(false));
@ -54,21 +47,21 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
let system_set_config = || {
(
Grid::<P>::tag_low_precision_roots // loose ordering on this set
Grid::tag_low_precision_roots // loose ordering on this set
.after(FloatingOriginSystem::Init)
.before(FloatingOriginSystem::PropagateLowPrecision),
(
GridCell::<P>::recenter_large_transforms,
GridCell::recenter_large_transforms,
BigSpace::find_floating_origin,
)
.in_set(FloatingOriginSystem::RecenterLargeTransforms),
LocalFloatingOrigin::<P>::compute_all
LocalFloatingOrigin::compute_all
.in_set(FloatingOriginSystem::LocalFloatingOrigins)
.after(FloatingOriginSystem::RecenterLargeTransforms),
Grid::<P>::propagate_high_precision
Grid::propagate_high_precision
.in_set(FloatingOriginSystem::PropagateHighPrecision)
.after(FloatingOriginSystem::LocalFloatingOrigins),
Grid::<P>::propagate_low_precision
Grid::propagate_low_precision
.in_set(FloatingOriginSystem::PropagateLowPrecision)
.after(FloatingOriginSystem::PropagateHighPrecision),
)
@ -79,9 +72,8 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
// Reflect
.register_type::<Transform>()
.register_type::<GlobalTransform>()
.register_type::<GridCell<P>>()
.register_type::<GridCellAny>()
.register_type::<Grid<P>>()
.register_type::<GridCell>()
.register_type::<Grid>()
.register_type::<BigSpace>()
.register_type::<FloatingOrigin>()
// Meat of the plugin, once on startup, as well as every update
@ -90,7 +82,7 @@ impl<P: GridPrecision + Reflect + FromReflect + TypePath + bevy_reflect::GetType
// Validation
.add_systems(
PostUpdate,
crate::validation::validate_hierarchy::<crate::validation::SpatialHierarchyRoot<P>>
crate::validation::validate_hierarchy::<crate::validation::SpatialHierarchyRoot>
.after(TransformSystem::TransformPropagate)
.run_if({
let run = self.validate_hierarchies;

View File

@ -1,255 +0,0 @@
//! Contains the [`GridPrecision`] trait and its implementations.
use std::{
hash::Hash,
ops::{Add, Mul},
};
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath, Typed};
/// Used to make the floating origin plugin generic over many grid sizes.
///
/// Larger grids result in a larger useable volume, at the cost of increased memory usage. In
/// addition, some platforms may be unable to use larger numeric types (e.g. [`i128`]).
///
/// [`big_space`](crate) is generic over a few integer types to allow you to select the grid size
/// you need. Assuming you are using a grid cell edge length of 10,000 meters, and `1.0` == 1 meter,
/// these correspond to a total usable volume of a cube with the following edge lengths:
///
/// - `i8`: 2,560 km = 74% of the diameter of the Moon
/// - `i16`: 655,350 km = 85% of the diameter of the Moon's orbit around Earth
/// - `i32`: 0.0045 light years = ~4 times the width of the solar system
/// - `i64`: 19.5 million light years = ~100 times the width of the milky way galaxy
/// - `i128`: 3.6e+26 light years = ~3.9e+15 times the width of the observable universe
///
/// where `usable_edge_length = 2^(integer_bits) * cell_edge_length`, resulting in a worst case
/// precision of 0.5mm in any of these cases.
///
/// This can also be used for small scales. With a cell edge length of `1e-11`, and using `i128`,
/// there is enough precision to render objects the size of protons anywhere in the observable
/// universe.
///
/// # Note
///
/// Be sure you are using the same grid index precision everywhere. It might be a good idea to
/// define a type alias!
///
/// ```
/// # use big_space::prelude::*;
/// type GalacticGrid = GridCell<i64>;
/// ```
///
/// Additionally, consider using the provided command extensions in [`crate::commands`] to
/// completely eliminate the use of this generic, and prevent many errors.
pub trait GridPrecision:
Default
+ PartialEq
+ Eq
+ PartialOrd
+ Ord
+ Hash
+ Copy
+ Clone
+ Send
+ Sync
+ Reflect
+ FromReflect
+ GetTypeRegistration
+ TypePath
+ Typed
+ Add
+ Add<Self, Output = Self>
+ Mul<Self, Output = Self>
+ std::fmt::Debug
+ std::fmt::Display
+ 'static
{
/// The zero value for this type.
const ZERO: Self;
/// The value of `1` for this type.
const ONE: Self;
/// Adds `rhs` to `self`, wrapping when overflow would occur.
fn wrapping_add(self, rhs: Self) -> Self;
/// Adds `rhs` to `self`, wrapping when overflow would occur.
fn wrapping_add_i32(self, rhs: i32) -> Self;
/// Subtracts `rhs` from `self`, wrapping when overflow would occur.
fn wrapping_sub(self, rhs: Self) -> Self;
/// Multiplies `self` by `rhs`.
fn mul(self, rhs: Self) -> Self;
/// Casts `self` as a double precision float.
fn as_f64(self) -> f64;
/// Casts a double precision float into `Self`.
fn from_f64(input: f64) -> Self;
/// Casts a single precision float into `Self`.
fn from_f32(input: f32) -> Self;
}
impl GridPrecision for i8 {
const ZERO: Self = 0;
const ONE: Self = 1;
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::wrapping_add(self, rhs)
}
#[inline]
fn wrapping_add_i32(self, rhs: i32) -> Self {
Self::wrapping_add(self, rhs as Self)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
Self::wrapping_sub(self, rhs)
}
#[inline]
fn mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
#[inline]
fn from_f64(input: f64) -> Self {
input as Self
}
#[inline]
fn from_f32(input: f32) -> Self {
input as Self
}
}
impl GridPrecision for i16 {
const ZERO: Self = 0;
const ONE: Self = 1;
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::wrapping_add(self, rhs)
}
#[inline]
fn wrapping_add_i32(self, rhs: i32) -> Self {
Self::wrapping_add(self, rhs as Self)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
Self::wrapping_sub(self, rhs)
}
#[inline]
fn mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
#[inline]
fn from_f64(input: f64) -> Self {
input as Self
}
#[inline]
fn from_f32(input: f32) -> Self {
input as Self
}
}
impl GridPrecision for i32 {
const ZERO: Self = 0;
const ONE: Self = 1;
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::wrapping_add(self, rhs)
}
#[inline]
fn wrapping_add_i32(self, rhs: i32) -> Self {
Self::wrapping_add(self, rhs as Self)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
Self::wrapping_sub(self, rhs)
}
#[inline]
fn mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
#[inline]
fn from_f64(input: f64) -> Self {
input as Self
}
#[inline]
fn from_f32(input: f32) -> Self {
input as Self
}
}
impl GridPrecision for i64 {
const ZERO: Self = 0;
const ONE: Self = 1;
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::wrapping_add(self, rhs)
}
#[inline]
fn wrapping_add_i32(self, rhs: i32) -> Self {
Self::wrapping_add(self, rhs as Self)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
Self::wrapping_sub(self, rhs)
}
#[inline]
fn mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
#[inline]
fn from_f64(input: f64) -> Self {
input as Self
}
#[inline]
fn from_f32(input: f32) -> Self {
input as Self
}
}
impl GridPrecision for i128 {
const ZERO: Self = 0;
const ONE: Self = 1;
#[inline]
fn wrapping_add(self, rhs: Self) -> Self {
Self::wrapping_add(self, rhs)
}
#[inline]
fn wrapping_add_i32(self, rhs: i32) -> Self {
Self::wrapping_add(self, rhs as Self)
}
#[inline]
fn wrapping_sub(self, rhs: Self) -> Self {
Self::wrapping_sub(self, rhs)
}
#[inline]
fn mul(self, rhs: Self) -> Self {
self * rhs
}
#[inline]
fn as_f64(self) -> f64 {
self as f64
}
#[inline]
fn from_f64(input: f64) -> Self {
input as Self
}
#[inline]
fn from_f32(input: f32) -> Self {
input as Self
}
}

View File

@ -4,13 +4,13 @@ use bevy::prelude::*;
#[test]
fn changing_floating_origin_updates_global_transform() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default());
app.add_plugins(BigSpacePlugin::default());
let first = app
.world_mut()
.spawn((
Transform::from_translation(Vec3::new(150.0, 0.0, 0.0)),
GridCell::<i32>::new(5, 0, 0),
GridCell::new(5, 0, 0),
FloatingOrigin,
))
.id();
@ -19,12 +19,12 @@ fn changing_floating_origin_updates_global_transform() {
.world_mut()
.spawn((
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
GridCell::<i32>::new(0, -15, 0),
GridCell::new(0, -15, 0),
))
.id();
app.world_mut()
.spawn(BigSpaceRootBundle::<i32>::default())
.spawn(BigSpaceRootBundle::default())
.add_children(&[first, second]);
app.update();
@ -45,13 +45,13 @@ fn changing_floating_origin_updates_global_transform() {
#[test]
fn child_global_transforms_are_updated_when_floating_origin_changes() {
let mut app = App::new();
app.add_plugins(BigSpacePlugin::<i32>::default());
app.add_plugins(BigSpacePlugin::default());
let first = app
.world_mut()
.spawn((
Transform::from_translation(Vec3::new(150.0, 0.0, 0.0)),
GridCell::<i32>::new(5, 0, 0),
GridCell::new(5, 0, 0),
FloatingOrigin,
))
.id();
@ -60,13 +60,13 @@ fn child_global_transforms_are_updated_when_floating_origin_changes() {
.world_mut()
.spawn((
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
GridCell::<i32>::new(0, -15, 0),
GridCell::new(0, -15, 0),
))
.with_child(Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)))
.id();
app.world_mut()
.spawn(BigSpaceRootBundle::<i32>::default())
.spawn(BigSpaceRootBundle::default())
.add_children(&[first, second]);
app.update();

View File

@ -1,14 +1,11 @@
//! Tools for validating high-precision transform hierarchies
use std::marker::PhantomData;
use crate::prelude::*;
use bevy_ecs::prelude::*;
use bevy_hierarchy::prelude::*;
use bevy_transform::prelude::*;
use bevy_utils::{HashMap, HashSet};
use crate::{grid::Grid, precision::GridPrecision, BigSpace, FloatingOrigin, GridCell};
use crate::{grid::Grid, BigSpace, FloatingOrigin, GridCell};
struct ValidationStackEntry {
parent_node: Box<dyn ValidHierarchyNode>,
@ -110,7 +107,7 @@ Because it is a child of a {:#?}, the entity must be one of the following:
However, the entity has the following components, which does not match any of the allowed archetypes listed above:
{}
Common errors include:
- Using mismatched GridPrecisions, like GridCell<i32> and GridCell<i64>
- Using mismatched GridPrecisions, like GridCell and GridCell<i64>
- Spawning an entity with a GridCell as a child of an entity without a Grid.
If possible, use commands.spawn_big_space(), which prevents these errors, instead of manually assembling a hierarchy. See {} for details.", entity, stack_entry.parent_node.name(), stack_entry.parent_node.name(), possibilities, inspect, file!());
@ -166,9 +163,9 @@ mod sealed {
/// The root hierarchy validation struct, used as a generic parameter in [`crate::validation`].
#[derive(Default, Clone)]
pub struct SpatialHierarchyRoot<P: GridPrecision>(PhantomData<P>);
pub struct SpatialHierarchyRoot;
impl<P: GridPrecision> ValidHierarchyNode for SpatialHierarchyRoot<P> {
impl ValidHierarchyNode for SpatialHierarchyRoot {
fn name(&self) -> &'static str {
"Root"
}
@ -177,40 +174,40 @@ impl<P: GridPrecision> ValidHierarchyNode for SpatialHierarchyRoot<P> {
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<RootFrame<P>>::default(),
Box::<RootSpatialLowPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<RootFrame>::default(),
Box::<RootSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct AnyNonSpatial<P: GridPrecision>(PhantomData<P>);
struct AnyNonSpatial;
impl<P: GridPrecision> ValidHierarchyNode for AnyNonSpatial<P> {
impl ValidHierarchyNode for AnyNonSpatial {
fn name(&self) -> &'static str {
"Any non-spatial entity"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.without::<GridCellAny>()
.without::<GridCell>()
.without::<Transform>()
.without::<GlobalTransform>()
.without::<BigSpace>()
.without::<Grid<P>>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![Box::<AnyNonSpatial<P>>::default()]
vec![Box::<AnyNonSpatial>::default()]
}
}
#[derive(Default, Clone)]
struct RootFrame<P: GridPrecision>(PhantomData<P>);
struct RootFrame;
impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
impl ValidHierarchyNode for RootFrame {
fn name(&self) -> &'static str {
"Root of a BigSpace"
}
@ -218,9 +215,9 @@ impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<BigSpace>()
.with::<Grid<P>>()
.with::<Grid>()
.with::<GlobalTransform>()
.without::<GridCellAny>()
.without::<GridCell>()
.without::<Transform>()
.without::<Parent>()
.without::<FloatingOrigin>();
@ -228,18 +225,18 @@ impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildFrame<P>>::default(),
Box::<ChildSpatialLowPrecision<P>>::default(),
Box::<ChildSpatialHighPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildFrame>::default(),
Box::<ChildSpatialLowPrecision>::default(),
Box::<ChildSpatialHighPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct RootSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
struct RootSpatialLowPrecision;
impl<P: GridPrecision> ValidHierarchyNode for RootSpatialLowPrecision<P> {
impl ValidHierarchyNode for RootSpatialLowPrecision {
fn name(&self) -> &'static str {
"Root of a Transform hierarchy at the root of the tree outside of any BigSpace"
}
@ -248,33 +245,33 @@ impl<P: GridPrecision> ValidHierarchyNode for RootSpatialLowPrecision<P> {
query
.with::<Transform>()
.with::<GlobalTransform>()
.without::<GridCellAny>()
.without::<GridCell>()
.without::<BigSpace>()
.without::<Grid<P>>()
.without::<Grid>()
.without::<Parent>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildFrame<P: GridPrecision>(PhantomData<P>);
struct ChildFrame;
impl<P: GridPrecision> ValidHierarchyNode for ChildFrame<P> {
impl ValidHierarchyNode for ChildFrame {
fn name(&self) -> &'static str {
"Non-root Grid"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<Grid<P>>()
.with::<GridCell<P>>()
.with::<Grid>()
.with::<GridCell>()
.with::<Transform>()
.with::<GlobalTransform>()
.with::<Parent>()
@ -283,18 +280,18 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildFrame<P> {
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildFrame<P>>::default(),
Box::<ChildRootSpatialLowPrecision<P>>::default(),
Box::<ChildSpatialHighPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildFrame>::default(),
Box::<ChildRootSpatialLowPrecision>::default(),
Box::<ChildSpatialHighPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildRootSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
struct ChildRootSpatialLowPrecision;
impl<P: GridPrecision> ValidHierarchyNode for ChildRootSpatialLowPrecision<P> {
impl ValidHierarchyNode for ChildRootSpatialLowPrecision {
fn name(&self) -> &'static str {
"Root of a low-precision Transform hierarchy, within a BigSpace"
}
@ -305,24 +302,24 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildRootSpatialLowPrecision<P> {
.with::<GlobalTransform>()
.with::<Parent>()
.with::<crate::grid::propagation::LowPrecisionRoot>()
.without::<GridCellAny>()
.without::<GridCell>()
.without::<BigSpace>()
.without::<Grid<P>>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
struct ChildSpatialLowPrecision;
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialLowPrecision<P> {
impl ValidHierarchyNode for ChildSpatialLowPrecision {
fn name(&self) -> &'static str {
"Non-root low-precision spatial entity"
}
@ -332,42 +329,42 @@ impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialLowPrecision<P> {
.with::<Transform>()
.with::<GlobalTransform>()
.with::<Parent>()
.without::<GridCellAny>()
.without::<GridCell>()
.without::<BigSpace>()
.without::<Grid<P>>()
.without::<Grid>()
.without::<FloatingOrigin>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildSpatialLowPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}
#[derive(Default, Clone)]
struct ChildSpatialHighPrecision<P: GridPrecision>(PhantomData<P>);
struct ChildSpatialHighPrecision;
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialHighPrecision<P> {
impl ValidHierarchyNode for ChildSpatialHighPrecision {
fn name(&self) -> &'static str {
"Non-root high precision spatial entity"
}
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
query
.with::<GridCell<P>>()
.with::<GridCell>()
.with::<Transform>()
.with::<GlobalTransform>()
.with::<Parent>()
.without::<BigSpace>()
.without::<Grid<P>>();
.without::<Grid>();
}
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
vec![
Box::<ChildRootSpatialLowPrecision<P>>::default(),
Box::<AnyNonSpatial<P>>::default(),
Box::<ChildRootSpatialLowPrecision>::default(),
Box::<AnyNonSpatial>::default(),
]
}
}

View File

@ -12,26 +12,26 @@ use bevy_transform::prelude::*;
/// to read from the position, use [`GridTransformReadOnly`] instead, as this will allow the bevy
/// ECS to run multiple queries using [`GridTransformReadOnly`] at the same time (just like multiple
/// queries with `&Transform` are fine).
pub struct GridTransform<P: GridPrecision> {
pub struct GridTransform {
/// Grid local transform
pub transform: &'static mut Transform,
/// The grid to which `transform` is relative to.
pub cell: &'static mut GridCell<P>,
pub cell: &'static mut GridCell,
}
impl<P: GridPrecision> GridTransformItem<'_, P> {
impl GridTransformItem<'_> {
/// Compute the global position with double precision.
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
pub fn position_double(&self, grid: &Grid) -> DVec3 {
grid.grid_position_double(&self.cell, &self.transform)
}
/// Compute the global position.
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
pub fn position(&self, grid: &Grid) -> Vec3 {
grid.grid_position(&self.cell, &self.transform)
}
/// Get a copy of the fields to work with.
pub fn to_owned(&self) -> GridTransformOwned<P> {
pub fn to_owned(&self) -> GridTransformOwned {
GridTransformOwned {
transform: *self.transform,
cell: *self.cell,
@ -39,19 +39,19 @@ impl<P: GridPrecision> GridTransformItem<'_, P> {
}
}
impl<P: GridPrecision> GridTransformReadOnlyItem<'_, P> {
impl GridTransformReadOnlyItem<'_> {
/// Compute the global position with double precision.
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
pub fn position_double(&self, grid: &Grid) -> DVec3 {
grid.grid_position_double(self.cell, self.transform)
}
/// Compute the global position.
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
pub fn position(&self, grid: &Grid) -> Vec3 {
grid.grid_position(self.cell, self.transform)
}
/// Get a copy of the fields to work with.
pub fn to_owned(&self) -> GridTransformOwned<P> {
pub fn to_owned(&self) -> GridTransformOwned {
GridTransformOwned {
transform: *self.transform,
cell: *self.cell,
@ -61,14 +61,14 @@ impl<P: GridPrecision> GridTransformReadOnlyItem<'_, P> {
/// A convenience wrapper that allows working with grid and transform easily
#[derive(Copy, Clone)]
pub struct GridTransformOwned<P: GridPrecision> {
pub struct GridTransformOwned {
/// Grid local transform
pub transform: Transform,
/// The grid to which `transform` is relative to.
pub cell: GridCell<P>,
pub cell: GridCell,
}
impl<P: GridPrecision> std::ops::Sub for GridTransformOwned<P> {
impl std::ops::Sub for GridTransformOwned {
type Output = Self;
/// Compute a new transform that maps from `source` to `self`.
@ -81,7 +81,7 @@ impl<P: GridPrecision> std::ops::Sub for GridTransformOwned<P> {
}
}
impl<P: GridPrecision> std::ops::Add for GridTransformOwned<P> {
impl std::ops::Add for GridTransformOwned {
type Output = Self;
/// Compute a new transform that shifts, scales and rotates `self` by `diff`.
@ -94,14 +94,14 @@ impl<P: GridPrecision> std::ops::Add for GridTransformOwned<P> {
}
}
impl<P: GridPrecision> GridTransformOwned<P> {
impl GridTransformOwned {
/// Compute the global position with double precision.
pub fn position_double(&self, grid: &Grid<P>) -> DVec3 {
pub fn position_double(&self, grid: &Grid) -> DVec3 {
grid.grid_position_double(&self.cell, &self.transform)
}
/// Compute the global position.
pub fn position(&self, grid: &Grid<P>) -> Vec3 {
pub fn position(&self, grid: &Grid) -> Vec3 {
grid.grid_position(&self.cell, &self.transform)
}
}