mirror of
https://github.com/eliasstepanik/big_space_with_trim.git
synced 2026-01-10 08:48:28 +00:00
Explicit BigSpaces (#22)
Changes the design of the plugin to work with multiple, independent high-precision hierarchies at the root with the `BigSpace` component at the root of each of these hierarchies. Closes #17 Closes #19 Closes #21
This commit is contained in:
parent
14db5acb64
commit
8721911b49
10
Cargo.toml
10
Cargo.toml
@ -15,13 +15,17 @@ bevy = { version = "0.13", default_features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
bevy = { version = "0.13", default-features = false, features = [
|
||||
"bevy_scene",
|
||||
"bevy_gltf",
|
||||
"bevy_winit",
|
||||
"default_font",
|
||||
"bevy_ui",
|
||||
"bevy_pbr",
|
||||
"x11",
|
||||
"tonemapping_luts",
|
||||
"multi-threaded",
|
||||
] }
|
||||
bevy-inspector-egui = "0.24"
|
||||
bevy_framepace = { version = "0.15", default-features = false }
|
||||
rand = "0.8.5"
|
||||
|
||||
@ -60,3 +64,9 @@ name = "planets"
|
||||
path = "examples/planets.rs"
|
||||
required-features = ["default"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
[[example]]
|
||||
name = "split_screen"
|
||||
path = "examples/split_screen.rs"
|
||||
required-features = ["default"]
|
||||
doc-scrape-examples = true
|
||||
|
||||
10
README.md
10
README.md
@ -19,9 +19,9 @@ https://user-images.githubusercontent.com/2632925/215318129-5bab3095-a7dd-455b-a
|
||||
|
||||
Lots of space to play in.
|
||||
|
||||
This is a simple floating origin plugin, useful if you want to work with very, very large scales. It works with bevy's existing `f32`-based `Transform`s, which means it's largely compatible with the bevy ecosystem. The plugin positions entities within large fixed precision grids, effectively adding precision to the location of objects.
|
||||
This is a floating origin plugin, useful if you want to work with very large or very small scales. It works with bevy's existing `f32`-based `Transform`s, which means it's largely compatible with the bevy ecosystem. The plugin positions entities within large fixed precision grids, effectively adding precision to the location of objects.
|
||||
|
||||
Additionally, you can use reference frames to nest high precision coordinate systems. For example you might want to put all entities on a planet into the same reference frame. You can then rotate this reference frame with the planet, and orbit that planet around a star.
|
||||
Additionally, you can use reference frames to nest high precision coordinate systems. For example you might want to put all entities on a planet's surface into the same reference frame. You can then rotate this reference frame with the planet, and orbit that planet around a star.
|
||||
|
||||
The plugin is generic over a few integer types, to trade off scale and precision for memory use. Some fun numbers with a worst case precision of 0.5mm:
|
||||
- `i8`: 2,560 km = 74% of the diameter of the Moon
|
||||
@ -30,12 +30,12 @@ The plugin is generic over a few integer types, to trade off scale and precision
|
||||
- `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
|
||||
|
||||
From the docs: https://docs.rs/big_space/latest/big_space/struct.GridCell.html
|
||||
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 quarks anywhere in the observable universe.
|
||||
|
||||
From the docs: https://docs.rs/big_space/latest/big_space/precision/trait.GridPrecision.html
|
||||
|
||||
# Bevy Version Support
|
||||
|
||||
I intend to track the `main` branch of Bevy. PRs supporting this are welcome!
|
||||
|
||||
| bevy | big_space |
|
||||
| ---- | --------- |
|
||||
| 0.13 | 0.5, 0.6 |
|
||||
|
||||
11
assets/models/low_poly_spaceship/license.txt
Normal file
11
assets/models/low_poly_spaceship/license.txt
Normal file
@ -0,0 +1,11 @@
|
||||
Model Information:
|
||||
* title: Low Poly Spaceship
|
||||
* source: https://sketchfab.com/3d-models/low-poly-spaceship-f854128cf78d4dafb28d16b3c15001ba
|
||||
* author: FriendlyCreep (https://sketchfab.com/FriendlyCreep)
|
||||
|
||||
Model License:
|
||||
* license type: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||
* requirements: Author must be credited. Commercial use is allowed.
|
||||
|
||||
If you use this 3D model in your project be sure to copy paste this credit wherever you share it:
|
||||
This work is based on "Low Poly Spaceship" (https://sketchfab.com/3d-models/low-poly-spaceship-f854128cf78d4dafb28d16b3c15001ba) by FriendlyCreep (https://sketchfab.com/FriendlyCreep) licensed under CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)
|
||||
BIN
assets/models/low_poly_spaceship/scene.bin
Normal file
BIN
assets/models/low_poly_spaceship/scene.bin
Normal file
Binary file not shown.
470
assets/models/low_poly_spaceship/scene.gltf
Normal file
470
assets/models/low_poly_spaceship/scene.gltf
Normal file
@ -0,0 +1,470 @@
|
||||
{
|
||||
"accessors": [
|
||||
{
|
||||
"bufferView": 2,
|
||||
"componentType": 5126,
|
||||
"count": 712,
|
||||
"max": [
|
||||
4.986988067626953,
|
||||
0.5560435652732849,
|
||||
9.616311073303223
|
||||
],
|
||||
"min": [
|
||||
-4.986988067626953,
|
||||
-1.969908356666565,
|
||||
-4.9558634757995605
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 8544,
|
||||
"componentType": 5126,
|
||||
"count": 712,
|
||||
"max": [
|
||||
1.0,
|
||||
0.9917697906494141,
|
||||
0.9999937415122986
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-0.9730851054191589,
|
||||
-0.9999937415122986
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"componentType": 5126,
|
||||
"count": 712,
|
||||
"max": [
|
||||
0.7559658885002136,
|
||||
0.75
|
||||
],
|
||||
"min": [
|
||||
0.1249999925494194,
|
||||
0.24999994039535522
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"componentType": 5125,
|
||||
"count": 1164,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 17088,
|
||||
"componentType": 5126,
|
||||
"count": 1768,
|
||||
"max": [
|
||||
5.272348403930664,
|
||||
0.6022237539291382,
|
||||
9.626778602600098
|
||||
],
|
||||
"min": [
|
||||
-5.272348403930664,
|
||||
-2.1259989738464355,
|
||||
-4.9558634757995605
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 38304,
|
||||
"componentType": 5126,
|
||||
"count": 1768,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 5696,
|
||||
"componentType": 5126,
|
||||
"count": 1768,
|
||||
"max": [
|
||||
0.875,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 4656,
|
||||
"componentType": 5125,
|
||||
"count": 5070,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 59520,
|
||||
"componentType": 5126,
|
||||
"count": 866,
|
||||
"max": [
|
||||
3.563509702682495,
|
||||
0.6022237539291382,
|
||||
9.626778602600098
|
||||
],
|
||||
"min": [
|
||||
-3.563509702682495,
|
||||
-1.5118601322174072,
|
||||
-4.9558634757995605
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 69912,
|
||||
"componentType": 5126,
|
||||
"count": 866,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
1.0
|
||||
],
|
||||
"min": [
|
||||
-1.0,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 19840,
|
||||
"componentType": 5126,
|
||||
"count": 866,
|
||||
"max": [
|
||||
0.875,
|
||||
0.75
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 24936,
|
||||
"componentType": 5125,
|
||||
"count": 1476,
|
||||
"type": "SCALAR"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 80304,
|
||||
"componentType": 5126,
|
||||
"count": 296,
|
||||
"max": [
|
||||
4.863146781921387,
|
||||
0.5145567655563354,
|
||||
9.446455955505371
|
||||
],
|
||||
"min": [
|
||||
-4.863146781921387,
|
||||
-1.8699951171875,
|
||||
-4.738320827484131
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 2,
|
||||
"byteOffset": 83856,
|
||||
"componentType": 5126,
|
||||
"count": 296,
|
||||
"max": [
|
||||
1.0,
|
||||
1.0,
|
||||
0.9979816675186157
|
||||
],
|
||||
"min": [
|
||||
-0.8001577854156494,
|
||||
-1.0,
|
||||
-1.0
|
||||
],
|
||||
"type": "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView": 1,
|
||||
"byteOffset": 26768,
|
||||
"componentType": 5126,
|
||||
"count": 296,
|
||||
"max": [
|
||||
0.7559658885002136,
|
||||
0.735460638999939
|
||||
],
|
||||
"min": [
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"type": "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView": 0,
|
||||
"byteOffset": 30840,
|
||||
"componentType": 5125,
|
||||
"count": 600,
|
||||
"type": "SCALAR"
|
||||
}
|
||||
],
|
||||
"asset": {
|
||||
"extras": {
|
||||
"author": "FriendlyCreep (https://sketchfab.com/FriendlyCreep)",
|
||||
"license": "CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/)",
|
||||
"source": "https://sketchfab.com/3d-models/low-poly-spaceship-f854128cf78d4dafb28d16b3c15001ba",
|
||||
"title": "Low Poly Spaceship"
|
||||
},
|
||||
"generator": "Sketchfab-12.68.0",
|
||||
"version": "2.0"
|
||||
},
|
||||
"bufferViews": [
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 33240,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34963
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 29136,
|
||||
"byteOffset": 33240,
|
||||
"byteStride": 8,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34962
|
||||
},
|
||||
{
|
||||
"buffer": 0,
|
||||
"byteLength": 87408,
|
||||
"byteOffset": 62376,
|
||||
"byteStride": 12,
|
||||
"name": "floatBufferViews",
|
||||
"target": 34962
|
||||
}
|
||||
],
|
||||
"buffers": [
|
||||
{
|
||||
"byteLength": 149784,
|
||||
"uri": "scene.bin"
|
||||
}
|
||||
],
|
||||
"materials": [
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "Material.001",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "Material.002",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.226583,
|
||||
0.226583,
|
||||
0.226583,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"name": "Material.003",
|
||||
"pbrMetallicRoughness": {
|
||||
"baseColorFactor": [
|
||||
0.8,
|
||||
0.0113789,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"metallicFactor": 0.0,
|
||||
"roughnessFactor": 0.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"doubleSided": true,
|
||||
"emissiveFactor": [
|
||||
0.0,
|
||||
0.161466,
|
||||
1.0
|
||||
],
|
||||
"name": "Material.004"
|
||||
}
|
||||
],
|
||||
"meshes": [
|
||||
{
|
||||
"name": "Object_0",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 1,
|
||||
"POSITION": 0,
|
||||
"TEXCOORD_0": 2
|
||||
},
|
||||
"indices": 3,
|
||||
"material": 0,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_1",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 5,
|
||||
"POSITION": 4,
|
||||
"TEXCOORD_0": 6
|
||||
},
|
||||
"indices": 7,
|
||||
"material": 1,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_2",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 9,
|
||||
"POSITION": 8,
|
||||
"TEXCOORD_0": 10
|
||||
},
|
||||
"indices": 11,
|
||||
"material": 2,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Object_3",
|
||||
"primitives": [
|
||||
{
|
||||
"attributes": {
|
||||
"NORMAL": 13,
|
||||
"POSITION": 12,
|
||||
"TEXCOORD_0": 14
|
||||
},
|
||||
"indices": 15,
|
||||
"material": 3,
|
||||
"mode": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes": [
|
||||
{
|
||||
"children": [
|
||||
1
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2.220446049250313e-16,
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
2.220446049250313e-16,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"name": "Sketchfab_model"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
2
|
||||
],
|
||||
"name": "root"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
3
|
||||
],
|
||||
"matrix": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
2.220446049250313e-16,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0,
|
||||
2.220446049250313e-16,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"name": "GLTF_SceneRootNode"
|
||||
},
|
||||
{
|
||||
"children": [
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7
|
||||
],
|
||||
"name": "Cube_0"
|
||||
},
|
||||
{
|
||||
"mesh": 0,
|
||||
"name": "Object_4"
|
||||
},
|
||||
{
|
||||
"mesh": 1,
|
||||
"name": "Object_5"
|
||||
},
|
||||
{
|
||||
"mesh": 2,
|
||||
"name": "Object_6"
|
||||
},
|
||||
{
|
||||
"mesh": 3,
|
||||
"name": "Object_7"
|
||||
}
|
||||
],
|
||||
"scene": 0,
|
||||
"scenes": [
|
||||
{
|
||||
"name": "Sketchfab_Scene",
|
||||
"nodes": [
|
||||
0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,13 +1,13 @@
|
||||
#![allow(clippy::type_complexity)]
|
||||
|
||||
use bevy::prelude::*;
|
||||
use big_space::{reference_frame::ReferenceFrame, FloatingOrigin, GridCell};
|
||||
use big_space::{commands::BigSpaceCommands, reference_frame::ReferenceFrame, FloatingOrigin};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
big_space::FloatingOriginPlugin::<i64>::new(0.5, 0.01),
|
||||
big_space::BigSpacePlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
@ -65,69 +65,63 @@ fn setup(
|
||||
..default()
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_xyz(0.0, 0.0, 1.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
Mover::<1>,
|
||||
));
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_xyz(1.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
Mover::<2>,
|
||||
));
|
||||
commands
|
||||
.spawn((
|
||||
commands.spawn_big_space(ReferenceFrame::<i64>::new(1.0, 0.01), |root| {
|
||||
root.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
transform: Transform::from_xyz(0.0, 0.0, 1.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
ReferenceFrame::<i64>::new(0.2, 0.01),
|
||||
Rotator,
|
||||
Mover::<3>,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((
|
||||
Mover::<1>,
|
||||
));
|
||||
|
||||
root.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_xyz(1.0, 0.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
Mover::<2>,
|
||||
));
|
||||
|
||||
root.with_frame(ReferenceFrame::new(0.2, 0.01), |new_frame| {
|
||||
new_frame.insert((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
..default()
|
||||
},
|
||||
Rotator,
|
||||
Mover::<3>,
|
||||
));
|
||||
new_frame.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle,
|
||||
material: matl_handle,
|
||||
transform: Transform::from_xyz(0.0, 0.5, 0.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
Mover::<4>,
|
||||
));
|
||||
});
|
||||
|
||||
// light
|
||||
commands.spawn((
|
||||
PointLightBundle {
|
||||
// light
|
||||
root.spawn_spatial((PointLightBundle {
|
||||
transform: Transform::from_xyz(4.0, 8.0, 4.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
));
|
||||
},));
|
||||
|
||||
// camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.0, 8.0)
|
||||
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i64>::default(),
|
||||
FloatingOrigin,
|
||||
));
|
||||
// camera
|
||||
root.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.0, 8.0)
|
||||
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
FloatingOrigin,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
111
examples/demo.rs
111
examples/demo.rs
@ -5,27 +5,27 @@ use bevy::{
|
||||
};
|
||||
use big_space::{
|
||||
camera::{CameraController, CameraInput},
|
||||
propagation::IgnoreFloatingOrigin,
|
||||
reference_frame::RootReferenceFrame,
|
||||
commands::BigSpaceCommands,
|
||||
reference_frame::{local_origin::ReferenceFrames, ReferenceFrame},
|
||||
world_query::GridTransformReadOnly,
|
||||
FloatingOrigin, GridCell,
|
||||
FloatingOrigin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
big_space::FloatingOriginPlugin::<i128>::default(),
|
||||
big_space::BigSpacePlugin::<i128>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i128>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i128>::default(),
|
||||
bevy_framepace::FramepacePlugin,
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.add_systems(Startup, (setup, ui_setup))
|
||||
.add_systems(PreUpdate, cursor_grab_system)
|
||||
.add_systems(PreUpdate, (cursor_grab_system, ui_text_system))
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(highlight_nearest_sphere, ui_text_system).after(TransformSystem::TransformPropagate),
|
||||
highlight_nearest_sphere.after(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.run()
|
||||
}
|
||||
@ -35,57 +35,56 @@ fn setup(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
// camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.0, 8.0)
|
||||
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
projection: Projection::Perspective(PerspectiveProjection {
|
||||
near: 1e-18,
|
||||
commands.spawn_big_space(ReferenceFrame::<i128>::default(), |root| {
|
||||
root.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 0.0, 8.0)
|
||||
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
projection: Projection::Perspective(PerspectiveProjection {
|
||||
near: 1e-18,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
},
|
||||
FloatingOrigin, // Important: marks the floating origin entity for rendering.
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
));
|
||||
|
||||
let mesh_handle = meshes.add(Sphere::new(0.5).mesh().ico(32).unwrap());
|
||||
let matl_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
..default()
|
||||
},
|
||||
GridCell::<i128>::default(), // All spatial entities need this component
|
||||
FloatingOrigin, // Important: marks the floating origin entity for rendering.
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
));
|
||||
});
|
||||
|
||||
let mesh_handle = meshes.add(Sphere::new(0.5).mesh().ico(32).unwrap());
|
||||
let matl_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
..default()
|
||||
});
|
||||
let mut translation = Vec3::ZERO;
|
||||
for i in -16..=27 {
|
||||
let j = 10_f32.powf(i as f32);
|
||||
let k = 10_f32.powf((i - 1) as f32);
|
||||
translation.x += j / 2.0 + k;
|
||||
translation.y = j / 2.0;
|
||||
|
||||
let mut translation = Vec3::ZERO;
|
||||
for i in -16..=27 {
|
||||
let j = 10_f32.powf(i as f32);
|
||||
let k = 10_f32.powf((i - 1) as f32);
|
||||
translation.x += j / 2.0 + k;
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
root.spawn_spatial(PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_scale(Vec3::splat(j)).with_translation(translation),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i128>::default(),
|
||||
));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// light
|
||||
commands.spawn((DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 100_000.0,
|
||||
// light
|
||||
root.spawn_spatial(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
illuminance: 100_000.0,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
@ -112,7 +111,6 @@ fn ui_setup(mut commands: Commands) {
|
||||
..default()
|
||||
}),
|
||||
BigSpaceDebugText,
|
||||
IgnoreFloatingOrigin,
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
@ -133,7 +131,6 @@ fn ui_setup(mut commands: Commands) {
|
||||
})
|
||||
.with_text_justify(JustifyText::Center),
|
||||
FunFactText,
|
||||
IgnoreFloatingOrigin,
|
||||
));
|
||||
}
|
||||
|
||||
@ -162,18 +159,18 @@ fn ui_text_system(
|
||||
(With<BigSpaceDebugText>, Without<FunFactText>),
|
||||
>,
|
||||
mut fun_text: Query<&mut Text, (With<FunFactText>, Without<BigSpaceDebugText>)>,
|
||||
ref_frames: ReferenceFrames<i128>,
|
||||
time: Res<Time>,
|
||||
origin: Query<GridTransformReadOnly<i128>, With<FloatingOrigin>>,
|
||||
origin: Query<(Entity, GridTransformReadOnly<i128>), With<FloatingOrigin>>,
|
||||
camera: Query<&CameraController>,
|
||||
objects: Query<&Transform, With<Handle<Mesh>>>,
|
||||
reference_frame: Res<RootReferenceFrame<i128>>,
|
||||
) {
|
||||
let origin = origin.single();
|
||||
let translation = origin.transform.translation;
|
||||
let (origin_entity, origin_pos) = origin.single();
|
||||
let translation = origin_pos.transform.translation;
|
||||
|
||||
let grid_text = format!(
|
||||
"GridCell: {}x, {}y, {}z",
|
||||
origin.cell.x, origin.cell.y, origin.cell.z
|
||||
origin_pos.cell.x, origin_pos.cell.y, origin_pos.cell.z
|
||||
);
|
||||
|
||||
let translation_text = format!(
|
||||
@ -181,7 +178,11 @@ fn ui_text_system(
|
||||
translation.x, translation.y, translation.z
|
||||
);
|
||||
|
||||
let real_position = reference_frame.grid_position_double(origin.cell, origin.transform);
|
||||
let Some(ref_frame) = ref_frames.parent_frame(origin_entity) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let real_position = ref_frame.grid_position_double(origin_pos.cell, origin_pos.transform);
|
||||
let real_position_f64_text = format!(
|
||||
"Combined (f64): {}x, {}y, {}z",
|
||||
real_position.x, real_position.y, real_position.z
|
||||
|
||||
@ -7,14 +7,16 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
use big_space::{
|
||||
reference_frame::RootReferenceFrame, FloatingOrigin, GridCell, IgnoreFloatingOrigin,
|
||||
commands::BigSpaceCommands,
|
||||
reference_frame::{local_origin::ReferenceFrames, ReferenceFrame},
|
||||
FloatingOrigin, GridCell,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
big_space::FloatingOriginPlugin::<i128>::new(10.0, 1.0),
|
||||
big_space::BigSpacePlugin::<i128>::default(),
|
||||
))
|
||||
.add_systems(Startup, (setup_scene, setup_ui))
|
||||
.add_systems(Update, (rotator_system, toggle_plugin))
|
||||
@ -24,45 +26,52 @@ fn main() {
|
||||
/// You can put things really, really far away from the origin. The distance we use here is actually
|
||||
/// quite small, because we want the mesh to still be visible when the floating origin is far from
|
||||
/// the camera. If you go much further than this, the mesh will simply disappear in a *POOF* of
|
||||
/// floating point error.
|
||||
/// 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 = 21_000_000;
|
||||
const DISTANCE: i128 = 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>>,
|
||||
settings: Res<RootReferenceFrame<i128>>,
|
||||
ref_frames: ReferenceFrames<i128>,
|
||||
mut text: Query<&mut Text>,
|
||||
mut disabled: Local<bool>,
|
||||
mut floating_origin: Query<&mut GridCell<i128>, With<FloatingOrigin>>,
|
||||
mut floating_origin: Query<(Entity, &mut GridCell<i128>), With<FloatingOrigin>>,
|
||||
) {
|
||||
if input.just_pressed(KeyCode::Space) {
|
||||
*disabled = !*disabled;
|
||||
}
|
||||
|
||||
let mut origin_cell = floating_origin.single_mut();
|
||||
let index_max = DISTANCE / settings.cell_edge_length() as i128;
|
||||
let this_frame = ref_frames.parent_frame(floating_origin.single().0).unwrap();
|
||||
|
||||
let mut origin_cell = floating_origin.single_mut().1;
|
||||
let index_max = DISTANCE / this_frame.cell_edge_length() as i128;
|
||||
let increment = index_max / 100;
|
||||
|
||||
let msg = if *disabled {
|
||||
if origin_cell.x > 0 {
|
||||
origin_cell.x = 0.max(origin_cell.x - increment);
|
||||
origin_cell.y = 0.max(origin_cell.y - increment);
|
||||
origin_cell.z = 0.max(origin_cell.z - increment);
|
||||
|
||||
"Disabling..."
|
||||
} else {
|
||||
"Floating Origin Disabled"
|
||||
}
|
||||
} else if origin_cell.x < index_max {
|
||||
origin_cell.x = index_max.min(origin_cell.x.saturating_add(increment));
|
||||
origin_cell.y = index_max.min(origin_cell.y.saturating_add(increment));
|
||||
origin_cell.z = index_max.min(origin_cell.z.saturating_add(increment));
|
||||
"Enabling..."
|
||||
} else {
|
||||
"Floating Origin Enabled"
|
||||
};
|
||||
|
||||
let dist = index_max.saturating_sub(origin_cell.x) * settings.cell_edge_length() as i128;
|
||||
let dist = index_max.saturating_sub(origin_cell.x) * this_frame.cell_edge_length() as i128;
|
||||
|
||||
let thousands = |num: i128| {
|
||||
num.to_string()
|
||||
@ -84,96 +93,69 @@ struct Rotator;
|
||||
|
||||
fn rotator_system(time: Res<Time>, mut query: Query<&mut Transform, With<Rotator>>) {
|
||||
for mut transform in &mut query {
|
||||
transform.rotate_x(time.delta_seconds());
|
||||
transform.rotate_y(time.delta_seconds());
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_ui(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
TextBundle {
|
||||
style: Style {
|
||||
align_self: AlignSelf::FlexStart,
|
||||
flex_direction: FlexDirection::Column,
|
||||
..Default::default()
|
||||
commands.spawn(
|
||||
TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font_size: 30.0,
|
||||
..default()
|
||||
},
|
||||
text: Text {
|
||||
sections: vec![TextSection {
|
||||
value: "hello: ".to_string(),
|
||||
style: TextStyle {
|
||||
font_size: 30.0,
|
||||
color: Color::WHITE,
|
||||
..default()
|
||||
},
|
||||
}],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
IgnoreFloatingOrigin,
|
||||
));
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
top: Val::Px(12.0),
|
||||
left: Val::Px(12.0),
|
||||
..default()
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
reference_frame: Res<RootReferenceFrame<i128>>,
|
||||
) {
|
||||
let mesh_handle = meshes.add(Sphere::new(1.5).mesh());
|
||||
let matl_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.8, 0.7, 0.6),
|
||||
..default()
|
||||
});
|
||||
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn_big_space(ReferenceFrame::<i128>::default(), |root| {
|
||||
let d = DISTANCE / root.frame().cell_edge_length() as i128;
|
||||
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
|
||||
|
||||
let d = DISTANCE / reference_frame.cell_edge_length() as i128;
|
||||
let distant_grid_cell = GridCell::<i128>::new(d, d, d);
|
||||
// Normally, we would put the floating origin on the camera. However in this example, we
|
||||
// want to show what happens as the camera is far from the origin, to emulate what
|
||||
// happens when this plugin isn't used.
|
||||
root.spawn_spatial((distant_grid_cell, FloatingOrigin));
|
||||
|
||||
// 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 happens when this
|
||||
// plugin isn't used.
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::default().mesh()),
|
||||
material: materials.add(StandardMaterial::from(Color::RED)),
|
||||
transform: Transform::from_scale(Vec3::splat(10000.0)),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
FloatingOrigin,
|
||||
));
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
root.spawn_spatial((
|
||||
SceneBundle {
|
||||
scene: asset_server.load("models/low_poly_spaceship/scene.gltf#Scene0"),
|
||||
transform: Transform::from_scale(Vec3::splat(0.2)),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
Rotator,
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn(PbrBundle {
|
||||
mesh: mesh_handle,
|
||||
material: matl_handle,
|
||||
transform: Transform::from_xyz(0.0, 0.0, 4.0),
|
||||
parent.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/low_poly_spaceship/scene.gltf#Scene0"),
|
||||
transform: Transform::from_xyz(0.0, 0.0, 20.0),
|
||||
..default()
|
||||
});
|
||||
});
|
||||
// light
|
||||
commands.spawn((
|
||||
DirectionalLightBundle {
|
||||
transform: Transform::from_xyz(4.0, -10.0, -4.0),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
));
|
||||
// camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(8.0, -8.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
));
|
||||
// light
|
||||
root.spawn_spatial((
|
||||
DirectionalLightBundle {
|
||||
transform: Transform::from_xyz(4.0, -10.0, -4.0),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
));
|
||||
// camera
|
||||
root.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(8.0, 8.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
distant_grid_cell,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,105 +1,107 @@
|
||||
//! This example demonstrates error accumulating from parent to children in nested reference frames.
|
||||
use bevy::{math::DVec3, prelude::*};
|
||||
use big_space::{
|
||||
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||
FloatingOrigin, GridCell,
|
||||
};
|
||||
use big_space::{commands::BigSpaceCommands, reference_frame::ReferenceFrame, FloatingOrigin};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
big_space::FloatingOriginPlugin::<i128>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i128>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i128>::default(),
|
||||
bevy_inspector_egui::quick::WorldInspectorPlugin::new(),
|
||||
big_space::BigSpacePlugin::<i64>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
))
|
||||
.add_systems(Startup, setup_scene)
|
||||
.run()
|
||||
}
|
||||
|
||||
// The nearby object is 200 meters away from us. The distance object is 100 quadrillion meters away
|
||||
// from us, and has a child that is 100 quadrillion meters toward us (relative its parent) minus 200
|
||||
// meters. If we had infinite precision, the child of the distant object would be at the same
|
||||
// position as the nearby object, only 200 meters away.
|
||||
const DISTANT: DVec3 = DVec3::new(1e17, 1e17, 0.0);
|
||||
const NEARBY: DVec3 = DVec3::new(200.0, 200.0, 0.0);
|
||||
// The nearby object is NEARBY meters away from us. The distance object is DISTANT meters away from
|
||||
// us, and has a child that is DISTANT meters toward us (relative its parent) minus NEARBY meters.
|
||||
const DISTANT: DVec3 = DVec3::new(1e10, 1e10, 1e10);
|
||||
const SPHERE_RADIUS: f32 = 10.0;
|
||||
const NEARBY: Vec3 = Vec3::new(SPHERE_RADIUS * 20.0, SPHERE_RADIUS * 20.0, 0.0);
|
||||
|
||||
fn setup_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
root: Res<RootReferenceFrame<i128>>,
|
||||
) {
|
||||
let mesh_handle = meshes.add(Sphere::new(0.5).mesh());
|
||||
let mesh_handle = meshes.add(Sphere::new(SPHERE_RADIUS).mesh());
|
||||
let matl_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(0.8, 0.7, 0.6),
|
||||
..default()
|
||||
});
|
||||
|
||||
// A red sphere located nearby
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: materials.add(Color::RED),
|
||||
transform: Transform::from_translation(NEARBY.as_vec3()),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i128>::default(),
|
||||
));
|
||||
|
||||
let parent = root.translation_to_grid(DISTANT);
|
||||
// This function introduces a small amount of error, because it can only work up to double
|
||||
// precision floats. (f64).
|
||||
let child = root.translation_to_grid(-DISTANT + NEARBY);
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
// A sphere very far from the origin
|
||||
PbrBundle {
|
||||
commands.spawn_big_space(
|
||||
ReferenceFrame::<i64>::new(SPHERE_RADIUS * 100.0, 0.0),
|
||||
|root_frame| {
|
||||
root_frame.spawn_spatial(PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_translation(parent.1),
|
||||
material: materials.add(Color::BLUE),
|
||||
transform: Transform::from_translation(NEARBY),
|
||||
..default()
|
||||
},
|
||||
parent.0,
|
||||
ReferenceFrame::<i128>::default(),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
// A green sphere that is a child of the sphere very far from the origin. This child is
|
||||
// very far from its parent, and should be located exactly at the origin (if there was
|
||||
// no floating point error). The distance from the green sphere to the red sphere is the
|
||||
// error caused by float imprecision. Note that the sphere does not have any rendering
|
||||
// artifacts, its position just has a fixed error.
|
||||
parent.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle,
|
||||
material: materials.add(Color::GREEN),
|
||||
transform: Transform::from_translation(child.1),
|
||||
});
|
||||
|
||||
let parent = root_frame.frame().translation_to_grid(DISTANT);
|
||||
root_frame.with_frame(
|
||||
ReferenceFrame::new(SPHERE_RADIUS * 100.0, 0.0),
|
||||
|parent_frame| {
|
||||
// This function introduces a small amount of error, because it can only work up to
|
||||
// double precision floats. (f64).
|
||||
let child = parent_frame
|
||||
.frame()
|
||||
.translation_to_grid(-DISTANT + NEARBY.as_dvec3());
|
||||
parent_frame.insert(PbrBundle {
|
||||
mesh: mesh_handle.clone(),
|
||||
material: matl_handle.clone(),
|
||||
transform: Transform::from_translation(parent.1),
|
||||
..default()
|
||||
});
|
||||
parent_frame.insert(parent.0);
|
||||
|
||||
parent_frame.with_children(|child_builder| {
|
||||
// A green sphere that is a child of the sphere very far from the origin. This
|
||||
// child is very far from its parent, and should be located exactly at the
|
||||
// origin (if there was no floating point error). The distance from the green
|
||||
// sphere to the red sphere is the error caused by float imprecision. Note that
|
||||
// the sphere does not have any rendering artifacts, its position just has a
|
||||
// fixed error.
|
||||
child_builder.spawn((
|
||||
PbrBundle {
|
||||
mesh: mesh_handle,
|
||||
material: materials.add(Color::GREEN),
|
||||
transform: Transform::from_translation(child.1),
|
||||
..default()
|
||||
},
|
||||
child.0,
|
||||
));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
root_frame.spawn_spatial(DirectionalLightBundle {
|
||||
transform: Transform::from_xyz(4.0, -10.0, -4.0),
|
||||
..default()
|
||||
});
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_translation(
|
||||
NEARBY + Vec3::new(0.0, 0.0, SPHERE_RADIUS * 10.0),
|
||||
)
|
||||
.looking_at(NEARBY, Vec3::Y),
|
||||
projection: Projection::Perspective(PerspectiveProjection {
|
||||
near: (SPHERE_RADIUS * 0.1).min(0.1),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
child.0,
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
));
|
||||
});
|
||||
// light
|
||||
commands.spawn((
|
||||
DirectionalLightBundle {
|
||||
transform: Transform::from_xyz(4.0, -10.0, -4.0),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i128>::default(),
|
||||
));
|
||||
// camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_translation(NEARBY.as_vec3() + Vec3::new(0.0, 0.0, 20.0))
|
||||
.looking_at(NEARBY.as_vec3(), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
GridCell::<i128>::default(),
|
||||
FloatingOrigin,
|
||||
big_space::camera::CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
));
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// Example with spheres at the scale and distance of the earth and moon around the sun, at 1:1
|
||||
/// scale. The earth is rotating on its axis, and the camera is in this reference frame, to
|
||||
/// demonstrate how high precision nested reference frames work at large scales.
|
||||
use bevy::{core_pipeline::bloom::BloomSettings, prelude::*, render::camera::Exposure};
|
||||
use bevy::{
|
||||
core_pipeline::bloom::BloomSettings,
|
||||
math::DVec3,
|
||||
pbr::{CascadeShadowConfigBuilder, NotShadowCaster},
|
||||
prelude::*,
|
||||
render::camera::Exposure,
|
||||
transform::TransformSystem,
|
||||
};
|
||||
use big_space::{
|
||||
camera::CameraController,
|
||||
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||
FloatingOrigin, GridCell,
|
||||
camera::{CameraController, CameraInput},
|
||||
commands::BigSpaceCommands,
|
||||
reference_frame::ReferenceFrame,
|
||||
FloatingOrigin,
|
||||
};
|
||||
use rand::Rng;
|
||||
|
||||
@ -13,22 +23,52 @@ fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
big_space::FloatingOriginPlugin::<i64>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
// bevy_inspector_egui::quick::WorldInspectorPlugin::new(),
|
||||
big_space::BigSpacePlugin::<i64>::new(true),
|
||||
// big_space::debug::FloatingOriginDebugPlugin::<i64>::default(),
|
||||
big_space::camera::CameraControllerPlugin::<i64>::default(),
|
||||
bevy_framepace::FramepacePlugin,
|
||||
))
|
||||
.insert_resource(ClearColor(Color::BLACK))
|
||||
.insert_resource(AmbientLight {
|
||||
color: Color::WHITE,
|
||||
brightness: 100.0,
|
||||
brightness: 200.0,
|
||||
})
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, rotate)
|
||||
.add_systems(Startup, spawn_solar_system)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
rotate,
|
||||
lighting
|
||||
.in_set(TransformSystem::TransformPropagate)
|
||||
.after(bevy::transform::systems::sync_simple_transforms)
|
||||
.after(bevy::transform::systems::propagate_transforms)
|
||||
.after(big_space::FloatingOriginSet::PropagateLowPrecision),
|
||||
cursor_grab_system,
|
||||
springy_ship
|
||||
.after(big_space::camera::default_camera_inputs)
|
||||
.before(big_space::camera::camera_controller::<i64>),
|
||||
),
|
||||
)
|
||||
.register_type::<Sun>()
|
||||
.register_type::<Rotates>()
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
const EARTH_ORBIT_RADIUS_M: f64 = 149.60e9;
|
||||
const EARTH_RADIUS_M: f64 = 6.371e6;
|
||||
const SUN_RADIUS_M: f64 = 695_508_000_f64;
|
||||
const MOON_RADIUS_M: f64 = 1.7375e6;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
struct Sun;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
struct PrimaryLight;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
struct Spaceship;
|
||||
|
||||
#[derive(Component, Reflect)]
|
||||
struct Rotates(f32);
|
||||
|
||||
fn rotate(mut rotate_query: Query<(&mut Transform, &Rotates)>) {
|
||||
@ -37,157 +77,251 @@ fn rotate(mut rotate_query: Query<(&mut Transform, &Rotates)>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
fn lighting(
|
||||
mut light: Query<(&mut Transform, &mut GlobalTransform), With<PrimaryLight>>,
|
||||
sun: Query<&GlobalTransform, (With<Sun>, Without<PrimaryLight>)>,
|
||||
) {
|
||||
let sun_pos = sun.single().translation();
|
||||
let (mut light_tr, mut light_gt) = light.single_mut();
|
||||
light_tr.look_at(-sun_pos, Vec3::Y);
|
||||
*light_gt = (*light_tr).into();
|
||||
}
|
||||
|
||||
fn springy_ship(
|
||||
cam_input: Res<CameraInput>,
|
||||
mut ship: Query<&mut Transform, With<Spaceship>>,
|
||||
mut desired_dir: Local<(Vec3, Quat)>,
|
||||
mut smoothed_rot: Local<VecDeque<Vec3>>,
|
||||
) {
|
||||
desired_dir.0 = DVec3::new(cam_input.right, cam_input.up, -cam_input.forward).as_vec3()
|
||||
* (1.0 + cam_input.boost as u8 as f32);
|
||||
|
||||
smoothed_rot.truncate(15);
|
||||
smoothed_rot.push_front(DVec3::new(cam_input.pitch, cam_input.yaw, cam_input.roll).as_vec3());
|
||||
let avg_rot = smoothed_rot.iter().sum::<Vec3>() / smoothed_rot.len() as f32;
|
||||
|
||||
use std::f32::consts::*;
|
||||
desired_dir.1 = Quat::IDENTITY.slerp(
|
||||
Quat::from_euler(
|
||||
EulerRot::XYZ,
|
||||
avg_rot.x.clamp(-FRAC_PI_4, FRAC_PI_4),
|
||||
avg_rot.y.clamp(-FRAC_PI_4, FRAC_PI_4),
|
||||
avg_rot.z.clamp(-FRAC_PI_4, FRAC_PI_4),
|
||||
),
|
||||
0.2,
|
||||
) * Quat::from_rotation_y(PI);
|
||||
|
||||
ship.single_mut().translation = ship
|
||||
.single_mut()
|
||||
.translation
|
||||
.lerp(desired_dir.0 * Vec3::new(0.5, 0.5, -2.0), 0.02);
|
||||
|
||||
ship.single_mut().rotation = ship.single_mut().rotation.slerp(desired_dir.1, 0.02);
|
||||
}
|
||||
|
||||
fn spawn_solar_system(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
space: Res<RootReferenceFrame<i64>>,
|
||||
) {
|
||||
let mut sphere = |radius| meshes.add(Sphere::new(radius).mesh().ico(32).unwrap());
|
||||
let sun_mesh_handle = meshes.add(Sphere::new(SUN_RADIUS_M as f32).mesh().ico(6).unwrap());
|
||||
let earth_mesh_handle = meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap());
|
||||
let moon_mesh_handle = meshes.add(Sphere::new(MOON_RADIUS_M as f32).mesh().ico(15).unwrap());
|
||||
let ball_mesh_handle = meshes.add(Sphere::new(5.0).mesh().ico(5).unwrap());
|
||||
let plane_mesh_handle = meshes.add(Plane3d::new(Vec3::X));
|
||||
|
||||
let star = sphere(1e10);
|
||||
let star_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: Color::rgb_linear(100000., 100000., 100000.),
|
||||
..default()
|
||||
});
|
||||
let mut rng = rand::thread_rng();
|
||||
for _ in 0..500 {
|
||||
commands.spawn((
|
||||
GridCell::<i64>::new(
|
||||
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||
((rng.gen::<f32>() - 0.5) * 1e11) as i64,
|
||||
),
|
||||
PbrBundle {
|
||||
mesh: star.clone(),
|
||||
material: star_mat.clone(),
|
||||
commands.spawn((
|
||||
PrimaryLight,
|
||||
DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
color: Color::WHITE,
|
||||
illuminance: 120_000.,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
num_cascades: 4,
|
||||
minimum_distance: 0.1,
|
||||
maximum_distance: 10_000.0,
|
||||
first_cascade_far_bound: 100.0,
|
||||
overlap_proportion: 0.2,
|
||||
}
|
||||
.build(),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
let sun_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: Color::rgb_linear(10000000., 10000000., 10000000.),
|
||||
..default()
|
||||
});
|
||||
let sun_radius_m = 695_508_000.0;
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
GridCell::<i64>::ZERO,
|
||||
PointLightBundle {
|
||||
point_light: PointLight {
|
||||
intensity: 35.73e27,
|
||||
range: 1e20,
|
||||
radius: sun_radius_m,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((PbrBundle {
|
||||
mesh: sphere(sun_radius_m),
|
||||
material: sun_mat,
|
||||
..default()
|
||||
},));
|
||||
});
|
||||
|
||||
let earth_orbit_radius_m = 149.60e9;
|
||||
let earth_radius_m = 6.371e6;
|
||||
|
||||
let earth_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
..default()
|
||||
});
|
||||
|
||||
let (earth_cell, earth_pos): (GridCell<i64>, _) =
|
||||
space.imprecise_translation_to_grid(Vec3::Z * earth_orbit_radius_m);
|
||||
|
||||
commands
|
||||
.spawn((
|
||||
PbrBundle {
|
||||
mesh: sphere(earth_radius_m),
|
||||
material: earth_mat,
|
||||
transform: Transform::from_translation(earth_pos),
|
||||
..default()
|
||||
},
|
||||
earth_cell,
|
||||
ReferenceFrame::<i64>::default(),
|
||||
Rotates(0.001),
|
||||
))
|
||||
.with_children(|commands| {
|
||||
let moon_orbit_radius_m = 385e6;
|
||||
let moon_radius_m = 1.7375e6;
|
||||
|
||||
let moon_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::GRAY,
|
||||
perceptual_roughness: 1.0,
|
||||
reflectance: 0.0,
|
||||
..default()
|
||||
});
|
||||
|
||||
let (moon_cell, moon_pos): (GridCell<i64>, _) =
|
||||
space.imprecise_translation_to_grid(Vec3::X * moon_orbit_radius_m);
|
||||
|
||||
commands.spawn((
|
||||
commands.spawn_big_space(ReferenceFrame::<i64>::default(), |root_frame| {
|
||||
root_frame.with_frame_default(|sun| {
|
||||
sun.insert((Sun, Name::new("Sun")));
|
||||
sun.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: sphere(moon_radius_m),
|
||||
material: moon_mat,
|
||||
transform: Transform::from_translation(moon_pos),
|
||||
mesh: sun_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: Color::rgb_linear(100000000., 100000000., 100000000.),
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
moon_cell,
|
||||
NotShadowCaster,
|
||||
));
|
||||
|
||||
let (cam_cell, cam_pos): (GridCell<i64>, _) =
|
||||
space.imprecise_translation_to_grid(Vec3::X * (earth_radius_m + 1.0));
|
||||
|
||||
// camera
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_translation(cam_pos)
|
||||
.looking_to(Vec3::NEG_Z, Vec3::X),
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
let earth_pos = DVec3::Z * EARTH_ORBIT_RADIUS_M;
|
||||
let (earth_cell, earth_pos) = sun.frame().translation_to_grid(earth_pos);
|
||||
sun.with_frame_default(|earth| {
|
||||
earth.insert((
|
||||
Name::new("Earth"),
|
||||
earth_cell,
|
||||
PbrBundle {
|
||||
mesh: earth_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
perceptual_roughness: 0.8,
|
||||
reflectance: 1.0,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_translation(earth_pos)
|
||||
.with_scale(Vec3::splat(EARTH_RADIUS_M as f32)),
|
||||
..default()
|
||||
},
|
||||
exposure: Exposure::SUNLIGHT,
|
||||
..default()
|
||||
},
|
||||
BloomSettings::default(),
|
||||
cam_cell,
|
||||
FloatingOrigin, // Important: marks the floating origin entity for rendering.
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([10e-18, 10e35])
|
||||
.with_smoothness(0.9, 0.8)
|
||||
.with_speed(1.0),
|
||||
));
|
||||
Rotates(0.000001),
|
||||
));
|
||||
|
||||
let (ball_cell, ball_pos): (GridCell<i64>, _) = space.imprecise_translation_to_grid(
|
||||
Vec3::X * (earth_radius_m + 1.0) + Vec3::NEG_Z * 5.0,
|
||||
);
|
||||
let moon_orbit_radius_m = 385e6;
|
||||
let moon_pos = DVec3::NEG_Z * moon_orbit_radius_m;
|
||||
let (moon_cell, moon_pos) = earth.frame().translation_to_grid(moon_pos);
|
||||
earth.spawn_spatial((
|
||||
Name::new("Moon"),
|
||||
PbrBundle {
|
||||
mesh: moon_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::GRAY,
|
||||
perceptual_roughness: 1.0,
|
||||
reflectance: 0.0,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_translation(moon_pos),
|
||||
..default()
|
||||
},
|
||||
moon_cell,
|
||||
));
|
||||
|
||||
let ball_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::FUCHSIA,
|
||||
perceptual_roughness: 1.0,
|
||||
reflectance: 0.0,
|
||||
..default()
|
||||
let ball_pos =
|
||||
DVec3::X * (EARTH_RADIUS_M + 1.0) + DVec3::NEG_Z * 30.0 + DVec3::Y * 10.0;
|
||||
let (ball_cell, ball_pos) = earth.frame().translation_to_grid(ball_pos);
|
||||
earth
|
||||
.spawn_spatial((ball_cell, Transform::from_translation(ball_pos)))
|
||||
.with_children(|children| {
|
||||
children.spawn((PbrBundle {
|
||||
mesh: ball_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},));
|
||||
|
||||
children.spawn((PbrBundle {
|
||||
mesh: plane_mesh_handle,
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::DARK_GREEN,
|
||||
perceptual_roughness: 1.0,
|
||||
reflectance: 0.0,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_scale(Vec3::splat(100.0))
|
||||
.with_translation(Vec3::X * -5.0),
|
||||
..default()
|
||||
},));
|
||||
});
|
||||
|
||||
let cam_pos = DVec3::X * (EARTH_RADIUS_M + 1.0);
|
||||
let (cam_cell, cam_pos) = earth.frame().translation_to_grid(cam_pos);
|
||||
earth.with_frame_default(|camera| {
|
||||
camera.insert((
|
||||
Transform::from_translation(cam_pos).looking_to(Vec3::NEG_Z, Vec3::X),
|
||||
CameraController::default() // Built-in camera controller
|
||||
.with_speed_bounds([0.1, 10e35])
|
||||
.with_smoothness(0.98, 0.98)
|
||||
.with_speed(1.0),
|
||||
cam_cell,
|
||||
));
|
||||
|
||||
camera.spawn_spatial((
|
||||
FloatingOrigin,
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(0.0, 4.0, 22.0),
|
||||
camera: Camera {
|
||||
hdr: true,
|
||||
..default()
|
||||
},
|
||||
exposure: Exposure::SUNLIGHT,
|
||||
..default()
|
||||
},
|
||||
BloomSettings::default(),
|
||||
));
|
||||
|
||||
camera.with_children(|camera| {
|
||||
camera.spawn((
|
||||
Spaceship,
|
||||
SceneBundle {
|
||||
scene: asset_server
|
||||
.load("models/low_poly_spaceship/scene.gltf#Scene0"),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_y(
|
||||
std::f32::consts::PI,
|
||||
)),
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: sphere(1.0),
|
||||
material: ball_mat,
|
||||
transform: Transform::from_translation(ball_pos),
|
||||
..default()
|
||||
},
|
||||
ball_cell,
|
||||
let star_mat = materials.add(StandardMaterial {
|
||||
base_color: Color::WHITE,
|
||||
emissive: Color::rgb_linear(100000., 100000., 100000.),
|
||||
..default()
|
||||
});
|
||||
let star_mesh_handle = meshes.add(Sphere::new(1e10).mesh().ico(5).unwrap());
|
||||
let mut rng = rand::thread_rng();
|
||||
(0..1000).for_each(|_| {
|
||||
root_frame.spawn_spatial((
|
||||
star_mesh_handle.clone(),
|
||||
star_mat.clone(),
|
||||
Transform::from_xyz(
|
||||
(rng.gen::<f32>() - 0.5) * 1e14,
|
||||
(rng.gen::<f32>() - 0.5) * 1e14,
|
||||
(rng.gen::<f32>() - 0.5) * 1e14,
|
||||
),
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn cursor_grab_system(
|
||||
mut windows: Query<&mut Window, With<bevy::window::PrimaryWindow>>,
|
||||
mut cam: ResMut<big_space::camera::CameraInput>,
|
||||
btn: Res<ButtonInput<MouseButton>>,
|
||||
key: Res<ButtonInput<KeyCode>>,
|
||||
) {
|
||||
let Some(mut window) = windows.get_single_mut().ok() else {
|
||||
return;
|
||||
};
|
||||
|
||||
if btn.just_pressed(MouseButton::Right) {
|
||||
window.cursor.grab_mode = bevy::window::CursorGrabMode::Locked;
|
||||
window.cursor.visible = false;
|
||||
// window.mode = WindowMode::BorderlessFullscreen;
|
||||
cam.defaults_disabled = false;
|
||||
}
|
||||
|
||||
if key.just_pressed(KeyCode::Escape) {
|
||||
window.cursor.grab_mode = bevy::window::CursorGrabMode::None;
|
||||
window.cursor.visible = true;
|
||||
// window.mode = WindowMode::Windowed;
|
||||
cam.defaults_disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
270
examples/split_screen.rs
Normal file
270
examples/split_screen.rs
Normal file
@ -0,0 +1,270 @@
|
||||
//! Demonstrates how a single bevy world can contain multiple big_space hierarchies, each rendered
|
||||
//! relative to a floating origin inside that big space.
|
||||
//!
|
||||
//! This takes the simplest approach, of simply duplicating the worlds and players for each split
|
||||
//! screen, and synchronizing the player locations between both.
|
||||
|
||||
use bevy::{
|
||||
prelude::*,
|
||||
render::{camera::Viewport, view::RenderLayers},
|
||||
transform::TransformSystem,
|
||||
};
|
||||
use big_space::{
|
||||
camera::{CameraController, CameraControllerPlugin},
|
||||
commands::BigSpaceCommands,
|
||||
reference_frame::ReferenceFrame,
|
||||
world_query::{GridTransform, GridTransformReadOnly},
|
||||
BigSpacePlugin, FloatingOrigin,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.build().disable::<TransformPlugin>(),
|
||||
BigSpacePlugin::<i32>::default(),
|
||||
big_space::debug::FloatingOriginDebugPlugin::<i32>::default(),
|
||||
CameraControllerPlugin::<i32>::default(),
|
||||
))
|
||||
.add_systems(Startup, setup)
|
||||
.add_systems(Update, set_camera_viewports)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
update_cameras
|
||||
.after(big_space::camera::camera_controller::<i32>)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.run()
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
struct LeftCamera;
|
||||
|
||||
#[derive(Component)]
|
||||
struct RightCamera;
|
||||
|
||||
#[derive(Component)]
|
||||
struct LeftCameraReplicated;
|
||||
|
||||
#[derive(Component)]
|
||||
struct RightCameraReplicated;
|
||||
|
||||
fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
commands.spawn((
|
||||
DirectionalLightBundle {
|
||||
transform: Transform::default().looking_to(Vec3::NEG_ONE, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::all(),
|
||||
));
|
||||
|
||||
// Big Space 1
|
||||
commands.spawn_big_space(ReferenceFrame::<i32>::default(), |root_frame| {
|
||||
root_frame
|
||||
.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(1_000_000.0 - 10.0, 100_005.0, 0.0)
|
||||
.looking_to(Vec3::NEG_X, Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
CameraController::default().with_smoothness(0.8, 0.8),
|
||||
RenderLayers::layer(2),
|
||||
LeftCamera,
|
||||
FloatingOrigin,
|
||||
))
|
||||
.with_children(|child_builder| {
|
||||
child_builder.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 2.0, 1.0)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::YELLOW,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
});
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
RightCameraReplicated,
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 2.0, 1.0)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap()),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(1_000_000.0, 0.0, 0.0)
|
||||
.with_scale(Vec3::splat(100_000.0)),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap()),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::GREEN,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-1_000_000.0, 0.0, 0.0)
|
||||
.with_scale(Vec3::splat(100_000.0)),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(2),
|
||||
));
|
||||
});
|
||||
|
||||
// Big Space 2
|
||||
commands.spawn_big_space(ReferenceFrame::<i32>::default(), |root_frame| {
|
||||
root_frame
|
||||
.spawn_spatial((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(1_000_000.0, 100_005.0, 0.0)
|
||||
.looking_to(Vec3::NEG_X, Vec3::Y),
|
||||
camera: Camera {
|
||||
order: 1,
|
||||
clear_color: ClearColorConfig::None,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
RightCamera,
|
||||
FloatingOrigin,
|
||||
))
|
||||
.with_children(|child_builder| {
|
||||
child_builder.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 2.0, 1.0)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
));
|
||||
});
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
LeftCameraReplicated,
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Cuboid::new(1.0, 2.0, 1.0)),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::YELLOW,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
));
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap()),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::BLUE,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(1_000_000.0, 0.0, 0.0)
|
||||
.with_scale(Vec3::splat(100_000.0)),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
));
|
||||
|
||||
root_frame.spawn_spatial((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Sphere::new(1.0).mesh().ico(35).unwrap()),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::GREEN,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_xyz(-1_000_000.0, 0.0, 0.0)
|
||||
.with_scale(Vec3::splat(100_000.0)),
|
||||
..default()
|
||||
},
|
||||
RenderLayers::layer(1),
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn update_cameras(
|
||||
left: Query<GridTransformReadOnly<i32>, With<LeftCamera>>,
|
||||
mut left_rep: Query<
|
||||
GridTransform<i32>,
|
||||
(
|
||||
With<LeftCameraReplicated>,
|
||||
Without<RightCameraReplicated>,
|
||||
Without<LeftCamera>,
|
||||
Without<RightCamera>,
|
||||
),
|
||||
>,
|
||||
right: Query<GridTransformReadOnly<i32>, With<RightCamera>>,
|
||||
mut right_rep: Query<
|
||||
GridTransform<i32>,
|
||||
(
|
||||
With<RightCameraReplicated>,
|
||||
Without<LeftCameraReplicated>,
|
||||
Without<LeftCamera>,
|
||||
Without<RightCamera>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
*left_rep.single_mut().cell = *left.single().cell;
|
||||
*left_rep.single_mut().transform = *left.single().transform;
|
||||
|
||||
*right_rep.single_mut().cell = *right.single().cell;
|
||||
*right_rep.single_mut().transform = *right.single().transform;
|
||||
}
|
||||
|
||||
fn set_camera_viewports(
|
||||
windows: Query<&Window>,
|
||||
mut resize_events: EventReader<bevy::window::WindowResized>,
|
||||
mut left_camera: Query<&mut Camera, (With<LeftCamera>, Without<RightCamera>)>,
|
||||
mut right_camera: Query<&mut Camera, With<RightCamera>>,
|
||||
) {
|
||||
// We need to dynamically resize the camera's viewports whenever the window size changes
|
||||
// so then each camera always takes up half the screen.
|
||||
// A resize_event is sent when the window is first created, allowing us to reuse this system for initial setup.
|
||||
for resize_event in resize_events.read() {
|
||||
let window = windows.get(resize_event.window).unwrap();
|
||||
let mut left_camera = left_camera.single_mut();
|
||||
left_camera.viewport = Some(Viewport {
|
||||
physical_position: UVec2::new(0, 0),
|
||||
physical_size: UVec2::new(
|
||||
window.resolution.physical_width() / 2,
|
||||
window.resolution.physical_height(),
|
||||
),
|
||||
..default()
|
||||
});
|
||||
|
||||
let mut right_camera = right_camera.single_mut();
|
||||
right_camera.viewport = Some(Viewport {
|
||||
physical_position: UVec2::new(window.resolution.physical_width() / 2, 0),
|
||||
physical_size: UVec2::new(
|
||||
window.resolution.physical_width() / 2,
|
||||
window.resolution.physical_height(),
|
||||
),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
||||
69
src/bundles.rs
Normal file
69
src/bundles.rs
Normal file
@ -0,0 +1,69 @@
|
||||
//! Component bundles for big_space.
|
||||
|
||||
use crate::{precision::GridPrecision, reference_frame::ReferenceFrame, BigSpace, GridCell};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Minimal bundle needed to position an entity in floating origin space.
|
||||
///
|
||||
/// This is the floating origin equivalent of the [`bevy::prelude::SpatialBundle`].
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigSpatialBundle<P: GridPrecision> {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: Visibility,
|
||||
/// The inherited visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub inherited: InheritedVisibility,
|
||||
/// The view visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub view: ViewVisibility,
|
||||
/// The transform of the entity.
|
||||
pub transform: Transform,
|
||||
/// The global transform of the entity.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// The grid position of the entity
|
||||
pub cell: GridCell<P>,
|
||||
}
|
||||
|
||||
/// A [`SpatialBundle`] that also has a reference frame, allowing other high precision spatial
|
||||
/// bundles to be nested within that reference frame.
|
||||
///
|
||||
/// This is the floating origin equivalent of the [`SpatialBundle`].
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigReferenceFrameBundle<P: GridPrecision> {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: Visibility,
|
||||
/// The inherited visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub inherited: InheritedVisibility,
|
||||
/// The view visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub view: ViewVisibility,
|
||||
/// The transform of the entity.
|
||||
pub transform: Transform,
|
||||
/// The global transform of the entity for rendering, computed relative to the floating origin.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// The grid position of the entity within
|
||||
pub cell: GridCell<P>,
|
||||
/// The reference frame
|
||||
pub reference_frame: ReferenceFrame<P>,
|
||||
}
|
||||
|
||||
/// The root of any [`BigSpace`] needs these components to function.
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct BigSpaceRootBundle<P: GridPrecision> {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: Visibility,
|
||||
/// The inherited visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub inherited: InheritedVisibility,
|
||||
/// The view visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub view: ViewVisibility,
|
||||
/// The root reference frame
|
||||
pub reference_frame: ReferenceFrame<P>,
|
||||
/// Tracks the current floating origin
|
||||
pub root: BigSpace,
|
||||
}
|
||||
@ -6,14 +6,14 @@ use bevy::{
|
||||
input::mouse::MouseMotion,
|
||||
math::{DQuat, DVec3},
|
||||
prelude::*,
|
||||
render::primitives::Aabb,
|
||||
render::{primitives::Aabb, view::RenderLayers},
|
||||
transform::TransformSystem,
|
||||
utils::hashbrown::HashSet,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
precision::GridPrecision,
|
||||
reference_frame::{local_origin::ReferenceFrames, RootReferenceFrame},
|
||||
world_query::{GridTransform, GridTransformReadOnly},
|
||||
precision::GridPrecision, reference_frame::local_origin::ReferenceFrames,
|
||||
world_query::GridTransform,
|
||||
};
|
||||
|
||||
/// Adds the `big_space` camera controller
|
||||
@ -27,7 +27,7 @@ impl<P: GridPrecision> Plugin for CameraControllerPlugin<P> {
|
||||
default_camera_inputs
|
||||
.before(camera_controller::<P>)
|
||||
.run_if(|input: Res<CameraInput>| !input.defaults_disabled),
|
||||
nearest_objects::<P>.before(camera_controller::<P>),
|
||||
nearest_objects_in_frame::<P>.before(camera_controller::<P>),
|
||||
camera_controller::<P>.before(TransformSystem::TransformPropagate),
|
||||
),
|
||||
);
|
||||
@ -93,7 +93,7 @@ impl Default for CameraController {
|
||||
Self {
|
||||
smoothness: 0.8,
|
||||
rotational_smoothness: 0.5,
|
||||
speed: 10e8,
|
||||
speed: 1.0,
|
||||
speed_bounds: [1e-17, 1e30],
|
||||
slow_near_objects: true,
|
||||
nearest_object: None,
|
||||
@ -163,8 +163,8 @@ pub fn default_camera_inputs(
|
||||
keyboard
|
||||
.pressed(KeyCode::ControlLeft)
|
||||
.then(|| cam.up -= 1.0);
|
||||
keyboard.pressed(KeyCode::KeyQ).then(|| cam.roll += 1.0);
|
||||
keyboard.pressed(KeyCode::KeyE).then(|| cam.roll -= 1.0);
|
||||
keyboard.pressed(KeyCode::KeyQ).then(|| cam.roll += 2.0);
|
||||
keyboard.pressed(KeyCode::KeyE).then(|| cam.roll -= 2.0);
|
||||
keyboard
|
||||
.pressed(KeyCode::ShiftLeft)
|
||||
.then(|| cam.boost = true);
|
||||
@ -174,25 +174,43 @@ pub fn default_camera_inputs(
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the object nearest the camera
|
||||
pub fn nearest_objects<P: GridPrecision>(
|
||||
settings: Res<RootReferenceFrame<P>>,
|
||||
objects: Query<(Entity, GridTransformReadOnly<P>, &Aabb)>,
|
||||
mut camera: Query<(&mut CameraController, GridTransformReadOnly<P>)>,
|
||||
/// Find the object nearest the camera, within the same reference frame as the camera.
|
||||
pub fn nearest_objects_in_frame<P: GridPrecision>(
|
||||
objects: Query<(
|
||||
Entity,
|
||||
&Transform,
|
||||
&GlobalTransform,
|
||||
&Aabb,
|
||||
Option<&RenderLayers>,
|
||||
)>,
|
||||
mut camera: Query<(
|
||||
Entity,
|
||||
&mut CameraController,
|
||||
&GlobalTransform,
|
||||
Option<&RenderLayers>,
|
||||
)>,
|
||||
children: Query<&Children>,
|
||||
) {
|
||||
let Ok((mut camera, cam_pos)) = camera.get_single_mut() else {
|
||||
let Ok((cam_entity, mut camera, cam_pos, cam_layer)) = camera.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
let cam_layer = cam_layer.copied().unwrap_or(RenderLayers::all());
|
||||
let cam_children: HashSet<Entity> = children.iter_descendants(cam_entity).collect();
|
||||
|
||||
let nearest_object = objects
|
||||
.iter()
|
||||
.map(|(entity, obj_pos, aabb)| {
|
||||
let center_distance = settings
|
||||
.grid_position_double(&(*obj_pos.cell - *cam_pos.cell), obj_pos.transform)
|
||||
- cam_pos.transform.translation.as_dvec3();
|
||||
.filter(|(entity, ..)| !cam_children.contains(entity))
|
||||
.filter(|(.., obj_layer)| {
|
||||
let obj_layer = obj_layer.copied().unwrap_or(RenderLayers::layer(0));
|
||||
cam_layer.intersects(&obj_layer)
|
||||
})
|
||||
.map(|(entity, object_local, obj_pos, aabb, _)| {
|
||||
let center_distance =
|
||||
obj_pos.translation().as_dvec3() - cam_pos.translation().as_dvec3();
|
||||
let nearest_distance = center_distance.length()
|
||||
- (aabb.half_extents.as_dvec3() * obj_pos.transform.scale.as_dvec3())
|
||||
- (aabb.half_extents.as_dvec3() * object_local.scale.as_dvec3())
|
||||
.abs()
|
||||
.max_element();
|
||||
.min_element();
|
||||
(entity, nearest_distance)
|
||||
})
|
||||
.filter(|v| v.1.is_finite())
|
||||
@ -208,10 +226,7 @@ pub fn camera_controller<P: GridPrecision>(
|
||||
mut camera: Query<(Entity, GridTransform<P>, &mut CameraController)>,
|
||||
) {
|
||||
for (camera, mut position, mut controller) in camera.iter_mut() {
|
||||
let Some(frame) = frames
|
||||
.get_handle(camera)
|
||||
.map(|handle| frames.resolve_handle(handle))
|
||||
else {
|
||||
let Some(frame) = frames.parent_frame(camera) else {
|
||||
continue;
|
||||
};
|
||||
let speed = match (controller.nearest_object, controller.slow_near_objects) {
|
||||
|
||||
211
src/commands.rs
Normal file
211
src/commands.rs
Normal file
@ -0,0 +1,211 @@
|
||||
//! Adds `big_space`-specific commands to bevy's `Commands`.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{reference_frame::ReferenceFrame, *};
|
||||
use bevy::prelude::*;
|
||||
|
||||
use self::precision::GridPrecision;
|
||||
|
||||
/// Adds `big_space` commands to bevy's `Commands`.
|
||||
pub trait BigSpaceCommands<P: GridPrecision> {
|
||||
/// Spawn a root [`BigSpace`] [`ReferenceFrame`].
|
||||
fn spawn_big_space(
|
||||
&mut self,
|
||||
root_frame: ReferenceFrame<P>,
|
||||
child_builder: impl FnOnce(&mut ReferenceFrameCommands<P>),
|
||||
);
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> BigSpaceCommands<P> for Commands<'_, '_> {
|
||||
fn spawn_big_space(
|
||||
&mut self,
|
||||
reference_frame: ReferenceFrame<P>,
|
||||
root_frame: impl FnOnce(&mut ReferenceFrameCommands<P>),
|
||||
) {
|
||||
let mut entity_commands = self.spawn((
|
||||
#[cfg(feature = "bevy_render")]
|
||||
Visibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
InheritedVisibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
ViewVisibility::default(),
|
||||
BigSpace::default(),
|
||||
));
|
||||
let mut cmd = ReferenceFrameCommands {
|
||||
entity: entity_commands.id(),
|
||||
commands: entity_commands.commands(),
|
||||
reference_frame,
|
||||
};
|
||||
root_frame(&mut cmd);
|
||||
}
|
||||
}
|
||||
|
||||
/// Build [`big_space`](crate) hierarchies more easily, with access to reference frames.
|
||||
pub struct ReferenceFrameCommands<'a, P: GridPrecision> {
|
||||
entity: Entity,
|
||||
commands: Commands<'a, 'a>,
|
||||
reference_frame: ReferenceFrame<P>,
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> ReferenceFrameCommands<'a, P> {
|
||||
/// Get a reference to the current reference frame.
|
||||
pub fn frame(&mut self) -> &ReferenceFrame<P> {
|
||||
&self.reference_frame
|
||||
}
|
||||
|
||||
/// Insert a component on this reference frame
|
||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.entity(self.entity).insert(bundle);
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a high-precision spatial entity ([`GridCell`]) to this reference frame, and insert the
|
||||
/// provided bundle.
|
||||
pub fn spawn_spatial(&mut self, bundle: impl Bundle) -> SpatialEntityCommands<P> {
|
||||
let mut entity_commands = self.commands.entity(self.entity);
|
||||
let parent = entity_commands.id();
|
||||
let mut commands = entity_commands.commands();
|
||||
|
||||
let entity = commands
|
||||
.spawn((
|
||||
#[cfg(feature = "bevy_render")]
|
||||
Visibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
InheritedVisibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
ViewVisibility::default(),
|
||||
Transform::default(),
|
||||
GlobalTransform::default(),
|
||||
GridCell::<P>::default(),
|
||||
))
|
||||
.insert(bundle)
|
||||
.id();
|
||||
|
||||
commands.entity(entity).set_parent(parent);
|
||||
|
||||
SpatialEntityCommands {
|
||||
entity,
|
||||
commands: self.commands.reborrow(),
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Entity``] id of the entity.
|
||||
pub fn id(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
|
||||
/// Add a high-precision spatial entity ([`GridCell`]) to this reference frame, and apply entity commands to it via the closure. This allows you to insert bundles on this new spatial entities, and add more children to it.
|
||||
pub fn with_spatial(
|
||||
&mut self,
|
||||
spatial: impl FnOnce(&mut SpatialEntityCommands<P>),
|
||||
) -> &mut Self {
|
||||
spatial(&mut self.spawn_spatial(()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a high-precision spatial entity ([`GridCell`]) to this reference frame, and apply entity commands to it via the closure. This allows you to insert bundles on this new spatial entities, and add more children to it.
|
||||
pub fn with_frame(
|
||||
&mut self,
|
||||
new_frame: ReferenceFrame<P>,
|
||||
builder: impl FnOnce(&mut ReferenceFrameCommands<P>),
|
||||
) -> &mut Self {
|
||||
builder(&mut self.spawn_frame(new_frame, ()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Same as [`Self::with_frame`], but using the default [`ReferenceFrame`] value.
|
||||
pub fn with_frame_default(
|
||||
&mut self,
|
||||
builder: impl FnOnce(&mut ReferenceFrameCommands<P>),
|
||||
) -> &mut Self {
|
||||
self.with_frame(ReferenceFrame::default(), builder)
|
||||
}
|
||||
|
||||
/// Spawn a reference frame as a child of the current reference frame.
|
||||
pub fn spawn_frame(
|
||||
&mut self,
|
||||
new_frame: ReferenceFrame<P>,
|
||||
bundle: impl Bundle,
|
||||
) -> ReferenceFrameCommands<P> {
|
||||
let mut entity_commands = self.commands.entity(self.entity);
|
||||
let parent = entity_commands.id();
|
||||
let mut commands = entity_commands.commands();
|
||||
|
||||
let entity = commands
|
||||
.spawn((
|
||||
#[cfg(feature = "bevy_render")]
|
||||
Visibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
InheritedVisibility::default(),
|
||||
#[cfg(feature = "bevy_render")]
|
||||
ViewVisibility::default(),
|
||||
Transform::default(),
|
||||
GlobalTransform::default(),
|
||||
GridCell::<P>::default(),
|
||||
ReferenceFrame::<P>::default(),
|
||||
))
|
||||
.insert(bundle)
|
||||
.id();
|
||||
|
||||
commands.entity(entity).set_parent(parent);
|
||||
|
||||
ReferenceFrameCommands {
|
||||
entity,
|
||||
commands: self.commands.reborrow(),
|
||||
reference_frame: new_frame,
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn a reference frame as a child of the current reference frame. The first argument in the
|
||||
/// closure is the paren't reference frame.
|
||||
pub fn spawn_frame_default(&mut self, bundle: impl Bundle) -> ReferenceFrameCommands<P> {
|
||||
self.spawn_frame(ReferenceFrame::default(), bundle)
|
||||
}
|
||||
|
||||
/// Takes a closure which provides this reference frame and a [`ChildBuilder`].
|
||||
pub fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self {
|
||||
self.commands
|
||||
.entity(self.entity)
|
||||
.with_children(|child_builder| spawn_children(child_builder));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert the reference frame on drop.
|
||||
impl<'a, P: GridPrecision> Drop for ReferenceFrameCommands<'a, P> {
|
||||
fn drop(&mut self) {
|
||||
self.commands
|
||||
.entity(self.entity)
|
||||
.insert(std::mem::take(&mut self.reference_frame));
|
||||
}
|
||||
}
|
||||
|
||||
/// Build [`big_space`](crate) hierarchies more easily, with access to reference frames.
|
||||
pub struct SpatialEntityCommands<'a, P: GridPrecision> {
|
||||
entity: Entity,
|
||||
commands: Commands<'a, 'a>,
|
||||
phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'a, P: GridPrecision> SpatialEntityCommands<'a, P> {
|
||||
/// Insert a component on this reference frame
|
||||
pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self {
|
||||
self.commands.entity(self.entity).insert(bundle);
|
||||
self
|
||||
}
|
||||
|
||||
/// Takes a closure which provides a [`ChildBuilder`].
|
||||
pub fn with_children(&mut self, spawn_children: impl FnOnce(&mut ChildBuilder)) -> &mut Self {
|
||||
self.commands
|
||||
.entity(self.entity)
|
||||
.with_children(|child_builder| spawn_children(child_builder));
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns the [`Entity``] id of the entity.
|
||||
pub fn id(&self) -> Entity {
|
||||
self.entity
|
||||
}
|
||||
}
|
||||
14
src/debug.rs
14
src/debug.rs
@ -28,17 +28,21 @@ impl<P: GridPrecision> Plugin for FloatingOriginDebugPlugin<P> {
|
||||
pub fn update_debug_bounds<P: GridPrecision>(
|
||||
mut gizmos: Gizmos,
|
||||
reference_frames: ReferenceFrames<P>,
|
||||
occupied_cells: Query<(Entity, &GridCell<P>), Without<FloatingOrigin>>,
|
||||
occupied_cells: Query<(Entity, &GridCell<P>, Option<&FloatingOrigin>)>,
|
||||
) {
|
||||
for (cell_entity, cell) in occupied_cells.iter() {
|
||||
let Some(frame) = reference_frames.get(cell_entity) else {
|
||||
for (cell_entity, cell, origin) in occupied_cells.iter() {
|
||||
let Some(frame) = reference_frames.parent_frame(cell_entity) else {
|
||||
continue;
|
||||
};
|
||||
let transform = frame.global_transform(
|
||||
cell,
|
||||
&Transform::from_scale(Vec3::splat(frame.cell_edge_length() * 0.999)),
|
||||
);
|
||||
gizmos.cuboid(transform, Color::GREEN)
|
||||
if origin.is_none() {
|
||||
gizmos.cuboid(transform, Color::GREEN)
|
||||
} else {
|
||||
// gizmos.cuboid(transform, Color::rgba(0.0, 0.0, 1.0, 0.5))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,7 +53,7 @@ pub fn update_reference_frame_axes<P: GridPrecision>(
|
||||
) {
|
||||
for (transform, frame) in frames.iter() {
|
||||
let start = transform.translation();
|
||||
let len = frame.cell_edge_length() * 1.0;
|
||||
let len = frame.cell_edge_length() * 2.0;
|
||||
gizmos.ray(start, transform.right() * len, Color::RED);
|
||||
gizmos.ray(start, transform.up() * len, Color::GREEN);
|
||||
gizmos.ray(start, transform.back() * len, Color::BLUE);
|
||||
|
||||
87
src/floating_origins.rs
Normal file
87
src/floating_origins.rs
Normal file
@ -0,0 +1,87 @@
|
||||
//! A floating origin for camera-relative rendering, to maximize precision when converting to f32.
|
||||
|
||||
use bevy::{log::error, prelude::*, reflect::Reflect, utils::hashbrown::HashMap};
|
||||
|
||||
/// Marks the entity to use as the floating origin.
|
||||
///
|
||||
/// The [`GlobalTransform`] of all entities within this [`BigSpace`] will be computed relative to
|
||||
/// this floating origin. There should always be exactly one entity marked with this component
|
||||
/// within a [`BigSpace`].
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct FloatingOrigin;
|
||||
|
||||
/// A "big space" is a hierarchy of high precision reference frames, rendered with a floating
|
||||
/// origin. It is the root of this high precision hierarchy, and tracks the [`FloatingOrigin`]
|
||||
/// inside this hierarchy.
|
||||
///
|
||||
/// This component must also be paired with a [`ReferenceFrame`](crate::ReferenceFrame), which
|
||||
/// defines the properties of this root reference frame. A hierarchy can have many nested
|
||||
/// `ReferenceFrame`s, but only one `BigSpace`, at the root.
|
||||
///
|
||||
/// Your world can have multiple [`BigSpace`]s, and they will remain completely independent. Each
|
||||
/// big space uses the floating origin contained within it to compute the [`GlobalTransform`] of all
|
||||
/// spatial entities within that `BigSpace`.
|
||||
#[derive(Debug, Default, Component, Reflect)]
|
||||
pub struct BigSpace {
|
||||
/// Set the entity to use as the floating origin within this high precision hierarchy.
|
||||
pub floating_origin: Option<Entity>,
|
||||
}
|
||||
|
||||
impl BigSpace {
|
||||
/// Return the this reference frame's floating origin if it exists and is a descendent of this
|
||||
/// root.
|
||||
///
|
||||
/// `this_entity`: the entity this component belongs to.
|
||||
pub(crate) fn validate_floating_origin(
|
||||
&self,
|
||||
this_entity: Entity,
|
||||
parents: &Query<&Parent>,
|
||||
) -> Option<Entity> {
|
||||
let floating_origin = self.floating_origin?;
|
||||
let origin_root_entity = parents.iter_ancestors(floating_origin).last()?;
|
||||
Some(floating_origin).filter(|_| origin_root_entity == this_entity)
|
||||
}
|
||||
|
||||
/// Automatically update all [`BigSpace`]s, finding the current floating origin entity within
|
||||
/// their hierarchy. There should be one, and only one, [`FloatingOrigin`] component in a
|
||||
/// `BigSpace` hierarchy.
|
||||
pub fn find_floating_origin(
|
||||
floating_origins: Query<Entity, With<FloatingOrigin>>,
|
||||
parent_query: Query<&Parent>,
|
||||
mut big_spaces: Query<(Entity, &mut BigSpace)>,
|
||||
) {
|
||||
let mut spaces_set = HashMap::new();
|
||||
// Reset all floating origin fields, so we know if any are missing.
|
||||
for (entity, mut space) in &mut big_spaces {
|
||||
space.floating_origin = None;
|
||||
spaces_set.insert(entity, 0);
|
||||
}
|
||||
// Navigate to the root of the hierarchy, starting from each floating origin. This is faster than the reverse direction because it is a tree, and an entity can only have a single parent, but many children. The root should have an empty floating_origin field.
|
||||
for origin in &floating_origins {
|
||||
let maybe_root = parent_query.iter_ancestors(origin).last();
|
||||
if let Some((root, mut space)) =
|
||||
maybe_root.and_then(|root| big_spaces.get_mut(root).ok())
|
||||
{
|
||||
let space_origins = spaces_set.entry(root).or_default();
|
||||
*space_origins += 1;
|
||||
if *space_origins > 1 {
|
||||
error!(
|
||||
"BigSpace {root:#?} has multiple floating origins. There must be exactly one. Resetting this big space and disabling the floating origin to avoid unexpected propagation behavior.",
|
||||
);
|
||||
space.floating_origin = None
|
||||
} else {
|
||||
space.floating_origin = Some(origin);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// Check if any big spaces did not have a floating origin.
|
||||
for space in spaces_set
|
||||
.iter()
|
||||
.filter(|(_k, v)| **v == 0)
|
||||
.map(|(k, _v)| k)
|
||||
{
|
||||
error!("BigSpace {space:#?} has no floating origins. There must be exactly one. Transform propagation will not work until there is a FloatingOrigin in the hierarchy.",)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,35 +2,18 @@
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::precision::GridPrecision;
|
||||
use crate::*;
|
||||
|
||||
/// Defines the grid cell this entity's `Transform` is relative to.
|
||||
///
|
||||
/// This component is generic over a few integer types to allow you to select the grid size you
|
||||
/// need. These correspond to a total usable volume of a cube with the following edge lengths:
|
||||
///
|
||||
/// **Assuming you are using a grid cell edge length of 10,000 meters, and `1.0` == 1 meter**
|
||||
///
|
||||
/// - 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) * grid_cell_edge_length`
|
||||
///
|
||||
/// # 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::GridCell;
|
||||
/// type GalacticGrid = GridCell<i64>;
|
||||
/// ```
|
||||
use self::{precision::GridPrecision, reference_frame::ReferenceFrame};
|
||||
|
||||
/// The cell index an entity within a [`crate::ReferenceFrame`]'s grid. The [`Transform`] of an
|
||||
/// entity with this component is a transformation from the center of this cell.
|
||||
///
|
||||
/// This component adds precision to the translation of an entity's [`Transform`]. In a
|
||||
/// high-precision [`BigSpace`] world, the position of an entity is described by a [`Transform`]
|
||||
/// *and* a [`GridCell`]. This component is the index of a cell inside a large grid defined by the
|
||||
/// [`ReferenceFrame`], and the transform is the position of the entity relative to the center of
|
||||
/// that cell.
|
||||
#[derive(Component, Default, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Reflect)]
|
||||
#[reflect(Component, Default, PartialEq)]
|
||||
pub struct GridCell<P: GridPrecision> {
|
||||
@ -61,7 +44,32 @@ impl<P: GridPrecision> GridCell<P> {
|
||||
y: P::ONE,
|
||||
z: P::ONE,
|
||||
};
|
||||
|
||||
/// If an entity's transform translation becomes larger than the limit specified in its
|
||||
/// [`ReferenceFrame`], it will be relocated to the nearest grid cell to reduce the size of the
|
||||
/// transform.
|
||||
pub fn recenter_large_transforms(
|
||||
reference_frames: Query<&ReferenceFrame<P>>,
|
||||
mut changed_transform: Query<(&mut Self, &mut Transform, &Parent), Changed<Transform>>,
|
||||
) {
|
||||
changed_transform
|
||||
.par_iter_mut()
|
||||
.for_each(|(mut grid_pos, mut transform, parent)| {
|
||||
let Ok(reference_frame) = reference_frames.get(parent.get()) else {
|
||||
return;
|
||||
};
|
||||
if transform.as_ref().translation.abs().max_element()
|
||||
> reference_frame.maximum_distance_from_origin()
|
||||
{
|
||||
let (grid_cell_delta, translation) = reference_frame
|
||||
.imprecise_translation_to_grid(transform.as_ref().translation);
|
||||
*grid_pos += grid_cell_delta;
|
||||
transform.translation = translation;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
@ -73,6 +81,7 @@ impl<P: GridPrecision> std::ops::Add for GridCell<P> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
@ -84,6 +93,7 @@ impl<P: GridPrecision> std::ops::Sub for GridCell<P> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Add for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
@ -91,6 +101,7 @@ impl<P: GridPrecision> std::ops::Add for &GridCell<P> {
|
||||
(*self).add(*rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Sub for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
@ -112,3 +123,23 @@ impl<P: GridPrecision> std::ops::SubAssign for GridCell<P> {
|
||||
*self = self.sub(rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Mul<P> for GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
fn mul(self, rhs: P) -> Self::Output {
|
||||
GridCell {
|
||||
x: self.x.mul(rhs),
|
||||
y: self.y.mul(rhs),
|
||||
z: self.z.mul(rhs),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> std::ops::Mul<P> for &GridCell<P> {
|
||||
type Output = GridCell<P>;
|
||||
|
||||
fn mul(self, rhs: P) -> Self::Output {
|
||||
(*self).mul(rhs)
|
||||
}
|
||||
}
|
||||
|
||||
427
src/lib.rs
427
src/lib.rs
@ -2,19 +2,70 @@
|
||||
//! the observable universe, with no added dependencies, while remaining largely compatible with the
|
||||
//! rest of the Bevy ecosystem.
|
||||
//!
|
||||
//! The next section explains the problem this solves in more detail, how this plugin works, and a
|
||||
//! list of other solutions that were considered. If you'd like, you can instead skip ahead to
|
||||
//! [Usage](crate#usage).
|
||||
//!
|
||||
//! ### Problem
|
||||
//!
|
||||
//! Objects far from the origin suffer from reduced precision, causing rendered meshes to jitter and
|
||||
//! jiggle, and transformation calculations to encounter catastrophic cancellation.
|
||||
//!
|
||||
//! As the camera moves farther from the origin, the scale of floats needed to describe the position
|
||||
//! of meshes and the camera get larger, which in turn means there is less precision available.
|
||||
//! Consequently, when the matrix math is done to compute the position of objects in view space,
|
||||
//! mesh vertices will be displaced due to this lost precision.
|
||||
//! As a camera moves far from the origin, the values describing its x/y/z coordinates become large,
|
||||
//! leaving less precision to the right of the decimal place. Consequently, when computing the
|
||||
//! position of objects in view space, mesh vertices will be displaced due to this lost precision.
|
||||
//!
|
||||
//! This is a great little tool to calculate how much precision a floating point value has at a
|
||||
//! given scale: <http://www.ehopkinson.com/floatprecision.html>.
|
||||
//!
|
||||
//! ### Possible Solutions
|
||||
//!
|
||||
//! There are many ways to solve this problem!
|
||||
//!
|
||||
//! - Periodic recentering: every time the camera moves far enough away from the origin, move it
|
||||
//! back to the origin and apply the same offset to all other entities.
|
||||
//! - Problem: Objects far from the camera will drift and accumulate error.
|
||||
//! - Problem: No fixed reference frame.
|
||||
//! - Problem: Recentering triggers change detection even for objects that did not move.
|
||||
//! - Camera-relative coordinates: don't move the camera, move the world around the camera.
|
||||
//! - Problem: Objects far from the camera will drift and accumulate error.
|
||||
//! - Problem: No fixed reference frame
|
||||
//! - Problem: Math is more complex when everything is relative to the camera.
|
||||
//! - Problem: Rotating the camera requires recomputing transforms for everything.
|
||||
//! - Problem: Camera movement triggers change detection even for objects that did not move.
|
||||
//! - Problem: Incompatible with existing plugins that use `Transform`.
|
||||
//! - Double precision coordinates: Store transforms in double precision
|
||||
//! - Problem: Rendering still requires positions be in single precision, which either requires
|
||||
//! using one of the above techniques, or emulating 64 bit precision in shaders.
|
||||
//! - Problem: Updating double precision transforms is more expensive than single precision.
|
||||
//! - Problem: Computing the `GlobalTransform` is more expensive than single precision.
|
||||
//! - Problem: Size is limited to approximately the orbit of Saturn at human scales.
|
||||
//! - Problem: Incompatible with existing plugins that use `Transform`.
|
||||
//! - Chunks: Place objects in a large grid, and track the grid cell they are in,
|
||||
//! - Problem: Requires a component to track the grid cell, in addition to the `Transform`.
|
||||
//! - Problem: Computing the `GlobalTransform` is more expensive than single precision.
|
||||
//!
|
||||
//! ### Solution
|
||||
//!
|
||||
//! While using the [`FloatingOriginPlugin`], the position of entities is now defined with the
|
||||
//! This plugin uses the last solution listed above. The most significant benefits of this method
|
||||
//! over the others are:
|
||||
//! - Absolute high-precision positions in space that do not change when the camera moves. The only
|
||||
//! component that is affected by precision loss is the `GlobalTransform` used for rendering. The
|
||||
//! `GridCell` and `Transform` only change when an entity moves. This is especially useful for
|
||||
//! multiplayer - the server needs a source of truth for position that doesn't drift over time.
|
||||
//! - Virtually limitless volume and scale; you can work at the scale of subatomic particles, across
|
||||
//! the width of the observable universe. Double precision is downright suffocating in comparison.
|
||||
//! - Uniform precision across the play area. Unlike double precision, the available precision
|
||||
//! does not decrease as you move to the edge of the play area, it is instead relative to the
|
||||
//! distance from the origin of the current grid cell.
|
||||
//! - High precision coordinates are invisible if you don't need them. You can move objects using
|
||||
//! their `Transform` alone, which results in decent ecosystem compatibility.
|
||||
//! - High precision is completely opt-in. If you don't add the `GridCell` component to an entity,
|
||||
//! it behaves like a normal single precision transform, with the same performance cost, yet it
|
||||
//! can exist in the high precision hierarchy. This allows you to load in GLTFs or other
|
||||
//! low-precision entity hierarchies with no added effort or cost.
|
||||
//!
|
||||
//! While using the [`BigSpacePlugin`], the position of entities is now defined with the
|
||||
//! [`ReferenceFrame`], [`GridCell`], and [`Transform`] components. The `ReferenceFrame` is a large
|
||||
//! integer grid of cells; entities are located within this grid using the `GridCell` component.
|
||||
//! Finally, the `Transform` is used to position the entity relative to the center of its
|
||||
@ -22,44 +73,72 @@
|
||||
//! recomputed relative to the center of that new cell. This prevents `Transforms` from ever
|
||||
//! becoming larger than a single grid cell, and thus prevents floating point precision artifacts.
|
||||
//!
|
||||
//! `ReferenceFrame`s can also be nested. This allows you to define moving reference frames, which
|
||||
//! can make certain use cases much simpler. For example, if you have a planet rotating, and
|
||||
//! The grid adds precision to your transforms. If you are using (32-bit) `Transform`s on an `i32`
|
||||
//! grid, you will have 64 bits of precision: 32 bits to address into a large integer grid, and 32
|
||||
//! bits of floating point precision within a grid cell. This plugin is generic up to `i128` grids,
|
||||
//! giving you up tp 160 bits of precision of translation.
|
||||
//!
|
||||
//! `ReferenceFrame`s - grids - can be nested. This allows you to define moving reference frames,
|
||||
//! which can make certain use cases much simpler. For example, if you have a planet rotating, and
|
||||
//! orbiting around its star, it would be very annoying if you had to compute this orbit and
|
||||
//! rotation for all object on the surface in high precision. Instead, you can place the planet and
|
||||
//! rotation for all objects on the surface in high precision. Instead, you can place the planet and
|
||||
//! all objects on its surface in the same reference frame. The motion of the planet will be
|
||||
//! inherited by all children in that reference frame, in high precision. Entities at the root of
|
||||
//! the hierarchy will be implicitly placed in the [`RootReferenceFrame`].
|
||||
//! inherited by all children in that reference frame, in high precision.
|
||||
//!
|
||||
//! The above steps are also applied to the entity marked with the [`FloatingOrigin`] component. The
|
||||
//! only difference is that the `GridCell` of the floating origin is used when computing the
|
||||
//! [`GlobalTransform`] of all other entities. To an outside observer, as the floating origin camera
|
||||
//! moves through space and reaches the limits of its `GridCell`, it would appear to teleport to the
|
||||
//! opposite side of the cell, similar to the spaceship in the game *Asteroids*.
|
||||
//! Entities at the root of bevy's entity hierarchy are not in any reference frame. This allows
|
||||
//! plugins from the 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 reference frame, you don't have to, as the process is opt-in. The
|
||||
//! high-precision hierarchical reference frames are explicit. Each high-precision tree must have a
|
||||
//! [`BigSpaceRootBundle`] at the root, and each `BigSpace` is independent. This means that each
|
||||
//! `BigSpace` has its own floating origin, which allows you to do things like rendering two players
|
||||
//! on opposite ends of the universe simultaneously.
|
||||
//!
|
||||
//! The `GlobalTransform` of all entities is computed relative to the floating origin's grid cell.
|
||||
//! Because of this, entities very far from the origin will have very large, imprecise positions.
|
||||
//! However, this is always relative to the camera (floating origin), so these artifacts will always
|
||||
//! be too far away to be seen, no matter where the camera moves. Because this only affects the
|
||||
//! `GlobalTransform` and not the `Transform`, this also means that entities will never permanently
|
||||
//! lose precision just because they were far from the origin at some point. The lossy calculation
|
||||
//! only occurs when computing the `GlobalTransform` of entities, the high precision `GridCell` and
|
||||
//! `Transform` are unaffected.
|
||||
//! All of the above applies to the entity marked with the [`FloatingOrigin`] component. The
|
||||
//! floating origin can be any high-precision entity in a `BigSpace`, it doesn't need to be a
|
||||
//! camera. The only thing special about the entity marked as the floating origin, is that it is
|
||||
//! used to compute the [`GlobalTransform`] of all other entities in that `BigSpace`. To an outside
|
||||
//! observer, every high-precision entity within a `BigSpace` is confined to a box the size of a
|
||||
//! grid cell - like a game of *Asteroids*. Only once you render the `BigSpace` from the point of
|
||||
//! view of the floating origin, by calculating their `GlobalTransform`s, do entities appear very
|
||||
//! distant from the floating origin.
|
||||
//!
|
||||
//! # Getting Started
|
||||
//! As described above. the `GlobalTransform` of all entities is computed relative to the floating
|
||||
//! origin's grid cell. Because of this, entities very far from the origin will have very large,
|
||||
//! imprecise positions. However, this is always relative to the camera (floating origin), so these
|
||||
//! artifacts will always be too far away to be seen, no matter where the camera moves. Because this
|
||||
//! only affects the `GlobalTransform` and not the `Transform`, this also means that entities will
|
||||
//! never permanently lose precision just because they were far from the origin at some point. The
|
||||
//! lossy calculation only occurs when computing the `GlobalTransform` of entities, the high
|
||||
//! precision `GridCell` and `Transform` are never touched.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! To start using this plugin, you will first need to choose how big your world should be! Do you
|
||||
//! need an i8, or an i128? See [`GridPrecision`](crate::precision::GridPrecision) for more details
|
||||
//! and documentation.
|
||||
//!
|
||||
//! To start using this plugin:
|
||||
//! 1. Disable Bevy's transform plugin: `DefaultPlugins.build().disable::<TransformPlugin>()`
|
||||
//! 2. Add the [`FloatingOriginPlugin`] to your `App`
|
||||
//! 3. Add the [`GridCell`] component to all spatial entities
|
||||
//! 4. Add the [`FloatingOrigin`] component to the active camera
|
||||
//! 5. Add the [`IgnoreFloatingOrigin`] component
|
||||
//! 2. Add the [`BigSpacePlugin`] to your `App`
|
||||
//! 3. Spawn a [`BigSpace`] with [`spawn_big_space`](BigSpaceCommands::spawn_big_space), and spawn
|
||||
//! entities in it.
|
||||
//! 4. Add the [`FloatingOrigin`] to your active camera in the [`BigSpace`].
|
||||
//!
|
||||
//! Take a look at [`ReferenceFrame`] component for some useful helper methods.
|
||||
//! To add more levels to the hierarchy, you can use [`ReferenceFrame`]s, which themselves can
|
||||
//! contain high-precision spatial entities. Reference frames are useful when you want all objects
|
||||
//! to move together in space, for example, objects on the surface of a planet rotating on its axis
|
||||
//! and orbiting a star.
|
||||
//!
|
||||
//! Take a look at the [`ReferenceFrame`] component for some useful helper methods. The component
|
||||
//! defines the scale of the grid, which is very important when computing distances between objects
|
||||
//! in different cells. Note that the root [`BigSpace`] also has a [`ReferenceFrame`] component.
|
||||
//!
|
||||
//! # Moving Entities
|
||||
//!
|
||||
//! For the most part, you can update the position of entities normally while using this plugin, and
|
||||
//! it will automatically handle the tricky bits. However, there is one big caveat:
|
||||
//! it will automatically handle the tricky bits. If you move an entity too far from the center of
|
||||
//! its grid cell, the plugin will automatically move it into the correct cell for you. However,
|
||||
//! there is one big caveat:
|
||||
//!
|
||||
//! **Avoid setting position absolutely, instead prefer applying a relative delta**
|
||||
//!
|
||||
@ -91,281 +170,37 @@
|
||||
//! However, if you have something that must not accumulate error, like the orbit of a planet, you
|
||||
//! can instead do the orbital calculation (position as a function of time) to compute the absolute
|
||||
//! position of the planet with high precision, then directly compute the [`GridCell`] and
|
||||
//! [`Transform`] of that entity using [`ReferenceFrame::translation_to_grid`]. If the star
|
||||
//! this planet is orbiting around is also moving through space, note that you can add/subtract grid
|
||||
//! cells. This means you can do each calculation in the reference frame of the moving body, and sum
|
||||
//! up the computed translations and grid cell offsets to get a more precise result.
|
||||
//! [`Transform`] of that entity using [`ReferenceFrame::translation_to_grid`].
|
||||
//!
|
||||
//! # Next Steps
|
||||
//!
|
||||
//! Take a look at the examples to see usage, as well as explanation of these use cases and topics.
|
||||
|
||||
#![allow(clippy::type_complexity)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use bevy::{prelude::*, transform::TransformSystem};
|
||||
use propagation::{propagate_transforms, sync_simple_transforms};
|
||||
use reference_frame::local_origin::ReferenceFrames;
|
||||
use std::marker::PhantomData;
|
||||
use world_query::GridTransformReadOnly;
|
||||
#![warn(missing_docs)]
|
||||
|
||||
pub mod bundles;
|
||||
pub mod commands;
|
||||
pub mod floating_origins;
|
||||
pub mod grid_cell;
|
||||
pub mod plugin;
|
||||
pub mod precision;
|
||||
pub mod propagation;
|
||||
pub mod reference_frame;
|
||||
pub mod validation;
|
||||
pub mod world_query;
|
||||
|
||||
pub use grid_cell::GridCell;
|
||||
pub use propagation::IgnoreFloatingOrigin;
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub mod debug;
|
||||
|
||||
#[cfg(feature = "camera")]
|
||||
pub mod camera;
|
||||
|
||||
use precision::*;
|
||||
|
||||
use crate::reference_frame::{
|
||||
local_origin::LocalFloatingOrigin, ReferenceFrame, RootReferenceFrame,
|
||||
};
|
||||
|
||||
/// Add this plugin to your [`App`] for floating origin functionality.
|
||||
pub struct FloatingOriginPlugin<P: GridPrecision> {
|
||||
/// The edge length of a single cell.
|
||||
pub grid_edge_length: f32,
|
||||
/// How far past the extents of a cell an entity must travel before a grid recentering occurs.
|
||||
/// This prevents entities from rapidly switching between cells when moving along a boundary.
|
||||
pub switching_threshold: f32,
|
||||
phantom: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> Default for FloatingOriginPlugin<P> {
|
||||
fn default() -> Self {
|
||||
Self::new(2_000f32, 100f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> FloatingOriginPlugin<P> {
|
||||
/// Construct a new plugin with the following settings.
|
||||
pub fn new(grid_edge_length: f32, switching_threshold: f32) -> Self {
|
||||
FloatingOriginPlugin {
|
||||
grid_edge_length,
|
||||
switching_threshold,
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: GridPrecision + Reflect + FromReflect + TypePath> Plugin for FloatingOriginPlugin<P> {
|
||||
fn build(&self, app: &mut App) {
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
enum FloatingOriginSet {
|
||||
RecenterLargeTransforms,
|
||||
LocalFloatingOrigins,
|
||||
RootGlobalTransforms,
|
||||
PropagateTransforms,
|
||||
}
|
||||
|
||||
let system_set_config = || {
|
||||
(
|
||||
recenter_transform_on_grid::<P>.in_set(FloatingOriginSet::RecenterLargeTransforms),
|
||||
LocalFloatingOrigin::<P>::update
|
||||
.in_set(FloatingOriginSet::LocalFloatingOrigins)
|
||||
.after(FloatingOriginSet::RecenterLargeTransforms),
|
||||
(
|
||||
sync_simple_transforms::<P>,
|
||||
update_grid_cell_global_transforms::<P>,
|
||||
)
|
||||
.in_set(FloatingOriginSet::RootGlobalTransforms)
|
||||
.after(FloatingOriginSet::LocalFloatingOrigins),
|
||||
propagate_transforms::<P>
|
||||
.in_set(FloatingOriginSet::PropagateTransforms)
|
||||
.after(FloatingOriginSet::RootGlobalTransforms),
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate)
|
||||
};
|
||||
|
||||
app.insert_resource(RootReferenceFrame::<P>(ReferenceFrame::new(
|
||||
self.grid_edge_length,
|
||||
self.switching_threshold,
|
||||
)))
|
||||
.register_type::<Transform>()
|
||||
.register_type::<GlobalTransform>()
|
||||
.register_type::<GridCell<P>>()
|
||||
.register_type::<ReferenceFrame<P>>()
|
||||
.register_type::<RootReferenceFrame<P>>()
|
||||
.add_plugins(ValidParentCheckPlugin::<GlobalTransform>::default())
|
||||
.add_systems(PostStartup, system_set_config())
|
||||
.add_systems(PostUpdate, system_set_config());
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal bundle needed to position an entity in floating origin space.
|
||||
///
|
||||
/// This is the floating origin equivalent of the [`SpatialBundle`].
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct FloatingSpatialBundle<P: GridPrecision> {
|
||||
/// The visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub visibility: Visibility,
|
||||
/// The inherited visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub inherited: InheritedVisibility,
|
||||
/// The view visibility of the entity.
|
||||
#[cfg(feature = "bevy_render")]
|
||||
pub view: ViewVisibility,
|
||||
/// The transform of the entity.
|
||||
pub transform: Transform,
|
||||
/// The global transform of the entity.
|
||||
pub global_transform: GlobalTransform,
|
||||
/// The grid position of the entity
|
||||
pub grid_position: GridCell<P>,
|
||||
}
|
||||
|
||||
/// Marks the entity to use as the floating origin. All other entities will be positioned relative
|
||||
/// to this entity's [`GridCell`].
|
||||
#[derive(Component, Reflect)]
|
||||
pub struct FloatingOrigin;
|
||||
|
||||
/// If an entity's transform becomes larger than the specified limit, it is relocated to the nearest
|
||||
/// grid cell to reduce the size of the transform.
|
||||
pub fn recenter_transform_on_grid<P: GridPrecision>(
|
||||
reference_frames: ReferenceFrames<P>,
|
||||
mut changed_transform: Query<(Entity, &mut GridCell<P>, &mut Transform), Changed<Transform>>,
|
||||
) {
|
||||
changed_transform
|
||||
.par_iter_mut()
|
||||
.for_each(|(entity, mut grid_pos, mut transform)| {
|
||||
let Some(frame) = reference_frames
|
||||
.get_handle(entity)
|
||||
.map(|handle| reference_frames.resolve_handle(handle))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if transform.as_ref().translation.abs().max_element()
|
||||
> frame.maximum_distance_from_origin()
|
||||
{
|
||||
let (grid_cell_delta, translation) =
|
||||
frame.imprecise_translation_to_grid(transform.as_ref().translation);
|
||||
*grid_pos += grid_cell_delta;
|
||||
transform.translation = translation;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Update the `GlobalTransform` of entities with a [`GridCell`], using the [`ReferenceFrame`] the
|
||||
/// entity belongs to.
|
||||
pub fn update_grid_cell_global_transforms<P: GridPrecision>(
|
||||
root: Res<RootReferenceFrame<P>>,
|
||||
reference_frames: Query<(&ReferenceFrame<P>, &Children)>,
|
||||
mut entities: ParamSet<(
|
||||
Query<(GridTransformReadOnly<P>, &mut GlobalTransform), With<Parent>>, // Node entities
|
||||
Query<(GridTransformReadOnly<P>, &mut GlobalTransform), Without<Parent>>, // Root entities
|
||||
)>,
|
||||
) {
|
||||
// Update the GlobalTransform of GridCell entities at the root of the hierarchy
|
||||
entities
|
||||
.p1()
|
||||
.par_iter_mut()
|
||||
.for_each(|(grid_transform, mut global_transform)| {
|
||||
*global_transform =
|
||||
root.global_transform(grid_transform.cell, grid_transform.transform);
|
||||
});
|
||||
|
||||
// Update the GlobalTransform of GridCell entities that are children of a ReferenceFrame
|
||||
for (frame, children) in &reference_frames {
|
||||
let mut with_parent_query = entities.p0();
|
||||
let mut frame_children = with_parent_query.iter_many_mut(children);
|
||||
while let Some((grid_transform, mut global_transform)) = frame_children.fetch_next() {
|
||||
*global_transform =
|
||||
frame.global_transform(grid_transform.cell, grid_transform.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub mod debug;
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
mod tests;
|
||||
|
||||
#[test]
|
||||
fn changing_floating_origin_updates_global_transform() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||
use bevy::prelude::*;
|
||||
|
||||
let first = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
150.0, 0.0, 0.0,
|
||||
))),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
|
||||
let second = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
0.0, 0.0, 300.0,
|
||||
))),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
|
||||
app.world.entity_mut(first).remove::<FloatingOrigin>();
|
||||
app.world.entity_mut(second).insert(FloatingOrigin);
|
||||
|
||||
app.update();
|
||||
|
||||
let second_global_transform = app.world.get::<GlobalTransform>(second).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
second_global_transform.translation(),
|
||||
Vec3::new(0.0, 0.0, 300.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_global_transforms_are_updated_when_floating_origin_changes() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||
|
||||
let first = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
150.0, 0.0, 0.0,
|
||||
))),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
|
||||
let second = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
0.0, 0.0, 300.0,
|
||||
))),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((TransformBundle::from_transform(
|
||||
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
|
||||
),));
|
||||
})
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
|
||||
app.world.entity_mut(first).remove::<FloatingOrigin>();
|
||||
app.world.entity_mut(second).insert(FloatingOrigin);
|
||||
|
||||
app.update();
|
||||
|
||||
let child = app.world.get::<Children>(second).unwrap()[0];
|
||||
let child_transform = app.world.get::<GlobalTransform>(child).unwrap();
|
||||
|
||||
assert_eq!(child_transform.translation(), Vec3::new(0.0, 0.0, 600.0));
|
||||
}
|
||||
}
|
||||
pub use bundles::{BigReferenceFrameBundle, BigSpaceRootBundle, BigSpatialBundle};
|
||||
pub use commands::{BigSpaceCommands, ReferenceFrameCommands, SpatialEntityCommands};
|
||||
pub use floating_origins::{BigSpace, FloatingOrigin};
|
||||
pub use grid_cell::GridCell;
|
||||
pub use plugin::{BigSpacePlugin, FloatingOriginSet};
|
||||
pub use reference_frame::ReferenceFrame;
|
||||
|
||||
117
src/plugin.rs
Normal file
117
src/plugin.rs
Normal file
@ -0,0 +1,117 @@
|
||||
//! The bevy plugin for big_space.
|
||||
|
||||
use bevy::{prelude::*, transform::TransformSystem};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
precision::GridPrecision,
|
||||
reference_frame::{local_origin::LocalFloatingOrigin, ReferenceFrame},
|
||||
validation, BigSpace, FloatingOrigin, GridCell,
|
||||
};
|
||||
|
||||
/// Add this plugin to your [`App`] for floating origin functionality.
|
||||
pub struct BigSpacePlugin<P: GridPrecision> {
|
||||
phantom: PhantomData<P>,
|
||||
validate_hierarchies: bool,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision> BigSpacePlugin<P> {
|
||||
/// 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> {
|
||||
fn default() -> Self {
|
||||
#[cfg(debug_assertions)]
|
||||
let validate_hierarchies = true;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let validate_hierarchies = false;
|
||||
|
||||
Self {
|
||||
phantom: Default::default(),
|
||||
validate_hierarchies,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub enum FloatingOriginSet {
|
||||
RecenterLargeTransforms,
|
||||
LocalFloatingOrigins,
|
||||
PropagateHighPrecision,
|
||||
PropagateLowPrecision,
|
||||
}
|
||||
|
||||
impl<P: GridPrecision + Reflect + FromReflect + TypePath> Plugin for BigSpacePlugin<P> {
|
||||
fn build(&self, app: &mut App) {
|
||||
let system_set_config = || {
|
||||
(
|
||||
(
|
||||
GridCell::<P>::recenter_large_transforms,
|
||||
BigSpace::find_floating_origin,
|
||||
)
|
||||
.in_set(FloatingOriginSet::RecenterLargeTransforms),
|
||||
LocalFloatingOrigin::<P>::compute_all
|
||||
.in_set(FloatingOriginSet::LocalFloatingOrigins)
|
||||
.after(FloatingOriginSet::RecenterLargeTransforms),
|
||||
ReferenceFrame::<P>::propagate_high_precision
|
||||
.in_set(FloatingOriginSet::PropagateHighPrecision)
|
||||
.after(FloatingOriginSet::LocalFloatingOrigins),
|
||||
ReferenceFrame::<P>::propagate_low_precision
|
||||
.in_set(FloatingOriginSet::PropagateLowPrecision)
|
||||
.after(FloatingOriginSet::PropagateHighPrecision),
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate)
|
||||
};
|
||||
|
||||
app.register_type::<Transform>()
|
||||
.register_type::<GlobalTransform>()
|
||||
.register_type::<GridCell<P>>()
|
||||
.register_type::<ReferenceFrame<P>>()
|
||||
.register_type::<BigSpace>()
|
||||
.register_type::<FloatingOrigin>()
|
||||
.add_systems(PostStartup, system_set_config())
|
||||
.add_systems(PostUpdate, system_set_config())
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
validation::validate_hierarchy::<validation::SpatialHierarchyRoot<P>>
|
||||
.before(TransformSystem::TransformPropagate)
|
||||
.run_if({
|
||||
let run = self.validate_hierarchies;
|
||||
move || run
|
||||
}),
|
||||
)
|
||||
// These are the bevy transform propagation systems. Because these start from the root
|
||||
// of the hierarchy, and BigSpace bundles (at the root) do not contain a Transform,
|
||||
// these systems will not interact with any high precision entities in big space. These
|
||||
// systems are added for ecosystem compatibility with bevy, although the rendered
|
||||
// behavior might look strange if they share a camera with one using the floating
|
||||
// origin.
|
||||
//
|
||||
// This is most useful for bevy_ui, which relies on the transform systems to work, or if
|
||||
// you want to render a camera that only needs to render a low-precision scene.
|
||||
.add_systems(
|
||||
PostStartup,
|
||||
(
|
||||
bevy::transform::systems::sync_simple_transforms,
|
||||
bevy::transform::systems::propagate_transforms,
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
bevy::transform::systems::sync_simple_transforms,
|
||||
bevy::transform::systems::propagate_transforms,
|
||||
)
|
||||
.in_set(TransformSystem::TransformPropagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,36 @@ use bevy::reflect::Reflect;
|
||||
///
|
||||
/// 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 quarks 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::GridCell;
|
||||
/// 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
|
||||
|
||||
@ -1,220 +0,0 @@
|
||||
//! Propagates transforms through the entity hierarchy.
|
||||
//!
|
||||
//! This is a modified version of Bevy's own transform propagation system.
|
||||
|
||||
use crate::{
|
||||
precision::GridPrecision,
|
||||
reference_frame::{ReferenceFrame, RootReferenceFrame},
|
||||
GridCell,
|
||||
};
|
||||
use bevy::prelude::*;
|
||||
|
||||
/// Entities with this component will ignore the floating origin, and will instead propagate
|
||||
/// transforms normally.
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
pub struct IgnoreFloatingOrigin;
|
||||
|
||||
/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy.
|
||||
pub fn sync_simple_transforms<P: GridPrecision>(
|
||||
root: Res<RootReferenceFrame<P>>,
|
||||
mut query: ParamSet<(
|
||||
Query<
|
||||
(&Transform, &mut GlobalTransform, Has<IgnoreFloatingOrigin>),
|
||||
(
|
||||
Or<(Changed<Transform>, Added<GlobalTransform>)>,
|
||||
Without<Parent>,
|
||||
Without<Children>,
|
||||
Without<GridCell<P>>,
|
||||
),
|
||||
>,
|
||||
Query<
|
||||
(
|
||||
Ref<Transform>,
|
||||
&mut GlobalTransform,
|
||||
Has<IgnoreFloatingOrigin>,
|
||||
),
|
||||
(Without<Parent>, Without<Children>, Without<GridCell<P>>),
|
||||
>,
|
||||
)>,
|
||||
mut orphaned: RemovedComponents<Parent>,
|
||||
) {
|
||||
// Update changed entities.
|
||||
query.p0().par_iter_mut().for_each(
|
||||
|(transform, mut global_transform, ignore_floating_origin)| {
|
||||
if ignore_floating_origin {
|
||||
*global_transform = GlobalTransform::from(*transform);
|
||||
} else {
|
||||
*global_transform = root.global_transform(&GridCell::ZERO, transform);
|
||||
}
|
||||
},
|
||||
);
|
||||
// Update orphaned entities.
|
||||
let mut query = query.p1();
|
||||
let mut iter = query.iter_many_mut(orphaned.read());
|
||||
while let Some((transform, mut global_transform, ignore_floating_origin)) = iter.fetch_next() {
|
||||
if !transform.is_changed() && !global_transform.is_added() {
|
||||
if ignore_floating_origin {
|
||||
*global_transform = GlobalTransform::from(*transform);
|
||||
} else {
|
||||
*global_transform = root.global_transform(&GridCell::ZERO, &transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the [`GlobalTransform`] of entities with a [`Transform`] that are children of a
|
||||
/// [`ReferenceFrame`] and do not have a [`GridCell`] component, or that are children of
|
||||
/// [`GridCell`]s.
|
||||
pub fn propagate_transforms<P: GridPrecision>(
|
||||
frames: Query<&Children, With<ReferenceFrame<P>>>,
|
||||
frame_child_query: Query<(Entity, &Children, &GlobalTransform), With<GridCell<P>>>,
|
||||
root_frame_query: Query<
|
||||
(Entity, &Children, &GlobalTransform),
|
||||
(With<GridCell<P>>, Without<Parent>),
|
||||
>,
|
||||
root_frame: Res<RootReferenceFrame<P>>,
|
||||
mut root_frame_gridless_query: Query<
|
||||
(
|
||||
Entity,
|
||||
&Children,
|
||||
&Transform,
|
||||
&mut GlobalTransform,
|
||||
Has<IgnoreFloatingOrigin>,
|
||||
),
|
||||
(Without<GridCell<P>>, Without<Parent>),
|
||||
>,
|
||||
transform_query: Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCell<P>>,
|
||||
Without<ReferenceFrame<P>>,
|
||||
),
|
||||
>,
|
||||
parent_query: Query<(Entity, Ref<Parent>)>,
|
||||
) {
|
||||
let update_transforms = |(entity, children, global_transform)| {
|
||||
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
actual_parent.get(), entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY:
|
||||
// - `child` must have consistent parentage, or the above assertion would panic. Since
|
||||
// `child` is parented to a root entity, the entire hierarchy leading to it is
|
||||
// consistent.
|
||||
// - We may operate as if all descendants are consistent, since `propagate_recursive`
|
||||
// will panic before continuing to propagate if it encounters an entity with
|
||||
// inconsistent parentage.
|
||||
// - Since each root entity is unique and the hierarchy is consistent and forest-like,
|
||||
// other root entities' `propagate_recursive` calls will not conflict with this one.
|
||||
// - Since this is the only place where `transform_query` gets used, there will be no
|
||||
// conflicting fetches elsewhere.
|
||||
unsafe {
|
||||
propagate_recursive(&global_transform, &transform_query, &parent_query, child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
frames.par_iter().for_each(|children| {
|
||||
children
|
||||
.iter()
|
||||
.filter_map(|child| frame_child_query.get(*child).ok())
|
||||
.for_each(|(e, c, g)| update_transforms((e, c, *g)))
|
||||
});
|
||||
root_frame_query
|
||||
.par_iter()
|
||||
.for_each(|(e, c, g)| update_transforms((e, c, *g)));
|
||||
root_frame_gridless_query.par_iter_mut().for_each(
|
||||
|(entity, children, local, mut global, ignore_floating_origin)| {
|
||||
if ignore_floating_origin {
|
||||
*global = GlobalTransform::from(*local);
|
||||
} else {
|
||||
*global = root_frame.global_transform(&GridCell::ZERO, local);
|
||||
}
|
||||
update_transforms((entity, children, *global))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// COPIED FROM BEVY
|
||||
///
|
||||
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
|
||||
/// propagating the transforms of any malformed entities and their descendants.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - While this function is running, `transform_query` must not have any fetches for `entity`, nor
|
||||
/// any of its descendants.
|
||||
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must remain
|
||||
/// as a tree or a forest. Each entity must have at most one parent.
|
||||
unsafe fn propagate_recursive<P: GridPrecision>(
|
||||
parent: &GlobalTransform,
|
||||
transform_query: &Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCell<P>>,
|
||||
Without<ReferenceFrame<P>>,
|
||||
),
|
||||
>,
|
||||
parent_query: &Query<(Entity, Ref<Parent>)>,
|
||||
entity: Entity,
|
||||
) {
|
||||
let (global_matrix, children) = {
|
||||
let Ok((transform, mut global_transform, children)) =
|
||||
// SAFETY: This call cannot create aliased mutable references.
|
||||
// - The top level iteration parallelizes on the roots of the hierarchy.
|
||||
// - The caller ensures that each child has one and only one unique parent throughout the entire
|
||||
// hierarchy.
|
||||
//
|
||||
// For example, consider the following malformed hierarchy:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C
|
||||
// \ /
|
||||
// D
|
||||
//
|
||||
// D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B,
|
||||
// the above check will panic as the origin parent does match the recorded parent.
|
||||
//
|
||||
// Also consider the following case, where A and B are roots:
|
||||
//
|
||||
// A B
|
||||
// \ /
|
||||
// C D
|
||||
// \ /
|
||||
// E
|
||||
//
|
||||
// Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
|
||||
// to mutably access E.
|
||||
(unsafe { transform_query.get_unchecked(entity) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
*global_transform = parent.mul_transform(*transform);
|
||||
|
||||
(*global_transform, children)
|
||||
};
|
||||
|
||||
let Some(children) = children else { return };
|
||||
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
actual_parent.get(), entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY: The caller guarantees that `transform_query` will not be fetched
|
||||
// for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
|
||||
//
|
||||
// The above assertion ensures that each child has one and only one unique parent throughout the
|
||||
// entire hierarchy.
|
||||
unsafe {
|
||||
propagate_recursive(&global_matrix, transform_query, parent_query, child);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
//! Describes how the floating origin's position is propagated through the hierarchy of reference
|
||||
//! frames, and used to compute the floating origin's position relative to each reference frame.
|
||||
//! frames, and used to compute the floating origin's position relative to each reference frame. See
|
||||
//! [`LocalFloatingOrigin`].
|
||||
|
||||
use bevy::{
|
||||
ecs::{
|
||||
@ -15,11 +16,12 @@ use bevy::{
|
||||
transform::prelude::*,
|
||||
};
|
||||
|
||||
use super::{ReferenceFrame, RootReferenceFrame};
|
||||
use crate::{FloatingOrigin, GridCell, GridPrecision};
|
||||
|
||||
pub use inner::LocalFloatingOrigin;
|
||||
|
||||
use crate::{precision::GridPrecision, BigSpace, GridCell};
|
||||
|
||||
use super::ReferenceFrame;
|
||||
|
||||
/// A module kept private to enforce use of setters and getters within the parent module.
|
||||
mod inner {
|
||||
use bevy::{
|
||||
@ -27,7 +29,7 @@ mod inner {
|
||||
reflect::prelude::*,
|
||||
};
|
||||
|
||||
use crate::{GridCell, GridPrecision};
|
||||
use crate::{precision::GridPrecision, GridCell};
|
||||
|
||||
/// An isometry that describes the location of the floating origin's grid cell's origin, in the
|
||||
/// local reference frame.
|
||||
@ -38,11 +40,11 @@ mod inner {
|
||||
/// transform every entity relative to the floating origin.
|
||||
///
|
||||
/// If the floating origin is in this local reference frame, the `float` fields will be
|
||||
/// identity. The `float` fields` will be non-identity when the floating origin is in a
|
||||
/// different reference frame that does not perfectly align with this one. Different reference
|
||||
/// frames can be rotated and offset from each other - consider the reference frame of a planet,
|
||||
/// spinning about its axis and orbiting about a star, it will not align with the reference
|
||||
/// frame of the star system!
|
||||
/// identity. The `float` fields will be non-identity when the floating origin is in a different
|
||||
/// reference frame that does not perfectly align with this one. Different reference frames can
|
||||
/// be rotated and offset from each other - consider the reference frame of a planet, spinning
|
||||
/// about its axis and orbiting about a star, it will not align with the reference frame of the
|
||||
/// star system!
|
||||
#[derive(Default, Debug, Clone, PartialEq, Reflect)]
|
||||
pub struct LocalFloatingOrigin<P: GridPrecision> {
|
||||
/// The local cell that the floating origin's grid cell origin falls into.
|
||||
@ -132,159 +134,164 @@ mod inner {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
/// Use the [`ReferenceFrames`] [`SystemParam`] to do useful things with this handle.
|
||||
///
|
||||
/// A reference frame can either be a node in the entity hierarchy stored as a component, or will be
|
||||
/// the root reference frame, which is tracked with a resource. This handle is used to unify access
|
||||
/// to reference frames with a single lightweight type.
|
||||
pub enum ReferenceFrameHandle {
|
||||
/// The reference frame is a node in the hierarchy, stored in a [`ReferenceFrame`] component.
|
||||
Node(Entity),
|
||||
/// The root reference frame, defined in the [`RootReferenceFrame`] resource.
|
||||
Root,
|
||||
fn propagate_origin_to_parent<P: GridPrecision>(
|
||||
this_frame_entity: Entity,
|
||||
reference_frames: &mut ReferenceFramesMut<P>,
|
||||
parent_frame_entity: Entity,
|
||||
) {
|
||||
let (this_frame, this_cell, this_transform) = reference_frames.get(this_frame_entity);
|
||||
let (parent_frame, _parent_cell, _parent_transform) = reference_frames.get(parent_frame_entity);
|
||||
|
||||
// Get this frame's double precision transform, relative to its cell. We ignore the grid
|
||||
// cell here because we don't want to lose precision - we can do these calcs relative to
|
||||
// this cell, then add the grid cell offset at the end.
|
||||
let this_transform = DAffine3::from_rotation_translation(
|
||||
this_transform.rotation.as_dquat(),
|
||||
this_transform.translation.as_dvec3(),
|
||||
);
|
||||
|
||||
// Get the origin's double position in this reference frame
|
||||
let origin_translation = this_frame.grid_position_double(
|
||||
&this_frame.local_floating_origin.cell(),
|
||||
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||
);
|
||||
let this_local_origin_transform = DAffine3::from_rotation_translation(
|
||||
this_frame.local_floating_origin.rotation(),
|
||||
origin_translation,
|
||||
);
|
||||
|
||||
// Multiply to move the origin into the parent's reference frame
|
||||
let origin_affine = this_transform * this_local_origin_transform;
|
||||
|
||||
let (_, origin_rot, origin_trans) = origin_affine.to_scale_rotation_translation();
|
||||
let (origin_cell_relative_to_this_cell, origin_translation_remainder) =
|
||||
parent_frame.translation_to_grid(origin_trans);
|
||||
|
||||
// Up until now we have been computing as if this cell is located at the origin, to maximize
|
||||
// precision. Now that we are done with floats, we can add the cell offset.
|
||||
let parent_origin_cell = origin_cell_relative_to_this_cell + this_cell;
|
||||
|
||||
reference_frames.update_reference_frame(parent_frame_entity, |parent_frame, _, _| {
|
||||
parent_frame.local_floating_origin.set(
|
||||
parent_origin_cell,
|
||||
origin_translation_remainder,
|
||||
origin_rot,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
impl ReferenceFrameHandle {
|
||||
/// Propagate the local origin position from `self` to `child`.
|
||||
///
|
||||
/// This is not a method on `References` to help prevent misuse when accidentally
|
||||
/// swapping the position of arguments.
|
||||
fn propagate_origin_to_child<P: GridPrecision>(
|
||||
self,
|
||||
reference_frames: &mut ReferenceFramesMut<P>,
|
||||
child: ReferenceFrameHandle,
|
||||
) {
|
||||
let (this_frame, _this_cell, _this_transform) = reference_frames.get(self);
|
||||
let (child_frame, child_cell, child_transform) = reference_frames.get(child);
|
||||
fn propagate_origin_to_child<P: GridPrecision>(
|
||||
this_frame_entity: Entity,
|
||||
reference_frames: &mut ReferenceFramesMut<P>,
|
||||
child_frame_entity: Entity,
|
||||
) {
|
||||
let (this_frame, _this_cell, _this_transform) = reference_frames.get(this_frame_entity);
|
||||
let (child_frame, child_cell, child_transform) = reference_frames.get(child_frame_entity);
|
||||
|
||||
// compute double precision translation of origin treating child as the origin grid cell. Add this to the origin's float translation in double,
|
||||
let origin_cell_relative_to_child = this_frame.local_floating_origin.cell() - child_cell;
|
||||
let origin_translation = this_frame.grid_position_double(
|
||||
&origin_cell_relative_to_child,
|
||||
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||
// compute double precision translation of origin treating child as the origin grid cell. Add this to the origin's float translation in double,
|
||||
let origin_cell_relative_to_child = this_frame.local_floating_origin.cell() - child_cell;
|
||||
let origin_translation = this_frame.grid_position_double(
|
||||
&origin_cell_relative_to_child,
|
||||
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||
);
|
||||
|
||||
// then combine with rotation to get a double transform from the child's cell origin to the origin.
|
||||
let origin_rotation = this_frame.local_floating_origin.rotation();
|
||||
let origin_transform_child_cell_local =
|
||||
DAffine3::from_rotation_translation(origin_rotation, origin_translation);
|
||||
|
||||
// Take the inverse of the child's transform as double (this is the "view" transform of the child reference frame)
|
||||
let child_view_child_cell_local = DAffine3::from_rotation_translation(
|
||||
child_transform.rotation.as_dquat(),
|
||||
child_transform.translation.as_dvec3(),
|
||||
)
|
||||
.inverse();
|
||||
|
||||
// then multiply this by the double transform we got of the origin. This is now a transform64 of the origin, wrt to the child.
|
||||
let origin_child_affine = child_view_child_cell_local * origin_transform_child_cell_local;
|
||||
|
||||
// We can decompose into translation (high precision) and rotation.
|
||||
let (_, origin_child_rotation, origin_child_translation) =
|
||||
origin_child_affine.to_scale_rotation_translation();
|
||||
let (child_origin_cell, child_origin_translation_float) =
|
||||
child_frame.translation_to_grid(origin_child_translation);
|
||||
|
||||
reference_frames.update_reference_frame(child_frame_entity, |child_frame, _, _| {
|
||||
child_frame.local_floating_origin.set(
|
||||
child_origin_cell,
|
||||
child_origin_translation_float,
|
||||
origin_child_rotation,
|
||||
);
|
||||
|
||||
// then combine with rotation to get a double transform from the child's cell origin to the origin.
|
||||
let origin_rotation = this_frame.local_floating_origin.rotation();
|
||||
let origin_transform_child_cell_local =
|
||||
DAffine3::from_rotation_translation(origin_rotation, origin_translation);
|
||||
|
||||
// Take the inverse of the child's transform as double (this is the "view" transform of the child reference frame)
|
||||
let child_view_child_cell_local = DAffine3::from_rotation_translation(
|
||||
child_transform.rotation.as_dquat(),
|
||||
child_transform.translation.as_dvec3(),
|
||||
)
|
||||
.inverse();
|
||||
|
||||
// then multiply this by the double transform we got of the origin. This is now a transform64 of the origin, wrt to the child.
|
||||
let origin_child_affine = child_view_child_cell_local * origin_transform_child_cell_local;
|
||||
|
||||
// We can decompose into translation (high precision) and rotation.
|
||||
let (_, origin_child_rotation, origin_child_translation) =
|
||||
origin_child_affine.to_scale_rotation_translation();
|
||||
let (child_origin_cell, child_origin_translation_float) =
|
||||
child_frame.translation_to_grid(origin_child_translation);
|
||||
|
||||
reference_frames.update(child, |child_frame, _, _| {
|
||||
child_frame.local_floating_origin.set(
|
||||
child_origin_cell,
|
||||
child_origin_translation_float,
|
||||
origin_child_rotation,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn propagate_origin_to_parent<P: GridPrecision>(
|
||||
self,
|
||||
reference_frames: &mut ReferenceFramesMut<P>,
|
||||
parent: ReferenceFrameHandle,
|
||||
) {
|
||||
let (this_frame, this_cell, this_transform) = reference_frames.get(self);
|
||||
let (parent_frame, _parent_cell, _parent_transform) = reference_frames.get(parent);
|
||||
|
||||
// Get this frame's double precision transform, relative to its cell. We ignore the grid
|
||||
// cell here because we don't want to lose precision - we can do these calcs relative to
|
||||
// this cell, then add the grid cell offset at the end.
|
||||
let this_transform = DAffine3::from_rotation_translation(
|
||||
this_transform.rotation.as_dquat(),
|
||||
this_transform.translation.as_dvec3(),
|
||||
);
|
||||
|
||||
// Get the origin's double position in this reference frame
|
||||
let origin_translation = this_frame.grid_position_double(
|
||||
&this_frame.local_floating_origin.cell(),
|
||||
&Transform::from_translation(this_frame.local_floating_origin.translation()),
|
||||
);
|
||||
let this_local_origin_transform = DAffine3::from_rotation_translation(
|
||||
this_frame.local_floating_origin.rotation(),
|
||||
origin_translation,
|
||||
);
|
||||
|
||||
// Multiply to move the origin into the parent's reference frame
|
||||
let origin_affine = this_transform * this_local_origin_transform;
|
||||
|
||||
let (_, origin_rot, origin_trans) = origin_affine.to_scale_rotation_translation();
|
||||
let (origin_cell_relative_to_this_cell, origin_translation_remainder) =
|
||||
parent_frame.translation_to_grid(origin_trans);
|
||||
|
||||
// Up until now we have been computing as if this cell is located at the origin, to maximize
|
||||
// precision. Now that we are done with floats, we can add the cell offset.
|
||||
let parent_origin_cell = origin_cell_relative_to_this_cell + this_cell;
|
||||
|
||||
reference_frames.update(parent, |parent_frame, _, _| {
|
||||
parent_frame.local_floating_origin.set(
|
||||
parent_origin_cell,
|
||||
origin_translation_remainder,
|
||||
origin_rot,
|
||||
);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Used to access a reference frame using a single system param. Needed because the reference frame
|
||||
/// could either be a component or a resource (if at the root of the hierarchy).
|
||||
/// A system param for more easily navigating a hierarchy of reference frames.
|
||||
#[derive(SystemParam)]
|
||||
pub struct ReferenceFrames<'w, 's, P: GridPrecision> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
frame_root: Res<'w, RootReferenceFrame<P>>,
|
||||
frame_query: Query<'w, 's, (Entity, Read<ReferenceFrame<P>>)>,
|
||||
children: Query<'w, 's, Read<Children>>,
|
||||
// position: Query<'w, 's, (Read<GridCell<P>>, Read<Transform>), With<ReferenceFrame<P>>>,
|
||||
frame_query: Query<'w, 's, (Entity, Read<ReferenceFrame<P>>, Option<Read<Parent>>)>,
|
||||
}
|
||||
|
||||
impl<'w, 's, P: GridPrecision> ReferenceFrames<'w, 's, P> {
|
||||
/// Get the reference frame from a handle.
|
||||
pub fn resolve_handle(&self, handle: ReferenceFrameHandle) -> &ReferenceFrame<P> {
|
||||
match handle {
|
||||
ReferenceFrameHandle::Node(frame_entity) => self
|
||||
.frame_query
|
||||
.get(frame_entity)
|
||||
.map(|(_entity, frame)| frame)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("{} {handle:?} failed to resolve.\n\tEnsure all GridPrecision components are using the <{}> generic, and all required components are present.\n\tQuery Error: {e}", std::any::type_name::<ReferenceFrameHandle>(), std::any::type_name::<P>())
|
||||
}),
|
||||
ReferenceFrameHandle::Root => {
|
||||
&self.frame_root
|
||||
}
|
||||
}
|
||||
/// Get a [`ReferenceFrame`] from its `Entity`.
|
||||
pub fn get(&self, frame_entity: Entity) -> &ReferenceFrame<P> {
|
||||
self.frame_query
|
||||
.get(frame_entity)
|
||||
.map(|(_entity, frame, _parent)| frame)
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Reference frame entity missing ReferenceFrame component.\n\tError: {e}");
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a handle to this entity's reference frame, if it exists.
|
||||
/// Get the [`ReferenceFrame`] that `this` `Entity` is a child of, if it exists.
|
||||
pub fn parent_frame(&self, this: Entity) -> Option<&ReferenceFrame<P>> {
|
||||
self.parent_frame_entity(this)
|
||||
.map(|frame_entity| self.get(frame_entity))
|
||||
}
|
||||
|
||||
/// Get the ID of the reference frame that `this` `Entity` is a child of, if it exists.
|
||||
#[inline]
|
||||
pub fn get_handle(&self, this: Entity) -> Option<ReferenceFrameHandle> {
|
||||
pub fn parent_frame_entity(&self, this: Entity) -> Option<Entity> {
|
||||
match self.parent.get(this).map(|parent| **parent) {
|
||||
Err(_) => Some(ReferenceFrameHandle::Root),
|
||||
Err(_) => None,
|
||||
Ok(parent) => match self.frame_query.contains(parent) {
|
||||
true => Some(ReferenceFrameHandle::Node(parent)),
|
||||
true => Some(parent),
|
||||
false => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to this entity's reference frame, if it exists.
|
||||
#[inline]
|
||||
pub fn get(&self, this: Entity) -> Option<&ReferenceFrame<P>> {
|
||||
self.get_handle(this)
|
||||
.map(|handle| self.resolve_handle(handle))
|
||||
/// Get handles to all reference frames that are children of this reference frame. Applies a
|
||||
/// filter to the returned children.
|
||||
fn child_frames_filtered(
|
||||
&mut self,
|
||||
this: Entity,
|
||||
mut filter: impl FnMut(Entity) -> bool,
|
||||
) -> Vec<Entity> {
|
||||
self.children
|
||||
.get(this)
|
||||
.iter()
|
||||
.flat_map(|c| c.iter())
|
||||
.filter(|entity| filter(**entity))
|
||||
.filter(|child| self.frame_query.contains(**child))
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get IDs to all reference frames that are children of this reference frame.
|
||||
pub fn child_frames(&mut self, this: Entity) -> Vec<Entity> {
|
||||
self.child_frames_filtered(this, |_| true)
|
||||
}
|
||||
|
||||
/// Get IDs to all reference frames that are siblings of this reference frame.
|
||||
pub fn sibling_frames(&mut self, this_entity: Entity) -> Vec<Entity> {
|
||||
if let Some(parent) = self.parent_frame_entity(this_entity) {
|
||||
self.child_frames_filtered(parent, |e| e != this_entity)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,18 +301,8 @@ impl<'w, 's, P: GridPrecision> ReferenceFrames<'w, 's, P> {
|
||||
pub struct ReferenceFramesMut<'w, 's, P: GridPrecision> {
|
||||
parent: Query<'w, 's, Read<Parent>>,
|
||||
children: Query<'w, 's, Read<Children>>,
|
||||
frame_root: ResMut<'w, RootReferenceFrame<P>>,
|
||||
frame_query: Query<
|
||||
'w,
|
||||
's,
|
||||
(
|
||||
Entity,
|
||||
Read<GridCell<P>>,
|
||||
Read<Transform>,
|
||||
Write<ReferenceFrame<P>>,
|
||||
Option<Read<Parent>>,
|
||||
),
|
||||
>,
|
||||
position: Query<'w, 's, (Read<GridCell<P>>, Read<Transform>)>,
|
||||
frame_query: Query<'w, 's, (Entity, Write<ReferenceFrame<P>>, Option<Read<Parent>>)>,
|
||||
}
|
||||
|
||||
impl<'w, 's, P: GridPrecision> ReferenceFramesMut<'w, 's, P> {
|
||||
@ -314,7 +311,7 @@ impl<'w, 's, P: GridPrecision> ReferenceFramesMut<'w, 's, P> {
|
||||
///
|
||||
/// ## Panics
|
||||
///
|
||||
/// This will panic if the handle passed in is invalid.
|
||||
/// This will panic if the entity passed in is invalid.
|
||||
///
|
||||
/// ## Why a closure?
|
||||
///
|
||||
@ -327,114 +324,82 @@ impl<'w, 's, P: GridPrecision> ReferenceFramesMut<'w, 's, P> {
|
||||
/// I tried returning an enum or a boxed trait object, but ran into issues expressing the
|
||||
/// lifetimes. Worth revisiting if this turns out to be annoying, but seems pretty insignificant
|
||||
/// at the time of writing.
|
||||
#[inline]
|
||||
pub fn update<T>(
|
||||
pub fn update_reference_frame<T>(
|
||||
&mut self,
|
||||
handle: ReferenceFrameHandle,
|
||||
frame_entity: Entity,
|
||||
mut func: impl FnMut(&mut ReferenceFrame<P>, &GridCell<P>, &Transform) -> T,
|
||||
) -> T {
|
||||
match handle {
|
||||
ReferenceFrameHandle::Node(frame_entity) => self
|
||||
.frame_query
|
||||
.get_mut(frame_entity)
|
||||
.map(|(_entity, cell, transform, mut frame, _parent)| {
|
||||
func(frame.as_mut(), cell, transform)
|
||||
})
|
||||
.expect("The supplied reference frame handle to node is no longer valid."),
|
||||
ReferenceFrameHandle::Root => func(
|
||||
&mut self.frame_root,
|
||||
&GridCell::default(), // the reference frame itself is not within another.
|
||||
&Transform::default(), // the reference frame itself is not within another.
|
||||
),
|
||||
}
|
||||
let (cell, transform) = self.position(frame_entity);
|
||||
self.frame_query
|
||||
.get_mut(frame_entity)
|
||||
.map(|(_entity, mut frame, _parent)| func(frame.as_mut(), &cell, &transform))
|
||||
.expect("The supplied reference frame handle to node is no longer valid.")
|
||||
}
|
||||
|
||||
/// Get the reference frame and the position of the reference frame from a handle.
|
||||
pub fn get(
|
||||
&self,
|
||||
handle: ReferenceFrameHandle,
|
||||
) -> (&ReferenceFrame<P>, GridCell<P>, Transform) {
|
||||
match handle {
|
||||
ReferenceFrameHandle::Node(frame_entity) => self
|
||||
.frame_query
|
||||
.get(frame_entity)
|
||||
.map(|(_entity, cell, transform, frame, _parent)| (frame, *cell, *transform))
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("{} {handle:?} failed to resolve.\n\tEnsure all GridPrecision components are using the <{}> generic, and all required components are present.\n\tQuery Error: {e}", std::any::type_name::<ReferenceFrameHandle>(), std::any::type_name::<P>())
|
||||
}),
|
||||
ReferenceFrameHandle::Root => {
|
||||
(&self.frame_root, GridCell::default(), Transform::default())
|
||||
}
|
||||
}
|
||||
/// Get the reference frame and the position of the reference frame from its `Entity`.
|
||||
pub fn get(&self, frame_entity: Entity) -> (&ReferenceFrame<P>, GridCell<P>, Transform) {
|
||||
let (cell, transform) = self.position(frame_entity);
|
||||
self.frame_query
|
||||
.get(frame_entity)
|
||||
.map(|(_entity, frame, _parent)| (frame, cell, transform))
|
||||
.unwrap_or_else(|e| {
|
||||
panic!("Reference frame entity {frame_entity:?} missing ReferenceFrame component.\n\tError: {e}");
|
||||
})
|
||||
}
|
||||
|
||||
/// Get a handle to this entity's reference frame, if it exists.
|
||||
/// Get the position of this reference frame, including its grid cell and transform, or return
|
||||
/// defaults if they are missing.
|
||||
///
|
||||
/// Needed because the root reference frame should not have a grid cell or transform.
|
||||
pub fn position(&self, frame_entity: Entity) -> (GridCell<P>, Transform) {
|
||||
let (cell, transform) = (GridCell::default(), Transform::default());
|
||||
let (cell, transform) = self.position.get(frame_entity).unwrap_or_else(|_| {
|
||||
assert!(self.parent.get(frame_entity).is_err(), "Reference frame entity {frame_entity:?} is missing a GridCell and Transform. This is valid only if this is a root reference frame, but this is not.");
|
||||
(&cell, &transform)
|
||||
});
|
||||
(*cell, *transform)
|
||||
}
|
||||
|
||||
/// Get the ID of the reference frame that `this` `Entity` is a child of, if it exists.
|
||||
#[inline]
|
||||
fn get_handle(&self, this: Entity) -> Option<ReferenceFrameHandle> {
|
||||
pub fn parent_frame(&self, this: Entity) -> Option<Entity> {
|
||||
match self.parent.get(this).map(|parent| **parent) {
|
||||
Err(_) => Some(ReferenceFrameHandle::Root),
|
||||
Err(_) => None,
|
||||
Ok(parent) => match self.frame_query.contains(parent) {
|
||||
true => Some(ReferenceFrameHandle::Node(parent)),
|
||||
true => Some(parent),
|
||||
false => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a handle to the parent reference frame of this reference frame, if it exists.
|
||||
#[inline]
|
||||
fn parent(&mut self, this: ReferenceFrameHandle) -> Option<ReferenceFrameHandle> {
|
||||
match this {
|
||||
ReferenceFrameHandle::Node(this) => self.get_handle(this),
|
||||
ReferenceFrameHandle::Root => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get handles to all reference frames that are children of this reference frame. Applies a
|
||||
/// filter to the returned children.
|
||||
#[inline]
|
||||
fn children_filtered(
|
||||
fn child_frames_filtered(
|
||||
&mut self,
|
||||
this: ReferenceFrameHandle,
|
||||
this: Entity,
|
||||
mut filter: impl FnMut(Entity) -> bool,
|
||||
) -> Vec<ReferenceFrameHandle> {
|
||||
match this {
|
||||
ReferenceFrameHandle::Node(this) => self
|
||||
.children
|
||||
.get(this)
|
||||
.iter()
|
||||
.flat_map(|c| c.iter())
|
||||
.filter(|entity| filter(**entity))
|
||||
.filter(|child| self.frame_query.contains(**child))
|
||||
.map(|child| ReferenceFrameHandle::Node(*child))
|
||||
.collect(),
|
||||
ReferenceFrameHandle::Root => self
|
||||
.frame_query
|
||||
.iter()
|
||||
.filter(|(entity, ..)| filter(*entity))
|
||||
.filter(|(.., parent)| parent.is_none())
|
||||
.map(|(entity, ..)| ReferenceFrameHandle::Node(entity))
|
||||
.collect(),
|
||||
}
|
||||
) -> Vec<Entity> {
|
||||
self.children
|
||||
.get(this)
|
||||
.iter()
|
||||
.flat_map(|c| c.iter())
|
||||
.filter(|entity| filter(**entity))
|
||||
.filter(|child| self.frame_query.contains(**child))
|
||||
.copied()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get handles to all reference frames that are children of this reference frame.
|
||||
#[inline]
|
||||
fn children(&mut self, this: ReferenceFrameHandle) -> Vec<ReferenceFrameHandle> {
|
||||
self.children_filtered(this, |_| true)
|
||||
/// Get IDs to all reference frames that are children of this reference frame.
|
||||
pub fn child_frames(&mut self, this: Entity) -> Vec<Entity> {
|
||||
self.child_frames_filtered(this, |_| true)
|
||||
}
|
||||
|
||||
/// Get handles to all reference frames that are siblings of this reference frame.
|
||||
#[inline]
|
||||
fn siblings(&mut self, this: ReferenceFrameHandle) -> Vec<ReferenceFrameHandle> {
|
||||
match this {
|
||||
ReferenceFrameHandle::Node(this_entity) => {
|
||||
if let Some(parent) = self.parent(this) {
|
||||
self.children_filtered(parent, |e| e != this_entity)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
ReferenceFrameHandle::Root => Vec::new(),
|
||||
/// Get IDs to all reference frames that are siblings of this reference frame.
|
||||
pub fn sibling_frames(&mut self, this_entity: Entity) -> Vec<Entity> {
|
||||
if let Some(parent) = self.parent_frame(this_entity) {
|
||||
self.child_frames_filtered(parent, |e| e != this_entity)
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -446,78 +411,89 @@ impl<P: GridPrecision> LocalFloatingOrigin<P> {
|
||||
/// frame. This is all done in high precision if possible, however any loss in precision will
|
||||
/// only affect the rendering precision. The high precision coordinates ([`GridCell`] and
|
||||
/// [`Transform`]) are the source of truth and never mutated.
|
||||
pub fn update(
|
||||
origin: Query<(Entity, &GridCell<P>), With<FloatingOrigin>>,
|
||||
pub fn compute_all(
|
||||
mut reference_frames: ReferenceFramesMut<P>,
|
||||
mut frame_stack: Local<Vec<ReferenceFrameHandle>>,
|
||||
mut frame_stack: Local<Vec<Entity>>,
|
||||
cells: Query<(Entity, &GridCell<P>)>,
|
||||
roots: Query<(Entity, &BigSpace)>,
|
||||
parents: Query<&Parent>,
|
||||
) {
|
||||
/// The maximum reference frame tree depth, defensively prevents infinite looping in case
|
||||
/// there is a degenerate hierarchy. It might take a while, but at least it's not forever?
|
||||
const MAX_REFERENCE_FRAME_DEPTH: usize = usize::MAX;
|
||||
const MAX_REFERENCE_FRAME_DEPTH: usize = 255;
|
||||
|
||||
let (origin_entity, origin_cell) = origin
|
||||
.get_single()
|
||||
.expect("There can only be one entity with the `FloatingOrigin` component.");
|
||||
// TODO: because each tree under a root is disjoint, these updates can be done in parallel
|
||||
// without aliasing. This will require unsafe, just like bevy's own transform propagation.
|
||||
'outer: for (origin_entity, origin_cell) in roots
|
||||
.iter() // TODO: If any of these checks fail, log to some diagnostic
|
||||
.filter_map(|(root_entity, root)| root.validate_floating_origin(root_entity, &parents))
|
||||
.filter_map(|origin| cells.get(origin).ok())
|
||||
{
|
||||
let Some(mut this_frame) = reference_frames.parent_frame(origin_entity) else {
|
||||
error!("The floating origin is not in a valid reference frame. The floating origin entity must be a child of an entity with the `ReferenceFrame` component.");
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(mut this_frame) = reference_frames.get_handle(origin_entity) else {
|
||||
error!("The floating origin is not in a valid reference frame. The floating origin entity must be a child of an entity with the `ReferenceFrame`, `GridCell`, and `Transform` components, or be at the root of the parent-child hierarchy.");
|
||||
return;
|
||||
};
|
||||
// Prepare by resetting the `origin_transform` of the floating origin's reference frame.
|
||||
// Because the floating origin is within this reference frame, there is no grid
|
||||
// misalignment and thus no need for any floating offsets.
|
||||
reference_frames.update_reference_frame(this_frame, |frame, _, _| {
|
||||
frame
|
||||
.local_floating_origin
|
||||
.set(*origin_cell, Vec3::ZERO, DQuat::IDENTITY);
|
||||
});
|
||||
|
||||
// Prepare by resetting the `origin_transform` of the floating origin's reference frame.
|
||||
// Because the floating origin is within this reference frame, there is no grid misalignment
|
||||
// and thus no need for any floating offsets.
|
||||
reference_frames.update(this_frame, |frame, _, _| {
|
||||
frame
|
||||
.local_floating_origin
|
||||
.set(*origin_cell, Vec3::ZERO, DQuat::IDENTITY);
|
||||
});
|
||||
// Seed the frame stack with the floating origin's reference frame. From this point out,
|
||||
// we will only look at siblings and parents, which will allow us to visit the entire
|
||||
// tree.
|
||||
frame_stack.clear();
|
||||
frame_stack.push(this_frame);
|
||||
|
||||
// Seed the frame stack with the floating origin's reference frame. From this point out, we
|
||||
// will only look at siblings and parents, which will allow us to visit the entire tree.
|
||||
frame_stack.clear();
|
||||
frame_stack.push(this_frame);
|
||||
// Recurse up and across the tree, updating siblings and their children.
|
||||
for _ in 0..MAX_REFERENCE_FRAME_DEPTH {
|
||||
// We start by propagating up to the parent of this frame, then propagating down to
|
||||
// the siblings of this frame (children of the parent that are not this frame).
|
||||
if let Some(parent_frame) = reference_frames.parent_frame(this_frame) {
|
||||
propagate_origin_to_parent(this_frame, &mut reference_frames, parent_frame);
|
||||
for sibling_frame in reference_frames.sibling_frames(this_frame) {
|
||||
// The siblings of this frame are also the children of the parent frame.
|
||||
propagate_origin_to_child(
|
||||
parent_frame,
|
||||
&mut reference_frames,
|
||||
sibling_frame,
|
||||
);
|
||||
frame_stack.push(sibling_frame); // We'll recurse through children next
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse up and across the tree, updating siblings and their children.
|
||||
for _ in 0..MAX_REFERENCE_FRAME_DEPTH {
|
||||
// We start by propagating up to the parent of this frame, then propagating down to the
|
||||
// siblings of this frame (children of the parent that are not this frame).
|
||||
if let Some(parent_frame) = reference_frames.parent(this_frame) {
|
||||
this_frame.propagate_origin_to_parent(&mut reference_frames, parent_frame);
|
||||
for sibling_frame in reference_frames.siblings(this_frame) {
|
||||
// The siblings of this frame are also the children of the parent frame.
|
||||
parent_frame.propagate_origin_to_child(&mut reference_frames, sibling_frame);
|
||||
frame_stack.push(sibling_frame); // We'll recurse through children next
|
||||
// All of the reference frames pushed on the stack have been processed. We can now
|
||||
// pop those off the stack and recursively process their children all the way out to
|
||||
// the leaves of the tree.
|
||||
while let Some(this_frame) = frame_stack.pop() {
|
||||
for child_frame in reference_frames.child_frames(this_frame) {
|
||||
propagate_origin_to_child(this_frame, &mut reference_frames, child_frame);
|
||||
frame_stack.push(child_frame) // Push processed child onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, now that the siblings of this frame have been recursively processed, we
|
||||
// process the parent and set it as the current reference frame. Note that every
|
||||
// time we step to a parent, "this frame" and all descendants have already been
|
||||
// processed, so we only need to process the siblings.
|
||||
match reference_frames.parent_frame(this_frame) {
|
||||
Some(parent_frame) => this_frame = parent_frame,
|
||||
None => continue 'outer, // We have reached the root of the tree, and can exit.
|
||||
}
|
||||
}
|
||||
|
||||
// All of the reference frames pushed on the stack have been processed. We can now pop
|
||||
// those off the stack and recursively process their children all the way out to the
|
||||
// leaves of the tree.
|
||||
while let Some(this_frame) = frame_stack.pop() {
|
||||
for child_frame in reference_frames.children(this_frame) {
|
||||
this_frame.propagate_origin_to_child(&mut reference_frames, child_frame);
|
||||
frame_stack.push(child_frame) // Push processed child onto the stack
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, now that the siblings of this frame have been recursively processed, we
|
||||
// process the parent and set it as the current reference frame. Note that every time we
|
||||
// step to a parent, "this frame" and all descendants have already been processed, so we
|
||||
// only need to process the siblings.
|
||||
match reference_frames.parent(this_frame) {
|
||||
Some(parent_frame) => this_frame = parent_frame,
|
||||
None => return, // We have reached the root of the tree, and can exit.
|
||||
}
|
||||
error!("Reached the maximum reference frame depth ({MAX_REFERENCE_FRAME_DEPTH}), and exited early to prevent an infinite loop. This might be caused by a degenerate hierarchy.")
|
||||
}
|
||||
|
||||
error!("Reached the maximum reference frame depth ({MAX_REFERENCE_FRAME_DEPTH}), and exited early to prevent an infinite loop. This might be caused by a degenerate hierarchy.")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy::{ecs::system::SystemState, math::DVec3};
|
||||
use bevy::{ecs::system::SystemState, math::DVec3, prelude::*};
|
||||
|
||||
use super::*;
|
||||
use crate::*;
|
||||
@ -526,7 +502,7 @@ mod tests {
|
||||
#[test]
|
||||
fn frame_hierarchy_getters() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
|
||||
let frame_bundle = (
|
||||
Transform::default(),
|
||||
@ -537,54 +513,59 @@ mod tests {
|
||||
let child_1 = app.world.spawn(frame_bundle.clone()).id();
|
||||
let child_2 = app.world.spawn(frame_bundle.clone()).id();
|
||||
let parent = app.world.spawn(frame_bundle.clone()).id();
|
||||
let root = app.world.spawn(frame_bundle.clone()).id();
|
||||
|
||||
app.world.entity_mut(root).push_children(&[parent]);
|
||||
app.world
|
||||
.entity_mut(parent)
|
||||
.push_children(&[child_1, child_2]);
|
||||
|
||||
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||
let mut ref_frame = state.get_mut(&mut app.world);
|
||||
let mut ref_frames = state.get_mut(&mut app.world);
|
||||
|
||||
// Children
|
||||
let result = ref_frame.children(ReferenceFrameHandle::Root);
|
||||
assert_eq!(result, vec![ReferenceFrameHandle::Node(parent)]);
|
||||
let result = ref_frame.children(ReferenceFrameHandle::Node(parent));
|
||||
assert!(result.contains(&ReferenceFrameHandle::Node(child_1)));
|
||||
assert!(result.contains(&ReferenceFrameHandle::Node(child_2)));
|
||||
let result = ref_frame.children(ReferenceFrameHandle::Node(child_1));
|
||||
let result = ref_frames.child_frames(root);
|
||||
assert_eq!(result, vec![parent]);
|
||||
let result = ref_frames.child_frames(parent);
|
||||
assert!(result.contains(&child_1));
|
||||
assert!(result.contains(&child_2));
|
||||
let result = ref_frames.child_frames(child_1);
|
||||
assert_eq!(result, Vec::new());
|
||||
|
||||
// Parent
|
||||
let result = ref_frame.parent(ReferenceFrameHandle::Root);
|
||||
let result = ref_frames.parent_frame(root);
|
||||
assert_eq!(result, None);
|
||||
let result = ref_frame.parent(ReferenceFrameHandle::Node(parent));
|
||||
assert_eq!(result, Some(ReferenceFrameHandle::Root));
|
||||
let result = ref_frame.parent(ReferenceFrameHandle::Node(child_1));
|
||||
assert_eq!(result, Some(ReferenceFrameHandle::Node(parent)));
|
||||
let result = ref_frames.parent_frame(parent);
|
||||
assert_eq!(result, Some(root));
|
||||
let result = ref_frames.parent_frame(child_1);
|
||||
assert_eq!(result, Some(parent));
|
||||
|
||||
// Siblings
|
||||
let result = ref_frame.siblings(ReferenceFrameHandle::Root);
|
||||
let result = ref_frames.sibling_frames(root);
|
||||
assert_eq!(result, vec![]);
|
||||
let result = ref_frame.siblings(ReferenceFrameHandle::Node(parent));
|
||||
let result = ref_frames.sibling_frames(parent);
|
||||
assert_eq!(result, vec![]);
|
||||
let result = ref_frame.siblings(ReferenceFrameHandle::Node(child_1));
|
||||
assert_eq!(result, vec![ReferenceFrameHandle::Node(child_2)]);
|
||||
let result = ref_frames.sibling_frames(child_1);
|
||||
assert_eq!(result, vec![child_2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_propagation() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
|
||||
let root = ReferenceFrameHandle::Root;
|
||||
|
||||
app.insert_resource(RootReferenceFrame(ReferenceFrame {
|
||||
let root_frame = ReferenceFrame {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i32>::new(1_000_000, -1, -1),
|
||||
Vec3::ZERO,
|
||||
DQuat::from_rotation_z(-std::f64::consts::FRAC_PI_2),
|
||||
),
|
||||
..default()
|
||||
}));
|
||||
};
|
||||
let root = app
|
||||
.world
|
||||
.spawn((Transform::default(), GridCell::<i32>::default(), root_frame))
|
||||
.id();
|
||||
|
||||
let child = app
|
||||
.world
|
||||
@ -595,13 +576,14 @@ mod tests {
|
||||
ReferenceFrame::<i32>::default(),
|
||||
))
|
||||
.id();
|
||||
let child = ReferenceFrameHandle::Node(child);
|
||||
|
||||
app.world.entity_mut(root).push_children(&[child]);
|
||||
|
||||
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||
let mut reference_frames = state.get_mut(&mut app.world);
|
||||
|
||||
// The function we are testing
|
||||
root.propagate_origin_to_child(&mut reference_frames, child);
|
||||
propagate_origin_to_child(root, &mut reference_frames, child);
|
||||
|
||||
let (child_frame, ..) = reference_frames.get(child);
|
||||
|
||||
@ -630,9 +612,14 @@ mod tests {
|
||||
#[test]
|
||||
fn parent_propagation() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i64>::default());
|
||||
app.add_plugins(BigSpacePlugin::<i64>::default());
|
||||
|
||||
let root = ReferenceFrameHandle::Root;
|
||||
let frame_bundle = (
|
||||
Transform::default(),
|
||||
GridCell::<i64>::default(),
|
||||
ReferenceFrame::<i64>::default(),
|
||||
);
|
||||
let root = app.world.spawn(frame_bundle.clone()).id();
|
||||
|
||||
let child = app
|
||||
.world
|
||||
@ -650,13 +637,14 @@ mod tests {
|
||||
},
|
||||
))
|
||||
.id();
|
||||
let child = ReferenceFrameHandle::Node(child);
|
||||
|
||||
app.world.entity_mut(root).push_children(&[child]);
|
||||
|
||||
let mut state = SystemState::<ReferenceFramesMut<i64>>::new(&mut app.world);
|
||||
let mut reference_frames = state.get_mut(&mut app.world);
|
||||
|
||||
// The function we are testing
|
||||
child.propagate_origin_to_parent(&mut reference_frames, root);
|
||||
propagate_origin_to_parent(child, &mut reference_frames, root);
|
||||
|
||||
let (root_frame, ..) = reference_frames.get(root);
|
||||
|
||||
@ -685,18 +673,23 @@ mod tests {
|
||||
#[test]
|
||||
fn origin_transform() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(FloatingOriginPlugin::<i32>::default());
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
|
||||
let root = ReferenceFrameHandle::Root;
|
||||
|
||||
app.insert_resource(RootReferenceFrame(ReferenceFrame {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i32>::new(0, 0, 0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
DQuat::from_rotation_z(0.0),
|
||||
),
|
||||
..default()
|
||||
}));
|
||||
let root = app
|
||||
.world
|
||||
.spawn((
|
||||
Transform::default(),
|
||||
GridCell::<i32>::default(),
|
||||
ReferenceFrame {
|
||||
local_floating_origin: LocalFloatingOrigin::new(
|
||||
GridCell::<i32>::new(0, 0, 0),
|
||||
Vec3::new(1.0, 1.0, 0.0),
|
||||
DQuat::from_rotation_z(0.0),
|
||||
),
|
||||
..default()
|
||||
},
|
||||
))
|
||||
.id();
|
||||
|
||||
let child = app
|
||||
.world
|
||||
@ -708,12 +701,13 @@ mod tests {
|
||||
ReferenceFrame::<i32>::default(),
|
||||
))
|
||||
.id();
|
||||
let child = ReferenceFrameHandle::Node(child);
|
||||
|
||||
app.world.entity_mut(root).push_children(&[child]);
|
||||
|
||||
let mut state = SystemState::<ReferenceFramesMut<i32>>::new(&mut app.world);
|
||||
let mut reference_frames = state.get_mut(&mut app.world);
|
||||
|
||||
root.propagate_origin_to_child(&mut reference_frames, child);
|
||||
propagate_origin_to_child(root, &mut reference_frames, child);
|
||||
|
||||
let (child_frame, ..) = reference_frames.get(child);
|
||||
let child_local_point = DVec3::new(5.0, 5.0, 0.0);
|
||||
|
||||
@ -3,37 +3,32 @@
|
||||
//! orbiting a star.
|
||||
|
||||
use bevy::{
|
||||
ecs::{component::Component, system::Resource},
|
||||
ecs::prelude::*,
|
||||
math::{Affine3A, DAffine3, DVec3, Vec3},
|
||||
prelude::{Deref, DerefMut},
|
||||
reflect::Reflect,
|
||||
transform::components::{GlobalTransform, Transform},
|
||||
transform::prelude::*,
|
||||
};
|
||||
|
||||
use crate::{GridCell, GridPrecision};
|
||||
use crate::{precision::GridPrecision, GridCell};
|
||||
|
||||
use self::local_origin::LocalFloatingOrigin;
|
||||
|
||||
pub mod local_origin;
|
||||
pub mod propagation;
|
||||
|
||||
/// All entities that have no parent are implicitly in the root [`ReferenceFrame`].
|
||||
///
|
||||
/// Because this relationship is implicit, it lives outside of the entity/component hierarchy, and
|
||||
/// is a singleton; this is why the root reference frame is a resource unlike all other
|
||||
/// [`ReferenceFrame`]s which are components.
|
||||
#[derive(Debug, Clone, Resource, Reflect, Deref, DerefMut)]
|
||||
pub struct RootReferenceFrame<P: GridPrecision>(pub(crate) ReferenceFrame<P>);
|
||||
|
||||
/// A component that defines a reference frame for children of this entity with [`GridCell`]s.
|
||||
///
|
||||
/// Entities without a parent are implicitly in the [`RootReferenceFrame`].
|
||||
/// A component that defines a reference frame for children of this entity with [`GridCell`]s. All
|
||||
/// entities with a [`GridCell`] must be children of an entity with a [`ReferenceFrame`]. The
|
||||
/// reference frame *defines* the grid that the `GridCell` indexes into.
|
||||
///
|
||||
/// ## Motivation
|
||||
///
|
||||
/// Reference frames are hierarchical, allowing more precision for objects with similar relative
|
||||
/// velocities. Entities in the same reference frame as the [`crate::FloatingOrigin`] will be
|
||||
/// rendered with the most precision. Reference frames are transformed relative to each other
|
||||
/// using 64 bit float transforms.
|
||||
/// velocities. All entities in the same reference frame will move together, like standard transform
|
||||
/// propagation, but with much more precision. Entities in the same reference frame as the
|
||||
/// [`crate::FloatingOrigin`] will be rendered with the most precision. Transforms are propagated
|
||||
/// starting from the floating origin, ensuring that references frames in a similar point in the
|
||||
/// hierarchy have accumulated the least error. Reference frames are transformed relative to each
|
||||
/// other using 64 bit float transforms.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
|
||||
181
src/reference_frame/propagation.rs
Normal file
181
src/reference_frame/propagation.rs
Normal file
@ -0,0 +1,181 @@
|
||||
//! Logic for propagating transforms through the hierarchy of reference frames.
|
||||
|
||||
use crate::{precision::GridPrecision, reference_frame::ReferenceFrame, GridCell};
|
||||
use bevy::prelude::*;
|
||||
|
||||
impl<P: GridPrecision> ReferenceFrame<P> {
|
||||
/// Update the `GlobalTransform` of entities with a [`GridCell`], using the [`ReferenceFrame`]
|
||||
/// the entity belongs to.
|
||||
pub fn propagate_high_precision(
|
||||
reference_frames: Query<&ReferenceFrame<P>>,
|
||||
mut entities: Query<(&GridCell<P>, &Transform, &Parent, &mut GlobalTransform)>,
|
||||
) {
|
||||
// Update the GlobalTransform of GridCell entities that are children of a ReferenceFrame
|
||||
entities
|
||||
.par_iter_mut()
|
||||
.for_each(|(grid, transform, parent, mut global_transform)| {
|
||||
if let Ok(frame) = reference_frames.get(parent.get()) {
|
||||
*global_transform = frame.global_transform(grid, transform);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Update the [`GlobalTransform`] of entities with a [`Transform`] that are children of a
|
||||
/// [`ReferenceFrame`] and do not have a [`GridCell`] component, or that are children of
|
||||
/// [`GridCell`]s. This will recursively propagate entities that only have low-precision
|
||||
/// [`Transform`]s, just like bevy's built in systems.
|
||||
pub fn propagate_low_precision(
|
||||
frames: Query<&Children, With<ReferenceFrame<P>>>,
|
||||
frame_child_query: Query<(Entity, &Children, &GlobalTransform), With<GridCell<P>>>,
|
||||
transform_query: Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCell<P>>,
|
||||
Without<ReferenceFrame<P>>,
|
||||
),
|
||||
>,
|
||||
parent_query: Query<(Entity, Ref<Parent>)>,
|
||||
) {
|
||||
let update_transforms = |(entity, children, global_transform)| {
|
||||
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
actual_parent.get(), entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
|
||||
// Unlike bevy's transform propagation, change detection is much more complex, because
|
||||
// it is relative to the floating origin, *and* whether entities are moving.
|
||||
// - If the floating origin changes grid cells, everything needs to update
|
||||
// - If the floating origin's reference frame moves (translation, rotation), every
|
||||
// entity outside of the reference frame subtree that the floating origin is in must
|
||||
// update.
|
||||
// - All entities or reference frame subtrees that move within the same frame as the
|
||||
// floating origin must be updated.
|
||||
//
|
||||
// Instead of adding this complexity and computation, is it much simpler to update
|
||||
// everything every frame.
|
||||
let changed = true;
|
||||
|
||||
// SAFETY:
|
||||
// - `child` must have consistent parentage, or the above assertion would panic. Since
|
||||
// `child` is parented to a root entity, the entire hierarchy leading to it is
|
||||
// consistent.
|
||||
// - We may operate as if all descendants are consistent, since `propagate_recursive`
|
||||
// will panic before continuing to propagate if it encounters an entity with
|
||||
// inconsistent parentage.
|
||||
// - Since each root entity is unique and the hierarchy is consistent and forest-like,
|
||||
// other root entities' `propagate_recursive` calls will not conflict with this one.
|
||||
// - Since this is the only place where `transform_query` gets used, there will be no
|
||||
// conflicting fetches elsewhere.
|
||||
unsafe {
|
||||
Self::propagate_recursive(
|
||||
&global_transform,
|
||||
&transform_query,
|
||||
&parent_query,
|
||||
child,
|
||||
changed,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
frames.par_iter().for_each(|children| {
|
||||
children
|
||||
.iter()
|
||||
.filter_map(|child| frame_child_query.get(*child).ok())
|
||||
.for_each(|(e, c, g)| update_transforms((e, c, *g)))
|
||||
});
|
||||
}
|
||||
|
||||
/// COPIED FROM BEVY
|
||||
///
|
||||
/// Recursively propagates the transforms for `entity` and all of its descendants.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
|
||||
/// propagating the transforms of any malformed entities and their descendants.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - While this function is running, `transform_query` must not have any fetches for `entity`, nor
|
||||
/// any of its descendants.
|
||||
/// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must remain
|
||||
/// as a tree or a forest. Each entity must have at most one parent.
|
||||
unsafe fn propagate_recursive(
|
||||
parent: &GlobalTransform,
|
||||
transform_query: &Query<
|
||||
(Ref<Transform>, &mut GlobalTransform, Option<&Children>),
|
||||
(
|
||||
With<Parent>,
|
||||
Without<GridCell<P>>, // ***ADDED*** Only recurse low-precision entities
|
||||
Without<ReferenceFrame<P>>, // ***ADDED*** Only recurse low-precision entities
|
||||
),
|
||||
>,
|
||||
parent_query: &Query<(Entity, Ref<Parent>)>,
|
||||
entity: Entity,
|
||||
mut changed: bool,
|
||||
) {
|
||||
let (global_matrix, children) = {
|
||||
let Ok((transform, mut global_transform, children)) =
|
||||
// SAFETY: This call cannot create aliased mutable references.
|
||||
// - The top level iteration parallelizes on the roots of the hierarchy.
|
||||
// - The caller ensures that each child has one and only one unique parent throughout the entire
|
||||
// hierarchy.
|
||||
//
|
||||
// For example, consider the following malformed hierarchy:
|
||||
//
|
||||
// A
|
||||
// / \
|
||||
// B C
|
||||
// \ /
|
||||
// D
|
||||
//
|
||||
// D has two parents, B and C. If the propagation passes through C, but the Parent component on D points to B,
|
||||
// the above check will panic as the origin parent does match the recorded parent.
|
||||
//
|
||||
// Also consider the following case, where A and B are roots:
|
||||
//
|
||||
// A B
|
||||
// \ /
|
||||
// C D
|
||||
// \ /
|
||||
// E
|
||||
//
|
||||
// Even if these A and B start two separate tasks running in parallel, one of them will panic before attempting
|
||||
// to mutably access E.
|
||||
(unsafe { transform_query.get_unchecked(entity) }) else {
|
||||
return;
|
||||
};
|
||||
|
||||
changed |= transform.is_changed() || global_transform.is_added();
|
||||
if changed {
|
||||
*global_transform = parent.mul_transform(*transform);
|
||||
}
|
||||
(*global_transform, children)
|
||||
};
|
||||
|
||||
let Some(children) = children else { return };
|
||||
for (child, actual_parent) in parent_query.iter_many(children) {
|
||||
assert_eq!(
|
||||
actual_parent.get(), entity,
|
||||
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
|
||||
);
|
||||
// SAFETY: The caller guarantees that `transform_query` will not be fetched
|
||||
// for any descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
|
||||
//
|
||||
// The above assertion ensures that each child has one and only one unique parent throughout the
|
||||
// entire hierarchy.
|
||||
unsafe {
|
||||
Self::propagate_recursive(
|
||||
&global_matrix,
|
||||
transform_query,
|
||||
parent_query,
|
||||
child,
|
||||
changed || actual_parent.is_changed(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/tests.rs
Normal file
96
src/tests.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::{BigSpacePlugin, BigSpaceRootBundle, FloatingOrigin, GridCell};
|
||||
|
||||
#[test]
|
||||
fn changing_floating_origin_updates_global_transform() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
|
||||
let first = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
150.0, 0.0, 0.0,
|
||||
))),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
|
||||
let second = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
0.0, 0.0, 300.0,
|
||||
))),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
))
|
||||
.id();
|
||||
|
||||
app.world
|
||||
.spawn(BigSpaceRootBundle::<i32>::default())
|
||||
.push_children(&[first, second]);
|
||||
|
||||
app.update();
|
||||
|
||||
app.world.entity_mut(first).remove::<FloatingOrigin>();
|
||||
app.world.entity_mut(second).insert(FloatingOrigin);
|
||||
|
||||
app.update();
|
||||
|
||||
let second_global_transform = app.world.get::<GlobalTransform>(second).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
second_global_transform.translation(),
|
||||
Vec3::new(0.0, 0.0, 300.0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_global_transforms_are_updated_when_floating_origin_changes() {
|
||||
let mut app = App::new();
|
||||
app.add_plugins(BigSpacePlugin::<i32>::default());
|
||||
|
||||
let first = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
150.0, 0.0, 0.0,
|
||||
))),
|
||||
GridCell::<i32>::new(5, 0, 0),
|
||||
FloatingOrigin,
|
||||
))
|
||||
.id();
|
||||
|
||||
let second = app
|
||||
.world
|
||||
.spawn((
|
||||
TransformBundle::from_transform(Transform::from_translation(Vec3::new(
|
||||
0.0, 0.0, 300.0,
|
||||
))),
|
||||
GridCell::<i32>::new(0, -15, 0),
|
||||
))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((TransformBundle::from_transform(
|
||||
Transform::from_translation(Vec3::new(0.0, 0.0, 300.0)),
|
||||
),));
|
||||
})
|
||||
.id();
|
||||
|
||||
app.world
|
||||
.spawn(BigSpaceRootBundle::<i32>::default())
|
||||
.push_children(&[first, second]);
|
||||
|
||||
app.update();
|
||||
|
||||
app.world.entity_mut(first).remove::<FloatingOrigin>();
|
||||
app.world.entity_mut(second).insert(FloatingOrigin);
|
||||
|
||||
app.update();
|
||||
|
||||
let child = app.world.get::<Children>(second).unwrap()[0];
|
||||
let child_transform = app.world.get::<GlobalTransform>(child).unwrap();
|
||||
|
||||
assert_eq!(child_transform.translation(), Vec3::new(0.0, 0.0, 600.0));
|
||||
}
|
||||
296
src/validation.rs
Normal file
296
src/validation.rs
Normal file
@ -0,0 +1,296 @@
|
||||
//! Tools for validating high-precision transform hierarchies
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::utils::HashMap;
|
||||
|
||||
use crate::{
|
||||
precision::GridPrecision, reference_frame::ReferenceFrame, BigSpace, FloatingOrigin, GridCell,
|
||||
};
|
||||
|
||||
struct ValidationStackEntry {
|
||||
parent_node: Box<dyn ValidHierarchyNode>,
|
||||
children: Vec<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
struct ValidatorCaches {
|
||||
query_state_cache: HashMap<&'static str, QueryState<(Entity, Option<&'static Children>)>>,
|
||||
validator_cache: HashMap<&'static str, Vec<Box<dyn ValidHierarchyNode>>>,
|
||||
root_query: Option<QueryState<Entity, Without<Parent>>>,
|
||||
stack: Vec<ValidationStackEntry>,
|
||||
}
|
||||
|
||||
/// Validate the entity hierarchy and report errors.
|
||||
pub fn validate_hierarchy<V: 'static + ValidHierarchyNode + Default>(world: &mut World) {
|
||||
world.init_resource::<ValidatorCaches>();
|
||||
let mut caches = world.remove_resource::<ValidatorCaches>().unwrap();
|
||||
|
||||
let root_entities = caches
|
||||
.root_query
|
||||
.get_or_insert(world.query_filtered::<Entity, Without<Parent>>())
|
||||
.iter(world)
|
||||
.collect();
|
||||
|
||||
caches.stack.push(ValidationStackEntry {
|
||||
parent_node: Box::<V>::default(),
|
||||
children: root_entities,
|
||||
});
|
||||
|
||||
while let Some(stack_entry) = caches.stack.pop() {
|
||||
let mut validators_and_queries = caches
|
||||
.validator_cache
|
||||
.entry(stack_entry.parent_node.name())
|
||||
.or_insert_with(|| stack_entry.parent_node.allowed_child_nodes())
|
||||
.iter()
|
||||
.map(|validator| {
|
||||
let query = caches
|
||||
.query_state_cache
|
||||
.remove(validator.name())
|
||||
.unwrap_or_else(|| {
|
||||
let mut query_builder = QueryBuilder::new(world);
|
||||
validator.match_self(&mut query_builder);
|
||||
query_builder.build()
|
||||
});
|
||||
(validator, query)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for entity in stack_entry.children.iter() {
|
||||
let query_result = validators_and_queries
|
||||
.iter_mut()
|
||||
.find_map(|(validator, query)| {
|
||||
query.get(world, *entity).ok().map(|res| (validator, res.1))
|
||||
});
|
||||
|
||||
match query_result {
|
||||
Some((validator, Some(children))) => {
|
||||
caches.stack.push(ValidationStackEntry {
|
||||
parent_node: validator.clone(),
|
||||
children: children.to_vec(),
|
||||
});
|
||||
}
|
||||
Some(_) => (), // Matched, but no children to push on the stack
|
||||
None => {
|
||||
let mut possibilities = String::new();
|
||||
stack_entry
|
||||
.parent_node
|
||||
.allowed_child_nodes()
|
||||
.iter()
|
||||
.for_each(|v| {
|
||||
possibilities.push('\t');
|
||||
possibilities.push('\t');
|
||||
possibilities.push_str(v.name());
|
||||
possibilities.push('\n');
|
||||
});
|
||||
|
||||
let mut inspect = String::new();
|
||||
world.inspect_entity(*entity).iter().for_each(|info| {
|
||||
inspect.push('\t');
|
||||
inspect.push('\t');
|
||||
inspect.push_str(info.name());
|
||||
inspect.push('\n');
|
||||
});
|
||||
|
||||
error!("big_space hierarchy validation error:\n\tEntity {:#?} is a child of the node {:#?}, but the entity does not match its parent's validation criteria.\n\tBecause it is a child of a {:#?}, the entity must be one of the following kinds of nodes:\n{}\tHowever, the entity has the following components, which does not match any of the above allowed archetypes:\n{}\tCommon errors include:\n\t - Using mismatched GridPrecisions, like GridCell<i32> and GridCell<i64>\n\t - Spawning an entity with a GridCell as a child of an entity without a ReferenceFrame.\n\tIf possible, use commands.spawn_big_space(), which prevents these errors, instead of manually assembling a hierarchy.\n\tSee {} for details.", entity, stack_entry.parent_node.name(), stack_entry.parent_node.name(), possibilities, inspect, file!());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (validator, query) in validators_and_queries.drain(..) {
|
||||
caches.query_state_cache.insert(validator.name(), query);
|
||||
}
|
||||
}
|
||||
|
||||
world.insert_resource(caches);
|
||||
}
|
||||
|
||||
/// Defines a valid node in the hierarchy: what components it must have, must not have, and what
|
||||
/// kinds of nodes its children can be. This can be used recursively to validate an entire entity
|
||||
/// hierarchy by starting from the root.
|
||||
pub trait ValidHierarchyNode: sealed::CloneHierarchy + Send + Sync {
|
||||
/// Add filters to a query to check if entities match this type of node
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>);
|
||||
/// The types of nodes that can be children of this node.
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>>;
|
||||
/// A unique identifier of this type
|
||||
fn name(&self) -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) mod sealed {
|
||||
use super::ValidHierarchyNode;
|
||||
|
||||
pub trait CloneHierarchy {
|
||||
fn clone_box(&self) -> Box<dyn ValidHierarchyNode>;
|
||||
}
|
||||
|
||||
impl<T: ?Sized> CloneHierarchy for T
|
||||
where
|
||||
T: 'static + ValidHierarchyNode + Clone,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ValidHierarchyNode> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn ValidHierarchyNode> {
|
||||
fn clone(&self) -> Self {
|
||||
self.clone_box()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The root hierarchy validation struct, used as a generic parameter in [`crate::validation`].
|
||||
#[derive(Default, Clone)]
|
||||
pub struct SpatialHierarchyRoot<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for SpatialHierarchyRoot<P> {
|
||||
fn match_self(&self, _: &mut QueryBuilder<(Entity, Option<&Children>)>) {}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<RootFrame<P>>::default(),
|
||||
Box::<RootSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct AnyNonSpatial<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for AnyNonSpatial<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.without::<GridCell<P>>()
|
||||
.without::<Transform>()
|
||||
.without::<GlobalTransform>()
|
||||
.without::<BigSpace>()
|
||||
.without::<ReferenceFrame<P>>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![Box::<AnyNonSpatial<P>>::default()]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct RootFrame<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for RootFrame<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<BigSpace>()
|
||||
.with::<ReferenceFrame<P>>()
|
||||
.without::<GridCell<P>>()
|
||||
.without::<Transform>()
|
||||
.without::<GlobalTransform>()
|
||||
.without::<Parent>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
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(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct RootSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for RootSpatialLowPrecision<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.without::<GridCell<P>>()
|
||||
.without::<BigSpace>()
|
||||
.without::<ReferenceFrame<P>>()
|
||||
.without::<Parent>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildFrame<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildFrame<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<ReferenceFrame<P>>()
|
||||
.with::<GridCell<P>>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.without::<BigSpace>();
|
||||
}
|
||||
|
||||
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(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildSpatialLowPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialLowPrecision<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.without::<GridCell<P>>()
|
||||
.without::<BigSpace>()
|
||||
.without::<ReferenceFrame<P>>()
|
||||
.without::<FloatingOrigin>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
struct ChildSpatialHighPrecision<P: GridPrecision>(PhantomData<P>);
|
||||
|
||||
impl<P: GridPrecision> ValidHierarchyNode for ChildSpatialHighPrecision<P> {
|
||||
fn match_self(&self, query: &mut QueryBuilder<(Entity, Option<&Children>)>) {
|
||||
query
|
||||
.with::<GridCell<P>>()
|
||||
.with::<Transform>()
|
||||
.with::<GlobalTransform>()
|
||||
.with::<Parent>()
|
||||
.without::<BigSpace>()
|
||||
.without::<ReferenceFrame<P>>();
|
||||
}
|
||||
|
||||
fn allowed_child_nodes(&self) -> Vec<Box<dyn ValidHierarchyNode>> {
|
||||
vec![
|
||||
Box::<ChildSpatialLowPrecision<P>>::default(),
|
||||
Box::<AnyNonSpatial<P>>::default(),
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user